PDFiumPas, de Delphi- en C++Builder-wrapper rond Google's PDFium-engine, slaat een document op in een exacte PDF-versie van 1.3 tot en met 1.7 via de PdfVersion-parameter van de methode TPdf.SaveAs. PDFium's eigen aanroep FPDF_SaveWithVersion herschrijft alleen de %PDF-M.m-header, zonder te controleren of de werkelijke inhoud van het document op die versie legaal is. PDFiumPas dicht dat gat met een conformiteitscontrole na het opslaan die de actieve cross-reference-revisieketen doorloopt en Adobe Extension Level-verklaringen controleert voordat het bestand de methode verlaat
Dat onderscheid doet er het meest toe in de drukproductie, waar een PDF/X-profiel een exacte PDF-versie noemt en een preflight-tool of RIP alles afwijst dat stilletjes van zijn eigen header afwijkt, een scenario dat vanuit de outputkant wordt behandeld in het valideren van drukklare PDF/X-documenten met PDFiumPas. SaveAs stelt het doel bloot als de enum TPdfVersion, pv13 tot en met pv17 naast de oudere waarden pv10 tot pv12, plus een onafhankelijke TSaveOption voor incrementele of volledige herschrijvingen. Geef PdfVersion door en PDFiumPas doet twee dingen in één aanroep: het vraagt PDFium om de gevraagde header te stempelen, en leest vervolgens de vers geschreven bytes opnieuw en weigert een bestand terug te geven waarvan de actieve inhoud op die versie niet legaal kan bestaan
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;
Waarom is de laatste objectdefinitie in het bestand niet te vertrouwen?
Het laatste fysieke object met een bepaald nummer in een PDF-bestand is niet noodzakelijk het object dat een conforme lezer vandaag voor dat nummer zou oplossen. Een PDF die door verschillende incrementele updates is gegaan, heeft niet één objectgraaf, maar een geschiedenis daarvan gelaagd binnen één bestand, en elke toevoegingscyclus kan een object vrijgeven, het onder een nieuw generatienummer opnieuw definiëren, of zijn oude fysieke inhoud tussen twee endobj-markeringen laten staan zonder dat er nog een cross-reference-item naar wijst
PDFiumPas liep precies tegen deze faalmodus aan voordat het xref-revisies expliciet bijhield: een Redact-annotatie die verweesd was geraakt door een latere herschrijving van een pagina-object, of een /MarkInfo-dictionary die fysiek aanwezig bleef zonder dat er een xref-item naar wees, kon nog steeds opduiken in een bytescan en nog steeds een versiefunctiecontrole triggeren die niet meer van toepassing was op het document dat een lezer daadwerkelijk zou openen. De richting van de storing was valse afwijzing, geen valse acceptatie: een bestand dat werkelijk voorbij een functie was gegaan in zijn huidige revisie, kon nog steeds worden geblokkeerd om op te slaan op een lagere versie vanwege inhoud die niemand meer kon bereiken
Hoe bepaalt PDFiumPas welke objectdefinities daadwerkelijk actief zijn?
PDFiumPas lost de actieve objectverzameling op dezelfde manier op als een conforme lezer, door de cross-reference-keten te doorlopen in plaats van bytes te scannen op objectheaders. De resolver begint bij de laatste startxref-offset in het bestand en volgt elke /Prev-koppeling achterwaarts door oudere revisies, en parst onderweg klassieke cross-reference-tabellen, hybride met /XRefStm gekoppelde streams, en pure cross-reference-streams. De doorloop verloopt van nieuw naar oud en legt elk objectnummer vast de eerste keer dat het wordt gezien, zodat een vrij-item in een latere revisie een objectinhoud die in een eerdere revisie is geschreven correct overschaduwt, en een herdefinitie onder een nieuwe offset of generatie altijd wint van wat het verving
Leden van objectstreams krijgen een extra controle die een gewone offsetopzoeking op zichzelf niet kan bieden, een mechanisme dat uitgebreider wordt behandeld in het valideren van object- en cross-reference-streams met PDFiumPas. Een gecomprimeerd object dat wordt hersteld uit een /ObjStm moet zijn bovenliggende stream bevestigd krijgen als actief in dezelfde doorloop, en zijn index moet overeenkomen met de eigen positie van het lid binnen de header van die stream voordat PDFiumPas het als levende inhoud behandelt. ISO 32000-1 paragraaf 7.5.8.4 beschrijft zelfs een hybride-verwijzingsgeval waarbij een klassieke compatibiliteitstabel een object als vrij markeert terwijl het /XRefStm-item van de trailer datzelfde object tegelijkertijd elders definieert als een gecomprimeerd lid; PDFiumPas voegt de aanvullende xref-stream samen in dezelfde revisie voordat de klassieke items worden toegepast, zodat de gecomprimeerde definitie wint zoals de specificatie bedoelt
Adobe Extension Levels: de poort boven het versienummer
Een %PDF-1.7-header belooft alleen de functieset die ISO 32000-1 in 2008 standaardiseerde, terwijl verschillende mogelijkheden waarop PDF-producenten vandaag vertrouwen, daarna zijn verschenen als Adobe-only aanvullingen die bovenop datzelfde versienummer worden gelegd. Adobe registreerde elke aanvulling als een BaseVersion- en ExtensionLevel-paar, vastgelegd in de /Extensions-dictionary van de documentcatalogus onder een ontwikkelaarsvoorvoegsel, ADBE voor Adobe's eigen extensies, zodat een lezer een gewoon PDF 1.7-bestand kan onderscheiden van een bestand dat ook een genummerd extensieniveau implementeert. Opslaan op pv17 zonder die verklaring is op zichzelf geen fout; het wordt er pas een op het moment dat de actieve inhoud daadwerkelijk afhankelijk is van een functie die de verklaring verondersteld wordt te dekken
Welke functies van hoge versie triggeren de expliciete-versiepoort?
PDFiumPas controleert een specifieke, door de specificatie gedreven lijst in plaats van te gokken op basis van alleen het versienummer. Beelddictionaries die een expliciet /SMaskInData-item dragen of een /BitsPerComponent-waarde van 16 vereisen beide PDF 1.5, waarbij het zestien-bits geval rechtstreeks de beeldcomponentregels van PDF Reference 1.5 paragraaf 4.8 volgt. RichMedia-annotaties en RichMediaExecute-acties vereisen /BaseVersion /1.7 met /ExtensionLevel 3 of hoger. PRC-3D-streams, geïdentificeerd door een dictionary die zowel /Type /3D als /Subtype /PRC draagt, vereisen dezelfde basisversie maar slechts /ExtensionLevel 1. Geospatiale Measure-dictionaries en Projection-annotaties vereisen /BaseVersion /1.7 met /ExtensionLevel 3, dezelfde Adobe-aanvulling waarvan RichMedia afhankelijk is
De geospatiale controle draagt een detail over het lezen van de specificatie dat de moeite waard is om te kennen als u ooit uw eigen versiegepoorte logica bovenop PDFiumPas bouwt. ISO 32000-1 Tabel 254 markeert het /Type-item van de Measure-dictionary als optioneel, en merkt alleen op dat "indien aanwezig, dit Measure moet zijn", terwijl Tabel 311 /Type verplicht maakt voor de 3D-stream-dictionary waarin PRC-inhoud leeft. Real-world GeoPDF-output van karteringstools laat routinematig /Type weg op de Measure-dictionary en schrijft alleen /Subtype /GEO, dus de geospatiale detector van PDFiumPas matcht alleen op /Subtype in plaats van beide sleutels te vereisen zoals zijn PRC-3D-detector veilig kan doen. Het vereisen van /Type op beide dictionaries zou conforme GeoPDF-inhoud onopgemerkt langs de poort hebben laten glippen, terechtkomend in een gewoon PDF 1.7-bestand zonder extensieniveau-verklaring om het te ondersteunen
Downgradet PDFiumPas automatisch niet-ondersteunde functies?
Niet als algemene mogelijkheid, en het tegendeel aannemen is precies de fout die u hier moet vermijden. SaveAs voert de doelversie door een interne routine, ValidatePdfVersionCompliance, en wanneer die routine een functie vindt die de doelversie of zijn extensieniveau-verklaring niet kan ondersteunen, werpt SaveAs een uitzondering op met de foutmelding van de routine in plaats van het bestand te schrijven; de aanroeper krijgt een precieze, functie-benoemde reden terug, nooit een stilzwijgend herschreven document. De ene plek waar PDFiumPas wel automatisch inhoud herschrijft is een PDF 1.3-doel, waar het de semantisch neutrale standaardwaarden /BM /Normal, /CA 1, en /ca 1 voor transparantie verwijdert die PDFium altijd in ExtGState-dictionaries schrijft ongeacht de doelversie, omdat die specifieke waarden geen visuele betekenis dragen en PDF 1.3 dateert van vóór die sleutels helemaal bestonden
// 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
Werkelijke niet-standaard transparantie en zachte beeldmaskers falen nog steeds regelrecht bij een PDF 1.3-doel, omdat het verwijderen ervan zou veranderen hoe de pagina er daadwerkelijk uitziet, en PDFiumPas zal die beslissing niet namens u nemen. Twee verwante beperkingen zijn de moeite waard om rekening mee te houden voordat een exacte versie in een batchpipeline terechtkomt. Output met expliciete versie draagt nooit een /Encrypt-dictionary; het opslaan mislukt onmiddellijk als de bron beveiligd is, wat toevallig overeenkomt met PDF/X- en PDF/A-profielen die versleuteling sowieso verbieden, maar het betekent wel dat ontsleuteling een aparte stap in uw workflow is in plaats van iets wat SaveAs voor u doet. PDFiumPas heeft ook geen publieke methode om een /Extensions /ADBE-verklaring op een catalogus te schrijven, dus een bronbestand dat RichMedia-, PRC-3D-, of geospatiale inhoud bevat maar die verklaring mist, komt de poort niet door, ongeacht welke PdfVersion u opvraagt; de verklaring moet al in de bron bestaan, typisch omdat het maaktool het schreef, of de functie moet eruit voordat wordt opgeslagen. De alleen-lezen eigenschap TPdf.PdfVersion is de moeite waard om te controleren voordat een opslag met exacte versie zelfs maar wordt geprobeerd, aangezien deze dezelfde catalogusbewuste effectieve versie oplost, header of /Version-override, wat er ook actueel is, waarop de validator ten tijde van opslaan zelf vertrouwt
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);
Behandel een SaveAs-uitzondering op een doel met exacte versie als een preflightrapport in plaats van een bug: de melding noemt precies de clausule die het brondocument overtreedt, wat exact de informatie is die een drukkerij of archiveringspipeline nodig heeft voordat een bestand verder gaat. Het opslagpad met expliciete versie, de resolver voor actieve xref-revisies, en de hier beschreven Adobe Extension Level-controles worden geleverd als onderdeel van de standaard PDFiumPas-component voor Delphi en C++Builder; de productpagina bevat de volledige TPdf.SaveAs-referentie naast de rest van de conformiteits- en formulieren-API