Pošljete pretvornik, ki vsako datoteko označi kot PDF/A-1b, strankin sistem za evidence jih eno leto požira, nato pa revizija celoten sveženj požene skozi veraPDF in tretjina se vrne kot neskladna. Nič se ni sesulo, nobena izjema ni bila vržena, datoteke se lepo odprejo v vsakem pregledovalniku na vaši mizi. Preprosto niso bile standard, s katerim ste jih ožigosali. To je običajen način odpovedi pri arhivskem PDF-ju in razlog, zakaj trditev "nastavili smo zastavico" nikoli ni isto kot "validira se"
Prva stvar, ki jo morate razumeti o PDFium in PDF/A, je, da motor s tem nima nič. PDFium izrisuje, razčlenjuje in zapisuje PDF, vendar njegova javna površina nima ConvertToPDFA, nobenega zapisovalnika OutputIntent in nobenega API-ja za XMP. Celoten del arhivske skladnosti, paket XMP, OutputIntent in njegov ICC profil, oznake v katalogu ter validacija, živi v samem PDFiumPas, v približno 2000 vrstic dolgi enoti v čistem Pascalu (FPdfPdfa.pas), ki razčleni shranjene bajte in jih z incremental update ponovno zapiše. Če veste, kje se delo dogaja, veste tudi, kje se skrivajo hrošči, in ne skrivajo se v PDFium
Kaj PDF/A dejansko zahteva in kje ugrizne
PDF/A ni en sam format. ISO 19005 definira tri dele (PDF/A-1, -2, -3) in znotraj vsakega ravni skladnosti, ki obljubljajo različne stvari. Raven B (basic) jamči le, da je vizualni videz mogoče reproducirati. Raven A (accessible) poleg B doda označeno strukturno drevo in preslikavo Unicode. Raven U, ki obstaja samo za dela 2 in 3, sedi vmes: zanesljivo besedilo Unicode brez celotnega strukturnega drevesa. ISO 19005-1 nima ravni U, knjižnica pa to omejitev kodira neposredno
V praksi ugrizne peščica pravil formata. Šifriranje je izrecno prepovedano (ISO 19005-1 §6.1.3 in njegovi nasledniki): datoteka PDF/A ne sme vsebovati slovarja /Encrypt. Dokument mora deklarirati pogoje izhodnega upodabljanja prek OutputIntent, katerega cilj je veljaven ICC profil (§6.2.3.2). Sama trditev o skladnosti mora nastopiti kot metapodatek XMP pod shemo identifikacije PDF/A. Raven A dodatno zahteva logično strukturo §6.8, to drevo oznak, zaradi katerega je dokument strojno berljiv. Če karkoli od tega manjka, preverjalnik skladnosti datoteko zavrne, četudi se izriše brezhibno
En klic, ki ustvari arhiv
PDFiumPas izpostavi celoten cevovod za TPdf.SaveAsPdfA. Preprosta preobremenitev sprejme ciljno skladnost in privzeto uporabi PDF/A-1b, kar je prava privzeta izbira za pogost primer "to mora biti za vedno izrisljivo"
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('invoice.pdf');
// Default conformance is pac1b (PDF/A-1b)
if Pdf.SaveAsPdfA('invoice_archive.pdf') then
// file now carries XMP, sRGB OutputIntent, and catalog markers
else
raise Exception.Create('PDF/A save failed');
finally
Pdf.Free;
end;
end;
Pod pokrovom gre za dvostopenjsko potezo. SaveAsPdfA najprej zahteva od PDFium, da dokument serializira z FPDF_SaveAsCopy, nato ta tok bajtov preda InjectPdfAMarkers, ki z incremental update pripne metapodatke XMP, sRGB OutputIntent z vgrajenim ICC profilom in ponovno zapisan katalog. Izvor se bere od položaja nič, cilj se zapisuje od položaja nič; izvirno drevo objektov ostane nedotaknjeno, oznake pa se pripeljejo za obstoječi %%EOF. Če potrebujete bajte namesto datoteke, SaveAsPdfAToStream sprejme TStream in iste možnosti
Izbira skladnosti z zapisom možnosti
Če želite ciljati določen del in raven, podajte zapis TPdfASaveOptions. Njegovo polje Conformance sprejme vrednost TPdfAConformance. Enumeracija pokriva vse veljavne kombinacije in ničesar drugega: pac1b, pac1a za del 1; pac2b, pac2u, pac2a za del 2; pac3b, pac3u, pac3a za del 3 ter pacUnknown in pacNone za validacijsko stran. pac1u ne obstaja, ker ta raven v standardu ne obstaja
var
Pdf: TPdf;
Opts: TPdfASaveOptions;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('report.pdf');
Opts := TPdfASaveOptions.Default;
Opts.Conformance := pac2u; // PDF/A-2u: reliable Unicode text
Opts.Title := 'Quarterly Report 2026';
Opts.Author := 'Finance';
// Leave IccProfileData empty to use the built-in sRGB IEC61966-2.1 profile
if not Pdf.SaveAsPdfA('report_a2u.pdf', Opts) then
raise Exception.Create('PDF/A-2u save failed');
finally
Pdf.Free;
end;
end;
Večina zapisa lahko ostane prazna. Pustite Title, Author, Subject, Keywords, Creator in Producer prazne, pa jih SaveAsPdfA samodejno zapolni iz dokumentovega slovarja Info prek FPDF_GetMetaText. Pustite CreationDate in ModDate prazna, pa za oba datuma XMP uporabi trenutni čas UTC. Pustite DocumentId in InstanceId prazna, pa ju knjižnica predhodno zapolni iz FPDF_GetFileIdentifier in ob tem po potrebi pade nazaj na deterministični ID, izpeljan iz izvornih bajtov. Polje, ki ga boste morda želeli zavestno prepisati, je IccProfileData: prazno pomeni priloženi profil sRGB IEC61966-2.1, delovni tok CMYK ali sivinske lestvice pa bi moral podati svojega
Zakaj se raven A zniža in zakaj je to poštena odločitev
Tukaj je niansa, ob kateri se spotaknejo ljudje, ki od zastavice pričakujejo jamstvo. Lahko zahtevate pac1a za dokument brez drevesa oznak, toda PDF/A-1a zahteva logično strukturo §6.8, knjižnica pa ne more izdelati strukturnega drevesa iz neoznačenega PDF-ja. Namesto da bi oddala datoteko, ki trdi raven A in jo hkrati krši, SaveAsPdfA preveri, ali obstaja resnična označena struktura (/StructTreeRoot plus /MarkInfo z /Marked true), in če je ni, zniža trditev: pac1a postane pac1b, pac2a postane pac2b in tako naprej skozi vse tri dele. Notranja pomočnika sta PdfAIsLevelA in PdfADowngradeToLevelB
Razlog je vreden neposredne izjave: datoteka, ki pošteno deklarira raven, ki jo dosega, je uporabnejša od datoteke, ki laže o ravni, ki je ne dosega. Z ravnijo U je drugače. Odkrivanje resnične pokritosti Unicode bi pomenilo naiven preizkus "ali ima /ToUnicode", ki bi legitimne dokumente preveč zniževal (WinAnsi in podobna kodiranja so izvzeta), zato stran za shranjevanje izjavo o U izda tako, kot jo je podal klicatelj, morebitno neskladje pa pusti, da se pokaže šele pri validaciji. Če potrebujete zagotovljen arhiv ravni A, dokument označite pred pretvorbo; pretvornik strukture, ki je ni, ne bo izumil
Past z ICC, ki jo ujame samo pravi validator
To je odpoved, ki je dala najtršo lekcijo, ker jo je lastni preverjalnik knjižnice spustil, veraPDF, referenčni validator ISO 19005, pa ne. PDF/A zahteva, da je ciljni profil v OutputIntent veljaven tok ICCBased, §6.2.3.2 pa od preverjalnika zahteva, da ta tok preveri kot barvni prostor. Tok ICCBased mora deklarirati /N, število barvnih komponent. Zgodnja različica injektorja je v slovar toka ICC zapisala samo /Length in brez /N, zato je veraPDF rezultat zavrnil z napako "The N entry (value null)... is missing"
Zahrbtno je bilo to, da se je zavrnitev sprožila samo za PDF/A-1b in -1a. Modela skladnosti za del 2 in del 3 tega konkretnega preverjanja nad ciljnim profilom nista izvajala, zato se je ista vbrizgana struktura validirala pod pac2b, pac3b in pac2u, spodletela pa pod pac1b zgolj zaradi vrednosti pdfaid:part. Tega enotski test nikoli ne bi videl, ker je lastni ValidatePdfACompliance preverjal samo, da ključ /DestOutputProfile obstaja, ne pa, kaj živi v slovarju toka. Notranji testi so ostali zeleni; prava arhivska validacija je padla
Popravek je IccComponentCount, ki na odmiku 16 v glavi ICC prebere podpis podatkovnega barvnega prostora in ga preslika v število komponent: GRAY je 1, RGB , Lab in XYZ so 3, CMYK je 4, pri neznanem profilu pa je privzeto 3. To število gre v slovar toka kot /N. Izračunano je, ne trdo zakodirano na 3, tako da klicatelj, ki prek IccProfileData poda profil CMYK ali sivinski profil, še vedno dobi pravilno vrednost. Širša lekcija je metodološka: lastni preverjalnik v knjižnici in avtoritativni validator imata vsak svoje slepe pege, zato je treba izhod PDF/A testirati od začetka do konca proti referenčni implementaciji, kot je veraPDF, in ne zaupati samo samopreverjanju. O isti disciplini incremental update za čiste arhive govori validiranje stisnjenih objektnih in xref tokov, kar je pomembno, ker so sodobni PDF-ji, ki jih injektor porablja, pogosto zgrajeni na tokovih medsklicev
Šifriranje, tokovi xref in drugi robovi
Ker ISO 19005 prepoveduje šifriranje, ga pot shranjevanja pred zapisom odstrani. SaveAsPdfA pri serializaciji uporabi FPDF_REMOVE_SECURITY, zato se šifriran izvor (naložen z geslom) na poti v arhiv dešifrira. Pri nešifriranem dokumentu je to no-op in ničesar ne spremeni. Posledica je ista omejitev, ki jo HotPDF uveljavlja z druge strani: ena datoteka ne more biti hkrati šifrirana in PDF/A. Kadar delovni tok potrebuje oboje, je odgovor v dveh artefaktih: šifrirani kopiji za distribucijo in ločeni čisti kopiji za arhiv
Še en rob je neviden, dokler ne ugrizne: dokumenti PDF 1.5+, ki uporabljajo čist tok medsklicev in nimajo ključne besede trailer. Injektor prebere trailer, da najde izvorni /Info in pripne svoj incremental update, pri čemer mora sprejeti obliko xref-stream, sicer bi tak dokument prešel skozi kopiranje z oznakami, ki bi tiho odpadle. ISO 32000-1 §7.5.6 izrecno dovoljuje, da klasičen trailer incremental update sledi dokumentu xref-stream, pri čemer /Prev kaže na odmik toka xref, kar je natanko struktura, ki jo injektor odda. PDFiumov lastni FPDF_SaveAsCopy vedno zapisuje klasičen trailer, zato injektor v običajnem cevovodu nikoli ne sreča čistega vira xref-stream, vendar pot branja to obliko obravnava za dokumente, ki pridejo od drugod
Preverjajte, preden verjamete trditvi
Knjižnica vsebuje bajtni preverjalnik TPdf.ValidatePdfA, ki vrne TPdfAValidationResult. Njegovo polje Conformance poroča o zaznani ravni, Issues pa je množica vrednosti TPdfAValidationIssue; priročna metoda IsCompliant vrne true samo, kadar je zaznana resnična raven in je množica težav prazna. V paketni obdelavi ga uporabite kot hitro prvo zaporo
var
Pdf: TPdf;
Res: TPdfAValidationResult;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('invoice_archive.pdf');
Res := Pdf.ValidatePdfA;
if Res.IsCompliant then
Writeln('Conformant: detected level ', Ord(Res.Conformance))
else
Writeln('Issues found: ', SizeOf(Res.Issues), ' flags set');
finally
Pdf.Free;
end;
end;
Bodite pošteni do tega, kaj vam to kupi. Bajtni preverjalnik z veliko gotovostjo ulovi strukturne težave, kot so manjkajoč OutputIntent, prepovedano dejanje, prisoten /Encrypt ali prosojnost tam, kjer jo del 1 prepoveduje, zaznavanje vdelanih pisav pa uporablja hevristiko štetja, ki namenoma poroča samo signal z visoko gotovostjo, namesto da bi lovila pokritost po posameznih glifih. Česar ne počne, je analiza operatorjev v vsebinskih tokovih; za to bi potreboval poln razčlenjevalnik vsebine, kar je namenoma zunaj obsega. Za izdajno zaporo združite preverjalnik iz knjižnice z veraPDF: prvi je takojšen in deluje povsod brez DLL, drugi je avtoritativen. Kako to dvojico povezati v paketni tek, obravnava CLI za paketna preflight poročila, kjer ta validacija sodi v resničnem arhivskem delovnem toku
API-ji SaveAsPdfA, InjectPdfAMarkers in ValidatePdfA, prikazani tukaj, so del PDFium Component za Delphi, C++Builder in Lazarus/FPC. Stran izdelka povezuje celoten API reference, vključno s popolno enumeracijo skladnosti in zapisom možnosti za temi primeri