veraPDF ataskaita, sakanti, kad glifo plotis nesutampa su įterpta šrifto programa, beveik nieko nepasako apie tai, kuris glifas, ar kodėl. PDFlibPas atsako į tą klausimą išspręsdamas kiekvieną simbolio kodą per įterptą cmap į glifo indeksą, suvienodindamas programos metriką į 1000 vienetų em ir lygindamas ten
Kodėl glifo pločiai nesutampa?
Nes du lyginami skaičiai gyvena skirtingose koordinačių sistemose, ir niekas PDF žodyne jums nepasako konversijos. Šrifto žodynas rašo /Widths glifo erdvėje, kurią PDF fiksuoja kaip vieną em tūkstantąją dalį (ISO 32000-1 §9.2.4). hmtx lentelė įterptos TrueType programos viduje rašo žingsnius šrifto projektavimo vienetais, o head lentelė nusprendžia, kiek jų sudaro em: 2048 daugumai TrueType šriftų, 1000 CFF kilmės, retkarčiais kažkas visai kito. Palyginkite žaliąsias reikšmes, ir kiekvienas 2048-upem šriftas jūsų korpuse atrodos sugedęs. Tai spąstai, kuriuos ISO 14289-1 §7.21.5 iškelia kiekvienam, norinčiam audituoti pločius skaitant žodyno laukus
PDFlibPas suvienodina įkėlimo metu. TPDFTrueTypeParser saugo Advance * 1000 div unitsPerEm savo pločių masyve, todėl Parser.GetWidth(GID) jau atsako tais pačiais em tūkstantosiomis dalimis, kurias vartoja PDF, o GetRawWidth lieka prieinamas, kai reikia projektavimo vienetų atgal. Tai vis tiek palieka sunkesniąją pusę: patekti iš simbolio kodo į glifo indeksą. Paprastam TrueType šriftui kelias priklauso nuo Symbolic ženkelio FontDescriptor, tai 3 bitas /Flags
Parser := TPDFTrueTypeParser.Create;
try
Parser.LoadFromString(FontProgram);
if Symbolic then
begin
// Simboliniai šriftai adresuojami tiesiogiai per programos cmap,
// o (3,0) aukšto baito konvencija yra atsarginis variantas
GID := Parser.GetGlyphIndex(Code);
if GID = 0 then
GID := Parser.GetGlyphIndex($F000 + Code);
end
else
begin
// Nesimboliniai: kodas -> glifo pavadinimas per koduotę, pavadinimas -> Unicode
// per Adobe Glyph List, Unicode -> GID per programos 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;
Dvi detalės tame fragmente neša svorį. Leistinoji paklaida yra vienas vienetas, o ne nulis, nes suvienodinimas yra sveikųjų skaičių dalyba, ir teisėtai pagamintas failas gali nuklysti vienetu; tai būtent ta „per vieną em tūkstantąją dalį“ formuluotė, kurią praneša diagnostika 10036. Ir GID < Parser.GlyphCount sargyba nėra puošmena. GetWidth parašyta būti atlaisinga atvaizdavimo kvietėjams: užspaudžia iš ribų išeinantį indeksą į paskutinį hmtx įrašą ir grąžina 750, kai lentelės nėra. Atlaisumas tinka atvaizdavimui ir netinka auditui, todėl auditas atmeta indeksą prieš prašydamas pločio, vietoj to, kad pasitikėtų užspaudimu
CIDFontType2 prideda dar vieną tarpinį žingsnį
PDFlibPas eina per kompozitinius šriftus taip pat, su /CIDToGIDMap, įterptu tarp CID ir glifo. Pločiai atkeliauja /W masyve, kurį ISO 32000-1 §9.7.4.3 leidžia dviejų formų, laisvai besikeičiančių viename masyve: pradinis CID, po kurio eina ištisinių pločių masyvas, arba pirmasis CID, paskutinis CID ir vienas pločis, taikomas visai atkarpai. Auditas analizuoja abi, tada kiekvieną gautą porą pateikia tai pačiai palyginimui, ir praneša sumą pagal diagnostiką 10037. Atvaizdavimo žingsnis yra tai, kur kompozitiniai šriftai skiriasi, ir todėl dingusio atvaizdavimo diagnostika 10021 svarbi, dar neskaitydami jokio pločio — absentuojantis arba iškraipytas /CIDToGIDMap ne tik pažeidžia §7.21.3.2, jis padaro pločio klausimą neatsakomu
// /CIDToGIDMap yra pavadinimas /Identity arba big-endian 16 bitų
// glifo indeksų srautas, po vieną CID (ISO 32000-1 skyrius 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 eilutės numeruojamos nuo 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;
Ką auditorius turi daryti, kai šrifto programa nesidekoduoja?
Nesakykite nieko. /CharSet ir /CIDSet išsamumo patikros, kurias reikalauja ISO 14289-1 §7.21.4.2 — diagnostika 10038 ir 10039 — yra vieta, kur per uolingas tikrintojas virsta našta, nes pranešimas „jūsų CharSet nepilnas“ skaitančiajam neatskiriamas nuo „mūsų Type 1 dekoderis pasidavė“. PDFlibPas todėl praneša dingusį įrašą tik tada, kai trys dalykai visi pavyksta: šrifto programa išsidekoduoja, kodas į glifą atvaizdavimas išsprendžiamas, ir pats rinkinys išsidekoduoja. TPDFType1Decoder.LoadPFBFromString turi grąžinti True ir duoti charstring skaičių, kol kas nors glifo pavadinimas tikrinamas prieš /CharSet eilutę; /CIDSet keliui reikia, kad srautas išsipūstų, o glifo skaičius grįžtų teigiamas, kol bent vienas bitas išbandomas. Bet kokia išimtis kelyje sugriūva į „nerasta“, o ne į defektą
Tai sąmoningas polinkis į klaidingus neigimus, ir verta tai pasakyti atvirai, o ne užkasti. Sugadinta CFF lentelė, nepalaikoma Type 1 atmaina arba /CIDSet, trumpesnis už glifo intervalą, visi duoda tylą vietoje diagnostikos. Samprotavimas toks: PDF/UA auditai persiunčiami autoriams, kurie nestatė įrankių, ir klaidingas kaltinimas kainuoja daugiau nei praleistas: autorius sudegina dieną, įrodinėdamas, kad atitinkantis failas atitinka, ir nustoja pasitikėti visa ataskaita. Matterhorn Protocol tą patį skirtumą daro kita forma, kai atskiria patikras, kurias gali nuspręsti mašina, nuo patikrų, kurias privalo žmogus, ir jo Fonts kontrolės taškas (31) yra vieta, kur tai gyvena. Jei reikia griežtesnio skaitymo, naudokite PDFlibPas kaip greitus vartus, o specializuotą tikrintoją kaip antrą nuomonę — ta pora aprašyta PDF/A ir PDF/UA preflight apžvalgoje
Puslapio /Contents yra sąrašas, o ne srautas
Brangiausia klaida turinio srautų audite — laikyti /Contents vienu srautu. ISO 32000-1 §7.7.3.3 leidžia puslapiui turėti srautų masyvą, kurio sujungimas, su tarpais tarp dalių, yra puslapio programa; gamintojai skelia savavališkose vietose, ir BT gali sėdėti viename naryje, o jo atitinkamas ET — kitame. Turinio doroklė laiko būseną — pažymėto turinio įdėtinumo gylį, paskutinio Tf pasirinktą šriftą, teksto objekto ženklą — ir Process atstato tą būseną įėjime. Iškvieskite jį po kartą kiekvienam masyvo nariui, ir kiekvienas srautas po pirmojo prasideda be esamo šrifto, todėl tobulai pažymėtas tekstas skaitomas kaip nepažymėtas, bešriftis triukšmas. PDFlibPas pirmiausia sujungia ir apdoroja kartą
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;
// Vienas Process kvietimas visam sujungimui, niekada po kvietimą nariui
Scanner.Process(ContentObjectData(FDoc, PageDict.FindValueByKeyName('Contents')));
Kurie Form XObjects iš tikrųjų skaitomi kaip nestruktūruoti?
Tik tie, kuriuos puslapis tikrai iškviečia, iš kvietimo vietos už pažymėto turinio, kurių pats turinys rodo tekstą. Diagnostika 10040 įgyvendina ISO 14289-1 §7.20, užrašydama tris nepriklausomus faktus objekto numeriui — turi tekstą, buvo iškviestas, buvo iškviestas pažymėto turinio viduje — ir praneša tik pirmų dviejų sankirtą be trečiojo. Kiekvienas iš dviejų trumpųjų kelių klysta taip, kad tai išsiųstumėte: žymėti kiekvieną teksto nešantį Form /Resources reiškia bausti šablonų biblioteką, iš kurios niekas nesibrėžia, o žymėti kiekvieną iškviestą Form reiškia bausti vektorinius logotipus, nesinešančius teksto ir nereikalaujančius žymėjimo. Kvietimo vieta išsprendžiama pagal objekto numerį, o ne pagal ištekliaus vardą, nes tas pats Form įprastai pasiekiamas skirtingais vardais skirtinguose puslapiuose. Porinė diagnostika 10041 eina tą patį sujungtą programą dėl §7.21.8, išspręsdama kiekvieną tekstą rodantį operandą per veikimo srities šriftą ir skaičiuodama kodus, nukrentančius į .notdef, kas draudžiama nepriklausomai nuo teksto atvaizdavimo režimo — įskaitant nematomą režimą, naudotą už skenuotų vaizdų. Kaip apgyventi išgyvenusius Form, yra struktūros medžio klausimas, aptartas straipsnyje apie žymėtos PDF struktūros statymą
Šriftai be jokio FontDescriptor
Neįterptas šriftas yra teisėta šio audito įvestis, o ne klaidos būsena, ir kiekvienas pagalbininkas žemiau įterpimo patikros turi jį išgyventi. Kai PDFlibPas neranda /FontDescriptor, arba aprašytojo be FontFile, FontFile2, FontFile3, jis užrašo diagnostiką 10020 — arba 10022, kai vardas yra vienas iš Standard 14, kurių §7.21.4 NOTE 5 ryškiai atsisako atleisti — ir tada tęsia per likusį failą. Visa to ataskaitos esmė: autorius nori kiekvienos išvados per vieną praėjimą, o ne po vieną išvadą per paleidimą. Todėl aprašytojo nuoroda, perduodama pločio, cmap, CharSet ir CIDSet pagalbininkams, gali būti Nil, ir kiekvienas iš jų tai patikrina įėjime, vietoj to, kad manytų, ankstesnė patikra nutraukė auditą. Jei pataisymas yra įterpti tai, ko trūksta, mechanika aprašyta pastaboje apie dingusių šriftų įterpimą į esamą PDF
Audito paleidimas
Vienas kvietimas, failui, kurio nebūtinai patys pagaminote. TPDFlib.CheckFileCompliance priima atitikties testo parinkiklį — 2 PDF/UA-1 pagal ISO 14289-1:2014 — ir grąžina arba nulį, arba eilučių sąrašo rankenėlę, kurios įrašai yra skaitinis kodas, dvitaškis ir skaitoma žinutė. Šrifto ir turinio srautų išvados, aptartos čia, užima 10020 iki 10041 tame intervale, laikomos skaitiniu atskirai nuo 00xxx PDF/A kodų, kad mišrus žurnalas liktų skaitomas. 1 perdavimas Options nutraukia ties pirmąja išvada — tai, ko norite statymo vartuose, o ne autorinio įrankio viduje. Dokumentui, vis dar atidarytam atmintyje, GetPDFUADiagnostics vykdo lygiavertę apžiūrą be apskritimo pro diską
var
Issues, Count, I: Integer;
begin
// ComplianceTest = 2 pasirenka PDF/UA-1; Options = 0 praneša kiekvieną išvadą
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)); // pvz. 10037 CIDFontType2 ...
end;
end;
Jokia tai dalis nereikalauja išorinio tikrintojo dvejetainio failo mašinoje — skirtumas tarp patikros, einančios prie kiekvieno statymo, ir patikros, einančios, kai kas nors prisimena. Atitikties ir diagnostikos API, aprašytos čia, tiekiamos standartiniame PDFlibPas Delphi PDF Library, kurio produkto puslapis neša visą PDF/UA-1 diagnostikos kodų lentelę šalia PDF/A, PDF/X ir PDF/E testų rinkinių