ข้ามไปยังเนื้อหา

Writer: ตัวซีเรียลไลซ์ PDF 2.0 + xref

โมดูล Writer ทำหน้าที่ซีเรียลไลซ์เอกสารเป็นไบต์ในรูปแบบ Portable Document Format (PDF) โดยเลือกกลยุทธ์เวอร์ชัน เขียนกราฟอ็อบเจ็กต์ แล้วสร้างโครงสร้างการอ้างอิงไขว้และเทรลเลอร์

Terminal window
composer require nextpdf/core:^3

ใช้ PdfWriter เป็นจุดเริ่มต้น โดยส่งค่าอ็อบเจ็กต์ DocumentData ไปยัง write() เมธอดนี้จะคืนค่า PDF ฉบับสมบูรณ์เป็นสตริงไบต์ Writer จะประกอบกราฟอ็อบเจ็กต์ กำหนดหมายเลขอ็อบเจ็กต์ บันทึกออฟเซ็ตไบต์ และเขียนโครงสร้างการอ้างอิงไขว้เป็นขั้นตอนสุดท้าย

ทุกครั้งที่เรียกใช้ Writer จะใช้กลยุทธ์การซีเรียลไลซ์เพียงกลยุทธ์เดียว อินเทอร์เฟซ PdfSerializationStrategy นิยามเมธอดไว้สี่เมธอด ได้แก่ writeHeader(), getCatalogVersion(), writeXrefAndTrailer() และ usesXrefStream() กลยุทธ์ที่อิมพลีเมนต์อินเทอร์เฟซนี้มีสามแบบ Pdf20StreamStrategy เขียนเฮดเดอร์ %PDF-2.0 ตั้งค่าเวอร์ชันในแคตาล็อกเป็น /2.0 และสร้างการอ้างอิงไขว้แบบ สตรีม Pdf17TableStrategy เขียน %PDF-1.7 และการอ้างอิงไขว้แบบ ตาราง ดั้งเดิม Pdf14TableStrategy เขียน %PDF-1.4 และตารางการอ้างอิงไขว้ PdfWriter เลือกกลยุทธ์ด้วย match บน DocumentData::$outputProfile โดยค่าเริ่มต้นจะใช้ Pdf20StreamStrategy เป็นกลยุทธ์

enum PdfOutputProfile ระบุเวอร์ชันเป้าหมายทั้งสาม ได้แก่ Pdf20, Pdf17 และ Pdf14 enum นี้เปิดเผยเมธอด headerVersion(), catalogVersion(), allowsObjectStreams() และ usesXrefStream() โหมดการสอดคล้องสำหรับการจัดเก็บถาวรจะลบล้างโปรไฟล์ที่เลือกไว้ก่อนเลือกกลยุทธ์ ระหว่างการเขียน Pdf14FeatureGuard จะปฏิเสธคุณลักษณะของ PDF 2.0 เมื่อโปรไฟล์ที่ใช้งานอยู่คือ Pdf14

สตรีมการอ้างอิงไขว้จะแมปหมายเลขอ็อบเจ็กต์แต่ละหมายเลขไปยังออฟเซ็ตไบต์ของอ็อบเจ็กต์นั้น ตามที่นิยามไว้ใน ISO 32000-2 §7 การอัปเดตแบบเพิ่มหน่วยจะผนวกอ็อบเจ็กต์ใหม่เข้าที่ท้ายไฟล์ ตามที่นิยามไว้ใน ISO 32000-2 §7.5.6 Writer จะหลีกอักขระสตริงลิเทอรัลทั้งหมดผ่านพาธมาตรฐาน PdfStringEscaper::escapeLiteral() ซึ่งเป็นไปตามตารางการหลีกอักขระที่กำหนดไว้ใน ISO 32000-2 §7.3.4.2 (ADR-015)

Writer รองรับเอาต์พุตแบบกำหนดผลได้แน่นอน setDeterministicMode() จะตรึงตัวระบุอ็อบเจ็กต์และลำดับคีย์ในดิกชันนารี setReproducibleClock() จะตรึงไทม์สแตมป์ของเอกสาร เมื่อตั้งค่าการตรึงครบทั้งสองส่วนแล้ว อินพุตที่คงที่จะให้เอาต์พุตที่เหมือนกันในระดับไบต์ เมธอด writeChunked() จะคืนค่าเจเนอเรเตอร์ที่ทยอยส่ง PDF ออกมาเป็นชังก์ขนาดคงที่ ส่วน Streaming/StreamingPdfWriter จะเขียนทีละหน้าไปยังสตรีมที่ผู้เรียกจัดเตรียมไว้ สำหรับเอกสารที่เกินงบประมาณหน่วยความจำ

Linearizer จะเขียน PDF ที่เสร็จสมบูรณ์แล้วใหม่เป็นเค้าโครงแบบลิเนียไรซ์ โดยวางหน้าแรกไว้ที่ต้นไฟล์เพื่อให้โปรแกรมดูแสดงหน้านั้นได้ก่อนที่การดาวน์โหลดทั้งไฟล์จะเสร็จสมบูรณ์ shadowValidate() จะตรวจสอบการเขียนใหม่โดยไม่เปลี่ยนแปลงอินพุต

ข้อควรระวัง PdfWriter.php และ Linearizer.php มีความสำคัญอย่างยิ่งต่อออฟเซ็ตไบต์ และต่อกราฟอ็อบเจ็กต์ (โซนอันตรายตามที่ระบุในแมนิเฟสต์) อย่าเปลี่ยนแปลงการกำหนดหมายเลขอ็อบเจ็กต์ หรือการคำนวณออฟเซ็ต xref หากไม่ได้ใช้ชุดทดสอบ golden ของ Writer

คลาสเมธอดสำคัญบทบาท
PdfWriterwrite(DocumentData): string, writeChunked(DocumentData, int): Generator, setDeterministicMode(), setReproducibleClock(), setOutputColorProfile(), getLastXrefOffset(), getFileId()ตัวซีเรียลไลซ์หลัก
PdfSerializationStrategy (อินเทอร์เฟซ)writeHeader(), getCatalogVersion(), writeXrefAndTrailer(), usesXrefStream()สัญญาของกลยุทธ์ตามเวอร์ชัน
Pdf20StreamStrategywriteHeader()%PDF-2.0, getCatalogVersion()/2.0, usesXrefStream()trueกลยุทธ์ xref-stream ของ PDF 2.0
Pdf17TableStrategywriteHeader()%PDF-1.7, ตาราง xrefกลยุทธ์ xref-table สำหรับ PDF 1.7
Pdf14TableStrategywriteHeader()%PDF-1.4, ตาราง xrefกลยุทธ์ xref-table สำหรับ PDF 1.4
PdfOutputProfile (enum)Pdf20, Pdf17, Pdf14; headerVersion(), catalogVersion(), allowsObjectStreams()ตัวเลือกเวอร์ชันเป้าหมาย
PdfXrefWritergenerateFileId(), finalizeTrailerAndXref()การสร้าง File ID + การสรุป trailer/xref
Linearizerlinearize(string): string, shadowValidate(string): arrayการเขียนใหม่เพื่อ Fast-web-view
Streaming\StreamingPdfWriteropen(), newPage(), close()ตัวเขียนแบบสตรีมมิงผ่านครั้งเดียว

รัน composer docs:generate-api-php -- --module=Writer เพื่อสร้างตาราง PHPDoc แบบเต็ม

แหล่งที่มา: examples/02-pdf-factory.php (ไฟล์ตัวอย่าง)

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Writer\PdfWriter;
$writer = new PdfWriter();
$pdfBytes = $writer->write($documentData);
file_put_contents('out.pdf', $pdfBytes);

โปรไฟล์เริ่มต้นคือ PDF 2.0 เอาต์พุตจะเริ่มด้วย %PDF-2.0 และจบด้วยสตรีมการอ้างอิงไขว้

ตัวอย่างนี้ตรึงโหมดกำหนดผลแน่นอนและนาฬิกาแบบคงที่เพื่อให้ได้เอาต์พุตที่เหมือนกันในระดับไบต์ จากนั้นจึงสตรีมผลลัพธ์ออกมาเป็นชังก์ขนาดคงที่

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use DateTimeImmutable;
use NextPDF\Writer\PdfWriter;
use NextPDF\Writer\ReproducibleClock;
$pinned = new DateTimeImmutable('2026-01-01T00:00:00Z');
$writer = new PdfWriter();
$writer->setDeterministicMode($pinned, 'nextpdf-fixed-file-id');
$writer->setReproducibleClock(new ReproducibleClock($pinned));
$out = fopen('php://output', 'wb');
foreach ($writer->writeChunked($documentData, chunkSize: 65536) as $chunk) {
fwrite($out, $chunk);
}
fclose($out);
  • ในการเรียก write() แต่ละครั้งจะมีกลยุทธ์ทำงานเพียงกลยุทธ์เดียว Writer จะรีเซ็ตกลยุทธ์จากโปรไฟล์ทุกครั้งที่เรียกใช้ การเรียกครั้งก่อนหน้าจะไม่ปล่อยให้เวอร์ชันของตนรั่วไหลออกมา
  • โหมดการสอดคล้องสำหรับการจัดเก็บถาวรจะลบล้างโปรไฟล์ที่ร้องขอ การสร้าง PDF/A-3 จะบังคับใช้ PDF 1.7 การสร้าง PDF/A-4 จะบังคับใช้ PDF 2.0
  • เอาต์พุตที่เหมือนกันในระดับไบต์จำเป็นต้องใช้การตรึงทั้งสองส่วน ให้ตั้งค่าโหมดกำหนดผลแน่นอน และ นาฬิกาแบบทำซ้ำได้ การตรึงเพียงอย่างเดียวยังไม่เพียงพอ
  • writeChunked() จะคืนค่าเป็นเจเนอเรเตอร์และต้องบริโภคเจเนอเรเตอร์นี้จนหมด การอ่านเพียงบางส่วนจะทำให้ได้ PDF ที่ถูกตัดทอนและไม่ถูกต้อง
  • Linearizer จะเขียนออฟเซ็ตการอ้างอิงไขว้ใหม่ ให้รัน shadowValidate() ก่อนในไปป์ไลน์ที่ยอมรับความล้มเหลวจากการเขียนใหม่ไม่ได้
  • Pdf14TableStrategy เป็น final readonly พาธ PDF 1.4 จะปฏิเสธคุณลักษณะของ PDF 2.0 ผ่าน Pdf14FeatureGuard โดยไม่ลดทอนคุณลักษณะเหล่านั้น

การซีเรียลไลซ์เป็นแบบเชิงเส้นตามจำนวนอ็อบเจ็กต์และขนาดไบต์รวม สตรีมการอ้างอิงไขว้จะเพิ่มการวนผ่านตารางอ็อบเจ็กต์อีกหนึ่งรอบ writeChunked() จะเก็บเอกสารที่ประกอบแล้วไว้ แต่ทยอยส่งออกมาเป็นสไลซ์ที่มีขอบเขตจำกัด ดังนั้นหน่วยความจำสูงสุดจึงเท่ากับขนาดเอกสารบวกหนึ่งชังก์ Streaming\StreamingPdfWriter จะไม่เก็บเอกสารทั้งฉบับไว้ ให้ใช้กับอินพุตที่ใหญ่กว่างบประมาณหน่วยความจำ งบประมาณของเวิร์กโหลดอ้างอิงคือ 1500 ms wall และ 64 MB peak การลิเนียไรซ์จะเพิ่มการวนผ่านทั้งหมดอีกหนึ่งรอบและการวนวัดผลอีกหนึ่งรอบ จึงควรกันงบประมาณสำหรับการลิเนียไรซ์ไว้อย่างชัดเจน

Writer ทำหน้าที่ซีเรียลไลซ์กราฟอ็อบเจ็กต์ในหน่วยความจำที่เชื่อถือได้ อินพุตของ Writer คือขอบเขตภัยคุกคามหลัก สตริงลิเทอรัลทุกตัวจะผ่าน PdfStringEscaper::escapeLiteral() มาตรฐาน (ADR-015) ดังนั้นไบต์ควบคุมที่ฝังอยู่จึงไม่สามารถหลุดออกจากโทเค็นสตริงได้ การเข้ารหัสลับทำงานผ่าน PdfEncryptionWriter และรายการ /Encrypt ในเทรลเลอร์ การเข้ารหัสลับด้วยคีย์สาธารณะจะถูกปฏิเสธพร้อมข้อยกเว้นที่ชัดเจน แทนที่จะถูกลดระดับลงอย่างเงียบ ๆ โหมดกำหนดผลแน่นอนและโหมดนาฬิกาแบบทำซ้ำได้จะลบช่องทางข้างเคียงด้านไทม์สแตมป์และลำดับออกจากเอาต์พุต ดู /modules/core/security/ สำหรับแบบจำลองภัยคุกคามของเอกสารและขอบเขตความเชื่อถือด้านการเข้ารหัสลับ

Writer สร้าง โครงสร้างไฟล์แบบ PDF 2.0 ได้แก่ เฮดเดอร์ %PDF-2.0 เวอร์ชันแคตาล็อก /2.0 สตรีมการอ้างอิงไขว้ และการหลีกอักขระสตริงลิเทอรัลตามตารางการหลีกอักขระใน ISO 32000-2 §7.3.4.2 สิ่งเหล่านี้เป็นข้อเท็จจริงของการอิมพลีเมนต์ หลักฐานอยู่ใน src/Writer/Pdf20StreamStrategy.php, src/Writer/PdfSerializationStrategy.php และการเลือกกลยุทธ์ใน src/Writer/PdfWriter.php พฤติกรรมนี้ได้รับการทดสอบโดย tests/Unit/Writer/ (192 การทดสอบ รวมถึงชุดทดสอบ Pdf20StreamStrategy, PdfXrefWriter และ Linearizer*) รวมถึงเบสไลน์ tests/Golden/PdfWriter/PdfWriterGoldenBaselineSmokeTest

นี่ ไม่ใช่ การอ้างว่าสอดคล้องกับ PDF 2.0 อย่างสมบูรณ์ การสอดคล้องกับ ISO 32000-2 อย่างสมบูรณ์เป็นคุณสมบัติของเอกสารฉบับสมบูรณ์ที่ได้รับการตรวจสอบโดยออราเคิลภายนอก ไม่ใช่คุณสมบัติของตัวซีเรียลไลซ์เพียงลำพัง การสอดคล้องแบบครบวงจรจะยืนยันเฉพาะในจุดที่ออราเคิลยืนยันเท่านั้น คือ tests/Integration/Accessibility/VeraPdfUa2GoldenTest จะตรวจสอบฟิกซ์เจอร์ที่สร้างขึ้นเทียบกับ veraPDF สำหรับ PDF/UA-2 และ tests/Standards/Profile/PdfRConformanceTest ครอบคลุมโปรไฟล์ PDF/R การทดสอบ golden ของ veraPDF จะ ข้ามเมื่อไม่มีไบนารี veraPDF อยู่บน runner ดังนั้นจึงเป็นเกตแบบออราเคิลที่เลือกเปิดได้ ไม่ใช่แบบไม่มีเงื่อนไข ตั้งค่า VERAPDF_BINARY เพื่อรันการทดสอบนี้ การเลือกโปรไฟล์การจัดเก็บถาวร (PDF/A-3 → PDF 1.7, PDF/A-4 → PDF 2.0) ตัดสินโดย ADR-011 และโหมดการสอดคล้อง และได้รับการตรวจสอบโดยชุดทดสอบการสอดคล้องใน /modules/core/conformance/ นอกเหนือจากโปรไฟล์ที่ออราเคิลรองรับเหล่านั้น ให้ระบุว่า Writer “สร้างโครงสร้าง PDF 2.0 ส่วนการสอดคล้องได้รับการตรวจสอบโดย veraPDF สำหรับโปรไฟล์ PDF/UA-2” แทนที่จะอ้างการสอดคล้องแบบไม่มีเงื่อนไข