Adicionar marcas d'água e planos de fundo com texto e imagem às páginas
Visão geral
Seção intitulada “Visão geral”Você pode adicionar uma marca “DRAFT” ou “CONFIDENTIAL” a cada página ou colocar um logotipo sutil atrás do conteúdo. Esta receita adiciona os dois às páginas do core do NextPDF com a superfície pública do documento: setAlpha() para transparência, startTransform() / rotate() / stopTransform() para um carimbo diagonal, text() para a marca e image() para um plano de fundo raster.
Uma marca d’água e um plano de fundo diferem em uma única escolha: a ordem de pintura.
- Plano de fundo: pinte primeiro e, depois, escreva o conteúdo da página sobre ele. A marca fica atrás do texto.
- Marca d’água sobreposta: escreva primeiro o conteúdo da página e, depois, pinte a marca sobre ele. A marca fica por cima.
O NextPDF pinta o conteúdo na ordem em que você o chama; portanto, a ordem das chamadas define a ordem das camadas. Não existe um “modo de plano de fundo” separado. Você escolhe a camada ao decidir quando desenhar.
Pré-requisitos: uma instalação do core (composer require nextpdf/core:^3) e, para um plano de fundo de imagem, um arquivo raster legível (PNG, JPEG ou WebP) em disco. Todo o pipeline é executado no processo, sem navegador headless nem chamada de rede.
Instalação
Seção intitulada “Instalação”composer require nextpdf/core:^3Visão geral conceitual
Seção intitulada “Visão geral conceitual”Cada marca que você adiciona é conteúdo de página comum, desenhado por meio de um estado gráfico. Três partes da superfície pública trabalham juntas para produzir uma marca d’água:
-
Transparência.
setAlpha(float $alpha, BlendMode $mode = BlendMode::Normal)define a opacidade de preenchimento de tudo o que você desenhar em seguida, de0.0(invisível) a1.0(opaco). Uma marca d’água geralmente funciona melhor entre0.1e0.3, para que o conteúdo abaixo continue legível. O modo de mesclagem vem do enumNextPDF\Graphics\BlendMode. Por exemplo,BlendMode::Multiplyescurece as áreas em que a marca se sobrepõe ao conteúdo. -
Rotação. Um carimbo diagonal é um texto rotacionado em torno de um ponto de pivô.
startTransform()salva o estado gráfico,rotate(float $angle, float $x, float $y)gira o sistema de coordenadas no sentido anti-horário em torno de($x, $y)estopTransform()restaura o estado salvo. Envolver a marca em um bloco de transform impede que a rotação e o alpha afetem o restante da página. -
A própria marca.
text(float $x, float $y, string $text)escreve uma string em uma posição absoluta na fonte, cor e alpha atuais.image(string $file, ?float $x, ?float $y, ?float $width, ?float $height)coloca uma imagem raster: a base para uma marca d’água de imagem ou um plano de fundo de página inteira.
O estado gráfico é restaurado de forma limpa porque startTransform() e stopTransform() delimitam a alteração. O valor de setAlpha() persiste até você defini-lo novamente. Se o conteúdo posterior precisar ser totalmente opaco, redefina a opacidade para 1.0 depois da marca. O padrão mais seguro, mostrado abaixo, desenha a marca dentro de seu próprio bloco de transform e define explicitamente o alpha do conteúdo da página.
O pacote também inclui os objetos de valor NextPDF\Graphics\Watermark e NextPDF\Graphics\WatermarkPosition. Watermark é um portador de configuração imutável para texto, tamanho da fonte, ângulo, cor, flag de sobreposição e predefinições de posição, como WatermarkPosition::Diagonal. Esses objetos modelam os parâmetros de uma marca d’água. Esta receita pinta a marca com os métodos de gravação na página apresentados acima, de modo que a saída chega diretamente ao fluxo de conteúdo (stream) da página.
Superfície da API
Seção intitulada “Superfície da API”Todos os métodos abaixo são públicos em NextPDF\Core\Document e retornam static, para que você possa encadeá-los.
setAlpha(float $alpha, BlendMode $mode = BlendMode::Normal): static: define a opacidade de preenchimento (0.0-1.0) e o modo de mesclagem para o conteúdo posterior.startTransform(): static: salva o estado gráfico (emiteq).rotate(float $angle, float $x = 0, float $y = 0): static: gira o sistema de coordenadas$anglegraus no sentido anti-horário em torno do pivô($x, $y).stopTransform(): static: restaura o estado salvo porstartTransform()(emiteQ), desfazendo a rotação e a alteração de alpha em conjunto.setFont(string $family, string $style = '', float $size = 12.0): static: seleciona a fonte para a marca. A família Base-14helveticaestá sempre disponível e não precisa de um arquivo de fonte.setTextColor(int $r, int $g = -1, int $b = -1): static: define a cor da marca em vermelho, verde e azul (ou um único valor em escala de cinza).text(float $x, float $y, string $text): static: escreve a marca em uma posição absoluta.image(string $file, ?float $x = null, ?float $y = null, ?float $width = null, ?float $height = null): static: coloca uma imagem raster, a base para uma marca d’água de imagem ou um plano de fundo de página inteira.getPageWidth(): float/getPageHeight(): float: lê o tamanho atual da página em pontos para que você possa centralizar a marca.
Os tipos de apoio ficam em NextPDF\Graphics: o enum BlendMode, o objeto de valor Color e o par de configuração Watermark / WatermarkPosition.
Exemplo de código — Início rápido
Seção intitulada “Exemplo de código — Início rápido”Este exemplo escreve uma página, pinta um carimbo “DRAFT” diagonal e sutil sobre o conteúdo e salva o arquivo. Ele omite o tratamento de erros para mostrar o formato das chamadas. O exemplo de produção abaixo acrescenta todas as proteções.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();
// Page content first, so the watermark lands on top of it.$doc->setFont('helvetica', '', 12);$doc->text(20.0, 40.0, 'Quarterly report: internal review copy.');
// Watermark second: a translucent, rotated stamp through the page center.$pivotX = $doc->getPageWidth() / 2.0;$pivotY = $doc->getPageHeight() / 2.0;
$doc->startTransform();$doc->setAlpha(0.15);$doc->setTextColor(150, 150, 150);$doc->setFont('helvetica', 'B', 72);$doc->rotate(45.0, $pivotX, $pivotY);$doc->text($pivotX - 110.0, $pivotY, 'DRAFT');$doc->stopTransform();
file_put_contents(__DIR__ . '/watermarked.pdf', $doc->getPdfData());Exemplo de código — Produção
Seção intitulada “Exemplo de código — Produção”Este programa autossuficiente pinta uma marca d’água diagonal em texto sobre o conteúdo gerado. Quando você fornece um caminho de imagem por meio da variável de ambiente NEXTPDF_WATERMARK_IMAGE, ele coloca essa imagem como um plano de fundo sutil e centralizado em uma segunda página. Ele valida o caminho da imagem antes de usá-lo, captura as exceções mais específicas do NextPDF e grava o resultado em um caminho controlado pelo servidor. Substitua o conteúdo em memória pelo seu próprio conteúdo e, em seguida, conecte a saída à camada de resposta ou de armazenamento.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Exception\ImageProcessingException;use NextPDF\Exception\NextPdfException;use NextPDF\Exception\PageLayoutException;
/** * Paint a translucent, rotated text stamp across the current page. * * The mark is bracketed in a transform block so the rotation and the alpha * change are undone together and never leak into later content. * * @param non-empty-string $mark The watermark text (for example "CONFIDENTIAL") */function paintTextWatermark(Document $doc, string $mark): void{ $pivotX = $doc->getPageWidth() / 2.0; $pivotY = $doc->getPageHeight() / 2.0;
// Estimate the mark width so the rotated text sits centered on the pivot. // Helvetica averages ~0.5 em per glyph; half the width offsets the origin. $fontSize = 64.0; $halfWidth = (\strlen($mark) * $fontSize * 0.5) / 2.0;
$doc->startTransform(); $doc->setAlpha(0.12); $doc->setTextColor(120, 120, 120); $doc->setFont('helvetica', 'B', $fontSize); $doc->rotate(45.0, $pivotX, $pivotY); $doc->text($pivotX - $halfWidth, $pivotY, $mark); $doc->stopTransform();}
/** * Place a raster image as a faint, full-page background behind later content. * * The image is drawn first and at low opacity; page content written after this * call sits over it. The path is validated by the caller before it arrives. * * @param non-empty-string $imagePath A readable raster image (PNG, JPEG, WebP) * * @throws ImageProcessingException If the file is missing, unreadable, or corrupt. * @throws PageLayoutException If the placement coordinates are rejected. */function paintImageBackground(Document $doc, string $imagePath): void{ $doc->startTransform(); $doc->setAlpha(0.08); // Cover the full page: origin at the top-left, sized to the page box. $doc->image( file: $imagePath, x: 0.0, y: 0.0, width: $doc->getPageWidth(), height: $doc->getPageHeight(), ); $doc->stopTransform();}
$doc = Document::createStandalone();$doc->setTitle('Watermark and background sample');
// Page 1: content first, then an overlay text watermark on top.$doc->addPage();$doc->setAlpha(1.0);$doc->setTextColor(0, 0, 0);$doc->setFont('helvetica', '', 12);$doc->text(20.0, 40.0, 'Quarterly report: internal review copy.');
try { paintTextWatermark($doc, 'CONFIDENTIAL');} catch (PageLayoutException $e) { // Raised if a coordinate or page state is rejected while placing the mark. throw new RuntimeException( sprintf('Watermark placement failed: %s', $e->getConstraint()), previous: $e, );}
// Page 2: an optional image background, then content over it.$imagePath = getenv('NEXTPDF_WATERMARK_IMAGE');
if ($imagePath !== false && $imagePath !== '') { // Validate the path before touching the image loader: reject NUL bytes, // require a real readable file, and resolve it to defeat path traversal. if (str_contains($imagePath, "\0")) { throw new RuntimeException('Image path must not contain NUL bytes.'); }
$resolved = realpath($imagePath);
if ($resolved === false || !is_file($resolved) || !is_readable($resolved)) { throw new RuntimeException( sprintf('Background image "%s" is not a readable file.', $imagePath), ); }
$doc->addPage();
try { paintImageBackground($doc, $resolved); } catch (ImageProcessingException $e) { // Raised when the file cannot be decoded as a supported raster format. throw new RuntimeException( sprintf( 'Background image rejected (%s, op "%s").', $e->getFormat(), $e->getOperation(), ), previous: $e, ); } catch (PageLayoutException $e) { throw new RuntimeException( sprintf('Background placement failed: %s', $e->getConstraint()), previous: $e, ); }
$doc->setAlpha(1.0); $doc->setTextColor(0, 0, 0); $doc->setFont('helvetica', '', 12); $doc->text(20.0, 40.0, 'Page two over a faint background.');}
try { $pdf = $doc->getPdfData();} catch (NextPdfException $e) { // Base of the NextPDF exception hierarchy: any output-stage failure. throw new RuntimeException( sprintf('Document output failed: %s', $e->getMessage()), previous: $e, );}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');$path = $out !== false && $out !== '' ? $out : __DIR__ . '/watermarked.pdf';
if (file_put_contents($path, $pdf) === false) { throw new RuntimeException(sprintf('Could not write PDF to "%s".', $path));}
printf("Wrote %d-byte PDF to %s\n", strlen($pdf), $path);Saída padrão (STDOUT) esperada (o tamanho em bytes depende do build e de você ter fornecido uma imagem ou não):
Wrote <n>-byte PDF to <path>Casos extremos e pegadinhas
Seção intitulada “Casos extremos e pegadinhas”- A ordem das camadas é a ordem das chamadas. Um plano de fundo é conteúdo desenhado antes do conteúdo da página. Uma marca d’água sobreposta é conteúdo desenhado depois dele. Nenhuma flag reordena as camadas; em vez disso, mova a chamada.
- O alpha persiste até ser redefinido.
setAlpha()altera o estado de tudo o que é desenhado em seguida. Você pode delimitar a marca comstartTransform()/stopTransform(), que restaura o alpha anterior, ou chamarsetAlpha(1.0)antes do conteúdo opaco. O exemplo de produção faz as duas coisas. - Equilibre cada bloco de transform. Cada
startTransform()precisa de umstopTransform()correspondente. Um bloco desequilibrado deixa a rotação ou o alpha aplicados ao conteúdo posterior, e a ausência destopTransform()cria um desequilíbrio de estado gráfico que o writer rejeita na saída. rotate()faz o pivô em coordenadas de usuário. O pivô($x, $y)está em unidades de usuário medidas a partir do canto superior esquerdo da página, no mesmo referencial quetext(). Para uma diagonal que passa pelo centro, use o centro da página (getPageWidth() / 2,getPageHeight() / 2).- O texto rotacionado precisa de um deslocamento manual de largura.
text()posiciona a origem da string; ele não centraliza por você. Subtraia do X do pivô aproximadamente metade da largura estimada do texto para que a marca rotacionada fique centralizada sobre o centro, como o auxiliar faz. - As imagens são dimensionadas para a caixa que você passa.
image()estica o raster até awidthe aheightque você informa. Para um plano de fundo de página inteira, passe a largura e a altura da página; para um logotipo no canto, passe seu tamanho natural. Uma dimensão zero ou negativa geraPageLayoutException. image()rejeita URLs e bytes NUL. Um caminhoscheme://ou um byte NUL em$filegeraPageLayoutExceptionantes de qualquer decodificação. Passe apenas um caminho local e validado.- A marca é conteúdo visível. Uma marca d’água desenhada dessa forma é conteúdo de página real, não uma anotação oculta. Qualquer pessoa com o arquivo pode lê-la. Ela é uma indicação visual, não um controle de acesso.
Desempenho
Seção intitulada “Desempenho”Uma marca d’água de texto usa um punhado de operadores de fluxo de conteúdo por página e acrescenta tempo ou memória desprezíveis. Uma marca d’água ou plano de fundo de imagem custa uma decodificação raster mais os bytes da imagem incorporada na saída. Reutilizar a mesma imagem em várias páginas reaproveita o XObject decodificado por meio do cache de imagens, de modo que você paga o custo da decodificação apenas uma vez. Dimensione as imagens de plano de fundo para a caixa de exibição antes de incorporá-las. Uma foto de 4000 px redimensionada para uma página carta armazena bytes que o leitor nunca vê. Uma marca d’água de texto típica de uma única página permanece com folga dentro de um orçamento de 500 ms de tempo e 32 MB de pico. Um plano de fundo de imagem acompanha o tamanho decodificado do raster de origem.
Notas de segurança
Seção intitulada “Notas de segurança”O pipeline é executado no processo. Nenhum byte do documento sai do host, e nenhuma chamada de rede é feita. Trate qualquer caminho de imagem que se origine fora do seu código como entrada não confiável.
- Valide o caminho da imagem antes de usá-lo. Rejeite bytes NUL, resolva o caminho com
realpath()e confirmeis_file()eis_readable()antes de chamarimage(), exatamente como o exemplo de produção faz. Isso bloqueia path traversal e rejeita diretórios e links pendentes logo no início. - Nunca interpole um campo de requisição em um caminho. Derive o caminho da imagem e o caminho de saída de valores controlados pelo servidor, não de um parâmetro de requisição. Isso evita que você leia ou grave arquivos fora do diretório previsto.
- Trate imagens não confiáveis como entrada hostil. Um raster malformado gera
ImageProcessingExceptionem vez de corromper o documento, e o carregador limita as dimensões da imagem para resistir a entradas de bomba de descompressão. Capture a exceção e rejeite o upload. Não tente de novo às cegas. - Uma marca d’água não é um cofre de segredos. A marca é conteúdo visível. Não codifique credenciais, tokens ou identificadores internos em uma marca d’água ou plano de fundo que você retorna a um cliente.
Conformidade
Seção intitulada “Conformidade”Esta receita não faz nenhuma reivindicação normativa de padrões por conta própria. Ela compõe as primitivas públicas de alpha, transform, text e image. Cada primitiva emite operadores de fluxo de conteúdo PDF padrão. O estado gráfico é isolado com os operadores q / Q que startTransform() e stopTransform() emitem, e a transparência é transportada por meio de um parâmetro de estado gráfico ExtGState. A saída é estruturalmente nova em vez de estável em bytes; portanto, esta página declara um perfil de reprodutibilidade structural. Para detalhes em nível de operador sobre a superfície de transform e estado gráfico, consulte a referência do módulo Graphics.
Consulte também
Seção intitulada “Consulte também”- Referência do módulo Graphics: a superfície completa de path, transform, cor e estado gráfico por trás desses métodos.
- Incorporar imagens: carregue, dimensione e posicione imagens raster, a base para uma marca d’água de imagem ou plano de fundo.
- Gradientes e transparência: a superfície de alpha e modo de mesclagem em detalhes, incluindo preenchimentos translúcidos.
- Transforme o espaço de coordenadas: rotacione, dimensione e mova conteúdo com blocos de transform equilibrados.
- Tratamento de erros consciente de exceções: a hierarquia de exceções do NextPDF por trás de
ImageProcessingException,PageLayoutExceptioneNextPdfException.