veraPDF-raportti, joka sanoo merkin leveyden eroavan upotetusta fonttiohjelmasta, ei kerro juuri mitään siitä, mikä merkki on kyseessä tai miksi. PDFlibPas vastaa kysymykseen ratkaisemalla jokaisen merkkikoodin upotetun cmapin kautta merkin indeksiin, normalisoimalla ohjelman metriikan 1000 yksikköön per em ja vertaamalla siellä
Miksi merkkien leveydet eroavat?
Koska verrattavat luvut elävät eri koordinaatistoissa, eikä PDF-sanakirja kerro muunnosta. Fonttisanakirja kirjoittaa /Widthsin merkkitilaan, jonka PDF kiinnittää tuhannesosaan emistä (ISO 32000-1 §9.2.4). Upotetun TrueType-ohjelman sisäinen hmtx-taulukko kirjoittaa edunsäät fontin suunnitteluyksiköissä, ja head-taulukko päättää, montako niistä muodostaa emin: 2048 useimmille TrueType-leikkeille, 1000 CFF-peräisille, satunnaisesti jotain aivan muuta. Vertaa raaka-arvoja, ja jokainen korpuksesi 2048-upem-fontti näyttää rikkineen. Se on ansa, jonka ISO 14289-1 §7.21.5 asettaa jokaiselle, joka yrittää auditoida leveyksiä lukemalla sanakirjakenttiä
PDFlibPas normalisoi latauksen yhteydessä. TPDFTrueTypeParser tallentaa Advance * 1000 div unitsPerEmin leveysataulukkoonsa, joten Parser.GetWidth(GID) vastaa jo samoissa tuhannesosissa emistä, joita PDF käyttää, ja GetRawWidth pysyy käytettävissä, kun tarvitset suunnitteluyksiköt takaisin. Se jättää yhä jäljelle vaikeamman puolen: pääsyn merkkikoodista merkki-indeksiin. Yksinkertaiselle TrueType-fontille reitti riippuu FontDescriptorin Symbolic-lipusta, /Flagsin bitistä 3
Parser := TPDFTrueTypeParser.Create;
try
Parser.LoadFromString(FontProgram);
if Symbolic then
begin
// Symbolisia leikkeitä osoitetaan suoraan ohjelman cmapin kautta,
// ja (3,0) high-byte-käytäntö toimii varajäsenenä
GID := Parser.GetGlyphIndex(Code);
if GID = 0 then
GID := Parser.GetGlyphIndex($F000 + Code);
end
else
begin
// Ei-symbolinen: koodi -> merkin nimi koodauksen kautta, nimi -> Unicode
// Adobe Glyph Listin kautta, Unicode -> GID ohjelman cmapin kautta
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;
Katkelmassa kantavat painoa kaksi yksityiskohtaa. Toleranssi on yksi yksikkö, ei nolla, koska normalisointi on kokonaislukujako ja kelvollisesti tuotettu tiedosto voi hajahtua yksikön verran; juuri sitä tarkoittaa diagnoosin 10036 muotoilu "yhtä tuhannesosaa emistä". Ja GID < Parser.GlyphCount-tarkistus ei ole pelkkää koristetta. GetWidth on kirjoitettu sietäväksi piirtäjäkutsuja varten: se rajaa alueen ulkopuolisen indeksin hmtx-taulukon viimeiseen alkioon ja palauttaa 750, kun taulukko puuttuu. Sietävyys sopii piirtämiseen ja on väärin auditoinnissa, joten auditointi hylkää indeksin ennen kuin kysyy leveyttä, sen sijaan että luottaisi rajaukseen
CIDFontType2 lisää yhden epäsuoran vaiheen
PDFlibPas kulkee yhdistelmäfontit läpi samalla tavalla, kun /CIDToGIDMap on lisätty CID:n ja merkin väliin. Leveydet saapuvat /W-taulukossa, jolla ISO 32000-1 §9.7.4.3 määrittää kaksi muotoa, jotka vuorottelevat vapaasti samassa taulukossa: aloitus-CID, jota seuraa peräkkäisten leveyksien taulukko, tai ensimmäinen CID, viimeinen CID ja yksi leveys, jota sovelletaan koko jaksoon. Auditointi jäsentää molemmat, antaa jokaisen syntyneen parin samalle vertailulle ja raportoi summan diagnoosissa 10037. Kartoitusvaihe on kohta, jossa yhdistelmäfontit eroavat toisistaan, ja siksi puuttuvan kartan diagnoosilla 10021 on väliä ennen kuin luet yhtään leveyttä — puuttuva tai epäkelpo /CIDToGIDMap ei pelkästään loukkaa kohtaa §7.21.3.2 vaan tekee leveyskysymyksestä vastaamattoman
// /CIDToGIDMap on joko nimi /Identity tai big-endian-koodattu 16-bittisten
// merkin indeksien tietovirta, yksi per CID (ISO 32000-1 kohta 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-merkkijonot alkavat indeksistä 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;
Mitä auditorin pitää tehdä, kun fonttiohjelma ei purkaudu?
Älä sano mitään. ISO 14289-1 §7.21.4.2 vaatimat /CharSet- ja /CIDSet-kattavuustarkistukset — diagnoosit 10038 ja 10039 — ovat paikka, jossa liian innokas validoija muuttuu ongelmaksi, koska lukijalle raportti "CharSet on puutteellinen" on erottamaton lauseesta "meidän Type 1 -purkumme luovutti". Siksi PDFlibPas raportoi puuttuvan tietueen vain, kun kolme asiaa onnistuu: fonttiohjelma purkautuu, koodista merkkiin -kartoitus ratkeaa ja joukko itsessään purkautuu. TPDFType1Decoder.LoadPFBFromString on palautettava True ja tuotettava charstringien lukumäärä ennen kuin yhtään merkin nimeä verrataan /CharSet-merkkijonoon; /CIDSet-polku vaatii, että tietovirta purkautuu ja merkkien lukumäärä palaa positiivisena ennen yhdenkään bitin testaamista. Mikä tahansa matkan varrella osuva poikkeus kutistuu tulokseksi "ei havaintoa", ei viaksi
Tuo on tahallinen kallistuminen vääriin negatiivisiin tuloksiin, ja sen kannattaa sanoa suoraan eikä haudata sitä. Rikkinäinen CFF-taulukko, tukematon Type 1 -muunnos tai merkkialuetta lyhyempi /CIDSet tuottavat kaikki hiljaisuutta diagnoosin sijaan. Perustelu kuuluu: PDF/UA-auditoinnit välitetään kirjoittajille, jotka eivät rakentaneet työkaluja, ja väärä syytös maksaa enemmän kuin ohitettu havainto. Kirjoittaja polttaa päivän todistaakseen, että kelvollinen tiedosto on kelvollinen, ja lopettaa luottamasta koko raporttiin. Matterhorn Protocol tekee saman eron toisessa muodossa erotellessään tarkistukset, jotka kone voi ratkaista, ja tarkistukset, jotka jäävät ihmiselle, ja sen Fonts-tarkistuskohta (31) on paikka, jossa nämä asuvat. Jos tarvitset tiukemman tulkinnan, aja PDFlibPas nopeana porttina ja erillinen validoija toisena mielipiteenä — sama paritus on kuvattuna artikkelissa PDF/A- ja PDF/UA-esitarkistuskierros
Sivun /Contents on lista, ei tietovirta
Kallein yksittäinen virhe sisältövirtojen auditoinnissa on käsitellä /Contents yhtenä tietovirtana. ISO 32000-1 §7.7.3.3 sallii sivun pitää taulukkoa tietovirtoja, joiden yhdistetty kokonaisuus osien välisine välilyönteineen on sivun ohjelma; tuottajat jakavat sisällön mielivaltaisista kohdista, ja BT voi sijaita yhdessä jäsenessä, kun taas sen pari ET istuu seuraavassa. Sisällönkäsittelijä ylläpitää tilaa — merkityn sisällön pesityssyvyyttä, viimeisen Tf-kutsun valitsemaa fonttia, tekstiobjektilippua — ja Process nollaa tilan alussa. Kutsu sitä kerran per taulukon jäsen, ja jokainen ensimmäisen jälkeinen tietovirta alkaa ilman voimassa olevaa fonttia, joten täydellisesti tagitettu teksti luetaan tagittomana ja fontittomana kohinana. PDFlibPas yhdistää ensin ja käsittelee kerran
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;
// Yksi Process-kutsu koko yhdisteelle, ei koskaan yhtä kutsua per jäsen
Scanner.Process(ContentObjectData(FDoc, PageDict.FindValueByKeyName('Contents')));
Mitkä Form XObjectit oikeasti lasketaan rakenteettomiksi?
Vain ne, jotka sivu oikeasti kutsuu kutsupaikasta, joka on merkityn sisällön ulkopuolella, ja joiden oma sisältö näyttää tekstiä. Diagnoosi 10040 panee täytäntöön ISO 14289-1 §7.20:n tallentamalla kolme riippumatonta tosiasiaa objektinumeroa kohden — sisältää tekstiä, kutsuttiin, kutsuttiin merkityn sisällön sisällä — ja raportoi vain kahden ensimmäisen leikkauksen vähennettynä kolmannella. Kummankin oikotien virhe on sellainen, jonka voisit viedä tuotantoon: kaikkien /Resources-tietueen tekstiä kantavien Formien liputtaminen rankaisee mallikirjastoa, josta kukaan ei piirrä mitään, ja kaikkien kutsuttujen Formien liputtaminen rankaisee vektorilogoja, jotka eivät kanna tekstiä eivätkä tarvitse tagitusta. Kutsupaikka ratkaistaan objektinumerolla eikä resurssin nimellä, sillä sama Form tavoitetaan tavallisesti eri nimillä eri sivuilla. Rinnakkainen diagnoosi 10041 kulkee saman yhdistetyn ohjelman läpi kohdan §7.21.8 takia, ratkaisee jokaisen tekstiä näyttävän operandin vaikutuspiirin fontin kautta ja laskee koodit, jotka osuvat .notdef-merkkiin, mikä on kielletty riippumatta tekstin piirtotavasta — mukaan lukien skannattujen kuvien takana käytetty näkymätön tila. Miten jäljelle jäävät Formit pitää kääriä, on rakennepuun kysymys, jonka käsittelee artikkeli tagged PDF -rakenteen rakentaminen
Fontit, joissa ei ole FontDescriptoria lainkaan
Upottamaton fontti on tämän auditoinnin laillinen syöte, ei virhetila, ja jokaisen upotustarkistuksen alapuolella olevan apurin on selviydyttävä siitä. Kun PDFlibPas ei löydä /FontDescriptoria, tai löytää deskriptorin, jossa ei ole FontFile-, FontFile2- eikä FontFile3-kenttää, se kirjaa diagnoosin 10020 — tai 10022, kun nimi kuuluu Standard 14 -joukkoon, jota §7.21.4 HUOMAUTUS 5 määrätietoisesti kieltäytyy vapauttamasta — ja jatkaa sitten tiedoston loppuun. Juuri tuo on raportin tarkoitus: kirjoittaja haluaa kaikki havainnot yhdellä ajolla, eikä yhtä havaintoa per ajo. Siksi leveys-, cmap-, CharSet- ja CIDSet-apureille annettava deskriptoriviite voi olla Nil, ja kukin niistä testaa asian alussa sen sijaan, että olettaisi aiemman tarkistuksen keskeyttäneen auditoinnin. Jos korjauksena on puuttuvan upottaminen, tekniikka on kuvattu muistiinpanossa puuttuvien fonttien upottaminen olemassa olevaan PDF-tiedostoon
Auditoinnin ajaminen
Yksi kutsu, tiedostoon, jota et välttämättä ole itse tuottanut. TPDFlib.CheckFileCompliance ottaa vastaan vaatimustestivalitsimen — 2 valitsee PDF/UA-1:n ISO 14289-1:2014:n alla — ja palauttaa joko nollan tai merkkijonolistan kahvan, jonka alkiot muodostuvat numeerisesta koodista, kaksoispisteestä ja luettavasta viestistä. Täällä käsitellyt fontti- ja sisältövirtahavainnot sijoittuvat alueelle 10020–10041, numeerisesti erilleen PDF/A:n 00xxx-koodeista, jotta sekaisin kirjoitettu loki pysyy luettavana. Arvon 1 antaminen Options-parametrissa pysäyttää suorituksen ensimmäiseen havaintoon, mikä on toivottavaa koontiportissa eikä niinkään kirjoitustyökalussa. Muistissa edelleen olevalle dokumentille GetPDFUADiagnostics tekee vastaavan tarkastuksen ilman kierrosta levyn kautta
var
Issues, Count, I: Integer;
begin
// ComplianceTest = 2 valitsee PDF/UA-1:n; Options = 0 raportoi kaikki havainnot
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)); // esim. 10037 CIDFontType2 ...
end;
end;
Mikään tästä ei vaadi ulkoista validoijabinaaria koneelle, ja juuri siinä on ero tarkistuksen, joka ajetaan jokaisessa koontiversiossa, ja tarkistuksen, joka ajetaan kun joku muistaa. Täällä kuvatut vaatimustenmukaisuus- ja diagnoosi-API:t kuuluvat vakiopakettiin PDFlibPas Delphi PDF Library, jonka tuotesivulla on koko PDF/UA-1-diagnoosikooditaulu PDF/A-, PDF/X- ja PDF/E-testisarjojen rinnalla