Technický článek

Předletová kontrola PDF/A a PDF/UA v Delphi s PDF Library for Delphi

PDF/A a PDF/UA odpovídají na dvě otázky, které spolu nemají nic společného, a brát je jako jedno zaškrtávací políčko „přístupnost a archivace“ je způsob, jak se do archivu dostanou rozbité soubory s nálepkou shody. PDF/A se ptá, zda se soubor bude věrně vykreslovat i za dvacet let. PDF/UA se ptá, zda jej dnes dokáže přečíst asistivní technologie. Dokument může jeden z nich splnit s přehledem a u druhého propadnout, takže poctivý verdikt vzejde jedině ze spuštění obou, a to dřív, než se soubor zapíše, ne poté, co systém dál v řetězci uvěří identifikátoru shody zapečenému v metadatech. Tento identifikátor je vlastní prohlášení. Nic ve formátu nevyžaduje, aby byl pravdivý, a aplikace, která do XMP zapíše „PDF/A-1b“ bez ověření vůči normě, vyrobí soubor, jenž vypadá konformně pro každého odběratele, který čte jen tu nálepku. losLab PDF Library (PDF Library for Delphi) tuto mezeru pro Delphi a C++Builder zavírá tím, že oba validátory staví přímo do knihovny, takže kontrola běží uvnitř procesu a není třeba stavět žádnou externí službu

Dvě normy, které soubory shazují z opačných důvodů

ISO 19005 (PDF/A) je smlouva o reprodukci. Konformní soubor se musí za desítky let vykreslit identicky na softwaru, který nikdy neviděl systém, jenž jej vyrobil, a proto pravidla útočí na vnější závislosti: každé písmo vložené, barva ukotvená ve vloženém ICC OutputIntentu nebo vyjádřená v prostoru nezávislém na zařízení, žádné šifrování v PDF/A-1, žádný JavaScript, metadata XMP souhlasící se slovníkem informací o dokumentu. ISO 14289 (PDF/UA) je naproti tomu smlouva o sémantice. Asistivní technologie musí dokument projít a odnést si z něj význam, a ten sídlí ve zcela jiné vrstvě: úplný strom struktury, alternativní text u obrázků, název dokumentu nastavený pro zobrazení, úrovně nadpisů, které nepřeskakují, vztahy záhlaví tabulek, jež přežijí i poté, co stránka zmizí z obrazovky

Protože každá z těch dvou norem hlídá jinou vrstvu, kousnou vás právě soubory, které leží mezi nimi. Archivně dokonalý dokument může být pro čtečku obrazovky němý. Nádherně otagovaný dokument může odkazovat na desktopové písmo, jež za deset let existovat nebude. Publikování ve veřejném sektoru je typickým místem, kde oba požadavky dopadnou naráz, a tamní pipeline je nesmí sloučit do jediné brány. Nálezy putují k různým lidem. Nevložená písma jsou vadou kódu, který PDF generuje, zatímco chybějící alternativní text patří tomu, kdo vlastní obsahové šablony, a zpráva, která obojí míchá, se prostě přepošle dvakrát

To, na kterou část PDF/A cílíte, je stejně důležité jako to, zda ji trefíte. PDF/A-1 je zamrzlé na PDF 1.4 a odmítá průhlednost i JPEG2000, po čemž obojím moderní reportovací výstup bez přemýšlení sahá. PDF/A-2 (ISO 19005-2, postavené na ISO 32000-1) přijímá obojí a je rozumnou výchozí volbou pro nový archiv. PDF/A-3 jde dál a povoluje vložené soubory libovolného typu, o což se opírají regulované formáty elektronické fakturace. Tým, který se v roce 2026 pořád standardizuje na PDF/A-1b, obvykle vleče požadavek, jejž kdosi sepsal před patnácti lety, a znovu vyjednat cílovou část bývá levnější než vyškrtat průhlednost z každého grafu, který systém vydá

Diagram PDF Library for Delphi srovnávající smlouvu reprodukce PDF/A se smlouvou sémantiky PDF/UA plus matici pass-fail dokumentů, jež splní jednu a u druhé selžou
PDF/A zajišťuje věrné vykreslení na desetiletí dopředu, zatímco PDF/UA zajišťuje asistované čtení dnes; ani jeden verdikt neimplikuje ten druhý

Strukturované nálezy už při příjmu

Vstupním bodem plochého API je CheckFileCompliance s volbou testu 1 pro PDF/A a 2 pro PDF/UA. Vrací handle seznamu řetězců, jehož položkami jsou jednotlivé nálezy, jeden na řádek, což je přesně tvar, jaký automatická brána potřebuje procházet:

function GateArchiveUpload(Pdf: TPDFlib; const FileName: string): Boolean;
var
  ListId, I: Integer;
begin
  ListId := Pdf.CheckFileCompliance(FileName, '', 1, 0);  // 1 = PDF/A
  if ListId = 0 then
  begin
    // 0 znamená „žádné nálezy“ NEBO „soubor nelze číst“ -- rozlište před pokračováním
    Result := Pdf.LastErrorCode = 0;
    Exit;
  end;
  for I := 0 to Pdf.GetStringListCount(ListId) - 1 do
    LogFinding(FileName, Pdf.GetStringListItem(ListId, I));
  Pdf.ReleaseStringList(ListId);
  Result := False;
end;

O tom, zda tohle poběží bez dozoru, rozhodují dva detaily. Prvním je návratová hodnota, která znamená dvě protikladné věci. CheckFileCompliance vrací 0, když je soubor plně konformní, a také když soubor nešlo vůbec otevřít, protože vnitřně se prázdný seznam výsledků v obou případech scvrkne na 0. Brána, jež 0 čte jako průchod, propustí poškozené nahrávky rovnou do archivu, takže tu nulu rozlište pomocí LastErrorCode dřív, než jí uvěříte, přesně jak to dělá brána výše. Druhý detail se týká toho, kde je soubor ve svém životním cyklu. Kontrolní modul běží nad proudovým čtecím modulem knihovny místo nad plným modelem dokumentu, otevírá soubor přímo se sdílením pro čtení a nikdy nevolá LoadFromFile, díky čemuž dokáže rozžvýkat i mnohagigabajtový vstup bez stavby objektového stromu. Totéž proudové otevření selže, dokud jiný proces drží soubor pro zápis, a probíhající nahrávání je přesně tento stav. Bránu zařaďte až po dokončení přenosu

Proudový návrh se vyplatí znovu při zátěži. Každá kontrola otevírá svůj vstup jen pro čtení a sdílí jej pro čtení, takže audit celého korpusu se škáluje napříč pracovními vlákny či procesy s jednou instancí TPDFlib na pracovníka a bez vzájemné soutěže. Zdrojem, který vyžaduje disciplínu, je samotný handle. Každý nenulový výsledek CheckFileCompliance zůstává alokovaný, dokud nezavoláte ReleaseStringList, a dlouho běžící brána, jež je zapomene uvolnit, nespadne, jen pomalu krvácí pamětí, dokud někdo nezačne pátrat proč

Zprávy pro lidi, rozdíly pro brány sestavení

Seznam nálezů má správný tvar pro bránu a špatný tvar pro e-mail týmu, který spravuje šablony. CreatePreflightReport vykreslí tutéž analýzu jako čitelný text, CreatePreflightReportEx přidává volbu formátu zprávy a SavePreflightReport ji zapíše na disk, aby mohla putovat uvnitř dodávaného balíku dokumentů. Řada archivních smluv z takové zprávy dělá samostatný dodávaný výstup, nejen interní artefakt

Členem téhle rodiny, který si své místo zaslouží nenápadně, je ComparePreflightReports. Shoda je regresní plocha jako každé jiné chování. Úprava šablony, nově licencované firemní písmo nebo povýšení knihovny mohou každé zavést nález, jenž tam minulé vydání nebyl, a žádné z nich se samo neohlásí. Držte referenční zprávy pro sadu reprezentativních dokumentů ve verzovacím systému, po každé změně je znovu vygenerujte a rozdíl spočítejte pomocí ComparePreflightReports. Prázdný rozdíl je artefakt vydání, který stojí za uschování. Překvapivý nález shodí sestavení, což je mnohem levnější místo, kde jej objevit, než audit

Výstup, který projde napoprvé

Preflight se vyplácí u souborů přicházejících odjinud. U dokumentů, které vyrábí váš vlastní kód, je hledání porušení až po generování a jejich zpětné záplatování ta pomalá cesta. PDF Library for Delphi nese pro každou normu režim na straně generování a oba lze zapnout pro tentýž dokument:

Diagram PDF Library for Delphi škálovaného příjmu preflight s jednou streamovací instancí knihovny na worker, bráněnými otevřeními až po dokončení uploadů a uvolněnými seznamy nálezů
Brány se otevírají až po dokončení přenosů, každý worker čte vstup read-only streamováním přes privátní instanci knihovny a každý vrácený handle má dlužné uvolnění
var
  Pdf: TPDFlib;
  Diag: WideString;
begin
  Pdf := TPDFlib.Create;
  try
    Pdf.NewDocument;
    Pdf.SetPDFAMode(1);
    Pdf.LoadOutputIntentProfile('sRGB-IEC61966-2.1.icc', 'RGB');
    Pdf.SetPDFUAMode('en-US');
    Pdf.SetInformation(1, 'Quarterly Statement');  // /Title: vyžadováno pro PDF/UA
    // ... zde vykreslete tagovaný obsah ...
    Diag := Pdf.GetPDFUADiagnostics;
    if Diag <> '' then
      Writeln('fix before shipping: ', Diag);
    Pdf.SaveToFile('statement.pdf');
    // preflight, na kterém záleží, běží nad uloženým souborem:
    Writeln(Pdf.CreatePreflightReport('statement.pdf', '', 1, 0));
  finally
    Pdf.Free;
  end;
end;

Past se skrývá v okamžiku ukládání. Několik oprav kvůli shodě proběhne během serializace dokumentu, ne ve chvíli, kdy režim zapnete: vynucení příznaku tisku u anotací, zápis výchozího AFRelationship u vložených souborů v PDF/A-3, normalizace pořadí tabulátoru a popisů polí formuláře kvůli PDF/UA. Dokument sedící v paměti není bajt po bajtu totožný s tím, který dosedne na disk, takže jediný verdikt preflightu, jenž má cenu, je ten spočítaný z uloženého souboru. Ověřujte samotný statement.pdf. Neodvozujte shodu z objektu, který je stále v paměti, protože bajty, jež byste posuzovali, nejsou bajty, které jste vydali

Diagram PDF Library for Delphi oprav souladu aplikovaných při ukládání během serializace, a proto patří preflight na uložený soubor PDF, nikoli na model v paměti
Serializace vynucuje příznaky tisku anotací, nastavuje výchozí AFRelationship a normalizuje pořadí tabulátorů, takže prohlídka paměti soudí bajty, které nikdo nikdy neexpeduje

Fakturační scénáře, jež vedle vizuálního dokumentu nesou strojově čitelné XML, sledují vzor ZUGFeRD a Factur-X, který stojí na PDF/A-3. Ty by měly vztah přílohy nastavit výslovně přes SetPDFA3DefaultAFRelationship, protože ISO 19005-3 vyžaduje, aby každý vložený soubor deklaroval svou roli vůči dokumentu. Nechte to nenastavené a vložené XML je jen blob bez uvedeného účelu, čehož si validátor všimne

Nezávislí rozhodčí: veraPDF a Acrobat

Producent by neměl být jediným soudcem svého vlastního výstupu. Kontrolní moduly PDF Library for Delphi vám dají rychlé strukturované verdikty uvnitř procesu, což je přesně to, co chcete na horké cestě, ale vydávací brána archivní dávky by výstup měla přesto prohnat validátorem, kterého nikdo z vašeho týmu nepsal. veraPDF je komunitně udržovaná referenční implementace pro PDF/A a nástroj, jejž archivy ve svých akceptačních kritériích jmenují nejčastěji, takže je to ten, s nímž se srovnávat. Preflightové profily Acrobatu se hodí jako rozhodčí ve chvíli, kdy se veraPDF a kontrola uvnitř procesu neshodnou. Vedle každé uložené zprávy zaznamenejte název validátoru i jeho verzi. Tvrzení, že soubor prošel přes veraPDF, říká bez čísla sestavení, které jej pustilo, jen velmi málo, protože nástroj svá pravidla mezi vydáními utahuje

Validátory se na okrajích norem opravdu rozcházejí, a když k tomu dojde, není odpovědí vybrat si nástroj, který se vám líbí. Zmenšete soubor na minimální vzorek, jenž ten rozpor stále vyvolává, a přečtěte si jej proti textu normy. Hodina takové práce obvykle vynese jedno ze dvou: buď skutečnou chybu nástroje, kterou stojí za to nahlásit jeho autorům, nebo ustanovení, jež váš tým čte špatně a měl by si je zapsat do poznámek o shodě, aby to příští člověk neřešil znovu

Zašifrovaný vstup má zkratku. Oba kontrolní moduly berou argument s heslem, jenže soubor PDF/A-1 se šifrovacím slovníkem už konformní není, protože ISO 19005-1 šifrování bez výjimky zakazuje, takže zašifrované podání lze odmítnout ještě před jakoukoli hlubší analýzou. Zjistit, co vlastně šifrovací slovník uděluje, je úloha sama pro sebe a věnuje se jí audit šifrování a oprávnění v PDF

Nálezy PDF/UA se téměř vždy vracejí k tomu, jak byl strom struktury vůbec vytvořen, a tagovací techniky za tím žijí v článku stavba stromů struktury tagovaného PDF v Delphi. Archivy, které navíc vyžadují digitální podpisy, by měly tuto bránu spárovat s postupem popsaným v článku podepisování a validace PAdES. Kompletní referenční příručka preflightového API žije na produktové stránce losLab PDF Library for Delphi