VeraPDF správa hovoriaca, že šírka glyfu nesúhlasí s vloženým fontovým programom, vám nepovie takmer nič o tom, ktorý glyf to je, alebo prečo. PDFlibPas odpovedá na túto otázku tak, že vyrieši každý kód znaku cez vložený cmap na index glyfu, normalizuje programovú metriku na 1000 jednotiek na em a porovnáva tam
Prečo sa šírky glyfov nezhodujú?
Pretože dve porovnávané čísla žijú v rôznych súradnicových systémoch a nič v PDF slovníku vám nepovie konverziu. Fontový slovník zapisuje /Widths v glyfovom priestore, ktorý PDF fixuje na jednu tisícinu em (ISO 32000-1 §9.2.4). Tabuľka hmtx vo vnútri vloženého TrueType programu zapisuje advance v dizajnových jednotkách fontu a tabuľka head rozhoduje, koľko z nich tvorí em: 2048 pre väčšinu TrueType rezov, 1000 pre CFF odvodené, občas niečo úplne iné. Porovnajte surové hodnoty a každý 2048-upem font vo vašom korpuse vyzerá rozbitý. To je pasca, ktorú ISO 14289-1 §7.21.5 kladie každému, kto sa pokúsi auditovať šírky čítaním polí slovníka
PDFlibPas normalizuje pri načítaní. TPDFTrueTypeParser ukladá Advance * 1000 div unitsPerEm do svojho poľa šírok, takže Parser.GetWidth(GID) už odpovedá v tých istých tisícinách em, ktoré PDF používa, a GetRawWidth zostáva dostupný, keď potrebujete dizajnové jednotky naspäť. Tým ostáva tažšia polovica: dostať sa od kódu znaku k indexu glyfu. Pre jednoduchý TrueType font závisí cesta od vlajky Symbolic vo FontDeskriptore, bit 3 z /Flags
Parser := TPDFTrueTypeParser.Create;
try
Parser.LoadFromString(FontProgram);
if Symbolic then
begin
// Symbolické rezy sa adresujú priamo cez programový cmap,
// s konvenciou (3,0) high-byte ako zálohou
GID := Parser.GetGlyphIndex(Code);
if GID = 0 then
GID := Parser.GetGlyphIndex($F000 + Code);
end
else
begin
// Nesymbolické: kód -> názov glyfu cez kódovanie, názov -> Unicode
// cez Adobe Glyph List, Unicode -> GID cez programový cmap
UnicodeValue := GetGlyphUnicode(EncodingNames[Code and $FF]);
if UnicodeValue = 0 then
Continue;
GID := Parser.GetGlyphIndex(UnicodeValue);
end;
if (GID > 0) and (GID < Parser.GlyphCount) then
if Abs(PDFWidth - Parser.GetWidth(GID)) > 1 then
Inc(MismatchCount);
finally
Parser.Free;
end;
Dva detaily v tomto fragmente nesú váhu. Tolerancia je jedna jednotka, nie nula, pretože normalizácia je celočíselné delenie a korektne vyrobený súbor môže pristáť o jednotku vedľa; to je presne formulácia „v rámci jednej tisíciny em", ktorú hlási diagnostika 10036. A strážca GID < Parser.GlyphCount nie je dekorácia. GetWidth je napísaná zhovievavo pre renderovacích volajúcich, svorkuje index mimo rozsahu na poslednú položku v hmtx a pada späť na 750, keď tabuľka chýba. Zhovievavosť je správna pre renderovanie a zlá pre audit, takže audit odmieta index predtým, než si vyžiada šírku, namiesto dôvery vo svorku
CIDFontType2 pridáva ešte jednu smerovanie
PDFlibPas prechádza kompozitné fonty rovnakým spôsobom, s /CIDToGIDMap vloženou medzi CID a glyf. Šírky prichádzajú v poli /W, ktoré ISO 32000-1 §9.7.4.3 dáva v dvoch tvaroch voľne sa striedajúcich v jednom poli: počiatočný CID nasledovaný poľom po sebe idúcich šírok, alebo prvý CID, posledný CID a jedna šírka aplikovaná na celý beh. Audit parsuje oba a potom odovzdáva každý výsledný pár tomu istému porovnaniu a hlási súčet pod diagnostikou 10037. Krok mapovania je miesto, kde sa kompozitné fonty líšia, a je dôvod, prečo diagnostika chýbajúcej mapy 10021 záleží skôr, než si prečítate akúkoľvek šírku — absentujúci alebo poškodený /CIDToGIDMap nelen porušuje §7.21.3.2, robí otázku šírky neodpovedateľnou
// /CIDToGIDMap je názov /Identity alebo stream big-endian 16-bitových
// indexov glyfov, jeden na CID (ISO 32000-1 kapitola 9.7.4.2)
Obj := DerefIndRef(FDoc, CIDFont.FindValueByKeyName('CIDToGIDMap'));
if (Obj is TPDFName) and (TPDFName(Obj).Name = 'Identity') then
begin
GID := CID;
Result := True;
end
else if Obj is TPDFStream then
begin
Data := TPDFStream(Obj).GetDecodedStream;
P := CID * 2 + 1; // Pascal reťazce sú 1-based
if (P >= 1) and (P + 1 <= Length(Data)) then
begin
GID := (Integer(Byte(Data[P])) shl 8) or Integer(Byte(Data[P + 1]));
Result := True;
end;
end;
Čo má audítor robiť, keď sa fontový program nedekóduje?
Nič nepovedať. Kompletnosťné kontroly /CharSet a /CIDSet, ktoré ISO 14289-1 §7.21.4.2 vyžaduje — diagnostiky 10038 a 10039 — sú miesto, kde sa nadšený validátor mení na záťaž, pretože hlásenie „váš CharSet je nekompletný" je pre človeka, ktorý ho číta, neodlíšiteľné od „náš dekodér Type 1 vzdal to". PDFlibPas preto hlási chýbajúcu položku len keď sa podarí tri veci naraz: fontový program sa dekóduje, mapovanie kódu na glyf sa rozlíši a samotná sada sa dekóduje. TPDFType1Decoder.LoadPFBFromString musí vrátiť True a dať počet charstringov skôr, než sa akýkoľvek názov glyfu skontroluje proti reťazcu /CharSet; cesta /CIDSet potrebuje, aby sa stream rozbalil a počet glyfov vrátil pozitívny skôr, než sa testuje jediný bit. Akákoľvek výnimka na ceste sa zrúti na „žiadny nález", nie na defekt
To je zámerná zaujatosť k falošným negatívom a stojí za to to povedať priamo, nie pochovať. Poškodená tabuľka CFF, nepodporovaná varianta Type 1 alebo /CIDSet kratšia než rozsah glyfov všetky produkujú ticho namiesto diagnostiky. Uvažovanie je, že audity PDF/UA sa preposielajú autorom, ktorí nástroj nestavili, a falošné obvinenie stojí viac než premeškané: autor spáli deň dokazovaním, že kompatibilný súbor je kompatibilný, a prestane dôverovať celej správe. Matterhorn Protocol robí to isté rozlíšenie v inej forme, keď oddeľuje kontroly, ktoré môže stroj rozhodnúť, od kontrol, ktoré musí človek, a jeho checkpoint Fonts (31) je miesto, kde tieto žijú. Ak potrebujete prísnejšie čítanie, spustite PDFlibPas ako rýchlu bránu a dedikovaný validátor ako druhý názor — toto párovanie je to isté, ktoré popisuje prechádzka preflight PDF/A a PDF/UA
Page /Contents je zoznam, nie stream
Najdrahšia chyba pri audite obsahových streamov je považovať /Contents za jeden stream. ISO 32000-1 §7.7.3.3 dovoľuje strane držať pole streamov, ktorých konatenácia s bielym miestom medzi časťami je program strany; producenti režú na ľubovoľných miestach a BT môže sedieť v jednom člene so svojím zodpovedajúcim ET v nasledujúcom. Obsahový procesor drží stav — hĺbku zanorenia marked-content, font vybraný posledným Tf, vlajku textového objektu — a Process resetuje tento stav na vstupe. Zavolajte ho raz na člena poľa a každý stream po prvom začína bez aktuálneho fontu, takže text, ktorý bol dokonale označkovaný, sa číta ako neoznačkovaný, nefontovaný šum. PDFlibPas najprv skonatenuje a procesuje raz
function ContentObjectData(FDoc: TSmartPDFDocument; Obj: TPDFObject): AnsiString;
var
I: Integer;
begin
Result := '';
Obj := DerefIndRef(FDoc, Obj);
if Obj is TPDFStream then
Result := TPDFStream(Obj).GetDecodedStream
else if Obj is TPDFArray then
for I := 0 to TPDFArray(Obj).Count - 1 do
Result := Result + ContentObjectData(FDoc, TPDFArray(Obj).Item[I]) + #10;
end;
// Jedno volanie Process nad celou konatenáciou, nikdy jedno na člena
Scanner.Process(ContentObjectData(FDoc, PageDict.FindValueByKeyName('Contents')));
Ktoré Form XObjects naozaj počítajú ako nestruktúrované?
Len tie, ktoré strana skutočne vyvolá, z miesta volania mimo marked content, ktorých vlastný obsah ukazuje text. Diagnostika 10040 vynucuje ISO 14289-1 §7.20 záznamom troch nezávislých skutočností na číslo objektu — má text, bol vyvolaný, bol vyvolaný vo vnútri marked content — a hlási len prienik prvých dvoch mínus tretí. Každá z oboch skratiek je zlá spôsobom, ktorý by ste odoslali: označkovanie každého textového Formu v /Resources trestá knižnicu šablón, z ktorej nikto nekreslí, a označkovanie každého vyvolaného Formu trestá vektorové logá, ktoré nemajú text a nepotrebujú označkovanie. Miesto vyvolania sa rozlíši číslom objektu namiesto názvu zdroja, keďže ten istý Form sa bežne dosahuje pod rôznymi názvami na rôznych stranách. Sprievodná diagnostika 10041 prechádza ten istý skonatený program pre §7.21.8, rozlíši každý operand ukazujúci text cez font v rozsahu a počíta kódy, ktoré pristanú na .notdef, čo je zakázané bez ohľadu na textový renderovací režim — vrátane neviditeľného režimu použitého za naskenovanými obrázkami. Ako by mali preživšie Formy byť zabalené, je otázka stromu štruktúry, pokrytá v článku o stavbe tagged PDF štruktúry
Fonty bez FontDescriptor vôbec
Nevložený font je legitimný vstup do tohto auditu, nie chybový stav, a každý pomocník pod kontrolou vkladania musí prežiť. Keď PDFlibPas nájde žiadny /FontDescriptor alebo deskriptor bez FontFile, FontFile2 alebo FontFile3, zaznamená diagnostiku 10020 — alebo 10022, keď je názov jeden zo Štandardných 14, ktoré §7.21.4 NOTE 5 cieľavedome odmieta vyňať — a potom pokračuje cez zvyšok súboru. To je celý zmysel správy: autor chce každý nález v jednom prechode, nie jeden nález na beh. Takže referencia deskriptora odovzdaná pomocníkom šírok, cmap, CharSet a CIDSet môže byť Nil a každý z nich to testuje na vstupe namiesto predpokladu, že skoršia kontrola prerušila audit. Ak je opravou vložiť to, čo chýba, mechanika je v poznámke o vkladaní chýbajúcich fontov do existujúceho PDF
Spustenie auditu
Jedno volanie, na súbore, ktorý ste nemuseli vy produkovať. TPDFlib.CheckFileCompliance berie selektor compliance testu — 2 pre PDF/UA-1 pod ISO 14289-1:2014 — a vracia buď nulu, alebo handle zoznamu reťazcov, ktorého položky sú číselný kód, dvojbodka a čitateľná správa. Nálezy fontov a obsahových streamov diskutované tu zaberajú 10020 až 10041 v tom rozsahu, numericky oddelené od kódov PDF/A 00xxx, takže zmiešaný log ostáva čitateľný. Posunutie 1 v Options skracuje na prvom náleze, čo je to, čo chcete v build bráne, nie v autorovskom nástroji. Pre dokument stále otvorený v pamäti spustí GetPDFUADiagnostics ekvivalentnú inšpekciu bez okruhu cez disk
var
Issues, Count, I: Integer;
begin
// ComplianceTest = 2 vyberá PDF/UA-1; Options = 0 hlási každý nález
Issues := PDF.CheckFileCompliance('delivery.pdf', '', 2, 0);
if Issues = 0 then
WriteLn('delivery.pdf: PDF/UA-1 conformant')
else
begin
Count := PDF.GetStringListCount(Issues);
for I := 1 to Count do
WriteLn(' ', PDF.GetStringListItem(Issues, I)); // napr. 10037 CIDFontType2 ...
end;
end;
Nič z tohto nepotrebuje externý validátorový binárka na stroji, čo je rozdiel medzi kontrolou, ktorá beží pri každom buildu, a kontrolou, ktorá beží, keď si niekto spomenie. Compliance a diagnostické API popísané tu sa dodávajú v štandardnej PDFlibPas Delphi PDF Library, ktorej produktová stránka nesie kompletnú tabuľku diagnostických kódov pre PDF/UA-1 vedľa testových sád PDF/A, PDF/X a PDF/E