Thêm dấu chìm SVG vào PDF có sẵn bằng Python
Ghép dấu chìm SVG vào PDF có sẵn bằng svglib, ReportLab và pypdf. Kiểm tra độ đục, trang cắt xén, xoay, văn bản giữ lại và thứ tự lớp phủ.
Trên trang này
Để thêm dấu chìm SVG vào PDF có sẵn bằng Python, chuyển SVG thành bản vẽ ReportLab, kết xuất lên một lớp phủ PDF nhỏ rồi ghép lớp đó vào từng trang gốc bằng pypdf. Tính kích thước và vị trí từ hộp cắt xén nhìn thấy của từng trang, đồng thời chuẩn hóa xoay trang trước khi ghép.
Dùng over=True khi dấu cần hiện trên nền trang đục. Dấu bán trong suốt vẫn có thể nằm trên. Với over=False, cùng thiết kế nằm dưới nội dung hiện có và có thể biến mất sau trang có màu nền.
Ví dụ đầy đủ bên dưới tạo SVG riêng và PDF nguồn ba trang. Nó gồm nhiều cỡ trang, hộp cắt xén có gốc khác không và trang xoay. Kết quả là PDF mới; giữ tệp gốc riêng.
Chọn thao tác tài liệu trước
| Điểm bắt đầu | Quy trình hữu ích |
|---|---|
| PDF có sẵn cần biểu trưng SVG trên mỗi trang | Kết xuất lớp phủ SVG và ghép vào trang hiện có như bên dưới |
| Tạo PDF mới từ đầu | Dùng trực tiếp quy trình vẽ SVG ReportLab |
| SVG hiện có đã chứa đúng hình | Giữ nguồn vector gốc đó; không cần đồ lại |
| Logo chỉ có ảnh raster | Chuẩn bị hình phù hợp trước, rồi thử riêng việc ghép PDF |
Hướng dẫn dấu chìm của pypdf phân biệt nội dung đặt trên và dưới trang. Ở đây, “dấu chìm” là biểu trưng nhìn thấy; over xác định thứ tự lớp thực tế. Đây không phải dấu bảo mật, phương pháp chống sửa đổi hay quy trình tài liệu đã ký.
1. Bắt đầu với SVG nhỏ được hỗ trợ
Phép thử dùng hai đường có tô: khung tam giác tâm rỗng và ba thanh. Cả hai mang fill-opacity="0.25". Không có văn bản, bộ lọc, ảnh nhúng hoặc tài nguyên ngoài.
Đầu vào hẹp đó quan trọng. svglib hỗ trợ tập con SVG hữu ích, nhưng giới hạn trong tài liệu gồm CSS hạn chế và tính năng chưa hỗ trợ như mặt nạ, foreignObject. SVG trông đúng trong trình duyệt vẫn cần kiểm tra sau chuyển đổi. Nếu tệp có raster, đọc cách tìm ảnh nhúng trong SVG trước khi coi kết quả hoàn toàn vector.
Bản mẫu đầu có văn bản SVG cùng khung. Trong bộ công cụ thử, văn bản chuyển đổi được tô đục hoàn toàn trong khi đường dẫn giữ độ đục 0,25. Tôi thay văn bản bằng hình đường dẫn tự tạo và thử bản thay. Kết quả cuối xác nhận trường hợp chỉ đường dẫn này; không xác nhận độ đục đúng cho mọi kết hợp văn bản và kiểu SVG.
2. Chạy ví dụ đầy đủ
Môi trường được thử là Python 3.12.14, svglib 2.2.0, ReportLab 4.4.9 và pypdf 6.10.0. Tài liệu pypdf hiện hành cũng mô tả bản mới hơn, nên phân biệt phiên bản thử với phiên bản tài liệu.
Trong môi trường riêng, cài các phiên bản đó:
python -m venv .venv
source .venv/bin/activate
python -m pip install svglib==2.2.0 reportlab==4.4.9 pypdf==6.10.0Lưu mã sau thành watermark_demo.py. Nó gồm mọi hình đầu vào và tạo cả ba tệp PDF, nên không thiếu PDF hoặc SVG mẫu cần tải.
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)Chạy:
python watermark_demo.pyMở watermark-output/source.pdf, overlay-top.pdf và underlay.pdf. Trang một cần hiện biểu trưng trong bản phủ trên nhưng giấu nó trong bản lót dưới vì nền gốc đục. Hai trang còn lại kiểm tra cắt xén và xoay thay vì lặp cùng hình học trang.
Với PDF thông thường chưa ký của bạn, giữ make_overlay() và add_artwork(), rồi gọi:
add_artwork(
Path("original.pdf"),
Path("emblem.svg"),
Path("watermarked.pdf"),
over=True
)Bộ ghi giữ phiên bản tiêu đề nguồn tối thiểu PDF 1.4 vì lớp phủ dùng độ trong suốt. Trong pypdf 6.10.0, chỉ nhân bản tài liệu có thể để tiêu đề mặc định PDF 1.3 của bộ ghi, ngay cả khi nguồn khai báo bản cao hơn. Phép gán rõ ràng tránh hạ phiên bản đó; không phải xác minh tuân thủ. Tài liệu tiêu đề của bộ ghi khuyến nghị phiên bản hỗ trợ mọi tính năng dùng trong tệp.
Các điều kiện bảo vệ từ chối đầu vào mã hóa và trường biểu mẫu được phát hiện. Chúng không phải kiểm toán tương thích hoặc chữ ký đầy đủ. Tài liệu đã ký, biểu mẫu, chú thích, mã hóa và yêu cầu riêng của tài liệu cần quy trình cùng xác minh riêng trước khi thay tệp đang dùng.
3. Đặt hình theo tọa độ trang nhìn thấy
Một point PDF bằng 1/72 inch. Trong phiên bản svglib này, kích thước SVG 240 × 80 không kèm đơn vị tạo bản vẽ 180 × 60 point: một pixel CSS thành 0,75 point. Mã đọc drawing.width và drawing.height thay vì mặc định pixel SVG bằng point PDF.
Ví dụ đặt vùng hiển thị SVG bằng 65% chiều rộng mỗi trang nhìn thấy, giữ tỷ lệ và căn giữa vùng đó. Nó căn hình chữ nhật bản vẽ khai báo, không phải khối lượng thị giác của biểu trưng. Giá trị 65% là lựa chọn bố cục ví dụ, không phải tiêu chuẩn dấu chìm. Tỷ lệ chỉ theo chiều rộng phù hợp biểu trưng ngang ở đây. Hình thay thế cao có thể vượt chiều cao trang; khi điều chỉnh hàm, giới hạn tỷ lệ theo cả chiều rộng và chiều cao có sẵn, rồi kiểm tra mọi trang trước khi dùng PDF của bạn.
| Trang | Tình trạng nguồn | Hộp cắt nhìn thấy đầu ra, theo point | Chiều rộng vùng SVG danh nghĩa |
|---|---|---|---|
| 1 | Trang ngang 420 × 300 | 0, 0 đến 420, 300 | 273pt |
| 2 | Trang 300 × 420 cắt xén vào trong | 10, 20 đến 290, 400 | 182pt |
| 3 | Trang 360 × 260 xoay 90° | 0, 0 đến 260, 360 sau chuẩn hóa | 169pt |
Gọi transfer_rotation_to_content() chuyển xoay vào nội dung và các hộp trang. Nó không làm thẳng chữ gốc theo người xem. Văn bản trang ba vẫn theo hướng hiển thị của nguồn, còn biểu trưng mới được đặt trong tọa độ nhìn thấy đã chuẩn hóa. API trang pypdf khuyến nghị chuyển xoay trước khi ghép trang.
Lớp phủ bắt đầu tại gốc riêng. Dịch theo cropbox.left và cropbox.bottom đưa nó vào vùng nhìn thấy của đích ngay cả khi hộp cắt bắt đầu khác không. expand=False ngăn phép ghép nới các hộp trang.
4. Kiểm tra PDF đã lưu, không chỉ bản xem SVG

Tôi mở lại PDF đã lưu và kiểm tra luồng nội dung, hộp trang, xoay, văn bản trích xuất cùng tài nguyên ảnh, rồi kết xuất hộp cắt nhìn thấy bằng Poppler 26.02 ở 96dpi. Kiểm tra tệp cuối vượt qua 40 mục. So sánh trình duyệt cũng xác minh mã băm tệp đã lưu, ảnh kết xuất tải vào và 12 điểm dò pixel ở độ phân giải gốc.
| Kiểm tra trên mẫu này | Kết quả quan sát |
|---|---|
| Số trang và văn bản gốc đã biết | Cả ba trang và văn bản nguồn dự kiến còn trong cả hai bản |
| Hình học lớp phủ | Hai đường có tô, 18 đỉnh được kiểm tra, alpha phần tô 0,25 trên mỗi trang |
| Tài nguyên ảnh thêm | Không có trong các trang nguồn và đầu ra tự tạo này |
| Vị trí và hộp trang | Khớp hộp cắt khai báo và tính toán phép biến đổi đã ghi; xoay đầu ra bằng không |
| Điểm dò khung và tâm rỗng | Pixel khung phủ trên khớp hòa màu dự kiến trong ±2 giá trị RGB; tâm rỗng giữ nền trang |
| Lớp lót dưới nền đục | Kết xuất nguồn và lớp lót trang một khác không pixel ở 96dpi |
Kiểm toán vị trí ban đầu không đạt so sánh nghiêm ngặt với tọa độ dấu phẩy động lý tưởng ở dung sai 0,00001pt. ReportLab ghi ma trận biến đổi với độ chính xác hữu hạn. Tôi giữ kết quả thất bại, tính độc lập ma trận ghi dự kiến và giới hạn làm tròn, rồi so đỉnh đã lưu với các giá trị đó. Chênh lệch tọa độ lý tưởng lớn nhất quan sát khoảng 0,000054pt, nằm trong giới hạn suy ra 0,000086pt. Điều này kiểm tra vị trí nhìn cân giữa trong các tệp này, không bảo đảm độ chính xác chung.
Để kiểm tra trực quan nhanh cục bộ khi đã cài 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/belowKiểm tra mọi trang ở mức phóng hữu ích. Cũng chọn hoặc trích xuất văn bản gốc dự kiến, kiểm tra kích thước trang và xem dấu chìm còn là đường hay thêm ảnh. Chỉ ảnh chụp sắc nét không xác nhận được các thuộc tính tệp đó. Phép thử không đo in ấn, tương thích trình xem, tiết kiệm dung lượng hoặc khả năng chống xóa dấu chìm.
Sửa lỗi ở đúng giai đoạn
| Triệu chứng | Kiểm tra hoặc thay đổi |
|---|---|
| Dấu biến mất | So over=False với over=True; tìm nền gốc đục |
| Dấu lệch trên trang cắt xén | Dùng kích thước và gốc hộp cắt, không dùng cỡ trang cố định |
| Trang xoay làm dấu theo hướng bất ngờ | Chuẩn hóa xoay trước khi tính kích thước nhìn thấy; kiểm tra kết quả |
| Độ đục hoặc kiểu đổi | Đơn giản SVG, kiểm tra trạng thái tô của PDF chuyển đổi và thử lại chính tệp đó |
| Vùng giống ảnh chụp hoặc có pixel vẫn còn | Kiểm tra ảnh nhúng trong SVG; lớp bọc SVG không biến bitmap thành đường |
| Văn bản, biểu mẫu hoặc hành vi tài liệu đổi | Dừng và xác minh tính năng thực của tài liệu thay vì cho rằng ghép thành công đã giữ mọi thứ |
PDF gốc vẫn là nội dung trang; mã không raster hóa toàn trang. Điều đó không chứng minh mọi PDF đầu vào sẽ qua phép ghép tùy ý mà giữ nguyên. Dùng bản sao và xác định nội dung cùng tính năng phải tiếp tục hoạt động trước khi xử lý lô lớn.
Chuẩn bị hình còn thiếu trước khi ghép
Nếu logo hiện có đã là SVG sạch, giữ nó. Nếu chỉ còn logo raster phẳng phù hợp, công cụ vector hóa logo của PerfectVector có thể giúp chuẩn bị hình chỉnh được trước bước Python. Kiểm tra hình bóng, lỗ và chi tiết nhỏ khôi phục, rồi chạy lại chuyển đổi SVG cùng kiểm tra PDF đã lưu. Ví dụ dùng đường gốc tự tạo và không chuyển đổi PerfectVector. Vector hóa không ghép trang, xác minh chữ ký hoặc bảo đảm tương thích svglib.
Câu hỏi thường gặp
pypdf có đọc trực tiếp dấu chìm SVG không? Ví dụ trước hết chuyển SVG qua svglib và ReportLab thành lớp phủ PDF. pypdf sau đó ghép nội dung trang PDF; nó không diễn giải nguồn SVG trong quy trình này.
Vì sao dấu chìm biến mất sau trang PDF?
Lớp lót có thể bị nền đục hiện có che. Dùng over=True cho lớp phủ trên nhìn thấy và đặt độ đục hình riêng. Kiểm tra kết quả thực trên từng trang.
Cách này có giữ vector và văn bản gốc không? Biểu trưng chỉ đường đã thử vẫn là đường có tô, và văn bản gốc dự kiến vẫn trích xuất được trên cả ba trang. Tính năng SVG và tài liệu PDF ngoài mẫu cần xác minh riêng.
Ghi lại PDF có giữ chữ ký số không? Đừng dùng ví dụ như quy trình tài liệu đã ký. Nó ghi lại tệp và không kiểm tra bảo toàn hoặc hiệu lực chữ ký. Giữ bản gốc đã ký và dùng quy trình phù hợp yêu cầu ký của nó.
Nguồn
- pypdf: Thêm con dấu hoặc dấu chìm — Thứ tự phủ trên, lót dưới, ghép và hướng dẫn xoay.
- pypdf: PageObject API — Hộp trang, chuyển xoay và hành vi ghép có biến đổi.
- Tài liệu dự án svglib — Chuyển SVG sang ReportLab, đơn vị và giới hạn tính năng hỗ trợ.
- Hướng dẫn đồ họa ReportLab — Đối tượng bản vẽ và kết xuất PDF trong hệ đồ họa ReportLab.
- pypdf 6.10.0: Phiên bản tiêu đề PDF — Khai báo phiên bản PDF hỗ trợ tính năng dùng trong đầu ra.
Giữ hình gốc khi bạn có. Khi chỉ còn logo raster phù hợp, chuẩn bị SVG chỉnh được bằng PerfectVector, kiểm tra hình rồi xác minh riêng PDF vừa ghép.
Thêm từ blog

Ảnh SVG trong fpdf2: Giữ đường dẫn trong PDF Python
Đặt logo SVG vào PDF Python bằng fpdf2, kiểm soát kích thước, kiểm tra đường dẫn đã lưu và tìm nguyên nhân mất chữ hoặc hình trước khi dùng ảnh raster dự phòng.

SVG PDFKit: Đặt logo vector mà không làm phẳng
Chèn logo SVG hoàn chỉnh vào tài liệu PDFKit, đặt kích thước theo point và kiểm tra PDF đã lưu về đường dẫn, hình có lỗ, tính năng bị mất và ảnh nhúng.
