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

ไบต์ชุดเดิมทุกครั้ง: PDF ที่ทำซ้ำได้

Spec: ISO 32000-2, §14.4Spec: ISO 32000-2, §14.3.3

สร้าง PDF จากอินพุตเดียวกันสองครั้งแล้วคุณคงคาดหวังไฟล์เดียวกัน ไลบรารี PDF ส่วนใหญ่สัญญาสิ่งนั้นไม่ได้ — สร้างใหม่แล้วเปรียบเทียบ ไบต์ก็คลาดเคลื่อน NextPDF สามารถตรึงสองสิ่งที่เคลื่อนได้ เพื่อให้อินพุตเดียวกันผลิตไบต์ชุดเดิม ทุกครั้ง

เอาต์พุตที่เหมือนกันแบบไบต์ต่อไบต์ไม่ใช่ตัวชี้วัดเพื่อความโก้ มันคือรากฐานที่อยู่ใต้สามสิ่งที่ทีมต้องการจริงๆ

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

อย่างที่สองคือ หลักฐานการดัดแปลง ไปป์ไลน์ที่สามารถสร้างไฟล์เดิมที่มันส่งออกไปขึ้นใหม่ได้อย่างเป๊ะ สามารถพิสูจน์ในภายหลังได้ว่าเอกสารที่เก็บถาวรไว้ไม่ได้ถูกแก้ไข: สร้างมันใหม่ คำนวณ hash ทั้งคู่ เปรียบเทียบ หากแม้แต่ไบต์เดียวต่างกันเพราะนาฬิกาที่ฝังตัว หลักฐานก็หายไปและคุณก็กลับไปอยู่ที่ “เชื่อฉันสิ”

อย่างที่สามคือ CI ที่เชื่อถือได้ golden-file test บันทึกเอาต์พุตที่รู้ว่าดีไว้ และล้มเหลวเมื่อการเปลี่ยนแปลงทำให้มันเปลี่ยนไป สัญญาณนั้นมีความหมายก็ต่อเมื่อเอนจินที่ไม่เปลี่ยนสร้างไฟล์ที่ไม่เปลี่ยนขึ้นมาได้ หากทุกการรันต่างกันที่ timestamp golden file ก็เป็นเพียงเสียงรบกวน และทีมก็เรียนรู้ที่จะเพิกเฉยต่อบิลด์สีแดง — นิสัยที่แพงที่สุดในการทดสอบ

ในโปรไฟล์ที่กำหนดผลแน่นอนของ NextPDF ฟิลด์สองตัวที่เอนจินควบคุมซึ่งมิฉะนั้นจะคลาดเคลื่อนกันระหว่างบิลด์ที่เหมือนกันคือวันที่และ /ID ทั้งนี้สมมติว่าส่วนที่เหลือของไปป์ไลน์เสถียรอยู่แล้ว — อินพุตเดียวกัน และ serialization ที่ไม่แปรเปลี่ยนด้วยตัวเอง (รายละเอียดด้านล่าง):

  • วันที่ที่ฝังตัว document information dictionary พก CreationDate และ ModDate (Spec: ISO 32000-2, §14.3.3) และเมตาดาตา XMP สะท้อนค่าเหล่านั้น จับ “ตอนนี้” ณ เวลาบิลด์ แล้วทุกการสร้างใหม่ก็ต่างกัน
  • ตัวระบุไฟล์ อาร์เรย์ /ID คือคู่ของสตริงไบต์ที่ระบุไฟล์ (Spec: ISO 32000-2, §14.4) จัดเก็บไว้ใน trailer dictionary (Spec: ISO 32000-2, §7.5.5) ไลบรารีมักได้มันมาจากเวลาปัจจุบันบวกไบต์สุ่ม ดังนั้นมันจึงต่างกันในทุกการรันโดยการออกแบบ

ตรึงทั้งสอง — timestamp ที่ตายตัวและ seed ที่ตายตัวสำหรับ /ID — แล้วเอาต์พุตจะกลายเป็นฟังก์ชันที่กำหนดผลแน่นอนของเนื้อหา ปล่อยเนื้อหาไว้แล้วไฟล์ก็เหมือนกันแบบไบต์ต่อไบต์ นี่คือวินัยเดียวกับที่โครงการ Reproducible Builds สถาปนาไว้สำหรับซอฟต์แวร์ที่คอมไพล์แล้ว นำมาประยุกต์กับชั้นเอกสาร

ความกำหนดผลแน่นอนใน NextPDF เป็นอ็อบเจกต์การกำหนดค่า ไม่ใช่เทคนิคลัดสำหรับการทดสอบ เอนจินเปิดเผยอ็อบเจกต์ค่า DeterministicSettings ใน namespace NextPDF\Core มันเป็น final readonly เปลี่ยนแปลงไม่ได้ และตรึงแหล่งที่มาของการคลาดเคลื่อนที่ได้มาจากนาฬิกาและความสุ่มสองแหล่งที่ระบุไว้ข้างต้นพอดี: วันที่และ /ID การตรึงทั้งสองนำแหล่งคลาดเคลื่อนที่พบบ่อยที่สุดสองแหล่งออกไป แต่ในตัวมันเองมันไม่รับประกันเอาต์พุตที่เหมือนกันแบบไบต์ต่อไบต์ พฤติกรรม serialization อื่นๆของเอนจิน — การเรียงลำดับอ็อบเจกต์ การทำ subset ฟอนต์ และการตั้งค่าการบีบอัด — ก็ต้องกำหนดผลแน่นอนด้วยเพื่อให้เอาต์พุตสร้างซ้ำได้ และ NextPDF รักษาสิ่งเหล่านั้นให้เสถียรโดยการออกแบบ

คอนสตรัคเตอร์ของมันรับสองอาร์กิวเมนต์:

public function __construct(
public DateTimeImmutable $timestamp,
public string $fileIdSeed,
) {
// ...
}

$timestamp คือชั่วขณะตายตัวเดียวที่เขียนลงในทุกฟิลด์วันที่ — CreationDate, ModDate และเงาสะท้อนใน XMP ของมัน ส่ง DateTimeImmutable หนึ่งตัวแล้วเอกสารก็หยุดถามนาฬิกาว่าตอนนี้กี่โมง $fileIdSeed คืออินพุตที่ตรึง /ID ใน trailer: สตริงเลขฐานสิบหก 32 ตัวอักษร ให้ seed เดียวกันแล้วเอนจินก็ได้ตัวระบุไฟล์เดียวกัน แทนที่จะสุ่มตัวอย่างนาฬิกาและแหล่งสุ่ม

อ็อบเจกต์ตรวจสอบอินพุตของตัวเอง seed ต้องเป็นเลขฐานสิบหก 32 ตัวอักษรพอดี อย่างอื่นจะถูกปฏิเสธ ณ การสร้างด้วย InvalidConfigException แทนที่จะผลิต /ID ที่ดูต่างออกไปอย่างเงียบๆ นี่คือจุดยืนปฏิเสธที่จะเดาเดียวกันที่ส่วนที่เหลือของเอนจินยึดถือ — อินพุตที่กำกวมล้มเหลวอย่างเสียงดังแทนที่จะเปลี่ยนไบต์อย่างเงียบๆ

เมื่อตรึงทั้งสองแล้ว สูตรก็คือสูตรที่โครงการ Reproducible Builds ทำให้คุ้นเคย: สร้างมันใหม่ เปรียบเทียบมัน และส่วนต่างก็ว่างเปล่า

  1. Fix the inputsThe same content, fonts, and settings that produced the original document.
  2. Pin the timestampOne DateTimeImmutable feeds CreationDate, ModDate, and the XMP dates — no wall clock.
  3. Pin the /ID seedA 32-character hex seed derives the trailer /ID instead of a clock-plus-random value.
  4. BuildThe output is now a pure function of content; the two moving parts are held still.
  5. Rebuild and diffRegenerate from the same inputs and compare bytes — an empty diff is the proof.
Reproducible build: identical inputs plus a pinned timestamp and a pinned /ID seed produce the same bytes, which a rebuild-and-diff step confirms.

รูปแบบเล็กๆที่สมบูรณ์ การตั้งค่าถูกสร้างขึ้นครั้งเดียวและนำมาใช้ซ้ำ ดังนั้นสองการรันของโปรแกรมเดียวกันจึงปล่อยไฟล์เดียวกันออกมา

<?php
declare(strict_types=1);
use NextPDF\Core\DeterministicSettings;
use NextPDF\Exception\InvalidConfigException;
// One fixed instant for every date field — never the wall clock.
$timestamp = new DateTimeImmutable('2026-01-01T00:00:00+00:00');
// A 32-character hex seed pins the trailer /ID. Same seed, same /ID.
$fileIdSeed = '0123456789abcdef0123456789abcdef';
try {
$deterministic = new DeterministicSettings(
timestamp: $timestamp,
fileIdSeed: $fileIdSeed,
);
} catch (InvalidConfigException $e) {
// A malformed seed (not exactly 32 hex chars) is refused here,
// before any document is built — not silently coerced.
error_log($e->getMessage());
throw $e;
}
// Hand $deterministic to the document configuration. With both moving
// parts pinned, building the same content twice yields identical bytes:
//
// sha256(build_one) === sha256(build_two)

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

กับดักแรกคือ “ฉันนำ timestamp ออกแล้ว ดังนั้นบิลด์ของฉันทำซ้ำได้แล้วตอนนี้” โดยทั่วไปมันไม่ เพราะอาร์เรย์ /ID เป็นแหล่งที่เงียบกว่าในสองแหล่ง วันที่มองเห็นได้ในแผงเมตาดาตาและจำง่าย ส่วน /ID ใน trailer มองไม่เห็นสำหรับโปรแกรมอ่านส่วนใหญ่และถูกสร้างขึ้นใหม่จากนาฬิกาและแหล่งสุ่มในทุกการรัน บิลด์ที่ตรึงเพียงวันที่ยังคงผลิตไฟล์ที่ต่างกันในแต่ละครั้ง คุณต้องตรึงทั้งสองให้นิ่ง

กับดักที่สองคือการปฏิบัติต่อความกำหนดผลแน่นอนเป็นคุณสมบัติด้านความปลอดภัยในตัวมันเอง /ID ที่ตรึงไว้ทำให้ไฟล์ ทำซ้ำได้ มันไม่ได้ทำให้ไฟล์ ลงนามแล้ว และในตัวมันเองมันก็ไม่ได้พิสูจน์ว่าบิลด์สองอันตรงกัน การเปรียบเทียบแบบไบต์ต่อไบต์หรือ hash พิสูจน์ว่าบิลด์ตรงกัน การตรึง /ID เพียงนำแหล่งของความต่างที่ผิดพลาดออกไปหนึ่งแหล่งเท่านั้น และทั้งสองอย่างนั้นก็ไม่ได้พิสูจน์ว่าบุคคลที่สามรับรองไฟล์ ความสามารถในการทำซ้ำและการลงนามเป็นชั้นที่เสริมกัน ไม่ใช่ตัวแทนกัน

ความกำหนดผลแน่นอนตรึงส่วนที่เคลื่อนของเอนจินเอง มันไม่ตรึงอินพุต ของคุณ หากเนื้อหาของคุณฝัง timestamp ที่มีชีวิต ดึงฟอนต์ที่เปลี่ยนไปบนดิสก์ หรือเรนเดอร์ค่าที่ขึ้นอยู่กับวันที่ปัจจุบัน เอาต์พุตก็เปลี่ยนเพราะ อินพุต เปลี่ยน — และนั่นถูกต้อง DeterministicSettings นำความไม่กำหนดผลแน่นอนของเอนจินออก ไม่ใช่ของคุณ บิลด์ที่ทำซ้ำได้ยังคงต้องการอินพุตที่ทำซ้ำได้

Deterministic byte-identical output — edition availability
EditionAvailability
Core

Full support. DeterministicSettings ships in the open-source core: pin the timestamp and the /ID seed and the same content rebuilds to the same bytes — no edition gate.

ProNot in this edition
EnterpriseNot in this edition
  • Golden-file testing — เทคนิค CI ที่พึ่งพาเอาต์พุตที่เหมือนกันแบบไบต์ต่อไบต์ และเหตุใดเอนจินที่กำหนดผลแน่นอนจึงเป็นเงื่อนไขก่อนของมัน
  • Incremental update — วิธีที่ PDF เติบโตด้วยการต่อท้าย ซึ่งอาร์เรย์ /ID สำคัญอีกครั้งสำหรับการเชื่อมโยงไฟล์เข้ากับเวอร์ชันก่อนหน้า
  • เมตาดาตาและ XMP packet — ที่ที่วันที่ที่ฝังตัวอยู่ และวิธีที่ XMP packet สะท้อน document information dictionary
  • กายวิภาคของไฟล์ PDF — trailer, cross-reference table และตำแหน่งที่อาร์เรย์ /ID ตั้งอยู่ในโครงสร้างของไฟล์
  • Byte-identical — สองไฟล์ที่ตรงกันเป๊ะ ไบต์ต่อไบต์ รูปแบบที่แข็งแรงที่สุดของ “เหมือนกัน” และเป็นรูปแบบที่ hash หรือการเปรียบเทียบยืนยันได้
  • /ID (file identifier) — อาร์เรย์ของสตริงไบต์สองตัวที่ระบุ PDF และเวอร์ชันของมัน (ISO 32000-2 §14.4) จัดเก็บไว้ใน trailer dictionary (§7.5.5) มักได้มาจากนาฬิกาบวกไบต์สุ่ม ซึ่งเป็นเหตุผลที่มันเปลี่ยนในทุกบิลด์ที่ไม่ตรึง
  • Document information dictionary — โครงสร้างที่พก CreationDate และ ModDate (ISO 32000-2 §14.3.3) หนึ่งในสองแหล่งของความไม่กำหนดผลแน่นอนที่บิลด์ที่กำหนดผลแน่นอนต้องตรึง
  • Golden file — เอาต์พุตที่รู้ว่าดีที่บันทึกไว้ซึ่งการทดสอบเปรียบเทียบด้วย มีความหมายก็ต่อเมื่อเอนจินที่ไม่เปลี่ยนสร้างไฟล์ที่ไม่เปลี่ยนขึ้นมา
  • Reproducible build — บิลด์ที่เอาต์พุตเป็นฟังก์ชันที่กำหนดผลแน่นอนของอินพุต ดังนั้นการสร้างใหม่จากอินพุตเดียวกันจึงให้ไบต์ชุดเดิม คำนี้มาจากโครงการ Reproducible Builds สำหรับซอฟต์แวร์ที่คอมไพล์แล้ว