Hợp nhất các tệp PDF bên ngoài hoặc nối thêm trang từ tài liệu hiện có
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”Bạn có nhiều tệp PDF trên đĩa và cần một tệp PDF duy nhất. Công thức này kết hợp các tài liệu hiện có từ đầu đến cuối bằng giao diện hợp nhất của Core, NextPDF\Document\PdfMerger. Bạn truyền vào các chuỗi byte PDF thô. Trình hợp nhất đánh số lại mọi đối tượng để tránh xung đột, xây dựng một cây trang và một bảng tham chiếu chéo, rồi trả về một NextPDF\Document\MergeResult mà bạn có thể ghi ra đĩa hoặc truyền trực tiếp đến máy khách.
Cùng giao diện này xử lý ba tác vụ bạn thường cần nhất:
- Hợp nhất một danh sách PDF đã được sắp xếp thành một tài liệu.
- Nối thêm một PDF thứ hai vào sau một PDF cơ sở.
- Chèn vào đầu các trang bằng cách đặt tài liệu mới lên đầu thứ tự đầu vào.
Quá trình hợp nhất chạy trong cùng tiến trình, không cần trình duyệt headless hay lời gọi mạng. Bạn cần cài đặt Core (composer require nextpdf/core:^3) và ít nhất hai tệp PDF có thể đọc được.
Cài đặt
Phần tiêu đề “Cài đặt”composer require nextpdf/core:^3Tổng quan về khái niệm
Phần tiêu đề “Tổng quan về khái niệm”Một tệp PDF tổ chức các trang trong một cây trang có nút gốc là /Pages và định vị từng đối tượng gián tiếp thông qua một bảng tham chiếu chéo. Khi bạn kết hợp hai tài liệu nguồn, số đối tượng của chúng bị trùng nhau. Cả hai tệp gần như luôn chứa một đối tượng 1 0 obj, một /Catalog, và một nút /Pages. Nếu chỉ nối các byte lại với nhau, bạn sẽ tạo ra một tệp hỏng vì các tham chiếu không còn trỏ đến những đối tượng mà chúng định danh.
PdfMerger giải quyết việc trùng số này. Nó trích xuất các đối tượng trang từ mỗi đầu vào, đánh số lại mọi đối tượng trong một không gian địa chỉ duy nhất, ghi lại tham chiếu /Parent của từng trang để trỏ đến một nút /Pages hợp nhất duy nhất, rồi xuất ra một catalog, một cây trang và một trailer. Kết quả là một tài liệu mới về mặt cấu trúc, không phải là một bản nối ghép tạm bợ.
Quy tắc sắp xếp rất đơn giản: các trang xuất hiện theo đúng thứ tự của tệp nguồn trong danh sách đầu vào. Để nối thêm, hãy đặt tài liệu cơ sở lên đầu. Để chèn vào đầu, hãy đặt tài liệu mới lên đầu. Không cần phương thức chèn vào đầu riêng, vì thứ tự đầu vào là điều khiển duy nhất bạn cần.
Giao diện API
Phần tiêu đề “Giao diện API”new NextPDF\Document\PdfMerger() cung cấp hai phương thức.
merge(list<string> $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000): MergeResultkết hợp một danh sách đã sắp xếp gồm các chuỗi byte PDF thô. Hai tham số giới hạn này khống chế số lượng tệp và tổng kích thước đầu vào. Cả hai đều mặc định về các giá trị an toàn cho môi trường thực tế; hãy siết chặt chúng cho từng khối lượng công việc.append(string $basePdf, string $appendPdf): MergeResultlà một lớp bao tiện lợi giúp hợp nhất đúng hai tài liệu theo thứ tự. Nó tương đương vớimerge([$basePdf, $appendPdf]).
Cả hai phương thức đều trả về một NextPDF\Document\MergeResult, một đối tượng readonly chứa $pdfData (các byte đã hợp nhất), $totalPages, $sourceCount, $mergedSize, và hàm trợ giúp isValid() để xác nhận kết quả bắt đầu bằng tiêu đề %PDF.
Đầu vào là các chuỗi byte thô, không phải đường dẫn tệp. Hãy tự đọc tệp bằng file_get_contents(), hoặc lấy các byte từ kho lưu trữ đối tượng. Điều này giúp trình hợp nhất không phải giả định về hệ thống tệp và cho phép bạn hợp nhất các tài liệu không bao giờ chạm tới đĩa.
Nếu bạn cần nhập một trang đơn lẻ từ một PDF bên ngoài dưới dạng Form XObject tái sử dụng được, ví dụ để đóng dấu một trang tiêu đề thư phía sau nội dung được tạo, hãy dùng hợp đồng nhập giữa các gói NextPDF\Contracts\ImportedFormObjectInterface, được triển khai bởi các trình nhập như nextpdf/artisan. Để ghép toàn bộ tài liệu và toàn bộ trang, hãy dùng giao diện PdfMerger được mô tả ở đây.
Mẫu mã — bắt đầu nhanh
Phần tiêu đề “Mẫu mã — bắt đầu nhanh”Mẫu này đọc hai tệp và ghi ra kết quả đã hợp nhất. Mẫu bỏ qua phần xử lý lỗi để cho thấy cấu trúc lời gọi; mẫu cho môi trường thực 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\Document\PdfMerger;
$merger = new PdfMerger();
$result = $merger->merge([ file_get_contents(__DIR__ . '/cover.pdf'), file_get_contents(__DIR__ . '/body.pdf'), file_get_contents(__DIR__ . '/appendix.pdf'),]);
file_put_contents(__DIR__ . '/combined.pdf', $result->pdfData);
printf("Merged %d source(s) into %d page(s).\n", $result->sourceCount, $result->totalPages);Mẫu mã — môi trường thực tế
Phần tiêu đề “Mẫu mã — môi trường thực tế”Chương trình tự chứa này xây dựng hai tài liệu nhỏ trong bộ nhớ, nên có thể chạy mà không cần tệp bên ngoài. Nó hợp nhất chúng, xác thực kết quả, và ghi kết quả đầu ra. Nó bắt hai ngoại lệ mà giao diện hợp nhất phát ra và ném lại từng ngoại lệ kèm ngữ cảnh thay vì bỏ qua chúng. Hãy thay các đầu vào trong bộ nhớ bằng các lần đọc file_get_contents() của riêng bạn hoặc các lần tải từ kho lưu trữ đối tượng, rồi đưa kết quả đầu ra vào phản hồi hoặc tầng lưu trữ của bạn.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Document\MergeResult;use NextPDF\Document\PdfMerger;use NextPDF\Exception\PageLayoutException;use NextPDF\Exception\WriterException;
/** * Build a tiny labelled PDF so the program is self-contained. * * In your own code, replace calls to this helper with reads of the external * PDFs you want to combine, for example file_get_contents($path). */function buildSample(string $label, int $pages): string{ $doc = Document::createStandalone(); $doc->setTitle($label);
for ($page = 1; $page <= $pages; $page++) { $doc->addPage(); $doc->setFont('helvetica', '', 12); $doc->cell(0, 10, sprintf('%s - page %d', $label, $page), newLine: true); }
return $doc->getPdfData();}
// Validate the input set before touching the merger. An empty set is a// configuration error, not an empty success./** @var list<string> $sources Raw PDF byte strings, in output order. */$sources = [ buildSample('Cover', 1), // first in the list -> first in the output (prepend position) buildSample('Body', 2), buildSample('Appendix', 1), // last in the list -> appended after the body];
if ($sources === []) { throw new RuntimeException('No source PDFs supplied to merge.');}
$merger = new PdfMerger();
try { // Bound the merge deliberately: at most 50 files, 100 MB total input. $result = $merger->merge($sources, maxFiles: 50, maxTotalBytes: 100_000_000);} catch (PageLayoutException $e) { // Raised when the list is empty or an input does not begin with %PDF. throw new RuntimeException( sprintf('Merge rejected an input: %s', $e->getConstraint()), previous: $e, );} catch (WriterException $e) { // Raised when the total input size exceeds the configured byte cap. throw new RuntimeException( sprintf('Merge exceeded its size budget at stage "%s".', $e->getWriterState()), previous: $e, );}
if (!$result->isValid()) { throw new RuntimeException('Merged output failed its structural header check.');}
emitResult($result);
/** * Write the merged document to the cookbook side-channel, or to a default file. */function emitResult(MergeResult $result): void{ printf( "Merged %d source(s) into %d page(s), %d bytes.\n", $result->sourceCount, $result->totalPages, $result->mergedSize, );
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT'); $path = $out !== false && $out !== '' ? $out : __DIR__ . '/combined.pdf';
if (file_put_contents($path, $result->pdfData) === false) { throw new RuntimeException(sprintf('Could not write merged PDF to "%s".', $path)); }}Đầu ra chuẩn dự kiến (tổng số trang là tổng số trang của các nguồn, và kích thước byte phụ thuộc vào bản dựng):
Merged 3 source(s) into 4 page(s), <n> bytes.Trường hợp biên & điểm cần lưu ý
Phần tiêu đề “Trường hợp biên & điểm cần lưu ý”- Đầu vào là byte, không phải đường dẫn.
merge()nhận các chuỗi PDF thô. Hãy đọc tệp bằngfile_get_contents()trước. Nếu truyền vào một chuỗi đường dẫn, đầu vào sẽ không vượt qua kiểm tra tiêu đề%PDFvà phát raPageLayoutException. - Thứ tự chính là thứ tự đầu ra. Các trang được đặt theo thứ tự mà tệp nguồn của chúng xuất hiện trong danh sách. Không có phương thức chèn vào đầu: đặt tài liệu mới lên đầu để chèn vào đầu, hoặc xuống cuối để nối thêm.
- Danh sách rỗng là một lỗi. Một
$pdfFilesrỗng sẽ phát raPageLayoutException, chứ không phải một kết quả rỗng. Hãy xác thực tập đầu vào trước khi bạn gọi trình hợp nhất. - Mọi đầu vào đều được xác thực ngay từ đầu. Mỗi mục phải không rỗng và bắt đầu bằng
%PDF. Đầu vào không hợp lệ đầu tiên sẽ phát raPageLayoutExceptionkèm theo ràng buộc bị vi phạm, và không có gì được hợp nhất. - Các giới hạn phát ra ngoại lệ chứ không cắt bớt. Vượt quá
maxFilessẽ phát ra ngoại lệ thông qua bộ bảo vệ tài nguyên nội bộ, còn vượt quámaxTotalBytessẽ phát raWriterException. Trình hợp nhất không bao giờ âm thầm loại bỏ tệp hay cắt bớt byte, vì vậy hãy điều chỉnh cả hai giới hạn cho khối lượng công việc của bạn. - Kết quả mới về mặt cấu trúc, không ổn định ở mức byte. Tài liệu đã hợp nhất mang theo một catalog, cây trang và trailer mới. Hai lần chạy trên cùng một đầu vào sẽ tương đương về mặt cấu trúc, nhưng không được đảm bảo giống hệt nhau ở mức byte. Đó là lý do công thức này khai báo một hồ sơ tái lập
structural. - Chú thích ở mức trang và tài nguyên dùng chung. Quá trình hợp nhất ghép các đối tượng trang vào một cây duy nhất. Các cấu trúc ở mức tài liệu nằm bên ngoài các đối tượng trang trong tệp nguồn sẽ không được chuyển sang. Khi bạn cần nhập một trang đơn lẻ dưới dạng đồ họa tái sử dụng được cùng với tài nguyên của nó, hãy dùng đường dẫn
ImportedFormObjectInterfacethông qua một trình nhập nhưnextpdf/artisan.
Hiệu năng
Phần tiêu đề “Hiệu năng”Quá trình hợp nhất tuyến tính theo tổng số trang. Phần phân tích cú pháp và đánh số lại đối tượng chiếm phần lớn khối lượng công việc, không phải phần ghi sổ nội bộ của riêng trình hợp nhất. Bộ nhớ đỉnh tỷ lệ với tổng số byte đầu vào vì mọi nguồn đều được giữ trong bộ nhớ dưới dạng chuỗi trong khi kết quả đầu ra được lắp ráp. Bộ bảo vệ maxTotalBytes giữ cho mức đỉnh đó nằm trong giới hạn. Với các pipeline khối lượng lớn, hãy đặt maxFiles và maxTotalBytes ở các giá trị nhỏ nhất mà khối lượng công việc của bạn cần, để một lô bị lỗi định dạng hoặc quá cỡ sẽ thất bại nhanh thay vì làm cạn kiệt bộ nhớ. Một lần hợp nhất nhỏ điển hình nằm trong ngân sách 1500 ms thời gian thực và 64 MB bộ nhớ đỉnh.
Lưu ý về bảo mật
Phần tiêu đề “Lưu ý về bảo mật”Quá trình hợp nhất chạy trong cùng tiến trình; không có byte tài liệu nào rời khỏi máy chủ, và không có lời gọi mạng nào được thực hiện. Hãy coi mọi PDF bên ngoài là đầu vào không đáng tin cậy:
- Giữ các giới hạn chặt chẽ.
maxFilesvàmaxTotalByteslà tuyến phòng thủ đầu tiên của bạn trước đầu vào dùng để tấn công từ chối dịch vụ. Với bất kỳ giao diện nào chấp nhận tải lên, hãy đặt chúng ở mức trần thực tế của bạn, chứ không phải các giá trị mặc định rộng rãi. - Xác thực trước khi tin tưởng. Một lần hợp nhất thành công có nghĩa là các byte đã được kết hợp, chứ không có nghĩa là các đầu vào an toàn. Hãy chạy các đầu vào không đáng tin cậy qua trình kiểm tra Core trước. Xem Phân tích và kiểm tra một tệp PDF để có một quá trình quét phân loại có giới hạn nhằm gắn cờ mã hóa, chữ ký và các dấu hiệu rủi ro trước khi xử lý nặng hơn.
- Không bao giờ chèn đầu vào của người dùng vào một đường dẫn. Công thức này ghi ra một đường dẫn cố định hoặc kênh phụ của cookbook. Hãy suy ra các đường dẫn đầu ra từ những giá trị do máy chủ kiểm soát, không bao giờ từ một trường yêu cầu, để tránh tấn công duyệt đường dẫn.
- Không để thông tin bí mật trong tài liệu. Không nhúng thông tin xác thực, token hay định danh nội bộ vào một tài liệu đã hợp nhất mà bạn trả về cho máy khách.
Tuân thủ
Phần tiêu đề “Tuân thủ”Công thức này không tự đưa ra tuyên bố tuân thủ chuẩn quy phạm nào của riêng nó. Nó ghép các tài liệu hiện có thông qua giao diện hợp nhất của Core và xác thực kết quả bằng kiểm tra tiêu đề MergeResult::isValid(). Mô hình cây trang mà PdfMerger dựng lại chính là cấu trúc cây trang PDF 2.0 được mô tả trong tài liệu tham khảo /modules/core/document/. Để đọc cấu trúc của bất kỳ tài liệu đầu vào hay đầu ra nào, bao gồm phiên bản, số trang, cờ mã hóa và cờ chữ ký, hãy dùng trình kiểm tra Core được mô tả trong Phân tích và kiểm tra một tệp PDF.
Xem thêm
Phần tiêu đề “Xem thêm”- Tài liệu tham khảo module Document — toàn bộ giao diện cho tài liệu, tách và hợp nhất.
- Phân tích và kiểm tra một tệp PDF — phân loại các đầu vào không đáng tin cậy trước khi bạn hợp nhất chúng.
- Xử lý lỗi theo ngoại lệ — hệ thống phân cấp ngoại lệ của NextPDF đằng sau
PageLayoutExceptionvàWriterException. - Xây dựng một tài liệu nhiều trang — tạo các trang để sau đó kết hợp lại.