Technischer Artikel

PDF/E-1-Engineeringdokumente in Delphi mit PDFlibPas

PDF/E-1 ist das Archivprofil für Engineering-Dokumente, und PDFlibPas setzt es als Author Mode um, den Sie mit SetPDFEMode einschalten, plus einen begrenzten Preflight, der Content Streams Operator für Operator liest. Das Profil ist kein PDF/A mit anderem Etikett: Es hat seinen eigenen Identifikations-Namensraum, seine eigene Lifecycle-Metadaten-Anforderung und eine Regel, die die Inhaltsvalidierung strenger macht als bei jedem Archivprofil, das Sie bisher getroffen haben

Engineering-Lieferobjekte sind der Grund, warum es dieses Profil gibt. Ein Zeichnungssatz, der in zwanzig Jahren noch lesbar und nachweislich unverändert sein muss, mit einer Revision History, die überlebt, und mit Farben, die auf dem Plotter im anderen Gebäude dasselbe bedeuten. Diese Anforderungen erzeugen eine Spezifikation, deren Forderungen größtenteils außerhalb des Seiteninhalts liegen, in Metadaten und Farbmanagement — genau dort, wo ein generischer PDF-Writer sie vermasselt

Eigene Identifikation, keine Variante von PDF/A

Das Wichtigste zuerst: Die PDF/E-1-Identifikation lässt sich nicht durch Anpassen des PDF/A- oder PDF/X-Musters erzeugen. Sie benutzt einen eigenen XMP-Namensraum, http://www.aim.org/pdfe/ns/id/, und der Versionswert muss an zwei Stellen auftauchen: als Document Information Entry und als namespace-qualifizierte XMP-Eigenschaft. Nur die XMP-Eigenschaft oder nur der Informationseintrag ergibt eine Datei, die die Absicht trägt und an der Validierung scheitert

Der Output Intent hat eine ebenso spezifische Form. PDF/E-1 verlangt ein eingebettetes ICC-Profil mit dem Subtyp-Identifikator ISO_PDFE1, und das Profil muss eine Komponentenzahl haben, die zur Gerätefarbfamilie passt, die das Dokument tatsächlich benutzt. Diese letzte Klausel ist der Punkt, an dem Implementierungen still scheitern, denn sie bedeutet: Der Intent lässt sich nicht vorab wählen und dann ignorieren

Warum braucht Gerätefarbe einen Sweep über das ganze Dokument?

Weil Farbräume sich in Ressourcenwörterbüchern verstecken, die ein Scan auf Seitenebene nie erreicht. PDF/E-1 behandelt DeviceRGB und DeviceCMYK als gegenseitig ausschließende Familien für ein Dokument, also bedeutet die Profilvalidierung, jeden Geräteraum zu kennen, den irgendetwas in der Datei benutzt. Ein Form XObject hat eigene Ressourcen. Ein Pattern ebenso, und ein Bild auch. Ein Tiling-Pattern in einem Form XObject in einer Seite liegt drei Ebenen tief, und ein Validator, der nur die Ressourcen der obersten Seitenebene prüft, segnet ein Dokument ab, das beide Familien nutzt

Der Sweep registriert Farbräume daher beim Durchlaufen von Seiten, Forms, Bildern und Patterns als eine einzige Traversierung und entscheidet erst danach, ob das Dokument konsistent ist und ob der Output Intent passt. Dasselbe Denken treibt generell die Preflight-Architektur: Partielle Traversierung erzeugt falsche Pässe, und ein falscher Pass bei einer Konformitätsprüfung ist schlimmer als keine Prüfung, weil er als Nachweis protokolliert wird

var
  Lib: TPDFlib;
  Diag: WideString;
begin
  Lib := TPDFlib.Create(nil);
  try
    Lib.LoadFromFile('assembly-drawings.pdf');

    if Lib.SetPDFEMode(1) = 0 then
      raise Exception.Create('PDF/E author mode was refused');

    // Der Author Mode hält die Lifecycle-Metadaten bei jedem Speichern auf Stand.
    // Vor dem Speichern fragen, ob das Dokument seine eigene Schranke passieren würde
    if not Lib.PDFEReadyForSave then
    begin
      Diag := Lib.GetPDFEDiagnostics;
      Writeln('PDF/E blockers: ', Diag);
      Exit;
    end;

    Lib.SaveToFile('assembly-drawings-pdfe.pdf');
  finally
    Lib.Free;
  end;
end;

Lifecycle-Metadaten sind eine Pflicht bei jedem Speichern

PDF/E-1 verlangt mehr als einen Dokumentenidentifikator. Der Mindestsatz umfasst den Media-Management-Dokumentenidentifikator, einen Versionsidentifikator, eine Rendition Class, Erstellungszeit, Änderungszeit, Metadatenzeit und einen Titel. Das ist ein Vokabular zur Revisionsverfolgung, und es existiert, weil von einem Engineering-Lieferobjekt erwartet wird, dass es neu aufgelegt wird, statt einmal geschrieben zu werden

Die Konsequenz für eine Implementierung: Diese Felder lassen sich nicht bei der Dokumenterstellung setzen. Wird die Änderungszeit beim Einschalten des Modus geschrieben und das Dokument danach bearbeitet, driftet der XMP-Snapshot vom tatsächlichen Dokumentzustand auseinander, und ein Validator, der beide vergleicht, meldet eine Inkonsistenz, die niemand beabsichtigt hat. Der Author Mode synchronisiert die Felder deshalb unmittelbar vor jedem Speichern, sodass die Metadaten die Bytes beschreiben, die gleich geschrieben werden, nicht die, die beim Einschalten des Modus existierten

Das ist ein allgemeines Prinzip für Konformitätsmetadaten, das sich lohnt, losgelöst von PDF/E festzuhalten: Abgeleitete Metadaten gehören auf den Speicherpfad, nicht auf den Bearbeitungspfad. Jedes aus dem Dokumentzustand berechnete Feld muss in dem Moment neu berechnet werden, in dem der Zustand eingefroren wird, sonst ist es ein Cache ohne Invalidierung

PDFlibPas-PDF/E-1-Diagramm des Gerätefarb-Sweeps über das ganze Dokument, der die Ressourcenwörterbücher von Seite, Form XObject, Tiling-Pattern und Bild durchläuft und die Familien DeviceRGB und DeviceCMYK sammelt, bevor die Kohärenz beurteilt wird, daneben die Lifecycle-Metadatenfelder, die der Author Mode unmittelbar vor jedem Speichern neu synchronisiert, damit der XMP-Snapshot zu den gleich geschriebenen Bytes passt
Farbkohärenz lässt sich erst beurteilen, nachdem eine Traversierung jedes Ressourcenwörterbuch erreicht hat, und abgeleitete Lifecycle-Metadaten werden neu berechnet, wenn der Dokumentzustand eingefroren wird — nicht beim Einschalten des Modus

Die Regel, die die Inhaltsvalidierung streng macht

PDF/E-1 erlaubt es nicht, dass die Compatibility-Section-Operatoren unbekannten Inhalt schlucken. In gewöhnlichem PDF klammern BX und EX einen Bereich, in dem ein Consumer Operatoren ignorieren muss, die er nicht erkennt — die Notluke, durch die ein Producer neuere Konstrukte ausgeben kann, ohne ältere Reader zu brechen. Unter PDF/E-1 ist diese Luke geschlossen, also wird jeder Operator, den der Preflight nicht erkennt, bedingungslos gemeldet, ob er nun in einer Compatibility Section sitzt oder nicht

Die Wirkung auf einen Validator ist erheblich. Er kann keine Bereiche überspringen, die er nicht versteht, das heißt, der Operandenparser muss wirklich jeden Operator in jedem Content Stream parsen. Genau hier kommen die Grenzen ins Spiel. Die Traversierung ist bei 128 Verschachtelungsebenen, einer Million Objekten und 64 MiB Inhalt gedeckelt, und diese Limits sind kein Performance-Tuning. Eine feindliche oder bloß kaputte Datei kann einen Objektgraphen mit Zyklen oder eine Verschachtelungstiefe präsentieren, die einen rekursiven Validator in einen Stack Overflow verwandelt, und die Limits verhindern, dass ein Validierungslauf zum Denial-of-Service-Vektor wird. Dieselbe defensive Haltung beschreibt sicheres Parsen nicht vertrauenswürdiger PDFs

// Eigenständige Validierung einer Datei, die Sie nicht erzeugt haben,
// ohne sie in eine Dokumentinstanz zu laden
var
  Issues: TStringList;
  Stream: TFileStream;
  I: Integer;
begin
  Issues := TStringList.Create;
  Stream := TFileStream.Create('incoming.pdf', fmOpenRead or fmShareDenyWrite);
  try
    if CheckCompliancePDFE(Stream, '', 0, Issues) = 0 then
      for I := 0 to Issues.Count - 1 do
        Writeln('PDF/E: ', Issues[I]);
  finally
    Stream.Free;
    Issues.Free;
  end;
end;

Was die Save-Schranke repariert und was sie verweigert

Die Schranke teilt ihre Arbeit in zwei Stufen, und diese Teilung ist für sich genommen eine brauchbare Designidee. Zuerst normalisiert sie das, was sich gefahrlos reparieren lässt: die Print-Flags von Annotationen, die No-Zoom- und No-Rotate-Flags an Textannotationen und das Appearance-Generation-Flag im Form-Wörterbuch. Das sind Einstellungen mit genau einem korrekten Wert unter dem Profil und ohne Informationsgehalt, sie lautlos zu korrigieren ist also richtig, und daran zu scheitern wäre Pedanterie

Dann prüft sie die Constraints, die sich nicht reparieren lassen, ohne zu ändern, was das Dokument bedeutet: Version, Identifikation, Verschlüsselung, Output Intent, Gerätefarbkohärenz und das Vorhandensein dynamischen Formularinhalts. Ein Dokument, das an einer dieser Bedingungen scheitert, wird verweigert, denn einen Output Intent zu erfinden oder eine Farbfamilie anstelle des Autors zu wählen, ergäbe eine Datei, die die Validierung passiert und den Inhalt falsch darstellt

PDFlibPas-PDF/E-1-Diagramm der Save-Schranke für Delphi mit dem begrenzten Preflight, der jeden Content-Stream-Operator bei einer Deckelung von 128 Verschachtelungsebenen, einer Million Objekten und 64 MiB scannt, Annotation-Print-, Zoom- und Rotate-Flags lautlos repariert, falsche Version, Identifikation, Verschlüsselung, Output Intent, Gerätefarbe oder dynamischen Formularinhalt verweigert und Blocker über GetPDFEDiagnostics meldet
Die Schranke repariert lautlos nur das, was keine Information trägt, verweigert jedes Constraint, das eine Reparatur verzerren würde, und macht aus der Verweigerung über GetPDFEDiagnostics eine Blockerliste, bevor irgendein Byte die Festplatte sieht

Die Diagnose vor dem Speichern über GetPDFEDiagnostics zurückzulesen, macht aus dieser Verweigerung eine umsetzbare Liste statt einer fehlgeschlagenen Operation. Rufen Sie sie in einer Batch-Pipeline bei jedem Dokument auf, protokollieren Sie die Blocker pro Datei und leiten Sie die Fehlfälle in eine Queue, die ein Mensch ansieht. Das ist weit nützlicher als ein Speichern, das eine Exception wirft, denn Blocker treten meist gehäuft auf: Vierzig Dokumente, die am selben fehlenden Output Intent scheitern, sind eine Korrektur, nicht vierzig

Wahl zwischen den Archivprofilen

PDF/E-1 ist das richtige Ziel, wenn das Lieferobjekt Engineering-Dokumentation mit einem Revisionszyklus ist, und insbesondere, wenn Gerätefarbkohärenz zählt, weil die Ausgabe auf Plotter und Großformatdrucker geht. PDF/A ist das richtige Ziel, wenn es um langfristige Lesbarkeit von Dokumenten allgemein geht, und es ist das Profil mit der breitesten Validator-Unterstützung. Beide sind nicht austauschbar, und ein Dokument kann das eine erfüllen und am anderen scheitern

PDFlibPas-Entscheidungsdiagramm zum Vergleich der Archivprofile PDF/E-1 und PDF/A für Delphi: PDF/E-1 für Engineering-Lieferobjekte mit Revisionszyklen, Plotterfarbe und vertraglich geforderter Validierung unter seinem eigenen XMP-Namensraum mit ISO_PDFE1-Output-Intent, PDF/A für allgemeine langfristige Lesbarkeit mit der breitesten Validator-Unterstützung
Beginnen Sie damit, wer die Datei am anderen Ende validiert: Die Profile verlangen unterschiedliche Identifikations-, Metadaten- und Farbgarantien, und ein Dokument kann das eine erfüllen und am anderen scheitern

Wenn Sie wählen, beginnen Sie damit, wer die Datei am anderen Ende validiert. PDF/A-Validierungswerkzeuge gibt es überall, und der zugehörige Preflight in PDFlibPas ist in PDF/A- und PDF/UA-Preflight beschrieben. PDF/E-Validierung ist spezieller und meist eine vertragliche Forderung statt ein Default. Muss ein bestehendes Archiv auf ein Profil gebracht werden, für das es nie geschrieben wurde, ist der Metadaten-Reparaturpfad in Konvertierung zu PDF/A mit Metadaten-Reparatur das Muster, dem man folgt — und dieselbe Form gilt hier: identifizieren, reparieren, was sicher ist, den Rest mit einer Liste verweigern

Author Mode, der begrenzte Inhalts-Preflight und die eigenständige Compliance-Prüfung liegen alle mit der PDFlibPas Delphi PDF library vor, sodass ein Dokument unter dem Profil erzeugt und danach über einen separaten Codepfad unabhängig verifiziert werden kann — die einzige Konstellation, der man bei einer Konformitätsaussage trauen sollte