Ein SVG-Wasserzeichen mit Python in eine PDF einfügen
Füge mit svglib, ReportLab und pypdf ein SVG-Wasserzeichen in eine bestehende PDF ein. Prüfe Deckkraft, beschnittene Seiten, Drehung, erhaltenen Text und Überlagerungsreihenfolge.
Auf dieser Seite
- Wähle zuerst den Dokumentvorgang
- 1. Beginne mit einer kleinen, unterstützten SVG
- 2. Führe das vollständige Beispiel aus
- 3. Platziere Grafiken in den sichtbaren Seitenkoordinaten
- 4. Prüfe die gespeicherte PDF, nicht nur die SVG-Vorschau
- Behebe den Fehler im richtigen Schritt
- Bereite fehlende Grafik vor dem Zusammenführen vor
- Häufige Fragen
- Quellen
Um einer bestehenden PDF mit Python ein SVG-Wasserzeichen hinzuzufügen, wandle die SVG in eine ReportLab-Zeichnung um, rendere diese auf eine kleine PDF-Überlagerung und füge die Überlagerung anschließend mit pypdf in jede Originalseite ein. Bestimme Größe und Position anhand der sichtbaren CropBox jeder Seite und normalisiere vor dem Zusammenführen die Seitendrehung.
Verwende over=True, wenn das Zeichen über einem deckenden Seitenhintergrund sichtbar bleiben muss. Auch ein durchscheinendes Zeichen kann oben liegen. Bei over=False kommt dieselbe Grafik unter den vorhandenen Inhalt und kann hinter einer gefüllten Seite verschwinden.
Das vollständige Beispiel unten erstellt seine eigene SVG und eine dreiseitige Quell-PDF. Es enthält unterschiedliche Seitengrößen, eine CropBox mit Ursprung ungleich null und eine gedrehte Seite. Das Ergebnis ist eine neue PDF. Behalte die Originaldatei separat.
Wähle zuerst den Dokumentvorgang
| Ausgangspunkt | Sinnvoller Ablauf |
|---|---|
| Eine bestehende PDF benötigt auf jeder Seite ein SVG-Emblem | SVG-Überlagerung rendern und wie unten in die bestehenden Seiten einfügen |
| Eine neue PDF wird von Grund auf erstellt | Den ReportLab-Ablauf für SVG-Zeichnungen direkt verwenden |
| Eine vorhandene SVG enthält bereits die richtige Grafik | Diese native Vektorquelle behalten; Nachzeichnen ist unnötig |
| Ein Logo liegt nur als Rasterbild vor | Zuerst geeignete Grafik vorbereiten, dann das PDF-Zusammenführen separat testen |
pypdfs Wasserzeichenanleitung unterscheidet Inhalte über und unter einer Seite. „Wasserzeichen“ bezeichnet hier das sichtbare Emblem; over bestimmt seine tatsächliche Stapelreihenfolge. Dies ist weder ein Sicherheitszeichen noch Manipulationsschutz oder ein Ablauf für signierte Dokumente.
1. Beginne mit einer kleinen, unterstützten SVG
Dieser Test verwendet zwei gefüllte Pfade: einen dreieckigen Rahmen mit offener Mitte und drei Balken. Beide Pfade tragen fill-opacity="0.25". Text, Filter, eingebettete Bilder oder externe Ressourcen sind nicht enthalten.
Diese enge Eingrenzung ist wichtig. svglib unterstützt eine nützliche SVG-Teilmenge, doch zu seinen dokumentierten Einschränkungen gehören begrenzte CSS-Unterstützung und nicht unterstützte Funktionen wie Masken und foreignObject. Eine im Browser richtig aussehende SVG muss nach der Umwandlung weiterhin geprüft werden. Enthält eine Datei Rasterinhalte, lies vor der Annahme eines vollständig vektorbasierten Ergebnisses, wie du eingebettete Bilder in SVG findest.
Eine erste Version dieser Beispieldatei enthielt neben dem Rahmen SVG-Text. Im getesteten Stack wurde der umgewandelte Text vollständig deckend gezeichnet, während der Pfad 0.25 Deckkraft behielt. Ich ersetzte den Text durch eigene Pfadgrafik und testete den Ersatz. Das endgültige Ergebnis belegt diesen reinen Pfadfall, nicht korrekte Deckkraft für jede SVG-Text- oder Stilkombination.
2. Führe das vollständige Beispiel aus
Die getestete Umgebung bestand aus Python 3.12.14, svglib 2.2.0, ReportLab 4.4.9 und pypdf 6.10.0. Die aktuelle pypdf-Dokumentation beschreibt auch neuere Veröffentlichungen. Halte deshalb getestete Versionen und Dokumentationsversion auseinander.
Installiere diese Versionen in einer separaten Umgebung:
python -m venv .venv
source .venv/bin/activate
python -m pip install svglib==2.2.0 reportlab==4.4.9 pypdf==6.10.0Speichere Folgendes als watermark_demo.py. Es enthält die gesamte Eingabegrafik und erstellt alle drei PDF-Dateien. Du musst deshalb keine fehlende Beispiel-PDF oder -SVG herunterladen.
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)Führe es aus:
python watermark_demo.pyÖffne watermark-output/source.pdf, overlay-top.pdf und underlay.pdf. Seite eins sollte das Emblem in der oberen Überlagerung zeigen, in der darunterliegenden Version aber verbergen, weil ihr ursprünglicher Hintergrund deckend ist. Die beiden anderen Seiten prüfen Beschnitt und Drehung, statt dieselbe Seitengeometrie zu wiederholen.
Behalte für deine eigene gewöhnliche, unsignierte PDF make_overlay() und add_artwork() und rufe anschließend Folgendes auf:
add_artwork(
Path("original.pdf"),
Path("emblem.svg"),
Path("watermarked.pdf"),
over=True
)Der Writer behält die Quell-Headerversion mit mindestens PDF 1.4 bei, weil die Überlagerung Transparenz verwendet. In pypdf 6.10.0 kann das reine Klonen des Dokuments den standardmäßigen PDF-1.3-Header des Writers hinterlassen, selbst wenn die Quelle eine spätere Version angibt. Die ausdrückliche Zuweisung verhindert diese Herabstufung; sie ist keine Konformitätsprüfung. Die Dokumentation zum Writer-Header empfiehlt eine Version, die alle in der Datei verwendeten Funktionen unterstützt.
Die Schutzprüfungen weisen verschlüsselte Eingaben und erkannte Formularfelder zurück. Sie sind kein vollständiges Kompatibilitäts- oder Signaturaudit. Signierte Dokumente, Formulare, Anmerkungen, Verschlüsselung und dokumentspezifische Anforderungen benötigen einen separaten Ablauf und eine Prüfung, bevor du eine funktionierende Datei ersetzt.
3. Platziere Grafiken in den sichtbaren Seitenkoordinaten
Ein PDF-Punkt entspricht 1/72 Zoll. In dieser svglib-Version ergeben die einheitenlosen SVG-Maße 240 × 80 eine Zeichnung von 180 × 60 Punkt: Ein CSS-Pixel entspricht 0.75 Punkt. Der Code liest drawing.width und drawing.height, statt anzunehmen, dass SVG-Pixel bereits PDF-Punkten entsprechen.
Das Beispiel macht den SVG-Ansichtsbereich 65% so breit wie jede sichtbare Seite, behält sein Seitenverhältnis und zentriert ihn. Es zentriert das deklarierte Zeichenrechteck, nicht die visuelle Masse des Emblems. Die 65% sind eine Layoutentscheidung des Beispiels, kein Wasserzeichenstandard. Diese nur auf der Breite beruhende Skalierung passt zum hier gezeigten breiten Emblem. Ein hoher Ersatz kann über die sichtbare Seitenhöhe hinausragen. Begrenze beim Anpassen der Funktion den Maßstab durch verfügbare Breite und Höhe und prüfe jede Seite vor der Verwendung deiner eigenen PDF.
| Seite | Zustand der Quelle | Sichtbare Ausgabe-CropBox in Punkt | Nominelle SVG-Ansichtsbereichsbreite |
|---|---|---|---|
| 1 | 420 × 300 große Querformatseite | 0, 0 bis 420, 300 | 273pt |
| 2 | 300 × 420 große Seite, nach innen beschnitten | 10, 20 bis 290, 400 | 182pt |
| 3 | 360 × 260 große Seite mit 90° Drehung | Nach Normalisierung 0, 0 bis 260, 360 | 169pt |
Der Aufruf transfer_rotation_to_content() überträgt die Drehung in Inhalt und Seitenboxen. Er richtet die ursprünglichen Wörter nicht relativ zum Betrachter gerade aus. Der Originaltext auf Seite drei bleibt so ausgerichtet wie in der angezeigten Quellansicht, während das neue Emblem in den normalisierten sichtbaren Koordinaten platziert wird. Die pypdf-Seiten-API empfiehlt die Übertragung der Drehung vor dem Zusammenführen von Seiten.
Die Überlagerung beginnt an ihrem eigenen Ursprung. Durch Verschieben um cropbox.left und cropbox.bottom liegt sie im sichtbaren Zielbereich, auch wenn die CropBox nicht bei null beginnt. expand=False verhindert, dass das Zusammenführen die Seitenboxen vergrößert.
4. Prüfe die gespeicherte PDF, nicht nur die SVG-Vorschau

Ich öffnete die gespeicherten PDFs erneut, prüfte Inhaltsströme, Seitenboxen, Drehung, extrahierten Text und Bildressourcen und renderte anschließend die sichtbaren CropBoxen mit Poppler 26.02 bei 96dpi. Die abschließende Dateiprüfung bestand 40 Kontrollen. Ein Browservergleich prüfte außerdem gespeicherte Dateihashes, geladene Renderings und 12 Pixelproben in nativer Auflösung.
| Prüfung dieses Beispiels | Beobachtetes Ergebnis |
|---|---|
| Seitenzahl und bekannter Originaltext | Alle drei Seiten und ihr erwarteter Quelltext blieben in beiden Ausgaben erhalten |
| Überlagerungsgeometrie | Zwei gefüllte Pfade, 18 geprüfte Eckpunkte, Alpha für Füllungen von 0.25 auf jeder Seite |
| Hinzugefügte Bildressourcen | Keine in diesen ursprünglichen Quell- und Ausgabeseiten |
| Platzierung und Seitenboxen | Entsprachen den deklarierten CropBoxen und den Berechnungen der gespeicherten Transformationen; Ausgabedrehung war null |
| Rahmen- und offene-Mitte-Proben | Rahmenpixel der oberen Überlagerung entsprachen der erwarteten Mischung innerhalb von ±2 RGB-Werten; offene Mitten behielten den Seitenhintergrund |
| Unterlegung bei deckendem Hintergrund | Die Renderings von Quelle und Unterlegung der ersten Seite unterschieden sich bei 96dpi in null Pixeln |
Die Platzierungsprüfung scheiterte zunächst an einem strengen Vergleich mit idealen Gleitkommakoordinaten bei 0.00001pt Toleranz. ReportLab speichert Transformationsmatrizen mit endlicher Genauigkeit. Ich behielt dieses fehlgeschlagene Ergebnis bei, berechnete unabhängig die erwarteten gespeicherten Matrizen samt Rundungsgrenze und verglich die gespeicherten Eckpunkte damit. Die größte beobachtete Abweichung von Idealkoordinaten betrug etwa 0.000054pt und lag innerhalb der hergeleiteten Grenze von 0.000086pt. Das prüft die visuell zentrierte Platzierung in diesen Dateien; es ist keine allgemeine Präzisionsgarantie.
Für eine schnelle lokale Sichtprüfung mit installiertem 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/belowPrüfe jede Seite mit sinnvoller Vergrößerung. Wähle oder extrahiere außerdem erwarteten Originaltext, prüfe die Seitenmaße und untersuche, ob das Wasserzeichen Pfade blieb oder Bilder einführte. Ein scharf wirkendes Bildschirmfoto allein belegt diese Dateieigenschaften nicht. Dieser Test maß weder Druck noch Viewerkompatibilität, Dateigrößeneinsparungen oder Widerstand gegen das Entfernen des Wasserzeichens.
Behebe den Fehler im richtigen Schritt
| Symptom | Prüfen oder ändern |
|---|---|
| Das Zeichen verschwindet | Vergleiche over=False mit over=True und suche nach deckenden Originalhintergründen |
| Das Zeichen sitzt auf einer beschnittenen Seite falsch | Verwende Größe und Ursprung der CropBox statt einer fest codierten Seitengröße |
| Gedrehte Seiten zeigen das Zeichen in unerwarteter Richtung | Normalisiere die Drehung vor dem Berechnen der sichtbaren Maße und prüfe die resultierende Ansicht |
| Deckkraft oder Stil verändern sich | Vereinfache die SVG, prüfe den Farbauftragszustand der umgewandelten PDF und teste genau diese Datei erneut |
| Ein fotoartiger oder pixeliger Bereich bleibt | Prüfe die SVG auf eingebettete Bilder; eine SVG-Hülle macht ein Bitmap nicht zu Pfaden |
| Text, Formulare oder Dokumentverhalten verändern sich | Halte an und prüfe die tatsächlichen Dokumentfunktionen, statt anzunehmen, dass ein erfolgreiches Zusammenführen alles erhalten hat |
Die Original-PDF bleibt Seiteninhalt; das Skript rastert keine vollständigen Seiten. Das beweist nicht, dass jede Eingabe-PDF eine beliebige Zusammenführung unverändert übersteht. Verwende eine Kopie und definiere vor einer größeren Stapelverarbeitung die Inhalte und Dokumentfunktionen, die weiter funktionieren müssen.
Bereite fehlende Grafik vor dem Zusammenführen vor
Wenn ein vorhandenes Logo bereits eine saubere SVG ist, behalte es. Ist nur ein geeignetes flächiges Rasterlogo übrig, kann PerfectVectors Logovektorisierer vor diesem Python-Schritt bearbeitbare Grafiken vorbereiten. Prüfe gewonnene Silhouette, Löcher und kleine Details und wiederhole anschließend SVG-Umwandlung und Prüfungen der gespeicherten PDF. Das Beispiel hier verwendete eigene native Pfade und führte keine PerfectVector-Konvertierung durch. Vektorisierung führt keine Seiten zusammen, validiert keine Signaturen und garantiert keine svglib-Kompatibilität.
Häufige Fragen
Kann pypdf ein SVG-Wasserzeichen direkt lesen? Dieses Beispiel wandelt SVG zuerst mit svglib und ReportLab in eine PDF-Überlagerung um. pypdf führt anschließend PDF-Seiteninhalte zusammen; es interpretiert in diesem Ablauf nicht die SVG-Quelle.
Warum verschwindet ein Wasserzeichen hinter meiner PDF-Seite?
Eine Unterlegung kann von einem bestehenden deckenden Hintergrund verdeckt werden. Verwende over=True für eine sichtbare obere Überlagerung und lege die Grafikdeckkraft separat fest. Prüfe das tatsächliche Ergebnis auf jeder Seite.
Erhält dies Vektorgrafiken und Originaltext? Das getestete reine Pfademblem blieb als gefüllte Pfade erhalten, und erwarteter Originaltext blieb auf allen drei Seiten extrahierbar. SVG-Funktionen und PDF-Dokumentfunktionen außerhalb dieses Beispiels benötigen eine separate Prüfung.
Bleibt eine digitale Signatur beim Neuschreiben der PDF erhalten? Verwende dieses Beispiel nicht als Ablauf für signierte Dokumente. Es schreibt die Datei neu und testet weder Signaturerhalt noch Gültigkeit. Behalte das signierte Original und nutze einen zu seinen Signaturanforderungen passenden Ablauf.
Quellen
- pypdf: Stempel oder Wasserzeichen hinzufügen — Überlagerungs- und Unterlegungsreihenfolge, Zusammenführen und Drehungshinweise.
- pypdf: PageObject-API — Seitenboxen, Drehungsübertragung und transformiertes Zusammenführen.
- svglib-Projektdokumentation — SVG-zu-ReportLab-Umwandlung, Einheiten und Grenzen unterstützter Funktionen.
- ReportLab-Grafikanleitung — Zeichenobjekte und PDF-Rendering im ReportLab-Grafiksystem.
- pypdf 6.10.0: PDF-Headerversion — deklariert eine PDF-Version, die die verwendeten Ausgabefunktionen unterstützt.
Behalte native Grafiken, wenn du sie hast. Ist nur ein geeignetes Rasterlogo übrig, bereite mit PerfectVector eine bearbeitbare SVG vor, prüfe die Grafik und verifiziere die neu zusammengeführte PDF anschließend separat.
Mehr aus dem Blog

fpdf2-SVG-Bilder: Pfade in deiner Python-PDF erhalten
Platziere ein SVG-Logo mit fpdf2 in einer Python-PDF, bestimme seine Größe, prüfe gespeicherte Pfade und untersuche fehlenden Text oder Formen vor einem Rasterersatz.

PDFKit-SVG: Ein Vektorlogo ohne Rasterisierung platzieren
Füge ein vollständiges SVG-Logo in ein PDFKit-Dokument ein, bestimme seine Größe in Punkten und prüfe die gespeicherte PDF auf Pfade, Aussparungen, fehlende Merkmale und eingebettete Bilder.
