Jedes Mal dieselben Bytes: reproduzierbare PDFs
Spec: ISO 32000-2, §14.4ISO 32000-2 §14.4Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Bauen Sie ein PDF zweimal aus denselben Eingaben, und Sie würden dieselbe Datei erwarten. Die meisten PDF-Bibliotheken können das nicht versprechen – bauen Sie neu und vergleichen Sie, und die Bytes wandern. NextPDF kann die zwei Dinge fixieren, die sich bewegen, sodass dieselben Eingaben dieselben Bytes erzeugen, jedes Mal.
Warum das wichtig ist
Abschnitt betitelt „Warum das wichtig ist“Byte-identische Ausgabe ist keine Eitelkeitskennzahl. Sie ist das Fundament unter drei Dingen, die Teams tatsächlich wollen.
Das erste ist Caching. Wenn ein Build eine reine Funktion seiner Eingaben ist, ist sein Ausgabe-Hash ein Cache-Schlüssel. Dieselben Eingaben, derselbe Hash, überspringen Sie die Arbeit und liefern Sie die gespeicherte Datei aus. Wenn die Bytes umherschweifen, schweift der Hash umher, und der Cache trifft nie.
Das zweite ist Manipulationsnachweis. Eine Pipeline, die genau die Datei neu erzeugen kann, die sie ausgeliefert hat, kann später beweisen, dass ein archiviertes Dokument nicht verändert wurde: neu bauen, beide hashen, vergleichen. Wenn auch nur ein Byte wegen einer eingebetteten Uhr abweicht, ist der Beweis dahin, und Sie sind zurück bei „vertrau mir”.
Das dritte ist vertrauenswürdige CI. Ein Golden-File-Test zeichnet eine bekanntermaßen gute Ausgabe auf und schlägt fehl, wenn eine Änderung sie verändert. Dieses Signal ist nur dann bedeutsam, wenn eine unveränderte Engine eine unveränderte Datei reproduziert. Wenn jeder Lauf an einem Zeitstempel abweicht, ist die Golden-File-Datei Rauschen, und das Team lernt, einen roten Build zu ignorieren – die teuerste Angewohnheit im Testen.
Die Kurzfassung
Abschnitt betitelt „Die Kurzfassung“In NextPDFs deterministischem Profil sind die zwei von der Engine kontrollierten
Felder, die andernfalls zwischen identischen Builds abweichen würden, die Daten und die
/ID. Dies setzt voraus, dass der Rest der Pipeline bereits stabil ist – dieselben
Eingaben und eine Serialisierung, die nicht von selbst variiert (mehr dazu unten):
- Eingebettete Daten. Das Document-Information-Dictionary trägt
CreationDateundModDate(Spec: ISO 32000-2, §14.3.3ISO 32000-2 §14.3.3), und die XMP-Metadaten spiegeln sie. Erfassen Sie „jetzt” zur Build-Zeit, und jeder Neuaufbau weicht ab. - Der Dateibezeichner. Das
/ID-Array ist ein Paar von Byte-Strings, das die Datei identifiziert (Spec: ISO 32000-2, §14.4ISO 32000-2 §14.4), gespeichert im Trailer-Dictionary (Spec: ISO 32000-2, §7.5.5ISO 32000-2 §7.5.5). Bibliotheken leiten ihn üblicherweise aus der aktuellen Zeit plus Zufallsbytes ab, sodass er von Natur aus bei jedem Lauf anders ist.
Fixieren Sie beide – einen festen Zeitstempel und einen festen Seed für die /ID –, und
die Ausgabe wird zu einer deterministischen Funktion ihres Inhalts. Lassen Sie den
Inhalt unberührt, und die Datei ist Byte für Byte identisch. Das ist dieselbe
Disziplin, die das Reproducible-Builds-Projekt für kompilierte Software etabliert hat,
angewendet auf die Dokumentebene.
Wie NextPDF dabei vorgeht
Abschnitt betitelt „Wie NextPDF dabei vorgeht“Determinismus in NextPDF ist ein Konfigurationsobjekt, kein Test-Hack. Die Engine stellt
ein DeterministicSettings-Wertobjekt im NextPDF\Core-Namespace bereit. Es ist
final readonly, unveränderlich, und es fixiert genau die zwei uhr- und
zufallsabgeleiteten Quellen der Abweichung, die oben genannt wurden: die Daten und die
/ID. Sie zu fixieren entfernt die zwei häufigsten Abweichungsquellen, garantiert aber
für sich genommen keine byte-identische Ausgabe. Das übrige
Serialisierungsverhalten der Engine – Objektreihenfolge, Font-Subsetting und
Komprimierungseinstellungen – muss ebenfalls deterministisch sein, damit die Ausgabe
sich reproduziert, und NextPDF hält diese von Natur aus stabil.
Sein Konstruktor nimmt zwei Argumente:
public function __construct( public DateTimeImmutable $timestamp, public string $fileIdSeed,) { // ...}$timestamp ist der einzelne feste Zeitpunkt, der in jedes Datumsfeld geschrieben
wird – CreationDate, ModDate und ihre XMP-Spiegelung. Übergeben Sie ein
DateTimeImmutable, und das Dokument hört auf, die Wanduhr zu fragen, wie spät es ist.
$fileIdSeed ist die Eingabe, die die Trailer-/ID fixiert: ein 32-stelliger
hexadezimaler String. Geben Sie denselben Seed, und die Engine leitet denselben
Dateibezeichner ab, statt die Uhr und eine Zufallsquelle abzutasten.
Das Objekt validiert seine eigene Eingabe. Der Seed muss genau 32 hexadezimale Zeichen
sein; alles andere wird bei der Konstruktion mit einer InvalidConfigException
abgelehnt, statt klammheimlich eine anders aussehende /ID zu erzeugen. Das ist
dieselbe Verweigern-zu-raten-Haltung, die der Rest der Engine einnimmt – eine
mehrdeutige Eingabe scheitert laut, statt leise die Bytes zu verändern.
Mit beiden fixiert ist das Rezept das, was das Reproducible-Builds-Projekt vertraut gemacht hat: neu bauen, vergleichen, und der Unterschied ist leer.
- 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.
Praktisches Beispiel
Abschnitt betitelt „Praktisches Beispiel“Eine kleine, vollständige Form. Die Einstellungen werden einmal konstruiert und wiederverwendet, sodass zwei Läufe desselben Programms dieselbe Datei ausgeben.
<?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)Der Seed ist eine Build-Eingabe, die Sie kontrollieren, kein Geheimnis. Speichern Sie ihn neben dem Rest Ihrer Build-Konfiguration. Der Punkt ist, dass er fest ist, sodass der Dateibezeichner, den er erzeugt, ebenfalls fest ist.
Häufiges Missverständnis
Abschnitt betitelt „Häufiges Missverständnis“Die erste Falle ist „Ich habe den Zeitstempel entfernt, also ist mein Build jetzt
reproduzierbar.” Das ist er gewöhnlich nicht, denn das /ID-Array ist die leisere der
beiden Quellen. Daten sind in einem Metadatenfenster sichtbar und leicht zu merken; die
Trailer-/ID ist für die meisten Reader unsichtbar und wird bei jedem Lauf aus der Uhr
und einer Zufallsquelle neu erzeugt. Ein Build, der nur die Daten fixiert, erzeugt
trotzdem jedes Mal eine andere Datei. Sie müssen beide ruhig halten.
Die zweite Falle ist, Determinismus für sich genommen als Sicherheitsfunktion zu
behandeln. Eine fixierte /ID macht eine Datei reproduzierbar; sie macht sie nicht
signiert, und sie beweist für sich genommen nicht, dass zwei Builds übereinstimmen.
Ein Byte-für-Byte-Vergleich oder ein Hash beweist, dass die Builds übereinstimmen; die
/ID zu fixieren entfernt nur eine Quelle des unechten Unterschieds. Und keines davon
beweist, dass ein Dritter für die Datei bürgt. Reproduzierbarkeit und Signierung sind
sich ergänzende Schichten, keine Ersätze.
Grenzen und Abgrenzungen
Abschnitt betitelt „Grenzen und Abgrenzungen“Determinismus fixiert die eigenen beweglichen Teile der Engine. Er fixiert nicht Ihre
Eingaben. Wenn Ihr Inhalt einen lebendigen Zeitstempel einbettet, eine Schrift zieht,
die sich auf der Platte geändert hat, oder einen Wert rendert, der vom aktuellen Datum
abhängt, ändert sich die Ausgabe, weil sich die Eingabe geändert hat – und das ist
korrekt. DeterministicSettings entfernt den Nichtdeterminismus der Engine, nicht
Ihren. Ein reproduzierbarer Build erfordert immer noch reproduzierbare Eingaben.
| Edition | Availability |
|---|---|
| Core | Volle Unterstützung. |
| Pro | Not in this edition |
| Enterprise | Not in this edition |
Verwandte Dokumente
Abschnitt betitelt „Verwandte Dokumente“- Golden-File-Testing – die CI-Technik, die von byte-identischer Ausgabe abhängt, und warum eine deterministische Engine ihre Voraussetzung ist.
- Inkrementelle Aktualisierungen – wie ein PDF
durch Anhängen wächst, wo das
/ID-Array erneut wichtig wird, um eine Datei mit ihren früheren Versionen in Beziehung zu setzen. - Metadaten und das XMP-Paket – wo die eingebetteten Daten leben und wie das XMP-Paket das Document-Information-Dictionary spiegelt.
- Die Anatomie einer PDF-Datei – der
Trailer, die Querverweistabelle und wo das
/ID-Array in der Dateistruktur sitzt.
Glossar
Abschnitt betitelt „Glossar“- Byte-identisch – zwei Dateien, die exakt übereinstimmen, Byte für Byte. Die stärkste Form von „dieselbe” und diejenige, die ein Hash oder ein Vergleich verifizieren kann.
/ID(Dateibezeichner) – das Array aus zwei Byte-Strings, das ein PDF und seine Versionen identifiziert (ISO 32000-2 §14.4), gespeichert im Trailer-Dictionary (§7.5.5). Üblicherweise aus der Uhr plus Zufallsbytes abgeleitet, weshalb es sich bei jedem nicht fixierten Build ändert.- Document-Information-Dictionary – die Struktur, die
CreationDateundModDateträgt (ISO 32000-2 §14.3.3). Eine der zwei Quellen von Nichtdeterminismus, die ein deterministischer Build fixieren muss. - Golden-File-Datei – eine aufgezeichnete, bekanntermaßen gute Ausgabe, gegen die ein Test vergleicht; nur bedeutsam, wenn eine unveränderte Engine eine unveränderte Datei reproduziert.
- Reproduzierbarer Build – ein Build, dessen Ausgabe eine deterministische Funktion seiner Eingaben ist, sodass ein Neuaufbau aus denselben Eingaben dieselben Bytes ergibt. Der Begriff stammt aus dem Reproducible-Builds-Projekt für kompilierte Software.