Tehnički članak

Spremanje točne verzije PDF-a u Delphiju: sukladnost PDFiumPas

PDFiumPas, omotač Delphi i C++Buildera oko Googleova mehanizma PDFium, sprema dokument u točnoj verziji PDF-a od 1.3 do 1.7 putem parametra PdfVersion metode TPdf.SaveAs. Vlastiti poziv FPDF_SaveWithVersion samo ponovno zapisuje zaglavlje %PDF-M.m, bez provjere je li stvarni sadržaj dokumenta dopušten u toj verziji. PDFiumPas uklanja taj nedostatak provjerom sukladnosti nakon spremanja koja prolazi aktivnim lancem revizija unakrsnih referenci i provjerava deklaracije Adobe Extension Level prije nego što datoteka napusti metodu

Ta je razlika najvažnija u grafičkoj proizvodnji, gdje profil PDF/X navodi točnu verziju PDF-a, a alat za preflight ili RIP odbija sve što se ne podudara s vlastitim zaglavljem, što je s izlazne strane obrađeno u članku provjera dokumenata PDF/X spremnih za tisak pomoću PDFiumPas. SaveAs cilj izlaže kao enum TPdfVersion, s vrijednostima pv13 do pv17 uz starije vrijednosti pv10 do pv12, te neovisnim TSaveOption za inkrementalno ili potpuno ponovno zapisivanje. Proslijedite PdfVersion i PDFiumPas u jednom pozivu obavlja dva posla: traži od PDFiuma da upiše zatraženo zaglavlje, zatim ponovno čita upravo zapisane bajtove i odbija vratiti datoteku čiji aktivni sadržaj pravno ne može postojati u toj verziji

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;

Zašto posljednja definicija objekta u datoteci nije pouzdana?

Posljednji fizički objekt s određenim brojem u PDF datoteci nije nužno objekt koji bi čitač koji poštuje sukladnost danas razriješio za taj broj. PDF koji je prošao kroz nekoliko inkrementalnih ažuriranja nema jedan graf objekata, nego njihovu povijest složenu unutar jedne datoteke, a svaki ciklus dodavanja može osloboditi objekt, ponovno ga definirati pod novim generacijskim brojem ili ostaviti njegovo staro fizičko tijelo između dvije oznake endobj bez unosa unakrsne reference koji na njega više pokazuje

PDFiumPas je prije eksplicitnog praćenja revizija xref-a nailazio upravo na taj način kvara: bilješka Redact koju je kasnija izmjena objekta stranice ostavila bez vlasnika ili rječnik /MarkInfo koji je fizički ostao prisutan bez unosa xref-a koji na njega pokazuje i dalje su se mogli pojaviti u pretraživanju bajtova te aktivirati provjeru značajke verzije koja se više nije odnosila na dokument koji bi čitač stvarno otvorio. Smjer pogreške bio je lažno odbijanje, a ne lažno prihvaćanje: datoteka koja je u trenutačnoj reviziji doista prerasla neku značajku i dalje je mogla biti blokirana pri spremanju u nižu verziju zbog sadržaja do kojeg više nitko nije mogao doći

Kako PDFiumPas određuje koje su definicije objekata stvarno aktivne?

PDFiumPas razrješava aktivni skup objekata na isti način kao čitač koji poštuje sukladnost, prolazeći lancem unakrsnih referenci umjesto pretraživanja bajtova u potrazi za zaglavljima objekata. Razrješivač počinje od posljednjeg pomaka startxref u datoteci i prati svaku poveznicu /Prev unatrag kroz starije revizije, usput raščlanjujući klasične tablice unakrsnih referenci, hibridne tokove povezane s /XRefStm i čiste tokove unakrsnih referenci. Prolaz se odvija od najnovijeg prema najstarijem i svaki broj objekta utvrđuje prvi put kada ga vidi, pa slobodni unos u kasnijoj reviziji ispravno zasjenjuje tijelo objekta zapisano u ranijoj, a redefinicija pod novim pomakom ili generacijom uvijek pobjeđuje ono što je zamijenila

Članovi toka objekata dobivaju dodatnu provjeru koju običan dohvat po pomaku ne može sam pružiti, a mehanizam je detaljnije obrađen u članku provjera tokova objekata i unakrsnih referenci pomoću PDFiumPas. Komprimirani objekt dohvaćen iz /ObjStm mora imati potvrđeno aktivan nadređeni tok u istom prolazu, a njegov se indeks mora podudarati s vlastitim položajem člana unutar zaglavlja tog toka prije nego što ga PDFiumPas proglasi živim sadržajem. Odjeljak 7.5.8.4 norme ISO 32000-1 čak opisuje slučaj hibridne reference u kojem klasična tablica kompatibilnosti označava objekt kao slobodan, dok unos /XRefStm u traileru istodobno definira taj isti objekt kao komprimirani član drugdje; PDFiumPas spaja dopunski tok xref-a u istu reviziju prije primjene klasičnih unosa, pa komprimirana definicija pobjeđuje kako specifikacija nalaže

Razine Adobeovih proširenja: provjera iznad broja verzije

Zaglavlje %PDF-1.7 obećava samo skup značajki koji je ISO 32000-1 standardizirao 2008, dok su neke mogućnosti na koje se proizvođači PDF-a danas oslanjaju isporučene poslije kao dodaci samo za Adobe, nadograđeni na isti broj verzije. Adobe je svaki dodatak registrirao kao par BaseVersion i ExtensionLevel, zapisan u rječniku /Extensions kataloga dokumenta pod prefiksom razvojnog programera, pri čemu je ADBE namijenjen vlastitim Adobeovim proširenjima, pa čitač može razlikovati običnu datoteku PDF 1.7 od one koja također implementira numeriranu razinu proširenja. Spremanje u pv17 bez te deklaracije samo po sebi nije pogreška; pogreška nastaje tek kada aktivni sadržaj stvarno ovisi o značajci koju bi deklaracija trebala pokriti

Koje funkcije viših verzija aktiviraju provjeru eksplicitne verzije?

PDFiumPas provjerava konkretan popis utemeljen na specifikaciji, umjesto da nagađa samo prema broju verzije. Slikovni rječnici s izričitim unosom /SMaskInData ili vrijednošću /BitsPerComponent 16 zahtijevaju PDF 1.5, pri čemu slučaj s 16 bitova izravno slijedi pravila za slikovne komponente iz odjeljka 4.8 dokumenta PDF Reference 1.5. Bilješke RichMedia i radnje RichMediaExecute zahtijevaju /BaseVersion /1.7 s /ExtensionLevel 3 ili višim. PRC 3D tokovi, prepoznati po rječniku koji sadrži i /Type /3D i /Subtype /PRC, zahtijevaju istu osnovnu verziju, ali samo /ExtensionLevel 1. Rječnici Measure za geoprostorne podatke i bilješke Projection zahtijevaju /BaseVersion /1.7 s /ExtensionLevel 3, isti Adobeov dodatak o kojem ovisi RichMedia

Geoprostorna provjera sadrži detalj iz čitanja specifikacije koji vrijedi znati ako ikada izgradite vlastitu logiku ograničenu verzijom povrh PDFiumPasa. Tablica 254 norme ISO 32000-1 označava unos /Type u rječniku Measure kao neobavezan i navodi samo da "ako je prisutan, mora biti Measure", dok tablica 311 čini /Type obveznim za rječnik 3D toka u kojem se nalazi sadržaj PRC-a. Stvarni izlaz GeoPDF-a iz alata za kartiranje redovito izostavlja /Type iz rječnika Measure i zapisuje samo /Subtype /GEO, pa PDFiumPasov geoprostorni detektor provjerava samo /Subtype umjesto da zahtijeva oba ključa, kao što to njegov PRC 3D detektor može sigurno učiniti. Zahtijevanje /Type u oba rječnika omogućilo bi da sukladan sadržaj GeoPDF-a neopaženo prođe provjeru i završi u običnoj datoteci PDF 1.7 bez deklaracije razine proširenja koja bi ga podržala

Snižava li PDFiumPas automatski verziju nepodržanih funkcija?

Ne kao opću mogućnost, a pretpostaviti suprotno upravo je pogreška koju ovdje treba izbjeći. SaveAs ciljnu verziju prosljeđuje internoj rutini ValidatePdfVersionCompliance, a kada ta rutina pronađe značajku koju ciljna verzija ili njezina deklaracija razine proširenja ne mogu podržati, SaveAs podiže iznimku s tekstom pogreške rutine umjesto da zapiše datoteku; pozivatelj dobiva precizan razlog s nazivom značajke, a nikada tiho prepisan dokument. Jedino mjesto na kojem PDFiumPas automatski prepisuje sadržaj jest cilj PDF 1.3, gdje uklanja semantički neutralne zadane vrijednosti prozirnosti /BM /Normal, /CA 1 i /ca 1 koje PDFium uvijek zapisuje u rječnike ExtGState neovisno o ciljnoj verziji, jer te konkretne vrijednosti nemaju vizualno značenje, a PDF 1.3 prethodi tim ključevima

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

Stvarna prozirnost koja nije zadana i meke maske slika i dalje odmah uzrokuju neuspjeh za cilj PDF 1.3, jer bi njihovo uklanjanje promijenilo stvarni izgled stranice, a PDFiumPas tu odluku neće donijeti umjesto vas. Prije uključivanja točne verzije u skupni tok rada vrijedi planirati oko dva povezana ograničenja. Izlaz s eksplicitnom verzijom nikada ne sadrži rječnik /Encrypt; spremanje odmah ne uspije ako je izvor zaštićen, što se podudara s profilima PDF/X i PDF/A koji ionako zabranjuju šifriranje, ali znači da je dešifriranje zaseban korak u vašem tijeku rada, a ne nešto što SaveAs obavlja umjesto vas. PDFiumPas također nema javnu metodu za zapis deklaracije /Extensions /ADBE u katalog, pa izvorna datoteka koja sadrži RichMedia, PRC 3D ili geoprostorni sadržaj, ali nema tu deklaraciju, neće proći provjeru bez obzira na zatraženi PdfVersion; deklaracija već mora postojati u izvoru, obično zato što ju je zapisao alat za izradu, ili se značajka mora ukloniti prije spremanja. Svojstvo samo za čitanje TPdf.PdfVersion vrijedi provjeriti prije pokušaja spremanja u točnoj verziji jer razrješava istu efektivnu verziju svjesnu kataloga, bilo da je trenutačna ona iz zaglavlja ili nadjačavanje /Version, na koju se oslanja i validator pri spremanju

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

Iznimku SaveAs pri spremanju u točnoj ciljnoj verziji tretirajte kao izvješće preflighta, a ne kao pogrešku: poruka navodi točnu odredbu koju izvorni dokument krši, upravo ono što tiskari ili arhivskom toku treba prije daljnje obrade datoteke. Ovdje opisani put spremanja s eksplicitnom verzijom, razrješivač aktivnih revizija xref-a i provjere Adobe Extension Level isporučuju se kao dio standardne komponente PDFiumPas Component za Delphi i C++Builder; stranica proizvoda sadrži potpunu referencu TPdf.SaveAs uz ostale API-je za sukladnost i obrasce