Te same bajty za każdym razem: powtarzalne pliki PDF
Spec: ISO 32000-2, §14.4ISO 32000-2 §14.4Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3
W skrócie
Dział zatytułowany „W skrócie”Zbuduj PDF z tych samych danych wejściowych dwukrotnie, a spodziewałbyś się tego samego pliku. Większość bibliotek PDF nie może tego obiecać — odbuduj i porównaj, a bajty się rozjadą. NextPDF potrafi ustalić te dwie rzeczy, które się zmieniają, więc te same dane wejściowe wytwarzają te same bajty, za każdym razem.
Dlaczego to ma znaczenie
Dział zatytułowany „Dlaczego to ma znaczenie”Identyczne bajt po bajcie wyjście to nie metryka próżności. To fundament pod trzema rzeczami, których zespoły naprawdę chcą.
Pierwszą jest buforowanie. Jeśli budowa jest czystą funkcją swoich danych wejściowych, skrót jej wyjścia jest kluczem bufora. Te same dane wejściowe, ten sam skrót, pomiń pracę i podaj zapisany plik. Gdy bajty błądzą, błądzi skrót, a bufor nigdy nie trafia.
Drugą jest wykrywalność manipulacji. Potok, który potrafi ponownie wygenerować dokładny plik, który wysłał, może później dowieść, że zarchiwizowany dokument nie został zmieniony: odbuduj go, policz skrót obu, porównaj. Jeśli choć jeden bajt się różni z powodu osadzonego zegara, dowód przepada, a wracasz do „zaufaj mi”.
Trzecią jest godne zaufania CI. Test na pliku wzorcowym zapisuje znane dobre wyjście i zawodzi, gdy zmiana je zmienia. Ten sygnał ma znaczenie tylko wtedy, gdy niezmieniony silnik odtwarza niezmieniony plik. Jeśli każde uruchomienie różni się znacznikiem czasu, plik wzorcowy to szum, a zespół uczy się ignorować czerwoną budowę — najdroższy nawyk w testowaniu.
Wersja skrócona
Dział zatytułowany „Wersja skrócona”W deterministycznym profilu NextPDF dwoma kontrolowanymi przez silnik polami,
które inaczej błądziłyby między identycznymi budowami, są daty oraz /ID. Zakłada
to, że reszta potoku jest już stabilna — te same dane wejściowe oraz
serializacja, która nie zmienia się sama z siebie (więcej o tym poniżej):
- Osadzone daty. Słownik informacji o dokumencie niesie
CreationDateorazModDate(Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3), a metadane XMP je odzwierciedlają. Uchwyć „teraz” przy budowie, a każda odbudowa się różni. - Identyfikator pliku. Tablica
/IDto para łańcuchów bajtów identyfikujących plik (Spec: ISO 32000-2, §14.4ISO 32000-2 §14.4), przechowywana w słowniku trailera (Spec: ISO 32000-2, §7.5.5ISO 32000-2 §7.5.5). Biblioteki zwykle wyprowadzają ją z bieżącego czasu plus losowych bajtów, więc jest z założenia inna przy każdym uruchomieniu.
Ustal oba — stały znacznik czasu i stałe ziarno dla /ID — a wyjście staje się
deterministyczną funkcją swojej treści. Pozostaw treść w spokoju, a plik jest
identyczny bajt po bajcie. To ta sama dyscyplina, którą projekt Reproducible
Builds ustanowił dla oprogramowania kompilowanego, zastosowana do warstwy
dokumentu.
Jak podchodzi do tego NextPDF
Dział zatytułowany „Jak podchodzi do tego NextPDF”Determinizm w NextPDF to obiekt konfiguracyjny, a nie testowa sztuczka. Silnik
udostępnia obiekt wartości DeterministicSettings w przestrzeni nazw
NextPDF\Core. Jest final readonly, niezmienialny i ustala dokładnie te dwa
wyprowadzone z zegara i losowości źródła rozjazdu nazwane powyżej: daty oraz
/ID. Ustalenie ich usuwa dwa najczęstsze źródła rozjazdu, ale samo w sobie nie
gwarantuje identycznego bajt po bajcie wyjścia. Inne zachowanie serializacji
silnika — kolejność obiektów, podzbiory czcionek oraz ustawienia kompresji —
też musi być deterministyczne, by wyjście się odtwarzało, a NextPDF utrzymuje je
stałe z założenia.
Jego konstruktor bierze dwa argumenty:
public function __construct( public DateTimeImmutable $timestamp, public string $fileIdSeed,) { // ...}$timestamp to pojedyncza stała chwila zapisywana do każdego pola daty —
CreationDate, ModDate oraz ich lustra w XMP. Podaj jeden
DateTimeImmutable, a dokument przestaje pytać zegar ścienny, która jest
godzina. $fileIdSeed to dane wejściowe, które ustalają trailerowe /ID:
32-znakowy łańcuch szesnastkowy. Podaj to samo ziarno, a silnik wyprowadzi ten
sam identyfikator pliku, zamiast próbkować zegar i źródło losowe.
Obiekt waliduje własne dane wejściowe. Ziarno musi mieć dokładnie 32 znaki
szesnastkowe; cokolwiek innego jest odrzucane przy konstrukcji za pomocą
InvalidConfigException, zamiast po cichu wytworzyć inaczej wyglądające /ID.
To ta sama postawa odmowy zgadywania, którą przyjmuje reszta silnika —
niejednoznaczne dane wejściowe zawodzą głośno, zamiast po cichu zmieniać bajty.
Gdy oba są ustalone, przepis jest tym, który projekt Reproducible Builds spopularyzował: odbuduj go, porównaj, a różnica jest pusta.
- Fix the inputsThe same content, fonts, and settings that produced the original document.
- Pin the timestampOne DateTimeImmutable feeds CreationDate, ModDate, and the XMP dates — no wall clock.
- Pin the /ID seedA 32-character hex seed derives the trailer /ID instead of a clock-plus-random value.
- BuildThe output is now a pure function of content; the two moving parts are held still.
- Rebuild and diffRegenerate from the same inputs and compare bytes — an empty diff is the proof.
Praktyczny przykład
Dział zatytułowany „Praktyczny przykład”Mały, kompletny kształt. Ustawienia są konstruowane raz i wielokrotnie używane, więc dwa uruchomienia tego samego programu emitują ten sam plik.
<?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)Ziarno to dane wejściowe budowy, które kontrolujesz, a nie sekret. Przechowuj je obok reszty swojej konfiguracji budowy. Sens jest taki, że jest stałe, więc identyfikator pliku, który wytwarza, też jest stały.
Częste nieporozumienie
Dział zatytułowany „Częste nieporozumienie”Pierwszą pułapką jest „usunąłem znacznik czasu, więc moja budowa jest teraz
powtarzalna”. Zwykle nie jest, ponieważ tablica /ID to cichsze z dwóch źródeł.
Daty są widoczne w panelu metadanych i łatwe do zapamiętania; trailerowe /ID
jest niewidoczne dla większości czytników i jest regenerowane z zegara i źródła
losowego przy każdym uruchomieniu. Budowa, która ustala tylko daty, wciąż
wytwarza inny plik za każdym razem. Musisz utrzymać oba w bezruchu.
Drugą pułapką jest traktowanie determinizmu jako funkcji bezpieczeństwa samej w
sobie. Ustalone /ID czyni plik powtarzalnym; nie czyni go podpisanym i samo
w sobie nie dowodzi, że dwie budowy się zgadzają. Porównanie bajt po bajcie lub
skrót dowodzi, że budowy się zgadzają; ustalenie /ID jedynie usuwa jedno
źródło pozornej różnicy. A żadne z nich nie dowodzi, że strona trzecia ręczy za
plik. Powtarzalność i podpisywanie to uzupełniające się warstwy, a nie
substytuty.
Ograniczenia i granice
Dział zatytułowany „Ograniczenia i granice”Determinizm ustala własne ruchome części silnika. Nie ustala twoich danych
wejściowych. Jeśli twoja treść osadza żywy znacznik czasu, pobiera czcionkę,
która zmieniła się na dysku, lub renderuje wartość zależną od bieżącej daty,
wyjście się zmienia, ponieważ zmieniły się dane wejściowe — i to jest
poprawne. DeterministicSettings usuwa niedeterminizm silnika, a nie twój.
Powtarzalna budowa wciąż wymaga powtarzalnych danych wejściowych.
| Edition | Availability |
|---|---|
| Core | Full support. |
| Pro | Not in this edition |
| Enterprise | Not in this edition |
Powiązane dokumenty
Dział zatytułowany „Powiązane dokumenty”- Testowanie na plikach wzorcowych — technika CI, która zależy od identycznego bajt po bajcie wyjścia, oraz dlaczego deterministyczny silnik jest jej warunkiem wstępnym.
- Aktualizacje przyrostowe — jak PDF rośnie
przez dołączanie, gdzie tablica
/IDznowu ma znaczenie dla powiązania pliku z jego wcześniejszymi wersjami. - Metadane i pakiet XMP — gdzie żyją osadzone daty oraz jak pakiet XMP odzwierciedla słownik informacji o dokumencie.
- Anatomia pliku PDF — trailer,
tablica odwołań krzyżowych oraz to, gdzie w strukturze pliku znajduje się
tablica
/ID.
Słownik pojęć
Dział zatytułowany „Słownik pojęć”- Identyczny bajt po bajcie — dwa pliki, które pasują dokładnie, bajt po bajcie. Najsilniejsza postać „tego samego” i ta, którą może zweryfikować skrót lub porównanie.
/ID(identyfikator pliku) — tablica dwóch łańcuchów bajtów, która identyfikuje PDF i jego wersje (ISO 32000-2 §14.4), przechowywana w słowniku trailera (§7.5.5). Zwykle wyprowadzana z zegara plus losowych bajtów, dlatego zmienia się przy każdej nieustalonej budowie.- Słownik informacji o dokumencie — struktura, która niesie
CreationDateorazModDate(ISO 32000-2 §14.3.3). Jedno z dwóch źródeł niedeterminizmu, które musi ustalić deterministyczna budowa. - Plik wzorcowy — zapisane znane dobre wyjście, z którym test porównuje; znaczące tylko wtedy, gdy niezmieniony silnik odtwarza niezmieniony plik.
- Powtarzalna budowa — budowa, której wyjście jest deterministyczną funkcją jej danych wejściowych, więc odbudowa z tych samych danych wejściowych daje te same bajty. Termin pochodzi z projektu Reproducible Builds dla oprogramowania kompilowanego.