Bỏ qua để đến nội dung

Thêm hình mờ và nền bằng văn bản hoặc hình ảnh vào trang

Bạn có thể thêm dấu “DRAFT” hoặc “CONFIDENTIAL” vào từng trang, hoặc đặt logo mờ phía sau nội dung. Công thức này thêm cả hai vào các trang lõi NextPDF bằng bề mặt tài liệu công khai: setAlpha() cho độ trong suốt, startTransform() / rotate() / stopTransform() cho dấu chéo, text() cho dấu, và image() cho nền raster.

Điểm khác nhau giữa hình mờ và nền nằm ở một lựa chọn: thứ tự vẽ.

  • Nền: vẽ trước, rồi viết nội dung trang lên trên. Dấu nằm phía sau văn bản.
  • Hình mờ phủ lên trên: viết nội dung trang trước, rồi vẽ dấu lên trên. Dấu nằm trên cùng.

NextPDF vẽ nội dung theo thứ tự các lệnh được gọi, nên thứ tự gọi quyết định thứ tự lớp. Không có “chế độ nền” riêng. Bạn chọn lớp bằng thời điểm vẽ.

Điều kiện tiên quyết: một bản cài đặt lõi (composer require nextpdf/core:^3), và, đối với nền hình ảnh, một tệp raster đọc được (PNG, JPEG, hoặc WebP) trên đĩa. Toàn bộ pipeline chạy trong cùng tiến trình, không cần trình duyệt headless hay lệnh gọi mạng.

Terminal window
composer require nextpdf/core:^3

Mỗi dấu bạn thêm vào đều là nội dung trang thông thường, được vẽ qua một trạng thái đồ họa. Ba phần của bề mặt công khai phối hợp với nhau để tạo ra một hình mờ:

  1. Độ trong suốt. setAlpha(float $alpha, BlendMode $mode = BlendMode::Normal) đặt độ mờ tô cho mọi thứ bạn vẽ sau đó, từ 0.0 (vô hình) đến 1.0 (đục). Một hình mờ thường hiệu quả nhất ở mức 0.1 đến 0.3, để nội dung bên dưới vẫn đọc được. Chế độ hòa trộn đến từ enum NextPDF\Graphics\BlendMode. Ví dụ, BlendMode::Multiply làm tối những vùng mà dấu chồng lên nội dung.

  2. Xoay. Dấu chéo là văn bản được xoay quanh một điểm trục. startTransform() lưu trạng thái đồ họa, rotate(float $angle, float $x, float $y) xoay hệ tọa độ ngược chiều kim đồng hồ quanh ($x, $y), và stopTransform() khôi phục trạng thái đã lưu. Bọc dấu trong một khối transform giúp thao tác xoay và alpha không ảnh hưởng đến phần còn lại của trang.

  3. Bản thân dấu. text(float $x, float $y, string $text) viết một chuỗi tại vị trí tuyệt đối theo phông chữ, màu sắc và alpha hiện tại. image(string $file, ?float $x, ?float $y, ?float $width, ?float $height) đặt một ảnh raster: cơ sở cho hình mờ dạng hình ảnh hoặc nền toàn trang.

Trạng thái đồ họa được khôi phục gọn gàng vì startTransform()stopTransform() bao quanh thay đổi. Giá trị setAlpha() vẫn giữ nguyên cho đến khi bạn đặt lại. Nếu nội dung sau đó phải hoàn toàn đục, hãy đặt lại độ mờ về 1.0 sau dấu. Mẫu an toàn hơn bên dưới vẽ dấu bên trong khối transform riêng và đặt alpha cho nội dung trang một cách rõ ràng.

Gói này cũng bao gồm các đối tượng giá trị NextPDF\Graphics\WatermarkNextPDF\Graphics\WatermarkPosition. Watermark là bộ chứa cấu hình bất biến cho văn bản, cỡ phông chữ, góc, màu sắc, cờ phủ lên trên và các giá trị vị trí định sẵn, chẳng hạn như WatermarkPosition::Diagonal. Các đối tượng này mô hình hóa những tham số của một hình mờ. Công thức này vẽ dấu bằng các phương thức ghi vào trang ở trên, nên kết quả đầu ra đi thẳng vào luồng nội dung trang.

Tất cả các phương thức bên dưới đều công khai trên NextPDF\Core\Document và trả về static, nên bạn có thể nối chuỗi chúng.

  • setAlpha(float $alpha, BlendMode $mode = BlendMode::Normal): static: đặt độ mờ tô (0.0-1.0) và chế độ hòa trộn cho nội dung sau đó.
  • startTransform(): static: lưu trạng thái đồ họa (phát ra q).
  • rotate(float $angle, float $x = 0, float $y = 0): static: xoay hệ tọa độ $angle độ ngược chiều kim đồng hồ quanh điểm trục ($x, $y).
  • stopTransform(): static: khôi phục trạng thái đã lưu bởi startTransform() (phát ra Q), hoàn tác việc xoay và thay đổi alpha cùng lúc.
  • setFont(string $family, string $style = '', float $size = 12.0): static: chọn phông chữ cho dấu. Họ Base-14 helvetica luôn có sẵn và không cần tệp phông chữ.
  • setTextColor(int $r, int $g = -1, int $b = -1): static: đặt màu dấu theo kênh đỏ, xanh lá, xanh dương (hoặc một giá trị thang xám duy nhất).
  • text(float $x, float $y, string $text): static: viết dấu tại vị trí tuyệt đối.
  • image(string $file, ?float $x = null, ?float $y = null, ?float $width = null, ?float $height = null): static: đặt một ảnh raster, cơ sở cho hình mờ dạng hình ảnh hoặc nền toàn trang.
  • getPageWidth(): float / getPageHeight(): float: đọc kích thước trang hiện tại bằng đơn vị điểm để bạn có thể căn giữa dấu.

Các kiểu hỗ trợ nằm dưới NextPDF\Graphics: enum BlendMode, đối tượng giá trị Color và cặp cấu hình Watermark / WatermarkPosition.

Mẫu này tạo một trang, vẽ dấu “DRAFT” chéo mờ lên trên nội dung và lưu tệp. Nó bỏ qua xử lý lỗi để làm rõ dạng lệnh gọi. Mẫu sản xuất bên dưới bổ sung đầy đủ các bước bảo vệ.

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

Chương trình độc lập này vẽ một hình mờ văn bản chéo phủ lên nội dung được tạo ra. Khi bạn cung cấp một đường dẫn hình ảnh qua biến môi trường NEXTPDF_WATERMARK_IMAGE, chương trình đặt hình ảnh đó làm nền mờ, căn giữa trên trang thứ hai. Chương trình xác thực đường dẫn hình ảnh trước khi dùng, bắt các ngoại lệ NextPDF cụ thể nhất và ghi kết quả vào một đường dẫn do máy chủ kiểm soát. Hãy thay nội dung trong bộ nhớ bằng nội dung của riêng bạn, rồi kết nối đầu ra với lớp phản hồi hoặc lưu trữ của bạn.

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

Đầu ra chuẩn (STDOUT) dự kiến (kích thước byte phụ thuộc vào bản dựng và vào việc bạn có cung cấp hình ảnh hay không):

Wrote <n>-byte PDF to <path>

Trường hợp đặc biệt & điểm cần lưu ý

Phần tiêu đề “Trường hợp đặc biệt & điểm cần lưu ý”
  • Thứ tự lớp là thứ tự gọi. Nền là nội dung được vẽ trước nội dung trang của bạn. Hình mờ phủ lên trên là nội dung được vẽ sau nội dung đó. Không có cờ nào sắp xếp lại các lớp; hãy di chuyển lệnh gọi thay vào đó.
  • Alpha giữ nguyên cho đến khi đặt lại. setAlpha() thay đổi trạng thái cho mọi thứ được vẽ sau đó. Hoặc bọc dấu trong startTransform() / stopTransform(), vốn khôi phục alpha trước đó, hoặc gọi setAlpha(1.0) trước nội dung đục. Mẫu môi trường sản xuất thực hiện cả hai.
  • Cân bằng mọi khối transform. Mỗi startTransform() cần một stopTransform() tương ứng. Một khối không cân bằng sẽ khiến xoay hoặc alpha tiếp tục áp dụng cho nội dung sau đó, và một stopTransform() bị thiếu tạo ra sự mất cân bằng trạng thái đồ họa mà trình ghi sẽ từ chối khi xuất.
  • rotate() lấy điểm trục theo tọa độ người dùng. Điểm trục ($x, $y) tính theo đơn vị người dùng, đo từ góc trên bên trái của trang, trong cùng hệ quy chiếu với text(). Với một đường chéo qua tâm, hãy dùng tâm trang (getPageWidth() / 2, getPageHeight() / 2).
  • Văn bản đã xoay cần bù trừ chiều rộng thủ công. text() đặt điểm gốc của chuỗi; nó không tự căn giữa cho bạn. Trừ khoảng một nửa chiều rộng văn bản ước tính khỏi X của điểm trục để dấu đã xoay nằm vắt qua tâm, như hàm trợ giúp làm.
  • Hình ảnh được co giãn theo hộp bạn truyền vào. image() kéo giãn ảnh raster theo widthheight bạn cung cấp. Đối với nền toàn trang, hãy truyền chiều rộng và chiều cao trang; đối với logo ở góc, hãy truyền kích thước tự nhiên của ảnh. Một kích thước bằng không hoặc âm sẽ làm phát sinh PageLayoutException.
  • image() từ chối URL và byte NUL. Đường dẫn scheme:// hoặc byte NUL trong $file làm phát sinh PageLayoutException trước bất kỳ việc giải mã nào. Chỉ truyền vào một đường dẫn cục bộ, đã được xác thực.
  • Dấu là nội dung hiển thị được. Một hình mờ được vẽ theo cách này là nội dung trang thực sự, không phải chú thích ẩn. Bất kỳ ai có tệp đều đọc được nó. Nó là một dấu hiệu trực quan, không phải kiểm soát truy cập.

Một hình mờ văn bản dùng một vài toán tử luồng nội dung trên mỗi trang và hầu như không làm tăng thời gian hoặc bộ nhớ. Một hình mờ hoặc nền dạng hình ảnh tốn một lần giải mã raster cộng với số byte hình ảnh được nhúng trong đầu ra. Tái sử dụng cùng một hình ảnh trên nhiều trang sẽ tái sử dụng XObject đã giải mã qua bộ nhớ đệm hình ảnh, nên bạn chỉ trả chi phí giải mã một lần. Hãy chỉnh kích thước hình ảnh nền theo hộp hiển thị của chúng trước khi nhúng. Một bức ảnh 4.000 px được co vào một trang khổ letter sẽ nhúng những byte mà trình đọc không bao giờ thấy. Một hình mờ văn bản một trang điển hình nằm gọn trong giới hạn 500 ms thời gian thực và 32 MB bộ nhớ đỉnh. Mức dùng bộ nhớ của nền hình ảnh bám theo kích thước đã giải mã của ảnh raster nguồn.

Pipeline chạy trong cùng tiến trình. Không byte tài liệu nào rời khỏi máy chủ và không lệnh gọi mạng nào được thực hiện. Hãy coi mọi đường dẫn hình ảnh có nguồn gốc bên ngoài mã của bạn là đầu vào không đáng tin cậy.

  • Xác thực đường dẫn hình ảnh trước khi dùng. Từ chối byte NUL, phân giải đường dẫn bằng realpath(), và xác nhận is_file()is_readable() trước khi bạn gọi image(), đúng như mẫu môi trường sản xuất thực hiện. Điều này chặn việc duyệt đường dẫn và từ chối sớm các thư mục cùng các liên kết treo.
  • Không bao giờ chèn một trường yêu cầu vào đường dẫn. Suy ra đường dẫn hình ảnh và đường dẫn đầu ra từ các giá trị do máy chủ kiểm soát, không phải từ một tham số yêu cầu. Điều này giúp bạn không đọc hoặc ghi các tệp nằm ngoài thư mục dự kiến.
  • Hãy coi các hình ảnh không đáng tin cậy là đầu vào thù địch. Một ảnh raster dị dạng làm phát sinh ImageProcessingException thay vì làm hỏng tài liệu, và trình tải giới hạn kích thước hình ảnh để chống lại các đầu vào bom giải nén. Hãy bắt ngoại lệ và từ chối tệp tải lên. Đừng thử lại một cách mù quáng.
  • Hình mờ không phải là nơi cất giữ bí mật. Dấu là nội dung hiển thị được. Đừng đưa thông tin đăng nhập, token, hoặc định danh nội bộ vào một hình mờ hay nền mà bạn trả về cho khách hàng.

Công thức này không tự đưa ra tuyên bố quy chuẩn nào về các tiêu chuẩn. Nó kết hợp các primitive alpha, transform, text và image công khai. Mỗi primitive phát ra các toán tử luồng nội dung PDF chuẩn. Trạng thái đồ họa được cô lập bằng các toán tử q / QstartTransform()stopTransform() phát ra, và độ trong suốt được mang qua một tham số trạng thái đồ họa ExtGState. Đầu ra tươi mới về mặt cấu trúc thay vì ổn định theo byte, nên trang này khai báo hồ sơ khả năng tái lập structural. Để biết chi tiết ở mức toán tử về bề mặt transform và trạng thái đồ họa, hãy xem Tài liệu tham khảo mô-đun Graphics.