Egy veraPDF jelentés, amely azt mondja, egy glyph szélessége nem egyezik a beágyazott betűtípusprogrammal, szinte semmit nem árul el arról, melyik glyphről van szó, és miért. A PDFlibPas úgy válaszol erre a kérdésre, hogy minden karakterkódot a beágyazott cmap útján glyph indexre old fel, a programmetrikát 1000 egységre normalizálja em-enként, és ott veti össze
Miért térnek el a glyph szélességek?
Mert a két összehasonlított szám különböző koordinátarendszerekben él, és semmi a PDF szótárban nem árulja el az átváltást. A betűtípus-szótár a /Widths-et glyph térben írja, amelyet a PDF az em ezredrészére rögzít (ISO 32000-1 §9.2.4). A beágyazott TrueType program hmtx táblája az előtolásokat font tervezőegységekben írja, a head tábla pedig eldönti, hány tesz ki egy em-et: 2048 a legtöbb TrueType változathoz, 1000 a CFF-ből származókhoz, olykor valami teljesen más. Hasonlítsa össze a nyers értékeket, és a korpusz minden 2048-upem betűtípusa sérültnek tűnik. Ez az a csapda, amelyet az ISO 14289-1 §7.21.5 mindenki számára állít, aki szótármezők olvasásával akarja auditálni a szélességeket
A PDFlibPas betöltéskor normalizál. A TPDFTrueTypeParser a Advance * 1000 div unitsPerEm-et tárolja a szélességtömbjében, így a Parser.GetWidth(GID) már ugyanabban az em-ezredrészben válaszol, amelyet a PDF használ, a GetRawWidth pedig elérhető marad, amikor tervezőegységekre van szüksége. Ez így is hagyja a nehezebb felét: az eljutást a karakterkódtól a glyph indexig. Egy egyszerű TrueType betűtípusnál az útvonal a FontDescriptor Symbolic jelzőjétől függ, a /Flags 3. bitjétől
Parser := TPDFTrueTypeParser.Create;
try
Parser.LoadFromString(FontProgram);
if Symbolic then
begin
// A szimbolikus változatok közvetlenül a program cmap-én keresztül címezhetők,
// a (3,0) magas bájt konvencióval tartalékként
GID := Parser.GetGlyphIndex(Code);
if GID = 0 then
GID := Parser.GetGlyphIndex($F000 + Code);
end
else
begin
// Nem szimbolikus: kód -> glyph név a kódolás útján, név -> Unicode
// az Adobe Glyph List útján, Unicode -> GID a program cmap-je útján
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;
Két részlet hordoz súlyt ebben a töredékben. A tűrés egy egység, nem nulla, mert a normalizálás egész osztás, és egy szabályosan előállított fájl eltérhet eggyel; ez pontosan az az em ezredrészén belüli megfogalmazás, amelyet a 10036-os diagnosztika jelent. És a GID < Parser.GlyphCount őr nem dísz. A GetWidth megbocsátóra van megírva a renderelést végző hívók számára, tartományon kívüli indexet a hmtx utolsó bejegyzésére szorít, és 750-re esik vissza, ha a tábla hiányzik. A megbocsátás jó a renderelésnek, rossz az auditnak, ezért az audit az indexet utasítja el, mielőtt szélességet kérne, ahelyett hogy a szorításra bízna
A CIDFontType2 egy további köztes lépést ad
A PDFlibPas ugyanígy járja be az összetett betűtípusokat, a /CIDToGIDMap-pel a CID és a glyph közé ékelve. A szélességek a /W tömbben érkeznek, amelyet az ISO 32000-1 §9.7.4.3 két alakban ad, és amelyek szabadon váltakoznak egy tömbben: egy kezdő CID, utána egymást követő szélességek tömbje, vagy egy első CID, egy utolsó CID, és egyetlen szélesség, amely a futamon át érvényes. Az audit mindkettőt értelmezi, majd minden keletkező párt ugyanahhoz az összehasonlításhoz ad, és az összesen a 10037-es diagnosztika alatt jelenik meg. A leképezési lépés az, ahol az összetett betűtípusok különböznek, és ezért fontos a hiányzó-térkép 10021-es diagnosztikája, mielőtt egyáltalán bármilyen szélességet olvasna — egy hiányzó vagy hibás /CIDToGIDMap nemcsak a §7.21.3.2-t sérti, hanem a szélesség kérdését megválaszolhatatlanná teszi
// A /CIDToGIDMap a /Identity név vagy big-endian 16 bites glyph indexek streamje,
// CID-enként egy (ISO 32000-1 9.7.4.2 fejezet)
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; // a Pascal sztringek 1-alapúak
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;
Mit tegyen az auditor, ha a betűtípusprogram nem dekódolható?
Ne mondjon semmit. A /CharSet és a /CIDSet teljesség-ellenőrzései, amelyeket az ISO 14289-1 §7.21.4.2 előír — a 10038-as és 10039-es diagnosztikák — azok a helyek, ahol a túl buzgó validátor terhessé válik, mert egy a CharSet-je hiányos jelentés az olvasó szemében megkülönböztethetetlen attól, hogy a Type 1 dekóderünk feladta. Ezért a PDFlibPas csak akkor jelent hiányzó bejegyzést, ha három dolog mind sikerül: a betűtípusprogram dekódol, a kód-glyph leképezés feloldódik, és maga a halmaz is dekódol. A TPDFType1Decoder.LoadPFBFromString-nek True-t kell visszaadnia és charstring számot kell adnia, mielőtt bármely glyph nevét a /CharSet sztringgel egybevetnék; a /CIDSet útvonalnak az streamet kicsomagoló és pozitív glyph számot visszaadó állapotra van szüksége, mielőtt egyetlen bitet tesztelne. Bármely kivétel az út során nincs-leletre omlik össze, nem hibára
Ez szándékos torzítás a hamis negatívok felé, és érdemes egyszerűen kimondani, nem eltemetni. Egy sérült CFF tábla, egy nem támogatott Type 1 változat vagy egy a glyph tartománynál rövidebb /CIDSet mind csendet termel diagnosztika helyett. A gondolatmenet az, hogy a PDF/UA auditokat olyan szerzőknek továbbítják, akik nem építették az eszközt, és egy hamis vád többe kerül, mint egy kihagyott: a szerző napot éget el azzal, hogy bebizonyítja, egy megfelelő fájl megfelelő, majd felhagy a teljes jelentésbe vetett bizalommal. A Matterhorn Protocol ugyanezt a különbséget teszi más alakban, amikor szétválasztja azokat az ellenőrzéseket, amelyeket gép eldönthet, azoktól, amelyeket embernek kell, és ennek Fonts ellenőrzőpontja (31) az, ahol ezek élnek. Ha a szigorúbb olvasatra van szüksége, futtassa a PDFlibPas-t gyors kapuként, és dedikált validátort másodvéleményként — ez a párosítás ugyanaz, amelyet a PDF/A és PDF/UA preflight átvonulás ír le
Az oldal /Contents-je lista, nem stream
A legdrágább hiba a tartalomstream-auditban az, hogy a /Contents-t egyetlen streamként kezelik. Az ISO 32000-1 §7.7.3.3 engedi, hogy egy oldal streamek tömbjét hordozza, amelyek összefűzése, a részek közt whitespace-szel, az oldal programja; a termelők tetszőleges ponton darabolnak, és egy BT ülhet egy tagban, a hozzá illő ET a következőben. A tartalomfeldolgozó állapotot tart — a marked-content beágyazási mélységet, az utolsó Tf által kiválasztott betűtípust, a szövegobjektum jelzőt — és a Process belépéskor visszaállítja azt az állapotot. Hívja tömbtagenként egyszer, és minden stream az első után aktuális betűtípus nélkül indul, így a tökéletesen jelölt szöveg jelöletlen, betűtípus nélküli zajként olvasódik. A PDFlibPas előbb fűz össze, majd egyszer dolgoz fel
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;
// Egyetlen Process hívás a teljes összefűzésen, sosem tagenként egy hívás
Scanner.Process(ContentObjectData(FDoc, PageDict.FindValueByKeyName('Contents')));
Melyek azok a Form XObjectek, amelyek ténylegesen struktúrálatlannak számítanak?
Csak azok, amelyeket egy oldal valóban meghív, marked-contenten kívüli hívási helyről, és amelyek saját tartalma szöveget mutat. A 10040-es diagnosztika az ISO 14289-1 §7.20-t lépteti érvénybe úgy, hogy objektumszámonként három független tényt rögzít — van szöveg, meg volt hívva, marked-contenten belül lett meghívva — és csak az első kettő metszetét jelenti a harmadik nélkül. A két rövidítés mindegyike úgy rossz, ahogy ki szállítaná: minden szöveget hordozó Form megjelölése a /Resources-ben megbüntet egy sablonkönyvtárat, amelyből senki nem merít, és minden meghívott Form megjelölése megbünteti azokat a vektoros logókat, amelyek nem hordoznak szöveget és nem is szorulnak jelölésre. A hívási hely objektumszám alapján oldódik fel, nem erőforrásnév alapján, mivel ugyanaz a Form szokásosan különböző neveken érhető el különböző oldalakon. A kísérő 10041-es diagnosztika ugyanazt az összefűzött programot járja be a §7.21.8 számára, minden szöveget mutató operandust a hatályban lévő betűtípus útján old fel, és megszámolja azokat a kódokat, amelyek a .notdef-re esnek, ami a szövegmegjelenítési módtól függetlenül tiltott — ideértve a szkennelt képek mögött használt láthatatlan módot is. Hogy a megmaradt Formeket hogyan kell becsomagolni, az szerkezetfa-kérdés, amelyet a tagged PDF szerkezet építéséről szóló cikk fed le
Betűtípusok, amelyeknek nincs FontDescriptor-je
A nem beágyazott betűtípus ennek az auditnak szabályos bemenet, nem hibaállapot, és a beágyazás-ellenőrzés alatti minden segédnek túlélnie kell. Amikor a PDFlibPas nem talál /FontDescriptor-t, vagy olyan leírót, amelynek nincs FontFile, FontFile2 vagy FontFile3, rögzíti a 10020-as diagnosztikát — vagy a 10022-est, ha a név a Standard 14 egyike, amelytől a §7.21.4 NOTE 5 nyilvánvalóan nem mentesít — majd folytatja a fájl többi részén. Ez a jelentés lényege: a szerző minden leletet egy menetben akar, nem egy leletet futásonként. Így a szélesség-, cmap-, CharSet- és CIDSet-segédnek átadott leíróhivatkozás Nil lehet, és mindegyik belépéskor erre tesztel, ahelyett hogy feltételezné, egy korábbi ellenőrzés megszakította az auditot. Ha a javítás a hiányzó beágyazása, ennek mechanikája a hiányzó betűtípusok beágyazása meglévő PDF-be jegyzetben van
Az audit futtatása
Egy hívás, olyan fájlon, amelyet nem feltétlenül Ön állított elő. A TPDFlib.CheckFileCompliance megfelelőségi tesztválasztót vesz át — 2 a PDF/UA-1-hez az ISO 14289-1:2014 alatt — és vagy nullát, vagy sztringlista-leírót ad vissza, amelynek bejegyzései egy numerikus kód, egy kettőspont és egy olvasható üzenet. Az itt tárgyalt betűtípus- és tartalomstream-leletek a 10020-tól 10041-ig foglalják el azt a tartományt, numerikusan elkülönítve a 00xxx PDF/A kódoktól, hogy a vegyes napló olvasható maradjon. Az Options-ban átadott 1 első leletnél rövidzárja a futást, ami build-kapuban az, amit akar, szerkesztőeszközben nem. A memóriában még nyitott dokumentumhoz a GetPDFUADiagnostics azonos vizsgálatot futtat, lemezre ugrás nélkül
var
Issues, Count, I: Integer;
begin
// ComplianceTest = 2 a PDF/UA-1-et választja; Options = 0 minden leletet jelent
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)); // pl. 10037 CIDFontType2 ...
end;
end;
Mindehhez nincs szükség külső validátor binárisra a gépen, és ez a különbség aközött, hogy egy ellenőrzés minden builden fut, és aközött, hogy akkor fut, amikor valaki eszébe jut. Az itt ismertetett megfelelőségi és diagnosztikai API-k a standard PDFlibPas Delphi PDF Library-ben szállítanak, amelynek termékoldala a PDF/UA-1 teljes diagnosztikakód-tábláját hordozza a PDF/A, PDF/X és PDF/E tesztkészletek mellett