Poročilo veraPDF, ki pravi, da se širina glifa ne sklada z vgrajenim programom pisave, ne pove skoraj nič o tem, kateri glif je, niti zakaj. PDFlibPas to vprašanje odgovori tako, da vsako kodo znaka razreši prek vgrajenega cmap do indeksa glifa, metriko programa normalizira na 1000 enot na em in primerja tam
Zakaj se širine glifov ne ujemajo?
Ker živita primerjani številki v različnih koordinatnih sistemih, in nič v slovarju PDF vam ne pove pretvorbe. Slovar pisave zapiše /Widths v prostoru glifov, ki ga PDF fiksira na tisočinko em (ISO 32000-1 §9.2.4). Tabela hmtx znotraj vgrajenega programa TrueType zapiše zasuke v projektivnih enotah pisave, tabela head pa odloča, koliko teh naredi em: 2048 za večino oblik TrueType, 1000 za tiste iz CFF, občasno pa kaj popolnoma drugega. Primerjajte surovi vrednosti in vsaka pisava z 2048 upem v vašem korpusu izgleda pokvarjena. To je past, ki jo ISO 14289-1 §7.21.5 nastavi vsakomur, ki poskuša pregledati širine z branjem polj slovarja
PDFlibPas normalizira ob nalaganju. TPDFTrueTypeParser shrani Advance * 1000 div unitsPerEm v svojo tabelo širin, zato Parser.GetWidth(GID) že odgovarja v istih tisočinkah em, ki jih uporablja PDF, GetRawWidth pa ostane na voljo, ko potrebujete projektivne enote nazaj. To še pusti težjo polovico: priti od kode znaka do indeksa glifa. Za preprosto pisavo TrueType pot je odvisna od zastavice Symbolic v FontDescriptor, bita 3 od /Flags
Parser := TPDFTrueTypeParser.Create;
try
Parser.LoadFromString(FontProgram);
if Symbolic then
begin
// Simbolične oblike se naslavljajo naravnost skozi cmap programa,
// s konvencijo (3,0) visokega bajta kot rezervo
GID := Parser.GetGlyphIndex(Code);
if GID = 0 then
GID := Parser.GetGlyphIndex($F000 + Code);
end
else
begin
// Nesimbolično: koda -> ime glifa prek kodiranja, ime -> Unicode
// prek Adobe Glyph List, Unicode -> GID prek cmap programa
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;
Dve podrobnosti v tem odlomku nosita težo. Toleranca je ena enota, ne nič, ker je normalizacija celoštevilčno deljenje in zakonito ustvarjena datoteka lahko zaide enoto zgrešena; to je natanko formulacija »znotraj ene tisočinke em«, ki jo poroča diagnostika 10036. Varnostni pogoj GID < Parser.GlyphCount pa ni okras. GetWidth je napisana odpuščajoče za klicatelje izrisa: indeks izven obsega prime na zadnji vnos v hmtx in se pri odsotni tabeli vrne na 750. Odpuščanje je prav za izris in narobe za pregled, zato pregled zavrne indeks, preden vpraša po širini, namesto da bi zaupal prijem
CIDFontType2 doda še eno posrednost
PDFlibPas prehodi sestavljene pisave na enak način, z /CIDToGIDMap vstavljenim med CID in glif. Širine prispejo v tabeli /W, ki ji ISO 32000-1 §9.7.4.3 daje dve obliki, ki se prosto izmenjujeta v eni tabeli: začetni CID, ki mu sledi tabela zaporednih širin, ali prvi CID, zadnji CID in ena sama širina, ki velja čez razpon. Pregled razčleni oboje, vsak nastali par pa da isti primerjavi in skupno poroča pod diagnostiko 10037. Korak preslikave je mesto, kjer se sestavljene pisave razlikujejo, in zato diagnostika 10021 o manjkajoči preslikavi šteje, preden berete katero koli širino — odsoten ali deformiran /CIDToGIDMap ne krši le §7.21.3.2, ampak vprašanje po širinah naredi neodgovorljivo
// /CIDToGIDMap je ime /Identity ali tok velikokončnih 16-bitnih
// indeksov glifov, enega na CID (ISO 32000-1, razdelek 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; // nizi v Pascalu so 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;
Kaj naj preverjevalnik naredi, ko se program pisave ne dekodira?
Ne recite nič. Preverjanji popolnosti /CharSet in /CIDSet, ki ju zahteva ISO 14289-1 §7.21.4.2 — diagnostiki 10038 in 10039 — sta mesto, kjer preveč vnemevalni validator postane odgovornost, ker je poročilo »vaš CharSet je nepopoln« za bralca nerazločljivo od »naš dekodirnik Type 1 je obupal«. PDFlibPas zato poroča o manjkajočem vnosu le, ko uspejo vse tri stvari: program pisave se dekodira, preslikava od kode do glifa se razreši in sam nabor se dekodira. TPDFType1Decoder.LoadPFBFromString mora vrniti True in dati število charstringov, preden se katero koli ime glifa preveri proti nizu /CharSet; pot /CIDSet potrebuje, da se tok razširi in da število glifov pride pozitivno nazaj, preden se preizkusi en sam bit. Katera koli izjema na poti se zruši v »brez ugotovitve«, ne v napako
To je namerna pristranskost k lažno negativnim in vredno je povedati odkrito, ne pokopati. Pokvarjena tabela CFF, nepodprta različica Type 1 ali /CIDSet, krajši od obsega glifov, vsi dajo tišino namesto diagnostike. Razlog je, da se pregledi PDF/UA posredujejo avtorjem, ki orodja niso zgradili, in lažna obtožba stane več kot spregledana: avtor izgori dan z dokazovanjem, da je skladna datoteka skladna, in neha zaupati celotnemu poročilu. Protokol Matterhorn dela isto razliko v drugi obliki, ko loči preizkuse, ki jih lahko odloči stroj, od preizkusov, ki jih mora človek, njegova kontrolna točka Fonts (31) pa je mesto, kjer ti živijo. Če potrebujete strožje branje, pognajte PDFlibPas kot hitra vrata in namenski validator kot drugo mnenje — ta par je isti, kot ga opisuje vodnik po predletu PDF/A in PDF/UA
Stran /Contents je seznam, ne tok
Najdražja napaka pri pregledovanju tokov vsebine je obravnavati /Contents kot en tok. ISO 32000-1 §7.7.3.3 pusti strani držati tabelo tokov, katere stičenje, s presledkom med deli, je program strani; proizvajalci režejo na poljubnih mestih, BT pa lahko sedi v enem članu z njegovim ujemajočim ET v naslednjem. Procesor vsebine drži stanje — globino gnezdenja označene vsebine, pisavo, izbrano od zadnjega Tf, zastavico besedilnega objekta — Process pa to stanje ponastavi ob vstopu. Klicite enkrat na člana tabele in vsak tok po prvem začne brez trenutne pisave, tako da besedilo, ki je bilo odlično označeno, bere kot neoznačen, nepisaven šum. PDFlibPas najprej stiči in procesira enkrat
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;
// En klic Process čez celo stičenje, nikoli en klic na člana
Scanner.Process(ContentObjectData(FDoc, PageDict.FindValueByKeyName('Contents')));
Kateri Form XObjects resnično štejejo kot nestrukturirani?
Le tiste, ki jih stran resnično pokliče, z mesta klica izven označene vsebine, in katerih lastna vsebina kaže besedilo. Diagnostika 10040 uveljavlja ISO 14289-1 §7.20 z zabeležbo treh neodvisnih dejstev na številko objekta — ima besedilo, je bil poklican, je bil poklican znotraj označene vsebine — in poroča le o preseku prvih dveh minus tretjega. Vsaka od obeh bližnjic je napačna na način, ki bi ga izdali: označevanje vsake Form z besedilom v /Resources kaznuje knjižnico predlog, iz katere nihče ne izrisuje, označevanje vsake poklicane Form pa kaznuje vektorske logotipe, ki ne nosijo besedila in ne potrebujejo označevanja. Mesto klica se razreši po številki objekta in ne po imenu vira, saj se ista Form rutinsko doseže pod različnimi imeni na različnih straneh. Spremljevalna diagnostika 10041 prehodi isti stičen program za §7.21.8, razreši vsak operand, ki kaže besedilo, skozi pisavo v obsegu, in šteje kode, ki pristanejo na .notdef, kar je prepovedano ne glede na način izrisa besedila — vključno z nevidnim načinom, uporabljenim za skeniranimi slikami. Kako naj se preživele Form ovijejo, je vprašanje drevesa strukture, pokrito v članku o gradnji označene strukture PDF
Pisave brez FontDescriptor sploh
Nevgrajena pisava je zakonit vhod v ta pregled, ne stanje napake, in vsak pomočnik pod preizkusom vgrajevanja ga mora preživeti. Ko PDFlibPas ne najde /FontDescriptor ali deskriptorja brez FontFile, FontFile2 ali FontFile3, zabeleži diagnostiko 10020 — ali 10022, kadar je ime eno od Standard 14, katerih §7.21.4 NOTE 5 izrazito ne oprosti — in nato nadaljuje čez preostanek datoteke. To je celotna poanta poročila: avtor želi vsako ugotovitev v enem prehodu, ne ene ugotovitve na zagon. Zato je lahko sklic na deskriptor, podan pomočnikom za širine, cmap, CharSet in CIDSet, Nil, vsak od njih pa ga preizkusi ob vstopu, namesto da bi privzel, da je zgodnejši preizkus prekinil pregled. Če je popravek vgrajevanje tistega, kar manjka, je mehanika v zapisu o vgrajevanju manjkajočih pisav v obstoječi PDF
Poganjanje pregleda
En klic, na datoteki, ki je niste nujno vi izdelali. TPDFlib.CheckFileCompliance vzame izbirnik preizkusa skladnosti — 2 za PDF/UA-1 pod ISO 14289-1:2014 — in vrne nič ali ročaj seznama nizov, katerega vnosi so številčna koda, dvopičje in berljivo sporočilo. Ugotovitve o pisavah in tokovih vsebine, obravnavane tukaj, zasedajo 10020 do 10041 v tem obsegu, številčno ločeno od kod 00xxx za PDF/A, da mešani dnevnik ostane berljiv. Podajanje 1 v Options skrajša pri prvi ugotovitvi, kar želite v gradbenih vratih in ne v avtorskem orodju. Za dokument, še odprt v pomnilniku, GetPDFUADiagnostics požene enakovreden pregled brez krožnega pota skozi disk
var
Issues, Count, I: Integer;
begin
// ComplianceTest = 2 izbere PDF/UA-1; Options = 0 poroča vsako ugotovitev
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č od tega ne potrebuje zunanje binarne validatorje na stroju, kar je razlika med preizkusom, ki teče na vsaki izgradnji, in preizkusom, ki teče, kadar se kdo spomni. API-ji skladnosti in diagnostike, opisani tukaj, pridejo v standardni PDFlibPas Delphi PDF Library, katerega produkcijska stran nosi celotno tabelo diagnostičnih kod za PDF/UA-1 ob testnih zbirkah PDF/A, PDF/X in PDF/E