Teknisk artikel

Exakta PDF-versionssparningar i Delphi: PDFiumPas konformitet

PDFiumPas, Delphi- och C++Builder-omslaget kring Googles PDFium-motor, sparar ett dokument vid en exakt PDF-version från 1.3 till 1.7 genom TPdf.SaveAs-metodens PdfVersion-parameter. PDFiums eget FPDF_SaveWithVersion-anrop skriver bara om %PDF-M.m-huvudet, utan att kontrollera om dokumentets faktiska innehåll är lagligt vid den versionen. PDFiumPas täcker den luckan med en efterlyfterande konformitetskontroll som går igenom den aktiva korsreferensrevisionskedjan och kontrollerar Adobe Extension Level-deklarationer innan filen lämnar metoden

Den distinktionen spelar störst roll i tryckproduktion, där en PDF/X-profil namnger en exakt PDF-version och ett förkontrollsverktyg eller en RIP avvisar allt som tyst är oense med sitt eget huvud, ett scenario täckt från utdatasidan i att validera tryckfärdiga PDF/X-dokument med PDFiumPas. SaveAs exponerar målet som enumen TPdfVersion, pv13 till pv17 tillsammans med de äldre värdena pv10 till pv12, plus en oberoende TSaveOption för inkrementella eller fullständiga omskrivningar. Skicka PdfVersion och PDFiumPas gör två jobb i ett anrop: den ber PDFium stämpla det begärda huvudet, och läser sedan om de nyskrivna byten och vägrar lämna tillbaka en fil vars aktiva innehåll inte lagligt kan existera vid den versionen

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;

Varför är den sista objektdefinitionen i filen fel sak att lita på?

Det sista fysiska objektet med ett givet nummer i en PDF-fil är inte nödvändigtvis objektet en konform läsare skulle lösa upp för det numret idag. En PDF som har genomgått flera inkrementella uppdateringar har inte en objektgraf, den har en historia av dem lagrade inuti en enda fil, och varje tilläggscykel kan frigöra ett objekt, omdefiniera det under ett nytt generationsnummer, eller lämna dess gamla fysiska kropp liggande mellan två endobj-markörer utan någon korsreferenspost som längre pekar på den

PDFiumPas råkade ut för exakt det felläget innan den spårade xref-revisioner explicit: en Redact-anteckning föräldralös efter en senare sidobjekts-omskrivning, eller en /MarkInfo-ordbok lämnad fysiskt närvarande utan någon xref-post som pekar på den, kunde fortfarande dyka upp i en byte-genomsökning och fortfarande utlösa en versionsfunktionskontroll som inte längre gällde för dokumentet en läsare faktiskt skulle öppna. Felriktningen var falsk avvisning, inte falsk godkännande: en fil som genuint hade lämnat en funktion bakom sig i sin nuvarande revision kunde ändå blockeras från att sparas vid en lägre version på grund av innehåll ingen längre kunde nå

Hur avgör PDFiumPas vilka objektdefinitioner som faktiskt är aktiva?

PDFiumPas löser upp den aktiva objektmängden på samma sätt en konform läsare gör, genom att gå igenom korsreferenskedjan istället för att genomsöka byte efter objekthuvuden. Uppslagaren startar vid det sista startxref-offsetet i filen och följer varje /Prev-länk bakåt genom äldre revisioner, och tolkar klassiska korsreferenstabeller, hybrida /XRefStm-länkade strömmar, och rena korsreferensströmmar längs vägen. Genomgången körs från nyast till äldst och avgör varje objektnummer första gången det ses, så en fri post i en senare revision skuggar korrekt en objektkropp skriven i en tidigare, och en omdefiniering under en ny offset eller generation vinner alltid över det den ersätter

Objektströmsmedlemmar får en extra kontroll en vanlig offsetuppslagning inte kan tillhandahålla på egen hand, en mekanism täckt mer djupgående i att validera objekt- och korsreferensströmmar med PDFiumPas. Ett komprimerat objekt återhämtat från en /ObjStm måste ha sin föräldraström bekräftad aktiv i samma genomgång, och dess index måste stämma med medlemmens egen position inuti den strömmens huvud innan PDFiumPas behandlar det som levande innehåll. ISO 32000-1 avsnitt 7.5.8.4 beskriver till och med ett hybridreferensfall där en klassisk kompatibilitetstabell markerar ett objekt som fritt medan efterskriftens /XRefStm-post samtidigt definierar samma objekt som en komprimerad medlem någon annanstans; PDFiumPas sammanfogar den kompletterande xref-strömmen i samma revision innan de klassiska posterna tillämpas, så den komprimerade definitionen vinner på det sätt specifikationen avser

Adobe Extension Levels: grinden ovanför versionsnumret

Ett %PDF-1.7-huvud lovar bara den funktionsuppsättning ISO 32000-1 standardiserade 2008, medan flera funktioner PDF-producenter förlitar sig på idag levererades efteråt som Adobe-exklusiva tillägg lagrade ovanpå samma versionsnummer. Adobe registrerade varje tillägg som ett BaseVersion- och ExtensionLevel-par registrerat i dokumentkatalogens /Extensions-ordbok under ett utvecklarprefix, ADBE för Adobes egna tillägg, så en läsare kan skilja en vanlig PDF 1.7-fil från en som också implementerar en numrerad tilläggsnivå. Att spara vid pv17 utan den deklarationen är inte i sig ett fel; det blir bara ett i det ögonblick det aktiva innehållet faktiskt beror på en funktion deklarationen förväntas täcka

Vilka högversionsfunktioner utlöser den explicita versionsgrinden?

PDFiumPas kontrollerar en specifik, specdriven lista snarare än att gissa enbart från versionsnumret. Bilddokument som bär en explicit /SMaskInData-post eller ett /BitsPerComponent-värde på 16 kräver båda PDF 1.5, där sextonbitfallet följer PDF Reference 1.5 avsnitt 4.8:s regler för bildkomponenter direkt. RichMedia-anteckningar och RichMediaExecute-åtgärder kräver /BaseVersion /1.7 med /ExtensionLevel 3 eller högre. PRC 3D-strömmar, identifierade av en ordbok som bär både /Type /3D och /Subtype /PRC, kräver samma basversion men bara /ExtensionLevel 1. Geospatiala Measure-ordböcker och Projection-anteckningar kräver /BaseVersion /1.7 med /ExtensionLevel 3, samma Adobe-tillägg RichMedia beror på

Den geospatiala kontrollen bär en spec-läsningsdetalj värd att känna till om man någonsin bygger sin egen versionsgrindade logik ovanpå PDFiumPas. ISO 32000-1 Tabell 254 markerar Measure-ordbokens /Type-post som valfri, och noterar bara att "om närvarande, ska vara Measure," medan Tabell 311 gör /Type obligatoriskt för 3D-strömordboken PRC-innehåll finns i. Verklig GeoPDF-utdata från kartläggningsverktyg utelämnar rutinmässigt /Type på Measure-ordboken och skriver bara /Subtype /GEO, så PDFiumPas geospatiala detektor matchar på /Subtype ensamt snarare än att kräva båda nycklarna på det sätt dess PRC 3D-detektor säkert kan. Att kräva /Type på båda ordböckerna skulle ha låtit konformt GeoPDF-innehåll slinka förbi grinden oupptäckt, och landa i en vanlig PDF 1.7-fil utan någon tilläggsnivådeklaration som backar upp det

Nedgraderar PDFiumPas automatiskt ostödda funktioner?

Inte som en generell förmåga, och att anta motsatsen är misstaget att undvika här. SaveAs för målversionen genom en intern rutin, ValidatePdfVersionCompliance, och när den rutinen hittar en funktion målversionen eller dess tilläggsnivådeklaration inte kan stödja, kastar SaveAs ett undantag som bär rutinens feltext istället för att skriva filen; anroparen får en exakt, funktionsnamngiven anledning tillbaka, aldrig ett tyst omskrivet dokument. Den enda platsen PDFiumPas faktiskt skriver om innehåll automatiskt är ett PDF 1.3-mål, där den tar bort de semantiskt neutrala /BM /Normal-, /CA 1- och /ca 1-transparensstandarderna som PDFium alltid skriver in i ExtGState-ordböcker oavsett målversion, eftersom de specifika värdena inte bär någon visuell mening och PDF 1.3 föregår nycklarna helt och hållet

// 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

Genuin icke-standard transparens och bildmjukmasker misslyckas fortfarande helt vid ett PDF 1.3-mål, eftersom att ta bort dem skulle ändra hur sidan faktiskt ser ut, och PDFiumPas kommer inte fatta det beslutet å dina vägnar. Två relaterade begränsningar är värda att planera kring innan en exakt version går in i en batch-pipeline. Explicit-versions-utdata bär aldrig en /Encrypt-ordbok; sparningen misslyckas omedelbart om källan är skyddad, vilket råkar stämma överens med PDF/X- och PDF/A-profiler som förbjuder kryptering ändå, men det betyder att dekryptering är ett separat steg i ditt arbetsflöde snarare än något SaveAs gör åt dig. PDFiumPas har heller ingen publik metod för att skriva en /Extensions /ADBE-deklaration till en katalog, så en källfil som innehåller RichMedia, PRC 3D, eller geospatialt innehåll men saknar den deklarationen kommer inte passera grinden oavsett vilken PdfVersion du begär; deklarationen måste redan finnas i källan, vanligtvis eftersom författningsverktyget skrev den, eller så måste funktionen tas bort innan sparningen. Den skrivskyddade TPdf.PdfVersion-egenskapen är värd att kontrollera innan en exakt-versions-sparning ens försöks, eftersom den löser upp samma katalogmedvetna effektiva version, huvud eller /Version-åsidosättande, vilken som än gäller, som spartidsvalideraren själv förlitar sig på

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);

Behandla ett SaveAs-undantag på ett exakt versionsmål som en förkontrollsrapport snarare än en bugg: meddelandet namnger den exakta klausulen källdokumentet bryter mot, vilket är precis den information ett tryckeri eller en arkivpipeline behöver innan en fil går vidare. Den explicita versionssparningsvägen, den aktiva xref-revisionsuppslagaren, och Adobe Extension Level-kontrollerna som beskrivs här levereras som en del av standard-PDFiumPas-komponenten för Delphi och C++Builder; produktsidan bär den fullständiga TPdf.SaveAs-referensen tillsammans med resten av konformitets- och formulär-API:et