Technický článek

Validace PDF/UA-2 v Delphi: není to jen UA-1 díl 2

PDFlibPas validuje soubory PDF/UA-2 (ISO 14289-2) v Delphi přes TPDFlib.CheckFilePDFUACompliance s cílovým dílem 2, který spouští samostatný profil PDF/UA-2: role ve stromu struktur překládá přes jmenné prostory PDF 2.0 a /RoleMapNS, zkrácený nebo nečitelný vstup hlásí jako selhání a vrací zprávu o pokrytí, která nikdy netvrdí plnou certifikaci

Ta poslední část znamená víc, než se zdá. Validace přístupnosti je obor, kde se zelená fajfka okopíruje do tabulky nákupního oddělení a už se nikdy nezkoumá. Validátor, který potichu přeskočí půlku normy a stejně vytiskne „pass“, je horší než žádný validátor, protože z neznámého udělá falešně známé. PDFlibPas zastává opačný postoj: zkontrolovat, co jde zkontrolovat strojově, nahlas říct, co zkontrolováno nebylo, a každé „nepodařilo se dokončit“ brát jako nález, ne jako úspěch

Proč nejde PDF/UA-2 validovat pravidly PDF/UA-1?

PDF/UA-2 není PDF/UA-1 s vyšším číslem dílu. PDF/UA-1 stojí na PDF 1.7 a jedné ploché mapě rolí; PDF/UA-2 stojí na PDF 2.0 (ISO 32000-2), kde každý strukturový element může patřit do jmenného prostoru a každý jmenný prostor má vlastní mapu rolí. UA-1 kontrola puštěná na soubor UA-2 jak přehlédne skutečné problémy, tak vymyslí falešné

Trapné jsou právě ty falešné nálezy. Validátory PDF/UA-1 typicky vynucují pravidla o tom, že elementy Note nesou ID, a o tom, jak smí být použit nečíslovaný nadpis H. Ani jedno pravidlo se nepřenáší. Ve strukturovém jmenném prostoru PDF 2.0 se poznámky pod čarou zapisují jako FENote a nadpisy jsou H1 až Hn bez stropu šesti úrovní. PDF/UA-2 jde dál a roli nečíslovaného H zakazuje úplně. Aplikujete-li na takový strom logiku nadpisů z UA-1, buď nadáváte na věci, které norma dovoluje, nebo necháte projít tu jedinou, kterou zakazuje

Proto je profil PDF/UA-2 v PDFlibPas samostatná cesta v kódu, ne příznak na té staré. Odmítá také znovupoužívat čísla klauzulí z UA-1. Diagnostika sdílená s UA-1 kontrolou se v zprávách UA-2 váže k ISO 14289-2 a tam, kde číslo klauzule nebylo ověřeno proti textu normy, nechává zpráva pole klauzule prázdné místo hádání. Prázdná klauzule neuspokojí tak jako sebevědomé číslo klauzule, ale je to jediné obhajitelné zadání, když mapování nikdo nepotvrdil

Jak PDFlibPas překládá role přes RoleMapNS

PDFlibPas překládá každou strukturovou roli na standardní roli ve známém jmenném prostoru, sleduje řetězce /RoleMapNS napříč prostory a hlásí každý řetězec, který končí vlastní rolí, smyčkou nebo porušením pravidel mapování. Toto je jádro validace struktury PDF/UA-2, protože asistivní technologie rozumí jen standardním rolím; vlastní /InvoiceLine neříká nic, dokud neskončí u něčeho jako P nebo TR

Překladač rolí vychází ze jména /S elementu a jeho jmenného prostoru /NS. Bez jmenného prostoru je element implicitně ve standardním prostoru PDF 1.7 a platí stará /RoleMap v kořeni stromu struktur. S prostorem platí vlastní /RoleMapNS ve slovníku prostoru a mapování může přes dvouprvkové pole ukazovat na roli v jiném prostoru. Kontroly, které z toho vycházejí, jsou konkrétní:

  • Jmenný prostor odkazovaný ze strukturového elementu musí být nepřímý slovník s platným URI /NS a musí být uveden v poli /Namespaces v kořeni stromu struktur (diagnostika 10054)
  • Vlastní role musí nakonec dojít na standardní roli v prostoru PDF 1.7, v prostoru PDF 2.0 (http://iso.org/pdf2/ssn) nebo v prostoru MathML; kruhové řetězce a slepé uličky se hlásí jako 10055
  • Standardní roli nelze přemapovat na vlastní a roli nelze přemapovat uvnitř jejího vlastního explicitního prostoru
  • Kořen struktury musí obsahovat právě jeden element Document v prostoru PDF 2.0 (10057) a /P každého elementu musí ukazovat na jeho skutečného rodiče (10058)
  • Nečíslovaný H nebo starý Note z PDF 1.7, který není namapován na roli PDF 2.0, se hlásí jako 10056

Jeden detail stojí za přenesení do vlastního nářadí: PDFlibPas validuje všechna deklarovaná mapování, ne jen ta, kam se ze stromu dostanete. Rozbitá položka v mapě rolí, kterou teď žádný element nepoužívá, zůstává latentní vadou, protože příští aktualizace obsahu ji začne používat. I MathML je řešeno poctivě. Prostor MathML je uznán jako platný cíl překladu, ale samotná slovní zásoba MathML se nevaliduje a zpráva o pokrytí to říká nahlas

Spuštění kontroly PDF/UA-2 z Delphi

TPDFlib.CheckFilePDFUACompliance je vstupní bod na jedno volání: předáte soubor, heslo, cílový díl a volby a dostanete zpět handle seznamu řetězců s kódovanými nálezy. Handle nula s LastErrorCode rovným nula znamená, že implementované kontroly nic nenašly; nenulové LastErrorCode znamená, že samotné volání selhalo

var
  Lib: TPDFlib;
  Issues, I: Integer;
begin
  Lib := TPDFlib.Create;
  try
    // TargetPart: 0 = detekce z XMP pdfuaid:part, 1 = PDF/UA-1, 2 = PDF/UA-2
    // Options: 1 = zastavit na první chybě, 0 = sesbírat každý nález
    Issues := Lib.CheckFilePDFUACompliance('report.pdf', '', 2, 0);
    if Issues = 0 then
    begin
      if Lib.LastErrorCode <> 0 then
        Writeln('Validation did not run, error ', Lib.LastErrorCode)
      else
        Writeln('No issues from the implemented PDF/UA-2 checks');
    end
    else
    try
      for I := 1 to Lib.GetStringListCount(Issues) do  // položky jsou od 1
        Writeln(Lib.GetStringListItem(Issues, I));
    finally
      Lib.ReleaseStringList(Issues);
    end;
  finally
    Lib.Free;
  end;
end;

Cílový díl volte vědomě. Cíl 0 přečte pdfuaid:part z XMP metadat a podle něj vybere profil, což sedí pro obecný validátor příchozí pošty. Pro vlastní výstupní pipeline předejte 2 explicitně. Když váš generátor zapomene identifikaci zapsat, automatická detekce spadne zpět na pravidla UA-1 a vy validujete špatnou normu; explicitní 2 místo toho vyprodukuje diagnostiku 10050 pro chybějící díl, plus 10051, pokud pdfuaid:rev není 2024, a 10052, pokud efektivní verze PDF není 2.0. Na straně tvorby zapíše identifikaci TPDFlib.SetPDFUA2Mode, zvýší verzi na 2.0 a vybere strukturový jmenný prostor PDF 2.0 jako výchozí; samotný model jmenných prostorů rozebírá průvodce jmennými prostory PDF 2.0, PDF/A-4 a PDF/UA-2

Co se stane, když validátoru dojdou limity?

PDFlibPas selhává zavřeně: když kontrola PDF/UA-2 narazí na limit průchodu, smyčku, limit parseru nebo dokument, který nedokáže otevřít, vypustí diagnostiku a výsledek bez diagnostiky se nikdy nepovýší na úspěch. Diagnostika 10059 je vyhrazena přesně pro tento případ „čtečka nedokončila“

Limity jsou explicitní a publikované v coverage JSON: hloubka struktury 256, 100 000 strukturových objektů a 256 skoků po jakémkoli řetězci mapy rolí. Ta čísla tam jsou, protože stromy struktur přicházejí z nedůvěryhodného vstupu. Nepřátelský nebo prostě rozbitý soubor dokáže zanořit elementy tisíce úrovní hluboko, nasměrovat potomka zpět na předka nebo postavit mapu rolí řetězenou přes stovky prostorů. Validátor bez limitů buď spadne, nebo zalezne, a validátor s limity, ale bez diagnostiky, udělá něco jemnějšího a horšího: skončí dřív, v přečtené části nenajde nic špatného a ohlásí úspěch

Týž princip platí o úroveň výš. Pokud podkladová čtečka vrátí skóre pod 100, ale žádné řádky nálezů — což se stane, když parsing zruší běh dřív, než kterékoli pravidlo vůbec běží —, vloží PDFlibPas řádek 10059, aby prázdný seznam nemohl být zaměněn za čistý výsledek. Výjimka během kontroly se rovněž promění v řádek 10059. Je to pár řádků kódu a uzavírá to nejběžnější způsob, jak validační pipeline lžou svým majitelům

Sdílení jednoho parsingu s PDF/A a sestavení zprávy

Pro dávkovou práci parsuje validation pipeline API soubor jednou a nad sdíleným parsingem spustí několik kontrol. RunPDFUAValidationPipelineTask je člen té rodiny za PDF/UA a bere stejné hodnoty cílového dílu jako funkce na jedno volání, takže preflight PDF/A plus PDF/UA-2 neplatí za dvě kompletní čtení velkého dokumentu

var
  Pipe, PDFAIssues, UAIssues: Integer;
  Json: WideString;
begin
  Pipe := Lib.BeginValidationPipeline('archive.pdf', '');
  if Pipe = 0 then
    Exit;
  try
    PDFAIssues := Lib.RunValidationPipelineTask(Pipe, 1, 0);       // 1 = PDF/A
    UAIssues := Lib.RunPDFUAValidationPipelineTask(Pipe, 2, 0);    // PDF/UA-2
    // ... oba handle prohlédněte a uvolněte jako v předchozím příkladu ...
    Writeln('Parses: ', Lib.GetValidationPipelineParseCount(Pipe));
    // ComplianceTests: 1 = PDF/A, 2 = PDF/UA, 0 = obojí; ReportFormat 1 = JSON
    Json := Lib.CreateValidationPipelineReport(Pipe, 0, 0, 1);
  finally
    Lib.ReleaseValidationPipeline(Pipe);
  end;
end;

Strukturovaná zpráva označí sekci přístupnosti jako PDF/UA-2 a ISO 14289-2, když se soubor hlásí jako díl 2, a každá sekce PDF/UA nese "fullCertification": false spolu s vloženým objektem pokrytí. Textový preflight report přidává řádek, že běžely jen implementované kontroly a že plná certifikace přístupnosti potřebuje další validaci a lidský dohled. Širší formáty zpráv, ID pravidel a vzory CI brány popisuje příručka preflight PDF/A a PDF/UA

Co zpráva o pokrytí doopravdy garantuje?

TPDFlib.GetPDFUAReadValidationCoverageJSON garantuje jedinou věc: přesný seznam toho, co kontrola PDF/UA-2 prozkoumala a co ne. Pro díl 2 vrací fullCertification fixingované na false, pole checked, pole unverified a limity průchodu. Negarantuje, že je soubor přístupný, a je navrženo tak, aby žádná cesta v kódu nemohla docílit opaku

var
  Coverage: WideString;
begin
  Coverage := Lib.GetPDFUAReadValidationCoverageJSON(2);
  // Uložte ji vedle validačního výsledku, nikoli místo něj
  TFile.WriteAllText('report.coverage.json', Coverage, TEncoding.UTF8);
  if Pos('"fullCertification":false', Coverage) > 0 then
    Writeln('Machine checks only; schedule a human accessibility review');
end;

Seznam checked pokrývá identifikaci (díl, revize, prefix), efektivní verzi PDF, metadata titulu, /MarkInfo, DisplayDocTitle, odkazy na prostory, překlad mapy rolí, kořen dokumentu, rodiče struktur, cykly ve stromu a pravidla pro H a Note. Seznam unverified je stejně konkrétní: značkování obsahu a pořadí čtení, uzávorkování struktury, fonty a mapování Unicode, anotace a formuláře, přirozený jazyk, alternativní popisy, slovní zásoba MathML a lidský dohled nad přístupností. Několik z toho není ani strojově rozhodnutelných. Žádný parser vám neřekne, zda alt text graf skutečně popisuje, nebo zda pořadí čtení dává smysl tomu, kdo si ho poslouchá

Berte coverage JSON jako součást výsledku. Archivujte ho s každým validačním během a až se auditor zeptá, co v daný den znamenalo „prošlo“, můžete předat přesný rozsah místo rekonstruování z paměti a čísla verze

Ověření proti veraPDF

Jádro PDF/UA-2 bylo zkříženě ověřeno proti příkazovému validátoru veraPDF 1.30.3 s jeho profilem ua2 a výsledky se kryjí tam, kde se překrývá pokrytí. Nezávisle vygenerovaný platný vzorek jádra prošel oběma nástroji; vzorek deklarující pdfuaid:rev 2023 a vzorek používající nečíslovanou roli H oběma propadly

Čtvrtý vzorek je ten, o kterém stojí za to mluvit. Obsahoval kreslicí obsah, který nebyl označkován vůbec, a veraPDF ho označila, zatímco kontrola jádra PDFlibPas ne, protože značkování obsahu je mimo aktuální pokrytí. Právě tato mezera je důvod, proč content-tagging-and-reading-order stojí v čele seznamu unverified. Pustit druhý validátor je levné pojištění a neshoda dvou nástrojů je často nejrychlejší cesta, jak najít hranici toho, co který kontroluje. Pro tvorbu téhož — stromy, které projdou na první pokus — viz příručka strukturového stromu tagovaného PDF

FAQ

Certifikuje PDFlibPas soubor jako vyhovující PDF/UA-2?

Ne. PDFlibPas spouští implementované strojové kontroly pro PDF/UA-2 a vrací zprávu o pokrytí s fullCertification vždy false, ve které vypisuje každou oblast, jež stále potřebuje jiné nástroje nebo lidský dohled

Jakou hodnotu TargetPart předat do CheckFilePDFUACompliance?

Předejte 2, když víte, že soubor musí být PDF/UA-2, aby chybějící identifikace byla nahlášena. Předejte 0 pro automatickou detekci dílu z XMP pdfuaid:part a 1 pro vynucení pravidel PDF/UA-1

Co znamená diagnostika 10059 v validaci PDF/UA-2?

Diagnostika 10059 znamená, že validátor nemohl dokončit: soubor se nepodařilo sparsovat ani otevřít, byl překročen limit průchodu, byla nalezena smyčka nebo nastala výjimka. Vždy se počítá jako selhání, nikdy jako úspěch

Validace PDF/UA-2, sdílená validační pipeline i API pro tvorbu PDF 2.0 jsou součástí losLab PDF Library for Delphi spolu s preflight PDF/A a strukturovanými validačními zprávami