เพิ่มลายน้ำ SVG ใน PDF เดิมด้วย Python
รวมลายน้ำ SVG ใน PDF เดิมด้วย svglib, ReportLab และ pypdf ตรวจความทึบ หน้าที่ครอป การหมุน ข้อความที่เก็บไว้ และลำดับภาพซ้อน
ในหน้านี้
การเพิ่มลายน้ำ SVG ใน PDF เดิมด้วย Python ทำได้โดยแปลง SVG เป็น drawing ของ ReportLab เรนเดอร์ลง PDF ซ้อนขนาดเล็ก แล้วใช้ pypdf รวมลงแต่ละหน้าเดิม กำหนดขนาดและตำแหน่งจาก crop box ที่มองเห็นของแต่ละหน้า และปรับการหมุนให้เป็นปกติก่อนรวม
ใช้ over=True เมื่อเครื่องหมายต้องเห็นเหนือพื้นหน้าทึบ ลายโปร่งแสงก็วางข้างบนได้ ส่วน over=False วางงานเดียวกันใต้เนื้อหาเดิมและอาจหายหลังหน้าที่เติมสี
ตัวอย่างเต็มด้านล่างสร้าง SVG และ PDF ต้นทางสามหน้าเอง มีขนาดหน้าต่างกัน crop box จุดกำเนิดไม่เป็นศูนย์ และหน้าหมุน ผลคือ PDF ใหม่ เก็บไฟล์เดิมแยก
เลือกงานเอกสารก่อน
| จุดเริ่มต้น | กระบวนการที่ใช้ได้ |
|---|---|
| PDF เดิมต้องมีตรา SVG ทุกหน้า | เรนเดอร์ PDF ซ้อนจาก SVG แล้วรวมกับหน้าเดิมตามด้านล่าง |
| กำลังสร้าง PDF ใหม่จากศูนย์ | ใช้ขั้นตอนวาด SVG ด้วย ReportLab โดยตรง |
| SVG เดิมมีงานที่ถูกต้องแล้ว | เก็บเวกเตอร์ต้นฉบับ ไม่ต้องลากเส้นใหม่ |
| โลโก้มีแค่ราสเตอร์ | เตรียมภาพเหมาะสมก่อน แล้วทดสอบรวม PDF แยก |
คู่มือลายน้ำ pypdf แยกเนื้อหาที่อยู่เหนือและใต้หน้า ในที่นี้ “ลายน้ำ” หมายถึงตราที่มองเห็น ส่วน over กำหนดลำดับซ้อนจริง ไม่ใช่เครื่องหมายรักษาความปลอดภัย วิธีป้องกันแก้ไข หรือขั้นตอนเอกสารลงนาม
1. เริ่มจาก SVG เล็กที่รองรับ
การทดสอบใช้พาธเติมสีสองเส้น ได้แก่กรอบสามเหลี่ยมมีช่องกลางและแถบสามเส้น ทั้งคู่มี fill-opacity="0.25" ไม่มีข้อความ ฟิลเตอร์ ภาพฝัง หรือทรัพยากรนอก
อินพุตจำกัดนี้สำคัญ svglib รองรับส่วนย่อย SVG ที่มีประโยชน์ แต่ข้อจำกัดตามเอกสาร รวม CSS จำกัดและไม่รองรับ mask กับ foreignObject SVG ที่ดูถูกในเบราว์เซอร์ยังต้องตรวจหลังแปลง หากมีราสเตอร์ อ่านวิธีหาภาพฝังใน SVG ก่อนถือว่าเป็นเวกเตอร์ทั้งหมด
เวอร์ชันแรกของชุดนี้มีข้อความ SVG คู่กรอบ ในสแต็กที่ทดสอบ ข้อความที่แปลงถูกระบายทึบเต็ม ส่วนพาธคงความทึบ 0.25 ฉันเปลี่ยนข้อความเป็นภาพพาธต้นฉบับแล้วทดสอบใหม่ ผลสุดท้ายยืนยันเฉพาะกรณีพาธล้วน ไม่ยืนยันความทึบถูกต้องของข้อความหรือสไตล์ SVG ทุกชุด
2. รันตัวอย่างเต็ม
สภาพแวดล้อมทดสอบคือ Python 3.12.14, svglib 2.2.0, ReportLab 4.4.9 และ pypdf 6.10.0 เอกสาร pypdf ปัจจุบันอธิบายรุ่นใหม่ด้วย จึงแยกรุ่นทดสอบจากรุ่นเอกสาร
ติดตั้งรุ่นเหล่านี้ในสภาพแวดล้อมแยก:
python -m venv .venv
source .venv/bin/activate
python -m pip install svglib==2.2.0 reportlab==4.4.9 pypdf==6.10.0บันทึกโค้ดเป็น watermark_demo.py มีภาพอินพุตครบและสร้าง PDF ทั้งสาม จึงไม่มีไฟล์ตัวอย่าง PDF หรือ SVG ให้ต้องดาวน์โหลดเพิ่ม
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)รัน:
python watermark_demo.pyเปิด watermark-output/source.pdf, overlay-top.pdf และ underlay.pdf หน้าแรกควรเห็นตราในผลซ้อนบน แต่ไม่เห็นในผลซ้อนล่าง เพราะพื้นเดิมทึบ อีกสองหน้าทดสอบการครอปและหมุนแทนทำซ้ำรูปทรงหน้าเดิม
สำหรับ PDF ธรรมดาที่ไม่ได้ลงนามของคุณ เก็บ make_overlay() และ add_artwork() แล้วเรียก:
add_artwork(
Path("original.pdf"),
Path("emblem.svg"),
Path("watermarked.pdf"),
over=True
)writer เก็บรุ่น header ต้นทางโดยขั้นต่ำ PDF 1.4 เพราะใช้ความโปร่งใส ใน pypdf 6.10.0 การ clone อย่างเดียวอาจเหลือ header เริ่มต้น PDF 1.3 แม้ต้นทางใหม่กว่า การกำหนดชัดช่วยเลี่ยงลดรุ่น ไม่ใช่ตรวจความสอดคล้องมาตรฐาน เอกสาร header ของ writer แนะนำรุ่นที่รองรับทุกคุณสมบัติในไฟล์
เงื่อนไขป้องกันปฏิเสธอินพุตเข้ารหัสและฟิลด์ฟอร์มที่ตรวจพบ ไม่ใช่ตรวจความเข้ากันหรือรายเซ็นครบ เอกสารลงนาม ฟอร์ม annotation การเข้ารหัส และข้อกำหนดเฉพาะต้องมีขั้นตอนกับการตรวจแยกก่อนแทนไฟล์ใช้งาน
3. วางภาพในพิกัดหน้าที่มองเห็น
หนึ่งพอยต์ PDF คือ 1/72 นิ้ว ใน svglib รุ่นนี้ ขนาด SVG เปล่า 240 × 80 ได้ drawing 180 × 60 พอยต์ หนึ่ง CSS พิกเซลแมปเป็น 0.75 พอยต์ โค้ดอ่าน drawing.width และ drawing.height แทนคิดว่าพิกเซล SVG เท่าพอยต์ PDF อยู่แล้ว
ตัวอย่างให้ viewport SVG กว้าง 65% ของหน้าที่เห็น รักษาสัดส่วนแล้วจัดกลาง viewport จัดกลางสี่เหลี่ยมที่ประกาศ ไม่ใช่น้ำหนักสายตาของตรา ค่า 65% เป็นตัวอย่างจัดวาง ไม่ใช่มาตรฐานลายน้ำ การอิงเฉพาะความกว้างเหมาะกับตรากว้างนี้ หากเปลี่ยนเป็นรูปสูงอาจเลยหน้า ตอนปรับฟังก์ชันให้จำกัดด้วยทั้งกว้างและสูง แล้วตรวจทุกหน้าก่อนใช้ PDF ของคุณ
| หน้า | ต้นทาง | crop box ที่เห็นในผลลัพธ์ หน่วยพอยต์ | ความกว้าง viewport SVG ตามกำหนด |
|---|---|---|---|
| 1 | หน้าแนวนอน 420 × 300 | 0, 0 ถึง 420, 300 | 273pt |
| 2 | หน้า 300 × 420 ครอปเข้า | 10, 20 ถึง 290, 400 | 182pt |
| 3 | หน้า 360 × 260 หมุน 90° | 0, 0 ถึง 260, 360 หลังปรับปกติ | 169pt |
transfer_rotation_to_content() ย้ายการหมุนเข้าสู่เนื้อหาและกรอบหน้า ไม่ได้ทำคำเดิมตรงเมื่อผู้ชมมอง ข้อความหน้าเดิมที่สามยังหันเหมือนภาพแสดงต้นทาง ส่วนตราใหม่วางในพิกัดเห็นที่ปรับแล้ว API หน้าของ pypdf แนะนำย้ายการหมุนก่อนรวมหน้า
ภาพซ้อนเริ่มที่จุดกำเนิดตัวเอง เลื่อนด้วย cropbox.left และ cropbox.bottom ทำให้อยู่ในพื้นที่เห็นของเป้าแม้ crop box ไม่เริ่มศูนย์ expand=False ป้องกันการรวมขยายกรอบหน้า
4. ตรวจ PDF ที่บันทึก ไม่ใช่เฉพาะภาพ SVG

ฉันเปิด PDF ที่บันทึกตรวจ content stream กรอบหน้า การหมุน ข้อความสกัด และทรัพยากรภาพ แล้วเรนเดอร์ crop box ด้วย Poppler 26.02 ที่ 96dpi การตรวจสุดท้ายผ่าน 40 ข้อ การเทียบเบราว์เซอร์ยังยืนยันแฮชไฟล์ ภาพเรนเดอร์ที่โหลด และจุดพิกเซลความละเอียดจริง 12 จุด
| การตรวจตัวอย่างนี้ | ผลที่พบ |
|---|---|
| จำนวนหน้าและข้อความเดิมที่ทราบ | ทั้งสองผลเก็บสามหน้าและข้อความต้นทางที่คาด |
| รูปทรงภาพซ้อน | พาธเติมสีสองเส้น จุดยอดตรวจ 18 จุด อัลฟา nonstroking 0.25 ทุกหน้า |
| ทรัพยากรภาพเพิ่ม | ไม่มีในหน้าเดิมและผลลัพธ์ตัวอย่างนี้ |
| ตำแหน่งและกรอบหน้า | ตรง crop box ที่ประกาศและการคำนวณ transform ที่บันทึก การหมุนผลเป็นศูนย์ |
| จุดกรอบและช่องกลาง | พิกเซลกรอบบนตรงการผสมคาดภายใน ±2 RGB ช่องกลางคงพื้นหน้า |
| ซ้อนล่างใต้พื้นทึบ | ภาพหน้าแรกต้นทางกับซ้อนล่างต่างศูนย์พิกเซลที่ 96dpi |
การตรวจตำแหน่งแรกไม่ผ่านเมื่อเทียบพิกัด floating-point อุดมคติอย่างเข้มที่ 0.00001pt ReportLab บันทึก matrix แปลงด้วยความละเอียดจำกัด ฉันเก็บผลล้มเหลว คำนวณ matrix ที่คาดหลังบันทึกกับขอบเขตการปัดแยก แล้วเทียบจุดยอดกับค่านั้น ผลต่างพิกัดอุดมคติสูงสุดประมาณ 0.000054pt อยู่ในขอบ 0.000086pt ที่หาได้ นี่ตรวจตำแหน่งกลางด้วยสายตาในไฟล์เหล่านี้ ไม่ใช่รับประกันความแม่นยำสากล
สำหรับตรวจภาพเร็วในเครื่องที่มี Poppler:
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ตรวจทุกหน้าที่ซูมเหมาะสม เลือกหรือสกัดข้อความที่คาด ตรวจขนาดหน้าและดูว่าลายน้ำยังเป็นพาธหรือเพิ่มภาพ ภาพหน้าจอคมอย่างเดียวบอกคุณสมบัติเหล่านี้ไม่ได้ การทดสอบไม่ได้วัดการพิมพ์ ความเข้ากันโปรแกรมดู ขนาดไฟล์ลด หรือการต้านการลบลายน้ำ
แก้ความผิดพลาดในขั้นที่ถูก
| อาการ | สิ่งที่ตรวจหรือเปลี่ยน |
|---|---|
| ตราหาย | เทียบ over=False กับ over=True และหาพื้นเดิมทึบ |
| ตราอยู่ผิดบนหน้าครอป | ใช้ขนาดและจุดกำเนิด crop box ไม่ใช่ขนาดหน้าตายตัว |
| หน้าหมุนทำตราหันผิดคาด | ปรับการหมุนก่อนคำนวณขนาดที่เห็น แล้วตรวจผล |
| ความทึบหรือสไตล์เปลี่ยน | ลดความซับซ้อน SVG ตรวจสถานะสี PDF ที่แปลง แล้วทดสอบไฟล์นั้น |
| ยังมีส่วนเหมือนภาพถ่ายหรือแตกพิกเซล | ตรวจภาพฝังใน SVG การห่อ SVG ไม่เปลี่ยนบิตแมปเป็นพาธ |
| ข้อความ ฟอร์ม หรือพฤติกรรมเปลี่ยน | หยุดตรวจคุณสมบัติจริง แทนคิดว่ารวมสำเร็จแล้วรักษาทุกอย่าง |
PDF เดิมยังเป็นเนื้อหาหน้า สคริปต์ไม่แปลงทั้งหน้าเป็นราสเตอร์ แต่ไม่พิสูจน์ว่าทุก PDF จะรอดจากการรวมใด ๆ โดยไม่เปลี่ยน ใช้สำเนาและกำหนดเนื้อหากับคุณสมบัติที่ต้องทำงานก่อนประมวลผลชุดใหญ่
เตรียมภาพที่ขาดก่อนรวม
หากโลโก้เดิมเป็น SVG สะอาด ให้เก็บไว้ หากมีแค่ราสเตอร์สีเรียบที่เหมาะสม เครื่องมือเวกเตอร์โลโก้ของ PerfectVector ช่วยเตรียมงานแก้ไขได้ก่อน Python ตรวจรูปเงา ช่อง และรายละเอียดที่กู้ แล้วรันแปลง SVG และตรวจ PDF ใหม่ ตัวอย่างนี้ใช้พาธต้นฉบับ ไม่ได้แปลง PerfectVector การแปลงเวกเตอร์ไม่รวมหน้า ตรวจลายเซ็น หรือรับประกัน svglib
คำถามที่พบบ่อย
pypdf อ่านลายน้ำ SVG โดยตรงได้หรือไม่? ตัวอย่างนี้แปลง SVG ผ่าน svglib และ ReportLab เป็น PDF ซ้อนก่อน แล้ว pypdf รวมเนื้อหา PDF ไม่ได้ตีความ SVG ในขั้นตอนนี้
ทำไมลายน้ำหายหลังหน้า PDF?
พื้นเดิมทึบอาจบังภาพซ้อนล่าง ใช้ over=True สำหรับซ้อนบนที่มองเห็นและตั้งความทึบภาพแยก ตรวจผลจริงทุกหน้า
เก็บเวกเตอร์และข้อความเดิมได้หรือไม่? ตราพาธล้วนที่ทดสอบยังเป็นพาธเติมสี และข้อความคาดยังสกัดได้ทั้งสามหน้า คุณสมบัติ SVG และ PDF นอกตัวอย่างต้องตรวจแยก
เขียน PDF ใหม่จะเก็บลายเซ็นดิจิทัลหรือไม่? อย่าใช้เป็นขั้นตอนเอกสารลงนาม เพราะเขียนไฟล์ใหม่และไม่ได้ทดสอบการเก็บหรือความถูกต้องลายเซ็น เก็บต้นฉบับลงนามและใช้กระบวนการตามข้อกำหนดนั้น
แหล่งข้อมูล
- pypdf: เพิ่มตราประทับหรือลายน้ำ — ลำดับซ้อนบนล่าง การรวม และการหมุน
- pypdf: PageObject API — กรอบหน้า ย้ายการหมุน และการรวมพร้อมแปลง
- เอกสารโครงการ svglib — SVG เป็น ReportLab หน่วย และข้อจำกัดคุณสมบัติ
- คู่มือกราฟิก ReportLab — drawing อ็อบเจ็กต์และการเรนเดอร์ PDF ในระบบกราฟิก
- pypdf 6.10.0: รุ่น header PDF — ประกาศรุ่นรองรับคุณสมบัติที่ใช้
เก็บงานต้นฉบับเมื่อมี หากเหลือแค่ราสเตอร์โลโก้ที่เหมาะสม ให้เตรียม SVG แก้ไขได้ด้วย PerfectVector ตรวจภาพแล้วตรวจ PDF ที่รวมใหม่แยกกัน
เพิ่มเติมจากบล็อก

ภาพ SVG ใน fpdf2: เก็บพาธใน PDF จาก Python
วางโลโก้ SVG ใน PDF จาก Python ด้วย fpdf2 ควบคุมขนาด ตรวจพาธที่บันทึก และวินิจฉัยข้อความหรือรูปร่างที่หายก่อนเลือกใช้ราสเตอร์แทน

SVG ใน PDFKit: วางโลโก้เวกเตอร์โดยไม่แบนเป็นภาพ
แทรกโลโก้ SVG ทั้งไฟล์ในเอกสาร PDFKit ตั้งขนาดเป็นพอยต์ และตรวจ PDF ที่บันทึกว่ามีพาธ ช่องเปิด คุณสมบัติที่หาย และภาพฝังหรือไม่
