Dodáte převodník, který označí každý soubor jako PDF/A-1b, zákaznický systém pro záznamy je rok přijímá, a pak audit nechá celou dávku projít přes veraPDF a třetina z nich se vrátí jako nevyhovující. Nic nespadlo, nevznikla žádná výjimka, soubory se ve všech prohlížečích na vašem stole otevírají bez problémů. Prostě nebyly tím standardem, kterým jste je označili. To je běžný způsob selhání u archivního PDF a důvod, proč tvrzení „nastavili jsme příznak“ nikdy neznamená totéž co „projde validací“
První věc, kterou je třeba chápat o PDFium a PDF/A, je, že engine s tím nemá nic společného. PDFium PDF vykresluje, parsuje a zapisuje, ale jeho veřejné rozhraní nemá žádný ConvertToPDFA, žádný zapisovač OutputIntentu ani žádné XMP API. Každá část archivní shody, paket XMP, OutputIntent a jeho ICC profil, značky v katalogu i validace, žije přímo v PDFiumPas, v čistě pascalové jednotce o zhruba 2 000 řádcích (FPdfPdfa.pas) která parsuje uložené bajty a přepisuje je prostřednictvím přírůstkové aktualizace. Když víte, kde se práce opravdu děje, víte i to, kde se schovávají chyby, a ty se neschovávají v PDFium
Co PDF/A skutečně vyžaduje a kde naráží
PDF/A není jeden formát. ISO 19005 definuje tři části (PDF/A-1, -2, -3) a v každé z nich úrovně shody, které slibují různé věci. Úroveň B (basic) zaručuje jen to, že vizuální podoba je reprodukovatelná. Úroveň A (accessible) k B přidává značkovaný strom struktury a mapování Unicode. Úroveň U, která existuje jen pro části 2 a 3, stojí mezi nimi: spolehlivý text v Unicode bez plného stromu struktury. ISO 19005-1 úroveň U nezná, a tuto omezenost knihovna přímo zakódovala
V praxi bolí jen několik pravidel tohoto formátu. Šifrování je výslovně zakázáno (ISO 19005-1 §6.1.3 i v dalších verzích): soubor PDF/A nesmí obsahovat /Encryptšifrovací slovník. Dokument musí deklarovat výstupní podmínku vykreslení prostřednictvím OutputIntentu, jehož cílem je platný ICC profil (§6.2.3.2). Samotné tvrzení o shodě se musí objevit jako metadata XMP pod identifikačním schématem PDF/A. Úroveň A navíc vyžaduje logickou strukturu podle §6.8, tedy strom tagů, který dělá dokument strojově čitelným. Když některou z těchto věcí vynecháte, ověřovač shody soubor odmítne, i když se zobrazuje naprosto správně
Jedno volání, které vytvoří archiv
PDFiumPas zpřístupňuje celý postup za TPdf.SaveAsPdfA. Jednoduché přetížení bere cílovou úroveň shody a výchozí hodnotu nastavuje na PDF/A-1b, což je správná volba pro běžný případ „udělej z toho něco, co půjde zobrazit navždy“
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 kapotou jde o dvoustupňový postup. SaveAsPdfA nejprve požádá PDFium o serializaci dokumentu s FPDF_SaveAsCopy, a pak ten bajtový proud předá InjectPdfAMarkers, která připojí metadata XMP, OutputIntent sRGB s vloženým ICC profilem a přepsaný katalog jako přírůstkovou aktualizaci. Zdroj se čte od pozice nula a cíl se zapisuje od pozice nula; původní strom objektů zůstává nedotčený a značky se přidají za existující %%EOFstrom objektů. Pokud potřebujete bajty místo souboru, SaveAsPdfAToStreambere TStream a stejné volby
Volba shody pomocí záznamu voleb
Chcete-li zacílit na konkrétní část a úroveň, předáte TPdfASaveOptions záznam. Jeho Conformance pole přijímá TPdfAConformance hodnotu. Výčet pokrývá všechny platné kombinace a nic dalšího: pac1b, pac1a for part 1; pac2b, pac2u, pac2a for part 2; pac3b, pac3u, pac3a for part 3, plus pacUnknown and pacNone for the validation side. There is no pac1u, because that level does not exist in the standard
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;
Většina záznamu může zůstat prázdná. Nechte Title, Author, Subject, Keywords, Creator, and Producer blank and SaveAsPdfAautomaticky doplnit z dokumentového Info slovníku pomocí FPDF_GetMetaText. Nechte CreationDate and ModDate prázdná a použije aktuální čas UTC pro obě data XMP. Nechte DocumentId and InstanceId prázdná a knihovna je předvyplní z FPDF_GetFileIdentifier, s návratem na deterministické ID odvozené ze zdrojových bajtů. Jediné pole, které možná budete chtít přepsat záměrně, je IccProfileData: prázdná hodnota znamená přibalený profil sRGB IEC61966-2.1, ale pracovní postup s CMYK nebo v odstínech šedi by měl dodat vlastní
Proč se úroveň A degraduje a proč je to poctivá volba
Tady je jemný detail, který zaskočí lidi, kteří od příznaku čekají záruku. Můžete si vyžádat pac1a na dokumentu, který nemá strom tagů, ale PDF/A-1a vyžaduje logickou strukturu podle §6.8 a knihovna neumí vyrobit strom struktury z neoznačeného PDF. Místo aby vytvořila soubor, který tvrdí, že má úroveň A, ale ve skutečnosti ji nesplňuje, SaveAsPdfA kontroluje skutečnou značkovanou strukturu (/StructTreeRoot plus /MarkInfo s /Marked true) a pokud chybí, sníží deklarovanou úroveň: pac1a se změní na pac1b, pac2a se změní na pac2b a tak dále napříč všemi třemi částmi. Interní pomocné funkce jsou PdfAIsLevelA a PdfADowngradeToLevelB
Důvod stojí za to říct otevřeně: soubor, který poctivě deklaruje úroveň, kterou skutečně splňuje, je užitečnější než ten, který lže o úrovni, kterou nesplňuje. Úroveň U se řeší jinak. Zjišťování skutečného pokrytí Unicode by znamenalo naivní test „má to /ToUnicode“ který by příliš degradoval legitimní dokumenty (WinAnsi a podobná kódování jsou vyňata), takže část pro ukládání vypíše tvrzení U tak, jak je zadal volající, a nesrovnalost nechá označit až na straně validace. Pokud potřebujete zaručený archiv úrovně A, před převodem dokument označkujte; převodník nevymyslí strukturu, která tam není
Past s ICC, kterou odhalí jen skutečný validátor
Tohle je selhání, které přineslo nejtvrdší lekci, protože vlastní kontrola knihovny ho prošla, zatímco veraPDF, referenční validátor ISO 19005, ne. PDF/A vyžaduje, aby cílový profil OutputIntentu byl platný proud ICCBased, a §6.2.3.2 nutí ověřovač validovat tento proud jako barevný prostor. Proud ICCBased musí deklarovat /N, počet barevných složek. Raná verze injektoru zapisovala slovník ICC proudu jen s /Length a bez /N, a veraPDF výsledek odmítl s „The N entry (value null)... is missing“
Zrádné bylo, že odmítnutí se spouštělo jen pro PDF/A-1b a -1a. Modely shody pro část 2 a část 3 na cílovém profilu tento konkrétní test nespouštěly, takže stejná vložená struktura prošla validací pod pac2b, pac3b, and pac2u ale selhala pod pac1b jen kvůli pouhé hodnotě pdfaid:part. Jednotkový test to nikdy nemohl zachytit, protože vlastní ValidatePdfACompliance kontrola knihovny jen ověřovala, že klíč /DestOutputProfile existuje, ne co je uvnitř slovníku proudu. Vnitřní testy zůstaly zelené, ale skutečná archivní validace selhala
Oprava je IccComponentCount, která čte podpis datového barevného prostoru na offsetu 16 hlavičky ICC a mapuje jej na počet složek: GRAY je 1, RGB , Lab , a XYZ jsou 3, CMYK je 4 a neznámý profil má výchozí hodnotu 3. Tento počet se zapíše do slovníku proudu jako /N. Je vypočtený, ne napevno nastavený na 3, aby volající, který dodá profil CMYK nebo odstínů šedi prostřednictvím IccProfileData stále dostal správnou hodnotu. Širší poučení je metodologické: kontrola v knihovně i autoritativní validátor mají své slepé skvrny a výstup PDF/A je třeba testovat end to end proti referenční implementaci, jako je veraPDF, místo aby se věřilo vlastním samokontrolám. Stejná disciplína přírůstkových aktualizací, která stojí za čistými archivy, je popsána v ověřování komprimovaných object a xref streamů, což je důležité, protože moderní PDF, která injektor zpracovává, jsou často postavená na cross-reference streamech
Šifrování, xref streamy a další okrajové případy
Protože ISO 19005 šifrování zakazuje, cesta ukládání jej před zápisem odstraní. SaveAsPdfA používá FPDF_REMOVE_SECURITY při serializaci, takže šifrovaný zdroj (načtený s heslem) se při cestě do archivu dešifruje. U nešifrovaného dokumentu je to no-op a nic to nemění. Z toho plyne stejná podmínka, kterou HotPDF vynucuje z opačné strany: jeden soubor nemůže být současně šifrovaný a PDF/A. Když pracovní postup potřebuje obojí, řešením jsou dva artefakty, šifrovaná kopie pro distribuci a samostatná čistá kopie pro archiv
Ještě jeden okrajový případ je neviditelný, dokud nezačne bolet: dokumenty PDF 1.5+ používající čistý cross-reference stream a nenesoucí žádné trailer klíč. Injektor čte trailer, aby našel zdrojové /Info a připojil jeho přírůstkovou aktualizaci, a musí přijmout i podobu xref-stream, jinak by se takový dokument prošel kopírováním s tiše odhozenými značkami. ISO 32000-1 §7.5.6 výslovně dovoluje, aby za dokumentem s xref-stream následovala klasická přírůstková aktualizace traileru, s /Prev ukazující na offset xref-streamu, což je přesně struktura, kterou injektor vytváří. Vlastní FPDF_SaveAsCopy vždy zapisuje klasický trailer, takže v běžném potrubí injektor nikdy nepotká čistý zdroj xref-stream, ale čtecí cesta si poradí s dokumenty, které přijdou odjinud
Ověření dřív, než uvěříte tvrzení
Knihovna dodává bajtovou kontrolu, TPdf.ValidatePdfA, která vrací TPdfAValidationResult. Její Conformance pole hlásí zjištěnou úroveň a Issues je množina TPdfAValidationIssue hodnot; pohodlná metoda IsCompliant vrací true jen tehdy, když byla zjištěna skutečná úroveň a množina problémů je prázdná. Použijte ji jako rychlou první bránu v dávce
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;
Buďte upřímní, co tím získáte. Bajtová kontrola zachytí strukturální problémy (chybějící OutputIntent, zakázanou akci, přítomný /Encrypt, průhlednost tam, kde ji část 1 zakazuje) s vysokou jistotou a detekce vkládání písem používá heuristiku založenou na počtu, která záměrně hlásí jen signál s vysokou jistotou místo honby za pokrytím po jednotlivých glyfech. Co nedělá, je analýza operátorů v content streamu, která by vyžadovala plný parser obsahu a je záměrně mimo rozsah. Pro release gate spojte kontrolu v knihovně s veraPDF: kontrola je okamžitá a běží všude bez DLL, veraPDF je autoritativní. Zapojení této dvojice do dávkového běhu je předmětem CLI nástroje pro batch preflight report, protože právě tam tato validace v reálném archivním pracovním postupu patří
Rozhraní SaveAsPdfA, InjectPdfAMarkers, and ValidatePdfA dostupná zde jsou součástí PDFium Component pro Delphi, C++Builder a Lazarus/FPC. Stránka produktu odkazuje na kompletní referenci API, včetně úplného výčtu shody a záznamu voleb za těmito příklady