Dodawanie tekstowych i graficznych znaków wodnych lub teł do stron
W skrócie
Dział zatytułowany „W skrócie”Możesz dodać oznaczenie „DRAFT” lub „CONFIDENTIAL” na każdej stronie albo umieścić wyblakłe logo za treścią. W tym przepisie dodasz oba elementy do stron rdzenia NextPDF za pomocą publicznego API dokumentu: setAlpha() do przezroczystości, startTransform() / rotate() / stopTransform() do ukośnego stempla, text() do oznaczenia oraz image() do tła rastrowego.
Znak wodny i tło sprowadzają się do jednego wyboru: kolejności rysowania.
- Tło: narysuj je najpierw, a następnie umieść nad nim treść strony. Oznaczenie znajduje się za tekstem.
- Nakładkowy znak wodny: najpierw umieść treść strony, a następnie narysuj oznaczenie nad nią. Oznaczenie znajduje się na wierzchu.
NextPDF rysuje treść w kolejności wywołań, więc to ona ustala kolejność warstw. Nie ma osobnego „trybu tła”. O warstwie decyduje moment rysowania.
Wymagania wstępne: instalacja rdzenia (composer require nextpdf/core:^3) oraz, w przypadku tła graficznego, plik rastrowy możliwy do odczytu (PNG, JPEG lub WebP) na dysku. Cały potok działa wewnątrz procesu, bez uruchamiania przeglądarki headless ani wykonywania wywołań sieciowych.
Instalacja
Dział zatytułowany „Instalacja”composer require nextpdf/core:^3Przegląd koncepcyjny
Dział zatytułowany „Przegląd koncepcyjny”Każde dodane oznaczenie jest zwykłą treścią strony rysowaną z użyciem stanu graficznego. Do utworzenia znaku wodnego służą trzy elementy publicznego API:
-
Przezroczystość.
setAlpha(float $alpha, BlendMode $mode = BlendMode::Normal)ustawia krycie wypełnienia dla wszystkiego, co rysujesz później, od0.0(niewidoczne) do1.0(nieprzezroczyste). Znak wodny zwykle wygląda najlepiej przy wartości od0.1do0.3, dzięki czemu treść pod spodem pozostaje czytelna. Tryb mieszania jest wartością z wyliczeniaNextPDF\Graphics\BlendMode. Na przykładBlendMode::Multiplyprzyciemnia obszary, w których oznaczenie nakłada się na treść. -
Obrót. Ukośny stempel to tekst obrócony wokół punktu obrotu.
startTransform()zapisuje stan graficzny,rotate(float $angle, float $x, float $y)obraca układ współrzędnych w kierunku przeciwnym do ruchu wskazówek zegara wokół($x, $y), astopTransform()przywraca zapisany stan. Umieszczenie oznaczenia w bloku transformacji sprawia, że obrót i krycie nie wpływają na resztę strony. -
Samo oznaczenie.
text(float $x, float $y, string $text)umieszcza ciąg znaków w pozycji bezwzględnej, z bieżącą czcionką, kolorem i kryciem.image(string $file, ?float $x, ?float $y, ?float $width, ?float $height)umieszcza obraz rastrowy: podstawę graficznego znaku wodnego lub pełnostronicowego tła.
Stan graficzny jest przywracany bez efektów ubocznych, ponieważ startTransform() i stopTransform() obejmują tę zmianę nawiasem. Wartość setAlpha() utrzymuje się do czasu jej ponownego ustawienia. Jeśli późniejsza treść musi być w pełni nieprzezroczysta, po oznaczeniu przywróć krycie do 1.0. Bezpieczniejszy wzorzec pokazany poniżej rysuje oznaczenie wewnątrz własnego bloku transformacji i jawnie ustawia krycie treści strony.
Pakiet udostępnia też obiekty wartości NextPDF\Graphics\Watermark i NextPDF\Graphics\WatermarkPosition. Watermark to niezmienny kontener konfiguracji dla tekstu, rozmiaru czcionki, kąta, koloru, flagi nakładki oraz gotowych ustawień położenia, takich jak WatermarkPosition::Diagonal. Te obiekty modelują parametry znaku wodnego. Ten przepis rysuje oznaczenie za pomocą powyższych metod zapisujących treść na stronie, dzięki czemu wynik trafia bezpośrednio do strumienia treści strony.
Powierzchnia API
Dział zatytułowany „Powierzchnia API”Wszystkie poniższe metody są publiczne w NextPDF\Core\Document i zwracają static, więc można je wywoływać łańcuchowo.
setAlpha(float $alpha, BlendMode $mode = BlendMode::Normal): static: ustawia krycie wypełnienia (0.0-1.0) i tryb mieszania dla treści rysowanej później.startTransform(): static: zapisuje stan graficzny (emitujeq).rotate(float $angle, float $x = 0, float $y = 0): static: obraca układ współrzędnych o$anglestopni w kierunku przeciwnym do ruchu wskazówek zegara wokół punktu obrotu($x, $y).stopTransform(): static: przywraca stan zapisany przezstartTransform()(emitujeQ), jednocześnie cofając obrót i zmianę krycia.setFont(string $family, string $style = '', float $size = 12.0): static: wybiera czcionkę dla oznaczenia. Rodzina Base-14helveticajest zawsze dostępna i nie wymaga pliku czcionki.setTextColor(int $r, int $g = -1, int $b = -1): static: ustawia kolor oznaczenia w kanałach czerwonym, zielonym, niebieskim (lub jedną wartość skali szarości).text(float $x, float $y, string $text): static: umieszcza oznaczenie w pozycji bezwzględnej.image(string $file, ?float $x = null, ?float $y = null, ?float $width = null, ?float $height = null): static: umieszcza obraz rastrowy, czyli podstawę graficznego znaku wodnego lub pełnostronicowego tła.getPageWidth(): float/getPageHeight(): float: odczytują bieżący rozmiar strony w punktach, dzięki czemu można wyśrodkować oznaczenie.
Typy pomocnicze znajdują się w NextPDF\Graphics: wyliczenie BlendMode, obiekt wartości Color oraz para konfiguracyjna Watermark / WatermarkPosition.
Przykład kodu — szybki start
Dział zatytułowany „Przykład kodu — szybki start”Ten przykład tworzy jedną stronę, rysuje blady ukośny stempel „DRAFT” nad treścią i zapisuje plik. Pomija obsługę błędów, aby pokazać kształt wywołań. Poniższy przykład produkcyjny dodaje pełne zabezpieczenia.
<?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());Przykład kodu — produkcja
Dział zatytułowany „Przykład kodu — produkcja”Ten samodzielny program rysuje ukośny tekstowy znak wodny nad wygenerowaną treścią. Gdy podasz ścieżkę do obrazu w zmiennej środowiskowej NEXTPDF_WATERMARK_IMAGE, umieszcza ten obraz jako blade, wyśrodkowane tło na drugiej stronie. Sprawdza ścieżkę obrazu przed użyciem, przechwytuje najbardziej szczegółowe wyjątki NextPDF i zapisuje wynik pod ścieżką kontrolowaną przez serwer. Zastąp przykładową treść własną treścią generowaną w pamięci, a następnie połącz wynik ze swoją warstwą odpowiedzi lub pamięci masowej.
<?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);Oczekiwane wyjście standardowe (STDOUT) (rozmiar w bajtach zależy od kompilacji i od tego, czy podano obraz):
Wrote <n>-byte PDF to <path>Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”- Kolejność warstw to kolejność wywołań. Tło to treść rysowana przed treścią strony. Nakładkowy znak wodny to treść rysowana po niej. Żadna flaga nie zmienia kolejności warstw; aby ją zmienić, przenieś wywołanie.
- Krycie utrzymuje się do zresetowania.
setAlpha()zmienia stan dla wszystkiego, co rysowane później. Albo obejmij oznaczenie nawiasemstartTransform()/stopTransform(), co przywraca poprzednie krycie, albo wywołajsetAlpha(1.0)przed nieprzezroczystą treścią. Przykład produkcyjny stosuje oba zabezpieczenia. - Zrównoważ każdy blok transformacji. Każdy
startTransform()wymaga odpowiadającego mustopTransform(). Niezrównoważony blok pozostawia obrót lub krycie zastosowane do późniejszej treści, a brakującystopTransform()zostawia niezrównoważony stan graficzny, który zapisujący odrzuca przy wyjściu. rotate()obraca względem współrzędnych użytkownika. Punkt obrotu($x, $y)jest w jednostkach użytkownika mierzonych od lewego górnego rogu strony, w tym samym układzie cotext(). Aby uzyskać przekątną przez środek, użyj środka strony (getPageWidth() / 2,getPageHeight() / 2).- Obrócony tekst wymaga ręcznego przesunięcia o szerokość.
text()umieszcza początek ciągu znaków; nie centruje go samoczynnie. Odejmij około połowy szacowanej szerokości tekstu od współrzędnej X punktu obrotu, aby obrócone oznaczenie rozkładało się symetrycznie wokół środka, jak w funkcji pomocniczej. - Obrazy są skalowane do przekazanego pola.
image()rozciąga raster do podanejwidthiheight. Dla pełnostronicowego tła przekaż szerokość i wysokość strony; dla logo w rogu przekaż jego naturalny rozmiar. Zerowy lub ujemny wymiar powoduje zgłoszeniePageLayoutException. image()odrzuca adresy URL i bajty NUL. Ścieżkascheme://lub bajt NUL w$filepowoduje zgłoszeniePageLayoutExceptionprzed jakimkolwiek dekodowaniem. Przekazuj wyłącznie lokalną, zweryfikowaną ścieżkę.- Oznaczenie jest treścią widoczną. Znak wodny narysowany w ten sposób jest prawdziwą treścią strony, a nie ukrytą adnotacją. Każdy, kto ma plik, może odczytać tę treść. Jest to wskazówka wizualna, a nie kontrola dostępu.
Wydajność
Dział zatytułowany „Wydajność”Tekstowy znak wodny używa kilku operatorów strumienia treści na stronę i ma znikomy narzut czasu oraz pamięci. Graficzny znak wodny lub tło kosztuje jedno dekodowanie rastra plus bajty obrazu osadzone w wyniku. Ponowne użycie tego samego obrazu na wielu stronach wykorzystuje zdekodowany XObject poprzez pamięć podręczną obrazów, więc koszt dekodowania ponosisz raz. Przeskaluj obrazy tła do pola, w którym będą wyświetlane, przed osadzeniem. Zdjęcie o szerokości 4000 px przeskalowane na stronę w formacie letter przechowuje bajty, których czytelnik nigdy nie zobaczy. Typowy jednostronicowy tekstowy znak wodny mieści się z dużym zapasem w budżecie 500 ms czasu zegarowego i 32 MB szczytowego zużycia pamięci. W przypadku tła graficznego zużycie zależy od zdekodowanego rozmiaru rastra źródłowego.
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”Potok działa wewnątrz procesu. Żadne bajty dokumentu nie opuszczają hosta i nie jest wykonywane żadne wywołanie sieciowe. Każdą ścieżkę obrazu pochodzącą spoza kodu aplikacji traktuj jako niezaufane dane wejściowe.
- Sprawdź ścieżkę obrazu przed użyciem. Odrzuć bajty NUL, rozwiąż ścieżkę za pomocą
realpath()i potwierdźis_file()orazis_readable()przed wywołaniemimage(), dokładnie jak w przykładzie produkcyjnym. Blokuje to przechodzenie po ścieżkach (path traversal) oraz wcześnie odrzuca katalogi i wiszące dowiązania. - Nigdy nie wstawiaj pola żądania do ścieżki. Wyznaczaj ścieżkę obrazu i ścieżkę wyjścia z wartości kontrolowanych przez serwer, a nie z parametru żądania. Chroni to przed odczytem lub zapisem plików poza zamierzonym katalogiem.
- Niezaufane obrazy traktuj jako wrogie dane wejściowe. Uszkodzony raster zgłasza
ImageProcessingExceptionzamiast uszkadzać dokument, a moduł ładujący ogranicza wymiary obrazu, aby przeciwdziałać danym typu bomba dekompresyjna. Przechwyć wyjątek i odrzuć przesłany plik. Nie ponawiaj na ślepo. - Znak wodny nie jest nośnikiem sekretów. Oznaczenie jest treścią widoczną. Nie koduj poświadczeń, tokenów ani wewnętrznych identyfikatorów w znaku wodnym lub tle, które zwracasz klientowi.
Zgodność
Dział zatytułowany „Zgodność”Ten przepis nie formułuje żadnego własnego normatywnego twierdzenia o zgodności ze standardami. Łączy publiczne prymitywy alpha, transform, text i image. Każdy prymityw emituje standardowe operatory strumienia treści PDF. Stan graficzny jest izolowany operatorami q / Q, które emitują startTransform() i stopTransform(), a przezroczystość jest przenoszona przez parametr stanu graficznego ExtGState. Wynik jest tworzony jako nowa struktura, bez gwarancji stabilności bajtowej, więc ta strona deklaruje profil odtwarzalności structural. Szczegóły na poziomie operatorów dotyczące API transformacji i stanu graficznego znajdziesz w dokumentacji modułu Graphics.
Zobacz też
Dział zatytułowany „Zobacz też”- Dokumentacja modułu Graphics: pełne API ścieżek, transformacji, kolorów i stanu graficznego wykorzystywane przez te metody.
- Osadzanie obrazów: wczytywanie, skalowanie i umieszczanie obrazów rastrowych, czyli podstawa graficznego znaku wodnego lub tła.
- Gradienty i przezroczystość: dogłębne omówienie API alpha i trybów mieszania, w tym półprzezroczystych wypełnień.
- Transformacja przestrzeni współrzędnych: obracaj, skaluj i przesuwaj treść za pomocą zrównoważonych bloków transformacji.
- Obsługa błędów oparta na wyjątkach: hierarchia wyjątków NextPDF stojąca za
ImageProcessingException,PageLayoutExceptioniNextPdfException.