Ajouter un filigrane SVG à un PDF existant avec Python
Fusionne un filigrane SVG avec un PDF existant grâce à svglib, ReportLab et pypdf. Vérifie opacité, recadrage, rotation, texte conservé et ordre de superposition.
Sur cette page
- Choisis d’abord l’opération sur le document
- 1. Pars d’un petit SVG pris en charge
- 2. Exécute l’exemple complet
- 3. Place l’illustration dans les coordonnées visibles de la page
- 4. Vérifie le PDF enregistré, pas seulement l’aperçu SVG
- Corrige l’échec à la bonne étape
- Prépare l’illustration manquante avant la fusion
- Questions fréquentes
- Sources
Pour ajouter un filigrane SVG à un PDF existant en Python, convertis le SVG en dessin ReportLab, rends-le dans une petite page PDF de superposition, puis fusionne cette page avec chaque page d’origine grâce à pypdf. Dimensionne et positionne la superposition d’après la zone de recadrage visible de chaque page, et normalise la rotation avant la fusion.
Utilise over=True lorsque le signe doit rester visible au-dessus d’un fond de page opaque. Un signe translucide peut tout de même être placé au-dessus. Avec over=False, la même illustration passe sous le contenu existant et peut disparaître derrière une page remplie.
L’exemple complet ci-dessous crée son propre SVG et un PDF source de trois pages. Il inclut des tailles de page différentes, une zone de recadrage à origine non nulle et une page tournée. Le résultat est un nouveau PDF ; conserve l’original séparément.
Choisis d’abord l’opération sur le document
| Point de départ | Processus utile |
|---|---|
| Un PDF existant nécessite un emblème SVG sur chaque page | Rendre une superposition SVG et la fusionner aux pages existantes, comme ci-dessous |
| Un nouveau PDF est composé à partir de zéro | Utiliser directement le processus de dessin SVG de ReportLab |
| Un SVG existant contient déjà la bonne illustration | Garder cette source vectorielle native ; la vectorisation est inutile |
| Un logo n’existe que comme image matricielle | Préparer une illustration adaptée, puis tester la fusion PDF séparément |
Le guide des filigranes de pypdf distingue le contenu placé au-dessus et au-dessous d’une page. Ici, « filigrane » désigne l’emblème visible ; over détermine son véritable ordre d’empilement. Ce n’est ni une marque de sécurité, ni une protection contre les modifications, ni un processus de document signé.
1. Pars d’un petit SVG pris en charge
Ce test utilise deux chemins remplis : un cadre triangulaire avec un centre ouvert et trois barres. Les deux chemins portent fill-opacity="0.25". Il n’y a ni texte, ni filtre, ni image incorporée, ni ressource externe.
Cette entrée limitée compte. svglib prend en charge un sous-ensemble SVG utile, mais ses limites documentées incluent une prise en charge CSS limitée et des fonctions non prises en charge comme les masques et foreignObject. Un SVG correct dans un navigateur doit encore être vérifié après conversion. Si un fichier contient des éléments matriciels, lis comment trouver les images incorporées dans un SVG avant de considérer le résultat comme entièrement vectoriel.
Une première version de cet exemple contenait du texte SVG en plus du cadre. Dans l’ensemble logiciel testé, le texte converti était peint à pleine opacité, tandis que le chemin gardait une opacité de 0.25. J’ai remplacé le texte par des formes originales en chemins et testé ce remplacement. Le résultat final établit ce cas uniquement composé de chemins ; il ne prouve pas une opacité correcte pour chaque combinaison de texte ou de style SVG.
2. Exécute l’exemple complet
L’environnement testé était Python 3.12.14, svglib 2.2.0, ReportLab 4.4.9 et pypdf 6.10.0. La documentation actuelle de pypdf décrit aussi des versions plus récentes ; distingue donc les versions testées de celle de la documentation.
Dans un environnement distinct, installe ces versions :
python -m venv .venv
source .venv/bin/activate
python -m pip install svglib==2.2.0 reportlab==4.4.9 pypdf==6.10.0Enregistre le code suivant sous watermark_demo.py. Il inclut toutes les illustrations d’entrée et crée les trois PDF ; aucun exemple PDF ou SVG manquant n’est donc à télécharger.
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)Exécute-le :
python watermark_demo.pyOuvre watermark-output/source.pdf, overlay-top.pdf et underlay.pdf. La première page doit montrer l’emblème dans la sortie superposée, mais le cacher dans la sortie sous-jacente, car son fond d’origine est opaque. Les deux autres pages mettent à l’épreuve le recadrage et la rotation au lieu de répéter la même géométrie de page.
Pour ton propre PDF ordinaire non signé, conserve make_overlay() et add_artwork(), puis appelle :
add_artwork(
Path("original.pdf"),
Path("emblem.svg"),
Path("watermarked.pdf"),
over=True
)Le générateur conserve la version d’en-tête source avec un minimum de PDF 1.4, car la superposition utilise la transparence. Dans pypdf 6.10.0, le seul clonage du document peut laisser l’en-tête PDF 1.3 par défaut du générateur, même si la source déclare une version ultérieure. L’attribution explicite évite cette rétrogradation ; ce n’est pas une validation de conformité. La documentation de l’en-tête du générateur recommande une version prenant en charge toutes les fonctions utilisées dans le fichier.
Les garde-fous rejettent les entrées chiffrées et les champs de formulaire détectés. Ils ne constituent pas un audit complet de compatibilité ou de signature. Les documents signés, formulaires, annotations, chiffrements et exigences propres au document demandent un processus et une validation distincts avant de remplacer un fichier de travail.
3. Place l’illustration dans les coordonnées visibles de la page
Un point PDF vaut 1/72 de pouce. Dans cette version de svglib, les dimensions SVG nues de 240 × 80 produisent un dessin de 180 × 60 points : un pixel CSS correspond à 0.75 point. Le code lit drawing.width et drawing.height au lieu de supposer que les pixels SVG égalent déjà les points PDF.
L’exemple donne à la fenêtre SVG 65% de la largeur de chaque page visible, conserve ses proportions et centre cette fenêtre. Il centre le rectangle de dessin déclaré, pas la masse visuelle de l’emblème. La valeur de 65% est un choix de disposition d’exemple, pas une norme de filigrane. Cette échelle fondée uniquement sur la largeur convient à l’emblème large montré ici. Un remplacement haut peut dépasser la hauteur visible ; lors de l’adaptation, borne l’échelle par la largeur et la hauteur disponibles et inspecte chaque page avant d’utiliser ton propre PDF.
| Page | État de la source | Zone de recadrage visible de sortie, en points | Largeur nominale de fenêtre SVG |
|---|---|---|---|
| 1 | Page paysage de 420 × 300 | 0, 0 à 420, 300 | 273pt |
| 2 | Page de 300 × 420, recadrée vers l’intérieur | 10, 20 à 290, 400 | 182pt |
| 3 | Page de 360 × 260 tournée de 90° | 0, 0 à 260, 360 après normalisation | 169pt |
Appeler transfer_rotation_to_content() transfère la rotation dans le contenu et les boîtes de page. Cela ne redresse pas les mots d’origine par rapport au lecteur. Le texte original de la troisième page garde l’orientation qu’il avait dans la vue affichée de la source, tandis que le nouvel emblème est placé dans les coordonnées visibles normalisées. L’API de page de pypdf recommande de transférer la rotation avant de fusionner des pages.
La superposition commence à sa propre origine. La translater de cropbox.left et cropbox.bottom la place dans la région visible de la cible même lorsque la zone de recadrage ne commence pas à zéro. expand=False empêche la fusion d’agrandir les boîtes de page.
4. Vérifie le PDF enregistré, pas seulement l’aperçu SVG

J’ai rouvert les PDF enregistrés et inspecté leurs flux de contenu, boîtes de page, rotation, texte extrait et ressources d’image, puis rendu les zones de recadrage visibles avec Poppler 26.02 à 96dpi. L’inspection finale a réussi 40 contrôles. Une comparaison dans le navigateur a aussi vérifié les empreintes des fichiers enregistrés, les rendus chargés et 12 sondes de pixels à résolution native.
| Contrôle de cet exemple | Résultat observé |
|---|---|
| Nombre de pages et texte original connu | Les trois pages et leur texte source attendu sont restés dans les deux sorties |
| Géométrie de superposition | Deux chemins remplis, 18 sommets inspectés, alpha de remplissage de 0.25 sur chaque page |
| Ressources d’image ajoutées | Aucune dans ces pages source originales et de sortie |
| Placement et boîtes de page | Correspondent aux zones de recadrage déclarées et aux calculs de transformation sérialisés ; la rotation de sortie était nulle |
| Sondes du cadre et du centre ouvert | Les pixels du cadre superposé correspondaient au mélange attendu à ±2 valeurs RVB ; les centres ouverts conservaient le fond de page |
| Sous-couche et fond opaque | Les rendus source et sous-couche de la première page différaient de zéro pixel à 96dpi |
L’audit de placement a d’abord échoué à une comparaison stricte avec des coordonnées flottantes idéales à 0.00001pt de tolérance. ReportLab sérialise les matrices de transformation avec une précision finie. J’ai conservé cet échec, calculé indépendamment les matrices sérialisées attendues et leur borne d’arrondi, puis comparé les sommets enregistrés à ces valeurs. La plus grande différence observée avec les coordonnées idéales était d’environ 0.000054pt, dans la borne dérivée de 0.000086pt. Cela vérifie un placement visuellement centré dans ces fichiers ; ce n’est pas une garantie universelle de précision.
Pour une vérification visuelle locale rapide avec Poppler installé :
pdftoppm -cropbox -r 96 -png watermark-output/overlay-top.pdf watermark-output/top
pdftoppm -cropbox -r 96 -png watermark-output/underlay.pdf watermark-output/belowInspecte chaque page à un zoom utile. Sélectionne ou extrais aussi le texte original attendu, vérifie les dimensions des pages et inspecte si le filigrane est resté en chemins ou a introduit des images. Une capture apparemment nette ne prouve pas à elle seule ces propriétés du fichier. Ce test n’a mesuré ni impression, ni compatibilité des lecteurs, ni gain de taille de fichier, ni résistance à la suppression du filigrane.
Corrige l’échec à la bonne étape
| Symptôme | Vérification ou modification |
|---|---|
| Le signe disparaît | Compare over=False à over=True ; cherche des fonds d’origine opaques |
| Le signe est mal placé sur une page recadrée | Utilise la taille et l’origine de la zone de recadrage, pas une taille de page codée en dur |
| Les pages tournées orientent le signe de manière inattendue | Normalise la rotation avant de calculer les dimensions visibles ; inspecte la vue obtenue |
| L’opacité ou le style change | Simplifie le SVG, inspecte l’état de peinture du PDF converti et reteste ce fichier précis |
| Une région photographique ou pixellisée reste | Inspecte les images incorporées dans le SVG ; une enveloppe SVG ne transforme pas un bitmap en chemins |
| Le texte, les formulaires ou le comportement du document change | Arrête et valide les véritables fonctions du document au lieu de supposer qu’une fusion réussie a tout préservé |
Le PDF original reste du contenu de page ; le script ne pixellise pas les pages entières. Cela ne prouve pas que chaque PDF d’entrée survivra inchangé à une fusion quelconque. Utilise une copie et définis les contenus et fonctions devant encore marcher avant de traiter un lot plus important.
Prépare l’illustration manquante avant la fusion
Si un logo existant est déjà un SVG propre, conserve-le. S’il ne reste qu’un logo matriciel en aplats adapté, le vectoriseur de logo de PerfectVector peut aider à préparer une illustration modifiable avant cette étape Python. Inspecte la silhouette récupérée, les trous et les petits détails, puis réexécute la conversion SVG et les contrôles du PDF enregistré. Cet exemple utilisait des chemins natifs originaux et n’a effectué aucune conversion PerfectVector. La vectorisation ne fusionne pas les pages, ne valide pas les signatures et ne garantit pas la compatibilité svglib.
Questions fréquentes
pypdf peut-il lire directement un filigrane SVG ? Cet exemple convertit d’abord le SVG en superposition PDF via svglib et ReportLab. pypdf fusionne ensuite du contenu de page PDF ; il n’interprète pas la source SVG dans cette chaîne.
Pourquoi un filigrane disparaît-il derrière ma page PDF ?
Une sous-couche peut être couverte par un fond opaque existant. Utilise over=True pour une superposition visible et règle séparément l’opacité de l’illustration. Vérifie le résultat réel sur chaque page.
Cela préserve-t-il l’illustration vectorielle et le texte original ? L’emblème testé, uniquement en chemins, est resté composé de chemins remplis et le texte original attendu est resté extractible sur les trois pages. Les fonctions SVG et PDF hors de cet exemple demandent une validation distincte.
Réécrire le PDF préserve-t-il une signature numérique ? N’utilise pas cet exemple comme processus de document signé. Il réécrit le fichier et ne teste ni la conservation ni la validité des signatures. Conserve l’original signé et utilise un processus adapté à ses exigences de signature.
Sources
- pypdf : Ajouter un tampon ou un filigrane — ordre de superposition et sous-couche, fusion et conseils de rotation.
- pypdf : API PageObject — boîtes de page, transfert de rotation et fusion transformée.
- Documentation du projet svglib — conversion SVG vers ReportLab, unités et limites des fonctions prises en charge.
- Guide graphique ReportLab — objets de dessin et rendu PDF dans le système graphique ReportLab.
- pypdf 6.10.0 : Version d’en-tête PDF — déclare une version PDF prenant en charge les fonctions de la sortie.
Conserve l’illustration native si tu en as une. S’il ne reste qu’un logo matriciel adapté, prépare un SVG modifiable avec PerfectVector, inspecte cette illustration, puis vérifie séparément le PDF nouvellement fusionné.
Plus depuis le blog

Images SVG fpdf2 : garder les tracés dans ton PDF Python
Place un logo SVG dans un PDF Python avec fpdf2, règle sa taille, examine les tracés enregistrés et diagnostique les éléments manquants avant une solution de repli matricielle.

SVG PDFKit : placer un logo vectoriel sans l’aplatir
Insère un logo SVG complet dans un document PDFKit, règle sa taille en points et examine le PDF enregistré : tracés, ouvertures, éléments manquants et images.
