veraPDF izveštaj koji kaže da se širina glifa ne slaže sa ugrađenim font programom ne govori vam skoro ništa o tome koji je glif, ni zašto. PDFlibPas odgovara na to pitanje razrešavanjem svakog koda znaka kroz ugrađeni cmap do indeksa glifa, normalizacijom metrike programa na 1000 jedinica po em, i poređenjem tamo
Zašto se širine glifova ne slažu?
Jer dva broja koja se porede žive u različitim koordinatnim sistemima, i ništa u PDF rečniku vam ne govori konverziju. Font rečnik upisuje /Widths u glyph prostoru, koji PDF fiksira na jednu hiljaditinu em (ISO 32000-1 §9.2.4). hmtx tabela unutar ugrađenog TrueType programa upisuje širine napredovanja u jedinicama dizajna fonta, a head tabela odlučuje koliko njih čini em: 2048 za većinu TrueType faca, 1000 za CFF-izvedene, povremeno nešto sasvim drugo. Uporedite sirove vrednosti i svaki 2048-upem font u vašem korpusu deluje pokvaren. To je zamka koju ISO 14289-1 §7.21.5 postavlja svakome ko pokuša reviziju širina čitanjem rečničkih polja
PDFlibPas normalizuje pri učitavanju. TPDFTrueTypeParser čuva Advance * 1000 div unitsPerEm u svom nizu širina, pa Parser.GetWidth(GID) već odgovara u istim hiljaditinama em koje PDF koristi, a GetRawWidth ostaje dostupan kada vam trebaju nazad jedinice dizajna. To još ostavlja težu polovinu: doći od koda znaka do indeksa glifa. Za jednostavni TrueType font ruta zavisi od Symbolic zastavice u FontDescriptor-u, bit 3 od /Flags
Parser := TPDFTrueTypeParser.Create;
try
Parser.LoadFromString(FontProgram);
if Symbolic then
begin
// Simboličke face se adresiraju direktno kroz programski cmap,
// sa (3,0) konvencijom visokog bajta kao rezervnim putem
GID := Parser.GetGlyphIndex(Code);
if GID = 0 then
GID := Parser.GetGlyphIndex($F000 + Code);
end
else
begin
// Nije simbolički: kod -> ime glifa preko kodiranja, ime -> Unicode
// preko Adobe Glyph List, Unicode -> GID preko programskog 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 detalja u tom odlomku nose težinu. Tolerancija je jedna jedinica, ne nula, jer je normalizacija deljenje celih brojeva i legitimno proizveden fajl može odstupiti za jedinicu; to je upravo formulacija „unutar jedne hiljaditine em" koju dijagnostika 10036 prijavljuje. A GID < Parser.GlyphCount čuvar nije dekoracija. GetWidth je napisan da bude popustljiv prema pozivaocima renderovanja, steže indeks van opsega na poslednji unos u hmtx i vraća 750 kada tabela nedostaje. Popustljivost je prava za renderovanje i pogrešna za reviziju, pa revizija odbija indeks pre nego što traži širinu, umesto da veruje stezanju
CIDFontType2 dodaje još jedno posredovanje
PDFlibPas prelazi kompozitne fontove na isti način, sa /CIDToGIDMap umetnutim između CID-a i glifa. Širine stižu u /W nizu, koji ISO 32000-1 §9.7.4.3 daje u dva oblika koja se slobodno smenjuju u jednom nizu: početni CID praćen nizom uzastopnih širina, ili prvi CID, poslednji CID i jedna širina primenjena preko celog niza. Revizija parsira oba, pa svaki dobijeni par predaje istom poređenju, i ukupno prijavljuje pod dijagnostikom 10037. Korak mapiranja je gde se kompozitni fontovi razlikuju, i zato dijagnostika 10021 za nedostajuću mapu važi pre nego što pročitate bilo koju širinu — odsutan ili deformisan /CIDToGIDMap ne samo da krši §7.21.3.2, on čini pitanje širine neodgovorivim
// /CIDToGIDMap je ime /Identity ili tok big-endian 16-bitnih
// indeksa glifova, jedan po CID-u (ISO 32000-1 sekcija 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 stringovi su indeksirani od 1
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;
Šta revizor da radi kada se font program ne može dekodirati?
Ne kaže ništa. Provere kompletnosti /CharSet i /CIDSet koje ISO 14289-1 §7.21.4.2 zahteva — dijagnostika 10038 i 10039 — su mesto gde se previše revan validator pretvara u odgovornost, jer je izveštaj „vaš CharSet je nepotpun" za osobu koja ga čita nerazlikoviv od „naš Type 1 dekoder je odustao". PDFlibPas zato prijavljuje nedostajući unos samo kada tri stvari uspeju: font program se dekodira, mapiranje koda na glif se razreši, i sam skup se dekodira. TPDFType1Decoder.LoadPFBFromString mora vratiti True i dati broj charstring-ova pre nego što bilo koje ime glifa bude provereno protiv /CharSet stringa; /CIDSet putanja traži da tok otpakuje i da broj glifova izađe pozitivan pre nego što se jedan bit testira. Bilo koji izuzetak na putu kolapsira u „nema nalaza", ne u defekt
To je namerna naklonost lažno negativnim nalazima, i vredi je izreći jasno umesto da je sakrijete. Oštećena CFF tabela, nepodržana varijanta Type 1 ili /CIDSet kraći od opsega glifova proizvode tišinu umesto dijagnostike. Obrazloženje je da se PDF/UA revizije prosleđuju autorima koji nisu gradili alat, a lažna optužba košta više od promašenog nalaza: autor sagori dan dok dokazuje da usklađen fajl jeste usklađen, i prestaje da veruje celom izveštaju. Matterhorn Protocol pravi istu razliku u drugom obliku kada razdvoji provere koje mašina može da odluči od onih koje mora čovek, a njegova Fonts kontrolna tačka (31) je mesto gde one žive. Ako vam treba strože čitanje, koristite PDFlibPas kao brzu kapiju i namenski validator kao drugo mišljenje — taj par opisan je u vodiču kroz PDF/A i PDF/UA preflight
Page /Contents je lista, a ne tok
Najskuplja greška u reviziji content tokova je tretiranje /Contents kao jednog toka. ISO 32000-1 §7.7.3.3 dozvoljava da stranica drži niz tokova čija konkatencacija, sa belinom između delova, čini program stranice; proizvođači seku na proizvoljnim mestima, pa BT može biti u jednom članu dok je njegov ET u sledećem. Procesor sadržaja održava stanje — dubinu ugnežđavanja marked-content, font izabran poslednjim Tf, zastavicu tekst objekta — a Process to stanje resetuje pri ulasku. Pozovite ga jednom po članu niza i svaki tok posle prvog kreće bez tekućeg fonta, pa tekst koji je bio besprekorno označen deluje kao neoznačena buka bez fonta. PDFlibPas prvo spaja, a zatim obrađuje jednom
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;
// Jedan poziv Process nad celom konkatencacijom, nikada jedan poziv po članu
Scanner.Process(ContentObjectData(FDoc, PageDict.FindValueByKeyName('Contents')));
Koji Form XObjects zaista spadaju u nestrukturisane?
Samo oni koje stranica zaista poziva, sa mesta poziva van marked content, i čiji sopstveni sadržaj prikazuje tekst. Dijagnostika 10040 sprovodi ISO 14289-1 §7.20 tako što po broju objekta beleži tri nezavisne činjenice — ima tekst, bio je pozvan, bio je pozvan unutar marked content — i prijavljuje samo presek prva dva bez treće. Svaka od te dve prečice je pogrešna na način koji biste i otpremili: označavanje svakog Form-a sa tekstom u /Resources kažnjava biblioteku šablona koju niko ne crta, a označavanje svakog pozvanog Form-a kažnjava vektorske logotipe koji ne nose tekst i ne treba im označavanje. Mesto poziva razrešava se po broju objekta, a ne po imenu resursa, jer se isti Form rutinski doseže kroz različita imena na različitim stranicama. Prateća dijagnostika 10041 prelazi isti spojeni program za §7.21.8, razrešavajući svaki operand koji prikazuje tekst kroz font koji je u opsegu i brojeći kodove koji padnu na .notdef, što je zabranjeno bez obzira na režim renderovanja teksta — uključujući i nevidljivi režim iza skeniranih slika. Omotavanje preživelih Form-ova pitanje je strukturnog stabla, i pokriveno je u članku o izgradnji tagged PDF strukture
Fontovi bez ikakvog FontDescriptor-a
Neugrađen font je legitimni ulaz za ovu reviziju, a ne stanje greške, i svaki pomoćnik ispod provere ugrađivanja mora to da preživi. Kada PDFlibPas ne nađe /FontDescriptor, ili nađe deskriptor bez FontFile, FontFile2 ili FontFile3, beleži dijagnostiku 10020 — ili 10022 kada je ime jedno od Standard 14, koje §7.21.4 NOTE 5 uporno odbija da izuzme — i zatim nastavlja kroz ostatak fajla. U tome je cela poenta izveštaja: autor želi svaki nalaz u jednom prolazu, a ne jedan nalaz po pokretanju. Zato referenca deskriptora koja se predaje pomoćnicima za širine, cmap, CharSet i CIDSet može biti Nil, i svaki od njih to proverava pri ulasku umesto da pretpostavi da je ranija provera prekinula reviziju. Ako je rešenje da se ugradi ono što nedostaje, mehanika je opisana u belešci o ugrađivanju nedostajućih fontova u postojeći PDF
Pokretanje revizije
Jedan poziv, na fajlu koji niste nužno ni vi proizveli. TPDFlib.CheckFileCompliance prima selektor compliance testa — 2 za PDF/UA-1 pod ISO 14289-1:2014 — i vraća nulu ili handle string liste čiji su unosi numerički kod, dvotačka i čitljiva poruka. Nalazi o fontovima i content tokovima razmatrani ovde zauzimaju 10020 do 10041 u tom opsegu, numerički odvojeni od 00xxx PDF/A kodova da mešani log ostane čitljiv. Vrednost 1 u Options prekida izvršavanje pri prvom nalazu, što je ono što želite u build kapiji, a ne u alatu za autorstvo. Za dokument koji je još otvoren u memoriji, GetPDFUADiagnostics izvodi ekvivalentan pregled bez odlaska na disk
var
Issues, Count, I: Integer;
begin
// ComplianceTest = 2 bira PDF/UA-1; Options = 0 prijavljuje svaki nalaz
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)); // npr. 10037 CIDFontType2 ...
end;
end;
Ništa od ovoga ne zahteva eksternu binarnu datoteku validatora na mašini, a to je razlika između provere koja se pokreće na svakom buildu i provere koja se pokrene kada se neko seti. Compliance i dijagnostički API opisani ovde stižu uz standardnu PDFlibPas Delphi PDF Library, čija stranica proizvoda nosi kompletnu tabelu dijagnostičkih kodova za PDF/UA-1 uz PDF/A, PDF/X i PDF/E test pakete