Technický článek

Implementační limity PDF/A a kontroly kódování písem

PDFium Component validuje implementační limity podle ISO 19005-1 Annex C — name tokeny 127 bytů, 8191 prvků pole, 4095 položek slovníku a 28 úrovní zanoření kontejnerů — a hlásí symbolické TrueType písmo, které nese položku /Encoding. Obě kontroly běží na cestě byte-scan, takže aplikace v Delphi nebo Lazarus dostane verdikt bez nutnosti načítat PDFium DLL vůbec

To jsou selhání, která lidi zarážejí nejvíc, protože dokument vypadá v pořádku. Renderuje se, tiskne, každé písmo je vložené, output intent je přítomen. Validátor ho pak odmítne kvůli slovníku, který má 4096 položek, a nic ve viditelném dokumentu nevysvětluje proč

Co vlastně limity Annex C chrání?

Interoperabilitu s implementacemi, které předcházejí vašemu generátoru. Annex C přenáší implementační limity z PDF Reference do každé části PDF/A a čísla nejsou libovolná — popisují to, co byl konformní čtenář historicky povinen zvládnout. Soubor, který je překračuje, se může v moderním prohlížeči otevřít perfektně a selhat v archivačním čtenáři, na kterém se před patnácti lety standardizoval records systém — přesně ten scénář, jemuž má PDF/A zabránit

Čtyři limity jsou inkluzivní. Name token přesně 127 bytů projde; 128 nikoliv. Pole přesně 8191 prvků projde; 8192 nikoliv. PDFium Component z toho důvodu ve své testovací sadě připíná obě strany každé hranice, protože off-by-one v kontrole limitu produkuje nejhorší druh validátoru: takový, který odmítá konformní soubory a přesto se mu věří

Diagram čtyř inkluzivních implementačních limitů ISO 19005-1 Příloha C validovaných čistým bajtovým skenem z Delphi: jmenné tokeny 127 bajtů, 8191 prvků pole, 4095 položek slovníku a 28 úrovní zanoření kontejnerů
Všechny čtyři meze jsou inkluzivní — hraniční hodnota sama projde a selže až další jednotka
uses FPdfPdfa;

var
  Src: TFileStream;
  Res: TPdfAValidationResult;
begin
  Src := TFileStream.Create('archive.pdf', fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfACompliance(Src);
    if pvaiArrayOverLimit in Res.Issues then
      Memo1.Lines.Add('An array carries more than 8191 elements');
    if pvaiDictOverLimit in Res.Issues then
      Memo1.Lines.Add('A dictionary carries more than 4095 entries');
    if pvaiNestingOverLimit in Res.Issues then
      Memo1.Lines.Add('Containers nest deeper than 28 levels');
    if pvaiNameOverLimit in Res.Issues then
      Memo1.Lines.Add('A name token is longer than 127 bytes');
  finally
    Src.Free;
  end;
end;

Kteří generátoři tyto limity skutečně překročí?

Takoví, kteří staví strukturu programově, což je většina line-of-business výstupu. Formulář s několika tisíci poli produkuje pole /Annots nebo pole AcroForm /Fields, které přerůstá 8191. Stránka, jejíž resources dictionary nahromadí jednu položku na vygenerovaný obrázek nebo instanci písma, překročí 4095. Hluboce generované stromy struktury — značkovaný dokument stavěný rekurzí přes vnořený datový model — překročí 28 úrovní, aniž si toho kdo všimne, protože nikdo nedívá se na hloubku zanoření

Dlouhé názvy přicházejí z jiného zvyku: kódování dat do name tokenů. Název kolorantu složený z identifikátoru zákazníka, optional-content group pojmenovaná po plné cestě k souboru, formulářové pole, jehož plně kvalifikovaný název concatenuje šest úrovní hierarchie. Názvy se generují snadno a dlouhé udělají se snadno a 127 bytů zmizí rychleji, než čekáte, jakmile se do toho namíchá UTF-8 kódovaný label

Oprava je v každém případě strukturální. Rozdělte pole, rozdělte slovník, zploštěte zanoření, zkraťte název — preflight doporučení pro každý problém pojmenuje konkrétní limit spíše než aby říkalo, že soubor je neplatný. Injekce markerů zde nepomůže: to nejsou tvrzení metadat, to je tvar grafu objektů

Proč symbolické TrueType písmo nesmí nést /Encoding

Protože ISO 19005-1 §6.3.7 u symbolických TrueType písem připouští jen vestavěný cmap písma a položka /Encoding by tomu odporovala. Symbolické písmo mapuje kódy na glyphy po svém — to je význam slova symbolické. Přidejte encoding tabulku a najednou existují dvě odpovědi na otázku „který glyph vybere bajt 0x41", bez pravidla v souboru, které by rozhodlo, která vyhrává. Různé čtenáře to řeší různě a dokument, který se v jednom prohlížeči renderuje jako text, se v jiném renderuje jako dingbats

PDFium Component čte příznak symbolic ze /FontDescriptor, ať už je descriptor zapsán inline ve slovníku písma nebo odkázán nepřímo. Nesymbolické TrueType písmo si ponechá svůj požadovaný /WinAnsiEncoding nebo /MacRomanEncoding bez příznaku, protože pro nesymbolická písma je encoding přesně to, co standard vyžaduje. Kontrola spouští na rozporu, nikoli na přítomnosti encoding

Diagram kontrastující symbolický TrueType font, jehož položka /Encoding vytvoří dvě soutěžící glyfová mapování, s nesymbolickým fontem, který legitimně drží WinAnsiEncoding, v kontrole PDFium Component v Delphi
Kontrola přečte symbolický příznak z font descriptoru a vystřelí na rozpor, ne na přítomnost kódování
if pvaiSymbolicTrueTypeEncoding in Res.Issues then
  Memo1.Lines.Add(
    'A symbolic TrueType font carries /Encoding; PDF/A admits only its ' +
    'built-in cmap (ISO 19005-1 6.3.7)');

Praktickým zdrojem této vady je subsetting písem producentem, který zachází se všemi TrueType písmy stejně. Symbol, Wingdings, čárové kódy a icon fonts jsou obvyklými nosiči — přesně ta písma, která obchodní dokument používá pro zaškrtávací políčka, loga a čárové kódy, a přesně ta, která nikdo nepřekontroluje, když dokument selhá v validaci kvůli „písmům"

Jak se problémy dostanou do preflight reportu

Čtyři limity kontejnerů se klasifikují pod structure; problém s encoding symbolického TrueType se klasifikuje pod content. Toto rozdělení má váhu, když report jde na dvě různé osoby: structure nálezy obvykle patří tomu, kdo napsal generátor, a content nálezy tomu, kdo dodal assety

Každý problém nese doporučení, které pojmenuje nápravu konkrétně — zkrátit name tokeny na 127 bytů nebo méně, rozdělit pole tak, aby žádné neneslo víc než 8191 prvků, odstranit /Encoding ze symbolických TrueType písem. Report, který říká „není konformní s PDF/A", startuje vyšetřování. Report, který říká, který limit byl překročen a o kolik, ho ukončuje

Validace bez DLL a proč to zde má váhu

Všechny výše uvedené kontroly běží proti bajtům souboru, takže fungují ve službě, která nemá nasazený PDFium binary, v build kroku nebo na stroji, kde je načítání nativní DLL politickým problémem. To je záměrná designová linie v PDFium Component: kontroly, které lze odpovědět ze struktury, se odpovídají ze struktury a DLL se rezervuje pro ty, které skutečně potřebují renderovací engine

Pro okolní workflow — spuštění validace nad složkou, produkci reportů a rozhodování, co s nálezy dělat — viz průvodci preflight validací PDF/A v Delphi a CLI pro dávkové preflight reporty. Pro volbu archivačního profilu, která stojí nad všemi těmito kontrolami, poznámky k archivační shodě PDF/A rozebírají, kterou část a úroveň cílit, než začnete opravovat nálezy

PDFium Component obaluje engine PDFium pro Delphi, C++Builder a Lazarus s vysokoúrovňovým VCL API a sadou validátorů shody, které běží s DLL i bez něj — viz stránka produktu PDFium Component pro podporované standardy a platformy