콘텐츠로 이동

페이지에 텍스트와 이미지 워터마크 또는 배경 추가

각 페이지에 “DRAFT” 또는 “CONFIDENTIAL” 표시를 넣거나 콘텐츠 뒤에 희미한 로고를 넣고 싶을 수 있습니다. 이 레시피는 공개 문서 표면을 사용해 두 가지를 모두 NextPDF core 페이지에 레이어로 추가합니다. 투명도에는 setAlpha(), 대각선 스탬프에는 startTransform() / rotate() / stopTransform(), 표시에는 text(), 래스터 배경에는 image()를 사용합니다.

워터마크와 배경은 단 하나의 결정에서만 다릅니다. 바로 그리는 순서입니다.

  • 배경: 먼저 그린 다음 그 위에 페이지 콘텐츠를 작성합니다. 표시가 텍스트 뒤에 위치합니다.
  • 오버레이 워터마크: 먼저 페이지 콘텐츠를 작성한 다음 그 위에 표시를 그립니다. 표시가 맨 위에 위치합니다.

NextPDF는 호출한 순서대로 콘텐츠를 그리므로 호출 순서가 곧 레이어 순서입니다. 별도의 “배경 모드”는 없습니다. 언제 그릴지를 선택해 레이어를 정합니다.

전제 조건: core 설치(composer require nextpdf/core:^3)와, 이미지 배경을 사용하는 경우 디스크에서 읽을 수 있는 래스터 파일(PNG, JPEG 또는 WebP)이 필요합니다. 전체 파이프라인은 헤드리스 브라우저나 네트워크 호출 없이 프로세스 내에서 실행됩니다.

Terminal window
composer require nextpdf/core:^3

추가하는 모든 표시는 그래픽 상태를 통해 그려지는 일반 페이지 콘텐츠입니다. 공개 표면의 세 가지 요소를 결합해 워터마크를 만듭니다.

  1. 투명도. setAlpha(float $alpha, BlendMode $mode = BlendMode::Normal)은 이후에 그리는 모든 요소의 채우기 불투명도를 0.0(보이지 않음)에서 1.0(불투명)까지 설정합니다. 워터마크는 일반적으로 0.1에서 0.3 사이로 설정해 아래 콘텐츠를 읽을 수 있게 유지합니다. 블렌드 모드는 NextPDF\Graphics\BlendMode 열거형에서 가져옵니다. 예를 들어 BlendMode::Multiply는 표시가 콘텐츠와 겹치는 부분을 어둡게 만듭니다.

  2. 회전. 대각선 스탬프는 피벗 지점을 중심으로 회전한 텍스트입니다. startTransform()는 그래픽 상태를 저장하고, rotate(float $angle, float $x, float $y)는 좌표계를 ($x, $y)를 중심으로 시계 반대 방향으로 회전하며, stopTransform()는 저장된 상태를 복원합니다. 표시를 transform 블록으로 감싸면 회전과 alpha가 페이지의 나머지 부분으로 새어 나가지 않습니다.

  3. 표시 자체. text(float $x, float $y, string $text)는 현재 글꼴, 색상, alpha로 절대 위치에 문자열을 작성합니다. image(string $file, ?float $x, ?float $y, ?float $width, ?float $height)는 래스터 이미지를 배치합니다. 이미지 워터마크 또는 전체 페이지 배경의 구성 요소입니다.

그래픽 상태는 startTransform()stopTransform()가 변경 사항을 감싸기 때문에 깔끔하게 복원됩니다. setAlpha() 값은 다시 설정할 때까지 유지됩니다. 따라서 이후 콘텐츠가 완전히 불투명해야 한다면 표시 후 불투명도를 1.0으로 재설정합니다. 아래의 더 안전한 패턴은 표시를 자체 transform 블록 안에서 그리고 페이지 콘텐츠의 alpha를 명시적으로 설정합니다.

이 패키지는 값 객체 NextPDF\Graphics\WatermarkNextPDF\Graphics\WatermarkPosition도 함께 제공합니다. Watermark는 불변 구성 보유자입니다. 텍스트, 글꼴 크기, 각도, 색상, 오버레이 플래그, 그리고 WatermarkPosition::Diagonal 같은 위치 프리셋을 담습니다. 이 객체들은 워터마크의 매개변수를 모델링합니다. 이 레시피는 위의 페이지 커밋 메서드로 표시를 그리므로 출력이 페이지 콘텐츠 스트림에 직접 도달합니다.

아래의 모든 메서드는 NextPDF\Core\Document에서 public이며 static를 반환하므로 체이닝할 수 있습니다.

  • setAlpha(float $alpha, BlendMode $mode = BlendMode::Normal): static: 후속 콘텐츠의 채우기 불투명도(0.0-1.0)와 블렌드 모드를 설정합니다.
  • startTransform(): static: 그래픽 상태를 저장합니다(q 방출).
  • rotate(float $angle, float $x = 0, float $y = 0): static: 좌표계를 $angle도만큼 피벗 ($x, $y)를 중심으로 시계 반대 방향으로 회전합니다.
  • stopTransform(): static: startTransform()로 저장된 상태를 복원해(Q 방출), 회전과 alpha 변경을 함께 되돌립니다.
  • setFont(string $family, string $style = '', float $size = 12.0): static: 표시에 사용할 글꼴을 선택합니다. Base-14 패밀리 helvetica는 항상 사용 가능하며 글꼴 파일이 필요하지 않습니다.
  • setTextColor(int $r, int $g = -1, int $b = -1): static: 표시 색상을 빨강, 초록, 파랑(또는 단일 회색조 값)으로 설정합니다.
  • text(float $x, float $y, string $text): static: 절대 위치에 표시를 작성합니다.
  • image(string $file, ?float $x = null, ?float $y = null, ?float $width = null, ?float $height = null): static: 래스터 이미지를 배치합니다. 이미지 워터마크 또는 전체 페이지 배경의 기반입니다.
  • getPageWidth(): float / getPageHeight(): float: 현재 페이지 크기를 포인트 단위로 읽어 표시를 가운데에 배치할 수 있습니다.

지원 타입은 NextPDF\Graphics 아래에 있습니다. BlendMode 열거형, Color 값 객체, 그리고 Watermark / WatermarkPosition 구성 쌍이 여기에 포함됩니다.

이 코드는 페이지 한 개를 만들고, 콘텐츠 위에 희미한 대각선 “DRAFT” 스탬프를 그린 다음 파일을 저장합니다. 호출 형태를 보여주기 위해 오류 처리는 생략했습니다. 아래의 프로덕션 샘플에는 전체 가드가 추가되어 있습니다.

<?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());

이 독립 실행형 프로그램은 생성된 콘텐츠 위에 대각선 텍스트 워터마크를 그립니다. 그런 다음 NEXTPDF_WATERMARK_IMAGE 환경 변수로 이미지 경로가 제공되면 해당 이미지를 두 번째 페이지에 희미하게 가운데 정렬된 배경으로 배치합니다. 사용하기 전에 이미지 경로를 검증하고, 가장 구체적인 NextPDF 예외를 포착하며, 결과를 서버가 제어하는 경로에 작성합니다. 메모리 내 콘텐츠를 자신의 콘텐츠로 바꾸고 출력을 응답 또는 스토리지 계층에 연결하세요.

<?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);

예상 STDOUT(바이트 크기는 빌드와 이미지 제공 여부에 따라 달라집니다):

Wrote <n>-byte PDF to <path>
  • 레이어 순서는 호출 순서입니다. 배경은 페이지 콘텐츠보다 먼저 그려지는 콘텐츠입니다. 오버레이 워터마크는 나중에 그려지는 콘텐츠입니다. 레이어 순서를 바꾸는 플래그는 없습니다. 대신 호출 위치를 옮기세요.
  • Alpha는 재설정할 때까지 유지됩니다. setAlpha()는 이후에 그려지는 모든 요소의 상태를 변경합니다. 표시를 startTransform() / stopTransform()로 감싸 이전 alpha를 복원하거나, 불투명 콘텐츠 전에 setAlpha(1.0)을 호출하세요. 프로덕션 샘플은 두 가지를 모두 수행합니다.
  • 모든 transform 블록의 균형을 맞추세요.startTransform()에는 대응하는 stopTransform()이 필요합니다. 균형이 맞지 않는 블록은 회전이나 alpha가 이후 콘텐츠에 적용된 상태로 남기며, stopTransform()이 누락되면 라이터가 출력 시 거부하는 그래픽 상태 불균형이 됩니다.
  • rotate()는 사용자 좌표에서 회전합니다. 피벗 ($x, $y)는 페이지 왼쪽 위에서 측정한 사용자 단위로, text()과 동일한 프레임입니다. 중심을 지나는 대각선의 경우 페이지 중심(getPageWidth() / 2, getPageHeight() / 2)을 사용하세요.
  • 회전된 텍스트는 수동 너비 오프셋이 필요합니다. text()는 문자열 원점을 배치할 뿐, 자동으로 가운데 정렬하지 않습니다. 헬퍼가 하는 것처럼, 추정 텍스트 너비의 대략 절반을 피벗 X에서 빼서 회전된 표시가 중심에 걸치도록 하세요.
  • 이미지는 전달한 상자에 맞게 크기가 조정됩니다. image()는 래스터를 지정한 widthheight에 맞게 늘립니다. 전체 페이지 배경의 경우 페이지 너비와 높이를 전달하고, 모서리 로고의 경우 자연 크기를 전달하세요. 0 또는 음수 치수는 PageLayoutException을 발생시킵니다.
  • image()는 URL과 NUL 바이트를 거부합니다. scheme:// 경로 또는 $file의 NUL 바이트는 디코딩 전에 PageLayoutException을 발생시킵니다. 검증된 로컬 경로만 전달하세요.
  • 표시는 보이는 콘텐츠입니다. 이런 방식으로 그린 워터마크는 숨겨진 주석이 아니라 실제 페이지 콘텐츠입니다. 파일을 가진 사람은 누구나 읽을 수 있습니다. 이는 접근 제어가 아니라 시각적 단서입니다.

텍스트 워터마크는 페이지당 몇 개의 콘텐츠 스트림 연산자만 추가하므로 시간이나 메모리를 거의 늘리지 않습니다. 이미지 워터마크 또는 배경은 래스터 디코딩 1회와 출력에 포함된 이미지 바이트만큼의 비용이 듭니다. 여러 페이지에서 동일한 이미지를 재사용하면 이미지 캐시를 통해 디코딩된 XObject를 재사용하므로 디코딩 비용은 한 번만 지불합니다. 배경 이미지를 포함하기 전에 표시 상자 크기에 맞게 조정하세요. letter 페이지에 맞게 축소된 4000px 사진은 독자가 절대 볼 수 없는 바이트를 저장합니다. 일반적인 단일 페이지 텍스트 워터마크는 500 ms 벽시계 시간과 32 MB 최대 예산 내에 충분히 들어갑니다. 이미지 배경은 소스 래스터의 디코딩된 크기에 따라 달라집니다.

파이프라인은 프로세스 내에서 실행됩니다. 문서 바이트는 호스트를 벗어나지 않으며 네트워크 호출도 이루어지지 않습니다. 코드 외부에서 비롯된 모든 이미지 경로는 신뢰할 수 없는 입력으로 취급하세요.

  • 사용하기 전에 이미지 경로를 검증하세요. 프로덕션 샘플과 정확히 동일하게, NUL 바이트를 거부하고, realpath()으로 경로를 해석한 뒤, is_file()is_readable()를 확인한 다음 image()을 호출하세요. 이렇게 하면 경로 탐색을 차단하고 디렉터리와 끊어진 링크를 조기에 거부할 수 있습니다.
  • 요청 필드를 경로에 삽입하지 마세요. 이미지 경로와 출력 경로를 요청 매개변수가 아니라 서버가 제어하는 값에서 도출하세요. 이렇게 하면 의도한 디렉터리 외부의 파일을 읽거나 쓰는 것을 방지합니다.
  • 신뢰할 수 없는 이미지를 적대적 입력으로 취급하세요. 잘못된 형식의 래스터는 문서를 손상시키는 대신 ImageProcessingException을 발생시키며, 로더는 압축 해제 폭탄 입력에 대응하기 위해 이미지 치수에 상한을 둡니다. 예외를 포착하고 업로드를 거부하세요. 무작정 재시도해서는 안 됩니다.
  • 워터마크는 비밀 저장소가 아닙니다. 표시는 보이는 콘텐츠입니다. 클라이언트에 반환하는 워터마크나 배경에 자격 증명, 토큰 또는 내부 식별자를 인코딩하지 마세요.

이 레시피는 자체적으로 어떠한 규범적 표준 주장도 하지 않습니다. 공개 alpha, transform, text, image 기본 요소를 조합할 뿐입니다. 이들 각각은 표준 PDF 콘텐츠 스트림 연산자를 방출합니다. 그래픽 상태는 q / Q 연산자로 격리되는데, 이 연산자는 startTransform()stopTransform()이 방출하며, 투명도는 ExtGState 그래픽 상태 매개변수를 통해 전달됩니다. 출력은 바이트 안정적이라기보다 구조적으로 새로 생성되므로 이 페이지는 structural 재현성 프로필을 선언합니다. transform 및 그래픽 상태 표면의 연산자 수준 세부 정보는 Graphics 모듈 레퍼런스를 참조하세요.

  • Graphics 모듈 레퍼런스: 이러한 메서드 뒤에 있는 전체 경로, transform, 색상, 그래픽 상태 표면입니다.
  • 이미지 포함: 이미지 워터마크 또는 배경의 구성 요소인 래스터 이미지를 로드하고, 크기를 조정하고, 배치합니다.
  • 그라데이션 및 투명도: 반투명 채우기를 포함한 alpha 및 블렌드 모드 표면을 심층적으로 다룹니다.
  • 좌표 공간 변환: 균형 잡힌 transform 블록으로 콘텐츠를 회전, 크기 조정, 이동합니다.
  • 예외 인식 오류 처리: ImageProcessingException, PageLayoutException, NextPdfException 뒤에 있는 NextPDF 예외 계층 구조입니다.