PDFiumPas, der Delphi- und C++Builder-Wrapper um Googles PDFium-Engine, speichert ein Dokument in einer exakten PDF-Version von 1.3 bis 1.7 über den PdfVersion-Parameter der Methode TPdf.SaveAs. PDFiums eigener FPDF_SaveWithVersion-Aufruf schreibt nur den %PDF-M.m-Header neu um, ohne zu prüfen, ob der tatsächliche Inhalt des Dokuments in dieser Version zulässig ist. PDFiumPas schließt diese Lücke mit einem Konformitätsdurchlauf nach dem Speichern, der die aktive Kreuzreferenz-Revisionskette durchläuft und Adobe-Extension-Level-Deklarationen prüft, bevor die Datei die Methode verlässt
Diese Unterscheidung zählt am meisten in der Druckproduktion, wo ein PDF/X-Profil eine exakte PDF-Version benennt und ein Preflight-Tool oder RIP alles ablehnt, was still mit dem eigenen Header nicht übereinstimmt, ein Szenario, das von der Ausgabeseite her in der Validierung druckfertiger PDF/X-Dokumente mit PDFiumPas behandelt wird. SaveAs legt das Ziel als TPdfVersion-Enum frei, pv13 bis pv17 neben den älteren Werten pv10 bis pv12, plus einen unabhängigen TSaveOption für inkrementelle oder vollständige Neuschreibvorgänge. Übergeben Sie PdfVersion, und PDFiumPas erledigt zwei Aufgaben in einem Aufruf: Es bittet PDFium, den angeforderten Header zu stempeln, liest dann die frisch geschriebenen Bytes erneut und weigert sich, eine Datei zurückzugeben, deren aktiver Inhalt in dieser Version rechtlich nicht existieren kann
var
Pdf: TPdf;
begin
Pdf:= TPdf.Create(nil);
try
Pdf.FileName:= 'source.pdf';
Pdf.Active:= True;
try
Pdf.SaveAs('press-ready.pdf', saNoIncremental, pv17);
except
on E: Exception do
// E.Message names the offending feature and the version or
// extension level it actually needs, for example:
// "RichMedia annotations and RichMediaExecute actions require
// /Extensions /ADBE with /BaseVersion /1.7 and /ExtensionLevel 3
// or newer."
raise;
end;
finally
Pdf.Free;
end;
end;
Warum ist die letzte Objektdefinition in der Datei das Falsche, dem man vertrauen sollte?
Das letzte physische Objekt mit einer gegebenen Nummer in einer PDF-Datei ist nicht notwendigerweise das Objekt, das ein konformer Reader heute für diese Nummer auflösen würde. Ein PDF, das mehrere inkrementelle Aktualisierungen durchlaufen hat, hat nicht einen Objektgraphen, es hat eine Geschichte davon, geschichtet innerhalb einer einzigen Datei, und jeder Anhängezyklus kann ein Objekt freigeben, es unter einer neuen Generationsnummer neu definieren oder seinen alten physischen Körper zwischen zwei endobj-Markern sitzen lassen, ohne dass noch ein Kreuzreferenz-Eintrag darauf zeigt
PDFiumPas traf genau auf diesen Fehlermodus, bevor es Xref-Revisionen explizit verfolgte: eine Redact-Annotation, verwaist durch einen späteren Seitenobjekt-Neuschreib, oder ein /MarkInfo-Wörterbuch, physisch vorhanden gelassen, ohne dass ein Xref-Eintrag darauf zeigt, konnte trotzdem in einem Byte-Scan auftauchen und trotzdem eine Versions-Feature-Prüfung auslösen, die nicht mehr auf das Dokument zutraf, das ein Reader tatsächlich öffnen würde. Die Fehlerrichtung war falsche Ablehnung, nicht falsche Akzeptanz: Eine Datei, die in ihrer aktuellen Revision ein Feature tatsächlich hinter sich gelassen hatte, konnte trotzdem am Speichern in einer niedrigeren Version gehindert werden, wegen Inhalt, den niemand mehr erreichen konnte
Wie bestimmt PDFiumPas, welche Objektdefinitionen tatsächlich aktiv sind?
PDFiumPas löst die aktive Objektmenge auf dieselbe Art auf wie ein konformer Reader, indem es die Kreuzreferenzkette durchläuft, statt Bytes nach Objekt-Headern zu scannen. Der Resolver beginnt beim letzten startxref-Offset in der Datei und folgt jedem /Prev-Link rückwärts durch ältere Revisionen, wobei er klassische Kreuzreferenz-Tabellen, hybride /XRefStm-verknüpfte Streams und reine Kreuzreferenz-Streams unterwegs parst. Der Durchlauf läuft neuste-zu-älteste und legt jede Objektnummer beim ersten Auftreten fest, sodass ein Free-Eintrag in einer späteren Revision einen Objektkörper korrekt überschattet, der in einer früheren geschrieben wurde, und eine Neudefinition unter einem neuen Offset oder einer neuen Generation immer über das gewinnt, was sie ersetzt
Object-Stream-Mitglieder bekommen eine zusätzliche Prüfung, die ein bloßes Offset-Lookup allein nicht liefern kann, ein Mechanismus, der ausführlicher in der Validierung von Objekt- und Kreuzreferenz-Streams mit PDFiumPas behandelt wird. Ein aus einem /ObjStm gewonnenes komprimiertes Objekt muss seinen übergeordneten Stream im selben Durchlauf als aktiv bestätigt bekommen, und sein Index muss mit der eigenen Position des Mitglieds innerhalb des Headers dieses Streams übereinstimmen, bevor PDFiumPas es als lebenden Inhalt behandelt. ISO 32000-1 Abschnitt 7.5.8.4 beschreibt sogar einen Hybrid-Reference-Fall, bei dem eine klassische Kompatibilitäts-Tabelle ein Objekt als frei markiert, während der /XRefStm-Eintrag des Trailers gleichzeitig dasselbe Objekt anderswo als komprimiertes Mitglied definiert; PDFiumPas führt den ergänzenden Xref-Stream in dieselbe Revision zusammen, bevor die klassischen Einträge angewendet werden, sodass die komprimierte Definition so gewinnt, wie die Spezifikation es beabsichtigt
Adobe Extension Levels: das Tor über der Versionsnummer
Ein %PDF-1.7-Header verspricht nur den Feature-Satz, den ISO 32000-1 2008 standardisierte, während mehrere Fähigkeiten, auf die sich PDF-Erzeuger heute verlassen, danach als Adobe-eigene Ergänzungen ausgeliefert wurden, geschichtet über dieselbe Versionsnummer. Adobe registrierte jede Ergänzung als ein BaseVersion/ExtensionLevel-Paar, protokolliert im /Extensions-Wörterbuch des Dokumentkatalogs unter einem Entwickler-Präfix, ADBE für Adobes eigene Erweiterungen, sodass ein Reader eine schlichte PDF-1.7-Datei von einer unterscheiden kann, die auch einen nummerierten Extension-Level implementiert. Bei pv17 ohne diese Deklaration zu speichern ist an sich kein Fehler; es wird erst einer in dem Moment, in dem der aktive Inhalt tatsächlich von einem Feature abhängt, das die Deklaration abdecken soll
Welche High-Version-Features lösen das Explizite-Versions-Tor aus?
PDFiumPas prüft eine spezifische, spezifikationsgetriebene Liste, statt allein aus der Versionsnummer zu raten. Bild-Wörterbücher mit einem expliziten /SMaskInData-Eintrag oder einem /BitsPerComponent-Wert von 16 erfordern beide PDF 1.5, wobei der Sechzehn-Bit-Fall direkt den Bildkomponenten-Regeln aus PDF Reference 1.5 Abschnitt 4.8 folgt. RichMedia-Annotationen und RichMediaExecute-Aktionen erfordern /BaseVersion /1.7 mit /ExtensionLevel 3 oder höher. PRC-3D-Streams, identifiziert durch ein Wörterbuch, das sowohl /Type /3D als auch /Subtype /PRC trägt, erfordern dieselbe Basisversion, aber nur /ExtensionLevel 1. Geospatial-Measure-Wörterbücher und Projection-Annotationen erfordern /BaseVersion /1.7 mit /ExtensionLevel 3, dieselbe Adobe-Ergänzung, von der RichMedia abhängt
Die Geospatial-Prüfung trägt ein Spezifikations-Lese-Detail, das sich zu kennen lohnt, falls Sie je eigene versionsgesteuerte Logik über PDFiumPas bauen. ISO 32000-1 Tabelle 254 markiert den /Type-Eintrag des Measure-Wörterbuchs als optional und merkt nur an, dass er "falls vorhanden, Measure sein muss", während Tabelle 311 /Type für das 3D-Stream-Wörterbuch, in dem PRC-Inhalt lebt, verpflichtend macht. Reale GeoPDF-Ausgabe von Kartierungswerkzeugen lässt /Type am Measure-Wörterbuch routinemäßig weg und schreibt nur /Subtype /GEO, sodass PDFiumPas' Geospatial-Detektor allein auf /Subtype abgleicht, statt beide Schlüssel zu verlangen, wie es sein PRC-3D-Detektor sicher tun kann. /Type an beiden Wörterbüchern zu verlangen hätte konformen GeoPDF-Inhalt unentdeckt am Tor vorbeigleiten lassen, gelandet in einer schlichten PDF-1.7-Datei ohne Extension-Level-Deklaration, die ihn stützt
Stuft PDFiumPas nicht unterstützte Features automatisch herab?
Nicht als allgemeine Fähigkeit, und das Gegenteil anzunehmen ist der hier zu vermeidende Fehler. SaveAs leitet die Zielversion durch eine interne Routine, ValidatePdfVersionCompliance, und wenn diese Routine ein Feature findet, das die Zielversion oder ihre Extension-Level-Deklaration nicht unterstützen kann, wirft SaveAs eine Exception mit dem Fehlertext der Routine, statt die Datei zu schreiben; der Aufrufer bekommt einen präzisen, feature-benannten Grund zurück, nie ein still umgeschriebenes Dokument. Der eine Ort, an dem PDFiumPas tatsächlich automatisch Inhalt umschreibt, ist ein PDF-1.3-Ziel, wo es die semantisch neutralen Transparenz-Standardwerte /BM /Normal, /CA 1 und /ca 1 entfernt, die PDFium unabhängig von der Zielversion immer in ExtGState-Wörterbücher schreibt, weil diese spezifischen Werte keine visuelle Bedeutung tragen und PDF 1.3 diese Schlüssel vollständig vordatiert
// PDF 1.3 targets rewrite the saved bytes to strip transparency
// defaults PDFium always emits, so incremental mode cannot apply
Pdf.SaveAs('legacy-archive.pdf', saIncremental, pv13);
// raises: PDF 1.3 normalization is incompatible with incremental
// save mode
Echte, nicht dem Standard entsprechende Transparenz und Bild-Soft-Masken scheitern bei einem PDF-1.3-Ziel weiterhin rundweg, weil ihr Entfernen ändern würde, wie die Seite tatsächlich aussieht, und PDFiumPas trifft diese Entscheidung nicht in Ihrem Namen. Zwei verwandte Grenzen lohnt es sich einzuplanen, bevor eine exakte Version in eine Batch-Pipeline geht. Explizite-Versions-Ausgabe trägt nie ein /Encrypt-Wörterbuch; das Speichern scheitert sofort, wenn die Quelle geschützt ist, was zufällig mit PDF/X- und PDF/A-Profilen übereinstimmt, die Verschlüsselung ohnehin verbieten, aber es bedeutet, dass Entschlüsselung ein separater Schritt in Ihrem Workflow ist, statt etwas, das SaveAs für Sie erledigt. PDFiumPas hat auch keine öffentliche Methode, um eine /Extensions /ADBE-Deklaration auf einen Katalog zu schreiben, sodass eine Quelldatei, die RichMedia-, PRC-3D- oder Geospatial-Inhalt enthält, aber diese Deklaration nicht hat, das Tor unabhängig davon nicht passiert, welche PdfVersion Sie anfordern; die Deklaration muss bereits in der Quelle existieren, typischerweise weil das Autorenwerkzeug sie geschrieben hat, oder das Feature muss vor dem Speichern entfernt werden. Die schreibgeschützte Eigenschaft TPdf.PdfVersion lohnt es sich zu prüfen, bevor überhaupt ein Speichern in exakter Version versucht wird, da sie dieselbe katalogbewusste effektive Version auflöst, Header- oder /Version-Override, welche auch immer aktuell ist, auf die sich der Validator zur Speicherzeit selbst verlässt
Pdf.FileName:= 'incoming.pdf';
Pdf.Active:= True;
// PdfVersion resolves the same catalog-aware effective version the
// save-time validator uses, so a mismatch here is worth investigating
// before spending a full SaveAs attempt on it
LogSourceVersion('incoming.pdf', Pdf.PdfVersion);
Behandeln Sie eine SaveAs-Exception bei einem exakten Versionsziel als Preflight-Bericht statt als Bug: Die Meldung benennt genau die Klausel, gegen die das Quelldokument verstößt, was genau die Information ist, die eine Druckerei oder Archiv-Pipeline braucht, bevor eine Datei weitergeht. Der Pfad für explizites Versions-Speichern, der Resolver für aktive Xref-Revisionen und die hier beschriebenen Adobe-Extension-Level-Prüfungen sind Teil der Standard-PDFiumPas-Komponente für Delphi und C++Builder; die Produktseite trägt die vollständige TPdf.SaveAs-Referenz zusammen mit dem Rest der Konformitäts- und Formular-API