PerfectVector
Oleh Irene Kim

Tambahkan Watermark SVG ke PDF yang Ada dengan Python

Gabungkan watermark SVG ke PDF dengan svglib, ReportLab, dan pypdf. Periksa opasitas, halaman terpotong, rotasi, teks yang dipertahankan, serta urutan lapisan.

12 menit baca
Di halaman ini

Untuk menambahkan watermark SVG ke PDF yang sudah ada dengan Python, konversi SVG menjadi objek gambar ReportLab, render ke PDF lapisan kecil, lalu gabungkan lapisan itu ke setiap halaman asli menggunakan pypdf. Tentukan ukuran dan posisi lapisan berdasarkan kotak pemotongan terlihat setiap halaman, serta normalkan rotasi halaman sebelum penggabungan.

Gunakan over=True jika tanda harus tetap terlihat di atas latar halaman yang opak. Tanda semitransparan pun dapat berada di atas. Dengan over=False, gambar yang sama berada di bawah konten yang ada dan bisa tersembunyi di balik halaman berisi warna.

Contoh lengkap di bawah membuat SVG dan PDF sumber tiga halamannya sendiri. Contoh mencakup ukuran halaman berbeda, kotak pemotongan dengan titik asal bukan nol, serta halaman yang diputar. Hasilnya adalah PDF baru; simpan berkas asli secara terpisah.

Pilih dahulu operasi dokumennya

Titik awalAlur yang berguna
PDF yang ada membutuhkan lambang SVG di setiap halamanMerender lapisan SVG dan menggabungkannya ke halaman yang ada, seperti di bawah
PDF baru disusun dari awalMenggunakan langsung alur gambar SVG ReportLab
SVG yang ada sudah berisi gambar yang benarMempertahankan sumber vektor asli itu; penelusuran tidak diperlukan
Logo hanya tersedia sebagai gambar rasterMenyiapkan gambar yang sesuai dahulu, lalu menguji penggabungan PDF secara terpisah

Panduan watermark pypdf membedakan konten di atas dan di bawah halaman. Di sini, “watermark” berarti lambang yang terlihat; over menentukan urutan penumpukan sebenarnya. Ini bukan tanda keamanan, cara mencegah manipulasi, atau alur untuk dokumen bertanda tangan.

1. Mulai dengan SVG kecil yang didukung

Uji ini menggunakan dua jalur dengan isian: bingkai segitiga dengan bagian tengah terbuka dan tiga batang. Kedua jalur memiliki fill-opacity="0.25". Tidak ada teks, filter, gambar tersemat, atau sumber daya eksternal.

Masukan yang terbatas itu penting. svglib mendukung subset SVG yang berguna, tetapi batasan yang didokumentasikan mencakup dukungan CSS terbatas dan fitur yang tidak didukung seperti mask serta foreignObject. SVG yang terlihat benar di browser tetap perlu diperiksa setelah konversi. Jika berkas mengandung raster, baca cara menemukan gambar tersemat dalam SVG sebelum menganggap hasil sepenuhnya vektor.

Versi awal bentuk uji ini mengandung teks SVG selain bingkai. Pada susunan perangkat lunak yang diuji, teks hasil konversi digambar dengan opasitas penuh sementara jalurnya mempertahankan opasitas 0,25. Saya mengganti teks itu dengan gambar jalur asli dan menguji penggantinya. Hasil akhir membuktikan kasus khusus jalur ini; bukan membuktikan opasitas yang benar untuk setiap kombinasi teks atau gaya SVG.

2. Jalankan contoh lengkap

Lingkungan yang diuji adalah Python 3.12.14, svglib 2.2.0, ReportLab 4.4.9, dan pypdf 6.10.0. Dokumentasi pypdf saat ini juga menjelaskan rilis lebih baru, jadi bedakan versi yang diuji dari versi dokumentasi.

Dalam lingkungan terpisah, instal versi-versi tersebut:

python -m venv .venv
source .venv/bin/activate
python -m pip install svglib==2.2.0 reportlab==4.4.9 pypdf==6.10.0

Simpan yang berikut sebagai watermark_demo.py. Contoh menyertakan semua gambar masukan dan membuat ketiga PDF, jadi tidak ada PDF atau SVG contoh yang masih perlu diunduh.

from io import BytesIO
from pathlib import Path
 
from pypdf import PdfReader, PdfWriter, Transformation
from pypdf.generic import RectangleObject
from reportlab.graphics import renderPDF
from reportlab.lib.colors import HexColor
from reportlab.pdfgen import canvas
from svglib.svglib import svg2rlg
 
SVG = '''<svg xmlns="http://www.w3.org/2000/svg" width="240" height="80"
             viewBox="0 0 240 80">
  <path fill="#315d6f" fill-opacity="0.25" fill-rule="nonzero"
        d="M10 60 L45 8 L80 60 Z M31 48 L59 48 L45 27 Z"/>
  <path fill="#315d6f" fill-opacity="0.25"
        d="M95 23 H216 V31 H95 Z M95 39 H190 V47 H95 Z
           M95 55 H205 V63 H95 Z"/>
</svg>'''
 
 
def make_demo_source(destination):
    stream = BytesIO()
    document = canvas.Canvas(stream, pagesize=(420, 300))
    specifications = [
        (420, 300, '#f5eddb', 'SOURCE PAGE ONE'),
        (300, 420, '#ffffff', 'SOURCE PAGE TWO'),
        (360, 260, '#e8eef0', 'SOURCE PAGE THREE')
    ]
    for width, height, background, label in specifications:
        document.setPageSize((width, height))
        document.setFillColor(HexColor(background))
        document.rect(0, 0, width, height, fill=1, stroke=0)
        document.setFillColor(HexColor('#183a42'))
        document.setFont('Helvetica-Bold', 16)
        document.drawString(28, height - 40, label)
        document.setFont('Helvetica', 10)
        document.drawString(28, height - 62, 'Original text remains in the PDF content.')
        document.drawString(28, 28, 'Original vector line and opaque page background')
        document.setStrokeColor(HexColor('#c47b44'))
        document.setLineWidth(3)
        document.line(28, 48, width - 28, 48)
        document.showPage()
    document.save()
    reader = PdfReader(stream)
    reader.pages[1].cropbox = RectangleObject([10, 20, 290, 400])
    reader.pages[2].rotate(90)
    writer = PdfWriter()
    for page in reader.pages:
        writer.add_page(page)
    writer.write(destination)
 
 
def make_overlay(svg_path, width, height):
    drawing = svg2rlg(str(svg_path))
    if drawing is None or drawing.width <= 0 or drawing.height <= 0:
        raise ValueError('The SVG did not produce a usable drawing')
    scale = width * 0.65 / drawing.width
    left = (width - drawing.width * scale) / 2
    bottom = (height - drawing.height * scale) / 2
    drawing.scale(scale, scale)
    stream = BytesIO()
    overlay = canvas.Canvas(stream, pagesize=(width, height))
    renderPDF.draw(drawing, overlay, left, bottom)
    overlay.save()
    return PdfReader(stream).pages[0]
 
 
def add_artwork(source_path, svg_path, output_path, *, over):
    reader = PdfReader(source_path)
    if reader.is_encrypted:
        raise ValueError('Encrypted inputs are outside this example')
    if reader.get_fields():
        raise ValueError('Form handling is outside this example')
    writer = PdfWriter()
    writer.clone_document_from_reader(reader)
    # Cloning in pypdf 6.10.0 does not retain the source header version.
    # This overlay uses transparency, introduced in PDF 1.4.
    writer.pdf_header = max(reader.pdf_header, '%PDF-1.4')
    for page in writer.pages:
        if page.rotation:
            page.transfer_rotation_to_content()
        box = page.cropbox
        width, height = float(box.width), float(box.height)
        overlay = make_overlay(svg_path, width, height)
        page.merge_transformed_page(
            overlay,
            Transformation().translate(float(box.left), float(box.bottom)),
            over=over,
            expand=False
        )
    writer.write(output_path)
 
 
if __name__ == '__main__':
    output = Path('watermark-output')
    output.mkdir(exist_ok=True)
    artwork = output / 'emblem.svg'
    artwork.write_text(SVG)
    source = output / 'source.pdf'
    make_demo_source(source)
    add_artwork(source, artwork, output / 'overlay-top.pdf', over=True)
    add_artwork(source, artwork, output / 'underlay.pdf', over=False)

Jalankan:

python watermark_demo.py

Buka watermark-output/source.pdf, overlay-top.pdf, dan underlay.pdf. Halaman pertama seharusnya menampilkan lambang dalam keluaran lapisan atas, tetapi menyembunyikannya dalam keluaran lapisan bawah karena latar aslinya opak. Dua halaman lain menguji pemotongan dan rotasi, bukan mengulangi geometri halaman yang sama.

Untuk PDF biasa Anda yang tidak ditandatangani, pertahankan make_overlay() dan add_artwork(), lalu panggil:

add_artwork(
    Path("original.pdf"),
    Path("emblem.svg"),
    Path("watermarked.pdf"),
    over=True
)

Writer mempertahankan versi header sumber dengan minimum PDF 1.4 karena lapisan menggunakan transparansi. Pada pypdf 6.10.0, hanya mengkloning dokumen dapat meninggalkan header PDF 1.3 bawaan writer meskipun sumber menyatakan versi lebih baru. Penetapan eksplisit menghindari penurunan itu; bukan validasi kesesuaian. Dokumentasi header writer menyarankan versi yang mendukung semua fitur dalam berkas.

Pemeriksaan pengaman menolak masukan terenkripsi dan kolom formulir yang terdeteksi. Itu bukan audit kompatibilitas atau tanda tangan lengkap. Dokumen bertanda tangan, formulir, anotasi, enkripsi, dan kebutuhan khusus dokumen memerlukan alur serta validasi terpisah sebelum mengganti berkas kerja.

3. Tempatkan gambar dalam koordinat halaman yang terlihat

Satu poin PDF adalah 1/72 inci. Dalam versi svglib ini, dimensi SVG tanpa satuan 240 × 80 menghasilkan gambar 180 × 60 poin: satu piksel CSS dipetakan ke 0,75 poin. Kode membaca drawing.width dan drawing.height alih-alih menganggap piksel SVG sudah sama dengan poin PDF.

Contoh membuat viewport SVG sebesar 65% lebar terlihat setiap halaman, mempertahankan rasio aspek, dan memusatkan viewport tersebut. Yang dipusatkan adalah persegi panjang gambar yang dinyatakan, bukan massa visual lambang. Nilai 65% merupakan pilihan tata letak contoh, bukan standar watermark. Skala berdasarkan lebar saja sesuai untuk lambang lebar di sini. Pengganti yang tinggi dapat melebihi tinggi halaman terlihat; saat menyesuaikan fungsi, batasi skala berdasarkan lebar dan tinggi yang tersedia serta periksa setiap halaman sebelum menggunakan PDF Anda sendiri.

HalamanKondisi sumberKotak pemotongan terlihat pada keluaran, dalam poinLebar nominal viewport SVG
1Halaman lanskap 420 × 3000, 0 hingga 420, 300273pt
2Halaman 300 × 420, dipotong ke dalam10, 20 hingga 290, 400182pt
3Halaman 360 × 260 dengan rotasi 90°0, 0 hingga 260, 360 setelah normalisasi169pt

Memanggil transfer_rotation_to_content() memindahkan rotasi ke konten dan kotak halaman. Fungsi itu tidak meluruskan kata-kata asli terhadap pembaca. Teks asli halaman ketiga tetap berorientasi seperti dalam tampilan sumber, sementara lambang baru ditempatkan pada koordinat terlihat yang dinormalisasi. API halaman pypdf menyarankan pemindahan rotasi sebelum penggabungan halaman.

Lapisan dimulai dari titik asalnya sendiri. Mentranslasikannya sebesar cropbox.left dan cropbox.bottom menempatkannya dalam daerah terlihat target meskipun kotak pemotongan tidak dimulai dari nol. expand=False mencegah penggabungan memperbesar kotak halaman.

4. Periksa PDF tersimpan, bukan hanya pratinjau SVG

Perbandingan PDF yang benar-benar dirender menunjukkan lapisan bawah tersembunyi, lapisan atas semitransparan terlihat, halaman potret terpotong, dan halaman sumber berotasi dengan lambang vektor terpusat
Render Poppler sebenarnya dari PDF tersimpan. Latar halaman asli menyembunyikan lapisan bawah; lapisan atas tetap terlihat. Dua tampilan terakhir memeriksa titik asal pemotongan bukan nol dan halaman yang awalnya berotasi.

Saya membuka ulang PDF tersimpan dan memeriksa aliran konten, kotak halaman, rotasi, teks hasil ekstraksi, serta sumber daya gambar, lalu merender kotak pemotongan terlihat dengan Poppler 26.02 pada 96dpi. Pemeriksaan akhir berkas lulus 40 pengujian. Perbandingan browser juga memverifikasi hash berkas tersimpan, render yang dimuat, dan 12 pemeriksaan piksel pada resolusi asli.

Pemeriksaan pada bentuk uji iniHasil yang diamati
Jumlah halaman dan teks asli yang diketahuiKetiga halaman dan teks sumber yang diharapkan tetap ada pada kedua keluaran
Geometri lapisanDua jalur dengan isian, 18 titik sudut diperiksa, alfa isian 0,25 pada setiap halaman
Sumber daya gambar yang ditambahkanTidak ada pada halaman sumber dan keluaran asli ini
Posisi dan kotak halamanCocok dengan kotak pemotongan yang dinyatakan dan perhitungan transformasi terserialisasi; rotasi keluaran nol
Pemeriksaan bingkai dan tengah terbukaPiksel bingkai lapisan atas cocok dengan pencampuran yang diharapkan dalam ±2 nilai RGB; tengah terbuka mempertahankan latar halaman
Lapisan bawah pada latar opakRender sumber dan lapisan bawah halaman pertama berbeda nol piksel pada 96dpi

Audit posisi awalnya gagal dalam perbandingan ketat dengan koordinat ideal bilangan pecahan bertoleransi 0,00001pt. ReportLab menserialisasi matriks transformasi dengan presisi terbatas. Saya mempertahankan hasil gagal itu, menghitung secara independen matriks terserialisasi yang diharapkan serta batas pembulatannya, lalu membandingkan titik sudut tersimpan dengan nilai-nilai tersebut. Selisih terbesar yang diamati dari koordinat ideal sekitar 0,000054pt, masih dalam batas turunan 0,000086pt. Ini memeriksa posisi yang tampak terpusat pada berkas ini; bukan jaminan presisi universal.

Untuk pemeriksaan visual lokal singkat dengan Poppler terinstal:

pdftoppm -cropbox -r 96 -png watermark-output/overlay-top.pdf watermark-output/top
pdftoppm -cropbox -r 96 -png watermark-output/underlay.pdf watermark-output/below

Periksa setiap halaman pada pembesaran yang berguna. Pilih atau ekstrak juga teks asli yang diharapkan, periksa dimensi halaman, dan lihat apakah watermark tetap berupa jalur atau menambahkan gambar. Tangkapan layar tajam saja tidak membuktikan properti berkas tersebut. Uji ini tidak mengukur pencetakan, kompatibilitas penampil, penghematan ukuran berkas, atau ketahanan terhadap penghapusan watermark.

Perbaiki kegagalan pada tahap yang tepat

GejalaYang diperiksa atau diubah
Tanda menghilangBandingkan over=False dengan over=True; cari latar asli yang opak
Posisi tanda salah pada halaman terpotongGunakan ukuran dan titik asal kotak pemotongan, bukan ukuran halaman tetap
Halaman berotasi menempatkan tanda ke arah tak terdugaNormalkan rotasi sebelum menghitung dimensi terlihat; periksa tampilan hasilnya
Opasitas atau gaya berubahSederhanakan SVG, periksa status pewarnaan PDF hasil konversi, lalu uji ulang berkas yang persis sama
Bagian seperti foto atau berpiksel tetap adaPeriksa SVG untuk gambar tersemat; pembungkus SVG tidak mengubah bitmap menjadi jalur
Teks, formulir, atau perilaku dokumen berubahBerhenti dan validasi fitur dokumen sebenarnya, jangan menganggap penggabungan berhasil mempertahankan semuanya

PDF asli tetap menjadi konten halaman; skrip tidak merasterisasi seluruh halaman. Itu tidak membuktikan bahwa setiap PDF masukan akan tetap tidak berubah setelah penggabungan sembarang. Gunakan salinan, dan tentukan konten serta fitur dokumen yang wajib tetap bekerja sebelum memproses kelompok lebih besar.

Siapkan gambar yang belum ada sebelum penggabungan

Jika logo yang ada sudah berupa SVG bersih, pertahankan. Jika hanya tersisa logo raster datar yang sesuai, vektorisator logo PerfectVector dapat membantu menyiapkan gambar yang dapat diedit sebelum tahap Python ini. Periksa siluet, lubang, dan detail kecil yang dipulihkan, lalu ulangi konversi SVG serta pemeriksaan PDF tersimpan. Contoh ini menggunakan jalur asli yang dibuat langsung dan tidak menjalankan konversi PerfectVector. Vektorisasi tidak menggabungkan halaman, memvalidasi tanda tangan, atau menjamin kompatibilitas svglib.

Tanya jawab

Bisakah pypdf langsung membaca watermark SVG? Contoh ini terlebih dahulu mengonversi SVG melalui svglib dan ReportLab menjadi lapisan PDF. pypdf lalu menggabungkan konten halaman PDF; dalam alur ini pypdf tidak menafsirkan sumber SVG.

Mengapa watermark menghilang di balik halaman PDF saya? Lapisan bawah dapat tertutup latar opak yang sudah ada. Gunakan over=True agar lapisan atas terlihat dan tetapkan opasitas gambar secara terpisah. Periksa hasil sebenarnya pada setiap halaman.

Apakah ini mempertahankan gambar vektor dan teks asli? Lambang khusus jalur yang diuji tetap berupa jalur dengan isian, dan teks asli yang diharapkan tetap dapat diekstrak pada ketiga halaman. Fitur SVG dan dokumen PDF di luar bentuk uji ini membutuhkan validasi terpisah.

Apakah menulis ulang PDF mempertahankan tanda tangan digital? Jangan gunakan contoh ini sebagai alur dokumen bertanda tangan. Contoh menulis ulang berkas dan tidak menguji pelestarian atau validitas tanda tangan. Simpan dokumen asli yang ditandatangani dan gunakan alur sesuai kebutuhan tanda tangannya.

Sumber

  1. pypdf: Menambahkan stempel atau watermark — Urutan lapisan atas dan bawah, penggabungan, serta panduan rotasi.
  2. pypdf: API PageObject — Kotak halaman, pemindahan rotasi, dan perilaku penggabungan dengan transformasi.
  3. Dokumentasi proyek svglib — Konversi SVG ke ReportLab, satuan, dan batasan fitur yang didukung.
  4. Panduan grafis ReportLab — Objek gambar dan render PDF dalam sistem grafis ReportLab.
  5. pypdf 6.10.0: Versi header PDF — Menyatakan versi PDF yang mendukung fitur dalam keluaran.

Pertahankan gambar asli jika tersedia. Jika hanya tersisa logo raster datar yang sesuai, siapkan SVG yang dapat diedit dengan PerfectVector, periksa gambar itu, lalu verifikasi PDF yang baru digabungkan secara terpisah.

Lebih banyak dari blog

PerfectVector

Mulai dengan SVG yang lebih rapi
dan lebih mudah diedit

Tanpa kartu kredit