Adicione uma marca-d'água SVG a um PDF com Python
Mescle uma marca-d'água SVG a um PDF com svglib, ReportLab e pypdf. Confira opacidade, páginas recortadas, rotação, texto preservado e ordem da sobreposição.
Nesta página
- Escolha primeiro a operação no documento
- 1. Comece com um SVG pequeno e suportado
- 2. Execute o exemplo completo
- 3. Posicione a arte nas coordenadas visíveis da página
- 4. Confira o PDF salvo, não apenas a prévia SVG
- Corrija a falha na etapa certa
- Prepare a arte ausente antes de mesclar
- Perguntas frequentes
- Fontes
Para adicionar uma marca-d'água SVG a um PDF existente em Python, converta o SVG em um objeto de desenho do ReportLab, renderize-o em um pequeno PDF de sobreposição e mescle essa camada a cada página original com pypdf. Defina tamanho e posição pelo quadro de recorte visível de cada página e normalize a rotação antes de mesclar.
Use over=True quando a marca precisar continuar visível acima de um fundo opaco de página. Uma marca translúcida também pode ficar por cima. Com over=False, a mesma arte vai para baixo do conteúdo existente e pode desaparecer atrás de uma página preenchida.
O exemplo completo abaixo cria seu próprio SVG e um PDF de origem com três páginas. Ele inclui tamanhos de página diferentes, um quadro de recorte com origem diferente de zero e uma página girada. O resultado é um novo PDF; preserve o original separadamente.
Escolha primeiro a operação no documento
| Ponto de partida | Fluxo útil |
|---|---|
| Um PDF existente precisa de um emblema SVG em cada página | Renderizar uma camada SVG e mesclá-la às páginas existentes, como abaixo |
| Um novo PDF é composto do zero | Usar diretamente o fluxo de desenho SVG com ReportLab |
| Um SVG existente já contém a arte correta | Preservar essa origem vetorial nativa; o traçado é desnecessário |
| Um logo só existe como imagem raster | Preparar primeiro a arte adequada e testar a mesclagem PDF separadamente |
O guia de marca-d'água do pypdf diferencia conteúdo colocado acima e abaixo de uma página. Aqui, “marca-d'água” significa o emblema visível; over determina sua ordem real de empilhamento. Não é uma marca de segurança, um método contra adulteração ou um fluxo para documentos assinados.
1. Comece com um SVG pequeno e suportado
Este teste usa dois caminhos preenchidos: uma moldura triangular com centro aberto e três barras. Os dois caminhos têm fill-opacity="0.25". Não há texto, filtros, imagens incorporadas ou recursos externos.
Essa entrada restrita importa. O svglib suporta um subconjunto útil de SVG, mas suas limitações documentadas incluem suporte limitado a CSS e recursos não suportados, como máscaras e foreignObject. Um SVG que parece correto no navegador ainda precisa ser verificado após a conversão. Se o arquivo contém raster, leia como encontrar imagens incorporadas em SVG antes de tratar o resultado como inteiramente vetorial.
Uma versão inicial desta amostra continha texto SVG além da moldura. No conjunto de ferramentas testado, o texto convertido era pintado com opacidade total, enquanto o caminho preservava opacidade 0,25. Substituí esse texto por arte original em caminhos e testei a substituição. O resultado final comprova este caso apenas com caminhos; não comprova opacidade correta para toda combinação de texto ou estilo SVG.
2. Execute o exemplo completo
O ambiente testado foi Python 3.12.14, svglib 2.2.0, ReportLab 4.4.9 e pypdf 6.10.0. A documentação atual do pypdf também descreve versões mais novas; portanto, diferencie as versões testadas da versão da documentação.
Em um ambiente separado, instale essas versões:
python -m venv .venv
source .venv/bin/activate
python -m pip install svglib==2.2.0 reportlab==4.4.9 pypdf==6.10.0Salve o conteúdo abaixo como watermark_demo.py. Ele inclui toda a arte de entrada e cria os três PDFs, por isso não falta um PDF ou SVG de exemplo para baixar.
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)Execute:
python watermark_demo.pyAbra watermark-output/source.pdf, overlay-top.pdf e underlay.pdf. A primeira página deve mostrar o emblema na saída com sobreposição superior, mas ocultá-lo na inferior, porque seu fundo original é opaco. As outras duas páginas exercitam recorte e rotação em vez de repetir a mesma geometria de página.
Para seu próprio PDF comum e não assinado, preserve make_overlay() e add_artwork() e chame:
add_artwork(
Path("original.pdf"),
Path("emblem.svg"),
Path("watermarked.pdf"),
over=True
)O gravador preserva a versão do cabeçalho de origem com um mínimo de PDF 1.4, porque a sobreposição usa transparência. No pypdf 6.10.0, apenas clonar o documento pode deixar o cabeçalho padrão PDF 1.3 do gravador, mesmo quando a origem declara uma versão posterior. A atribuição explícita evita esse rebaixamento; não é uma validação de conformidade. A documentação do cabeçalho do gravador recomenda uma versão que suporte todos os recursos usados no arquivo.
As verificações de proteção rejeitam entradas criptografadas e campos de formulário detectados. Não são uma auditoria completa de compatibilidade ou assinaturas. Documentos assinados, formulários, anotações, criptografia e requisitos específicos exigem outro fluxo e validação antes de substituir um arquivo em uso.
3. Posicione a arte nas coordenadas visíveis da página
Um ponto PDF equivale a 1/72 de polegada. Nesta versão do svglib, as dimensões SVG sem unidade de 240 × 80 produzem um desenho de 180 × 60 pontos: um pixel CSS corresponde a 0,75 ponto. O código lê drawing.width e drawing.height em vez de supor que pixels SVG já equivalem a pontos PDF.
O exemplo deixa o viewport SVG com 65% da largura visível de cada página, preserva sua proporção e centraliza esse viewport. Ele centraliza o retângulo de desenho declarado, não a massa visual do emblema. O valor de 65% é uma escolha de layout do exemplo, não um padrão de marca-d'água. Essa escala baseada apenas na largura serve para o emblema largo mostrado. Uma substituição alta pode ultrapassar a altura visível da página; ao adaptar a função, limite a escala tanto pela largura quanto pela altura disponíveis e inspecione cada página antes de usar seu próprio PDF.
| Página | Condição da origem | Quadro de recorte visível da saída, em pontos | Largura nominal do viewport SVG |
|---|---|---|---|
| 1 | Página horizontal de 420 × 300 | De 0, 0 a 420, 300 | 273pt |
| 2 | Página de 300 × 420, recortada para dentro | De 10, 20 a 290, 400 | 182pt |
| 3 | Página de 360 × 260 com rotação de 90° | De 0, 0 a 260, 360 após normalização | 169pt |
Chamar transfer_rotation_to_content() transfere a rotação para o conteúdo e os quadros de página. Não endireita as palavras originais em relação ao leitor. O texto original da terceira página mantém a orientação exibida na origem, enquanto o novo emblema é colocado nas coordenadas visíveis normalizadas. A API de página do pypdf recomenda transferir a rotação antes de mesclar páginas.
A sobreposição começa em sua própria origem. Transladá-la por cropbox.left e cropbox.bottom a coloca na região visível de destino mesmo quando o quadro de recorte começa fora de zero. expand=False impede a mesclagem de ampliar os quadros de página.
4. Confira o PDF salvo, não apenas a prévia SVG

Reabri os PDFs salvos e inspecionei fluxos de conteúdo, quadros de página, rotação, texto extraído e recursos de imagem; depois renderizei os quadros de recorte visíveis com Poppler 26.02 a 96dpi. A inspeção final passou em 40 verificações. Uma comparação no navegador também verificou hashes dos arquivos salvos, renderizações carregadas e 12 amostras de pixels em resolução nativa.
| Verificação nesta amostra | Resultado observado |
|---|---|
| Contagem de páginas e texto original conhecido | As três páginas e o texto esperado da origem permaneceram nas duas saídas |
| Geometria da sobreposição | Dois caminhos preenchidos, 18 vértices inspecionados e alfa de preenchimento 0,25 em cada página |
| Recursos de imagem adicionados | Nenhum nestas páginas originais e de saída |
| Posição e quadros de página | Corresponderam aos quadros declarados e cálculos de transformação serializados; a rotação de saída era zero |
| Amostras da moldura e do centro aberto | Pixels da moldura superior corresponderam à mistura esperada dentro de ±2 valores RGB; centros abertos preservaram o fundo |
| Camada inferior em fundo opaco | As renderizações de origem e inferior da primeira página diferiram em zero pixels a 96dpi |
A auditoria de posicionamento inicialmente falhou em uma comparação rigorosa com coordenadas ideais de ponto flutuante, com tolerância de 0,00001pt. O ReportLab serializa matrizes de transformação com precisão finita. Preservei esse resultado de falha, calculei de forma independente as matrizes serializadas esperadas e seu limite de arredondamento e comparei os vértices salvos com esses valores. A maior diferença observada em relação às coordenadas ideais foi de cerca de 0,000054pt, dentro do limite derivado de 0,000086pt. Isso verifica o posicionamento visualmente centralizado nestes arquivos; não é uma garantia universal de precisão.
Para uma verificação visual local rápida com Poppler instalado:
pdftoppm -cropbox -r 96 -png watermark-output/overlay-top.pdf watermark-output/top
pdftoppm -cropbox -r 96 -png watermark-output/underlay.pdf watermark-output/belowInspecione cada página com um zoom útil. Também selecione ou extraia o texto original esperado, confira as dimensões e inspecione se a marca-d'água continuou como caminhos ou introduziu imagens. Uma captura nítida sozinha não comprova essas propriedades. Este teste não mediu impressão, compatibilidade de visualizadores, economia de tamanho ou resistência à remoção da marca-d'água.
Corrija a falha na etapa certa
| Sintoma | O que verificar ou mudar |
|---|---|
| A marca desaparece | Compare over=False com over=True; procure fundos originais opacos |
| A marca fica fora de posição em uma página recortada | Use tamanho e origem do quadro de recorte, não um tamanho de página fixo |
| Páginas giradas orientam a marca de modo inesperado | Normalize a rotação antes de calcular as dimensões visíveis; inspecione a vista resultante |
| Opacidade ou estilo mudam | Simplifique o SVG, inspecione o estado de pintura do PDF convertido e teste novamente esse arquivo exato |
| Permanece uma região fotográfica ou pixelada | Inspecione o SVG em busca de imagens incorporadas; uma embalagem SVG não transforma bitmap em caminhos |
| Texto, formulários ou comportamento do documento mudam | Pare e valide os recursos reais do documento em vez de supor que uma mesclagem bem-sucedida preservou tudo |
O PDF original continua sendo conteúdo de página; o script não rasteriza páginas inteiras. Isso não prova que todo PDF de entrada sobreviverá inalterado a uma mesclagem arbitrária. Use uma cópia e defina quais conteúdos e recursos precisam continuar funcionando antes de processar um lote maior.
Prepare a arte ausente antes de mesclar
Se um logo existente já é um SVG limpo, preserve-o. Se só resta um logo raster plano adequado, o vetorizador de logos do PerfectVector pode ajudar a preparar arte editável antes desta etapa em Python. Inspecione silhueta, furos e pequenos detalhes recuperados; depois repita a conversão SVG e as verificações do PDF salvo. Este exemplo usou caminhos nativos originais e não executou uma conversão com o PerfectVector. A vetorização não mescla páginas, não valida assinaturas nem garante compatibilidade com svglib.
Perguntas frequentes
O pypdf pode ler uma marca-d'água SVG diretamente? Este exemplo primeiro converte o SVG por svglib e ReportLab em uma sobreposição PDF. O pypdf então mescla conteúdo de páginas PDF; não interpreta o código-fonte SVG nesta sequência.
Por que uma marca-d'água desaparece atrás da página PDF?
Uma camada inferior pode ser coberta por um fundo opaco existente. Use over=True para uma sobreposição superior visível e defina a opacidade da arte separadamente. Confira o resultado real em cada página.
Isso preserva a arte vetorial e o texto original? O emblema testado, composto apenas por caminhos, permaneceu como caminhos preenchidos, e o texto original esperado continuou extraível nas três páginas. Recursos SVG e PDF fora desta amostra precisam de validação separada.
Regravar o PDF preserva uma assinatura digital? Não use este exemplo como fluxo para documentos assinados. Ele regrava o arquivo e não testa a preservação ou validade de assinaturas. Preserve o original assinado e use um fluxo apropriado aos requisitos de assinatura.
Fontes
- pypdf: Adicionar um carimbo ou marca-d'água — Ordem superior e inferior, mesclagem e orientação de rotação.
- pypdf: API PageObject — Quadros de página, transferência de rotação e mesclagem transformada.
- Documentação do projeto svglib — Conversão SVG para ReportLab, unidades e limites dos recursos suportados.
- Guia de gráficos do ReportLab — Objetos de desenho e renderização PDF no sistema gráfico ReportLab.
- pypdf 6.10.0: Versão do cabeçalho PDF — Declara uma versão PDF que suporta os recursos usados na saída.
Preserve a arte nativa quando já tiver uma. Quando só restar um logo raster plano adequado, prepare um SVG editável com o PerfectVector, inspecione a arte e verifique separadamente o PDF recém-mesclado.
Mais do blog

Imagens SVG no fpdf2: traçados no PDF em Python
Insira um logo SVG em um PDF Python com fpdf2, controle seu tamanho, inspecione os traçados salvos e diagnostique texto ou formas ausentes antes de usar raster.

SVG no PDFKit: insira um logo vetorial sem achatar
Insira um logo SVG completo no PDFKit, defina seu tamanho em pontos e inspecione o PDF salvo em busca de traçados, aberturas, recursos ausentes e imagens incorporadas.
