En veraPDF-rapport som sier at en glyfbredde avviker fra det innebygde fontprogrammet forteller deg nesten ingenting om hvilken glyf, eller hvorfor. PDFlibPas svarer på det spørsmålet ved å løse hver tegnkode gjennom den innebygde cmap til en glyf-indeks, normalisere programmetrikken til 1000 enheter per em og sammenligne der
Hvorfor stemmer ikke glyfbredder?
Fordi de to tallene som sammenlignes lever i forskjellige koordinatsystem, og ingenting i PDF-ordboken forteller deg konverteringen. En fontordbok skriver /Widths i glyfrom, som PDF fastsetter til en tusendedel av em (ISO 32000-1 §9.2.4). hmtx-tabellen inne i det innebygde TrueType-programmet skriver fremskritt i fontdesignenheter, og head-tabellen bestemmer hvor mange av dem som utgjør et em: 2048 for de fleste TrueType-snitt, 1000 for CFF-avledede, av og til noe helt annet. Sammenlign råverdiene og hver 2048-upem-font i korpuset ditt ser ødelagt ut. Det er fellen ISO 14289-1 §7.21.5 setter for alle som prøver å revidere bredder ved å lese ordbokfelt
PDFlibPas normaliserer ved innlasting. TPDFTrueTypeParser lagrer Advance * 1000 div unitsPerEm i bredde-arrayen sin, så Parser.GetWidth(GID) svarer allerede i de samme tusendedelene av em som PDF-en bruker, og GetRawWidth står til rådighet når du trenger designenheter tilbake. Det etterlater fortsatt den vanskeligere halvdelen: å komme fra en tegnkode til en glyf-indeks. For en enkel TrueType-font avhenger ruten av Symbolic-flagget i FontDescriptor, bit 3 av /Flags
Parser := TPDFTrueTypeParser.Create;
try
Parser.LoadFromString(FontProgram);
if Symbolic then
begin
// Symboliske snitt adresseres gjennom program-cmapen direkte,
// med (3,0) høy-byte-konvensjonen som reserve
GID := Parser.GetGlyphIndex(Code);
if GID = 0 then
GID := Parser.GetGlyphIndex($F000 + Code);
end
else
begin
// Ikke-symbolisk: kode -> glyfnavn via kodingen, navn -> Unicode
// via Adobe Glyph List, Unicode -> GID via program-cmapen
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;
To detaljer i det fragmentet bærer vekt. Toleransen er én enhet, ikke null, fordi normaliseringen er heltallsdivisjon og en legitimt produsert fil kan lande én enhet ved siden av; det er nøyaktig «innenfor en tusendedel av et em»-formuleringen diagnostikk 10036 rapporterer. Og GID < Parser.GlyphCount-vokteren er ikke pynt. GetWidth er skrevet for å være ettergiven overfor gjengivelseskallere, klemmer en indeks utenfor området til den siste oppføringen i hmtx og faller tilbake til 750 når tabellen mangler. Ettergivenhet er riktig for gjengivelse og galt for revisjon, så revisjonen avviser indeksen før den ber om en bredde i stedet for å stole på klemmen
CIDFontType2 legger til enda en indireksjon
PDFlibPas går gjennom sammensatte fonter på samme måte, med /CIDToGIDMap satt inn mellom CID-en og glyfen. Bredder ankommer i /W-arrayen, som ISO 32000-1 §9.7.4.3 gir to former som vekselbrukes fritt i én array: en start-CID etterfulgt av en array av sammenhengende bredder, eller en første CID, en siste CID og én enkelt bredde brukt over hele strekket. Kontrollen tolker begge, og overleverer hvert resulterende par til samme sammenligning, og rapporterer totalen under diagnostikk 10037. Mapping-steget er der sammensatte fonter skiller seg, og det er hvorfor den manglende-mapping-diagnostikken 10021 betyr noe før du leser noen bredde i det hele tatt — en fraværende eller misdannet /CIDToGIDMap bryter ikke bare §7.21.3.2, den gjør bredde-spørsmålet usvarbart
// /CIDToGIDMap er navnet /Identity eller en stream av big-endian 16-bit
// glyf-indekser, én per CID (ISO 32000-1 section 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-strenger er 1-baserte
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;
Hva skal en revisor gjøre når fontprogrammet ikke lar seg dekode?
Si ingenting. /CharSet- og /CIDSet-fullstendighetssjekkene ISO 14289-1 §7.21.4.2 krever — diagnostikk 10038 og 10039 — er stedet der en overivrig validator blir en byrde, for en melding om «ditt CharSet er ufullstendig» er, for den som leser den, umulig å skille fra «vår Type 1-dekoder ga opp». PDFlibPas rapporterer derfor en manglende oppføring bare når tre ting alle lykkes: fontprogrammet dekoder, kode-til-glyf-mappingen løser seg, og settet selv dekoder. TPDFType1Decoder.LoadPFBFromString må returnere True og gi et charstring-antall før noe glyfnavn sjekkes mot /CharSet-strengen; /CIDSet-banen trenger at stream-en blåses opp og glyfantallet kommer tilbake positivt før én eneste bit testes. Ethvert unntak på veien kollapser til «ingen funn», ikke til en defekt
Det er en bevisst skjevhet mot falske negativer, og det er verdt å si rent ut i stedet for å begrave det. En korrupt CFF-tabell, en ustøttet Type 1-variant eller en /CIDSet kortere enn glyf-området produserer alle stillhet i stedet for en diagnostikk. Resonnementet er at PDF/UA-kontroller videresendes til forfattere som ikke bygde verktøyet, og en falsk anklage koster mer enn et bomskudd: forfatteren brenner en dag på å bevise at en samsvarig fil er samsvarig, og slutter å stole på hele rapporten. Matterhorn Protocol gjør samme skjelning i en annen form når det skiller sjekker en maskin kan avgjøre fra sjekker et menneske må, og Fonts-sjekkpunktet (31) er der disse bor. Trenger du den strengere lesingen, kjør PDFlibPas som den raske porten og en dedikert validator som andre mening — den parringen er den samme som beskrives i PDF/A- og PDF/UA-preflight-gjennomgangen
Side-/Contents er en liste, ikke en stream
Den aller dyreste feilen i innholdsstream-revisjon er å behandle /Contents som én stream. ISO 32000-1 §7.7.3.3 lar en side holde en array av streams hvis sammensetting, med mellomrom mellom delene, er sideprogrammet; produsenter deler på vilkårlige punkter, og en BT kan ligge i ett medlem med sin tilhørende ET i det neste. En innholdsprosessor holder tilstand — marked-content-nesteringsdybden, fonten valgt av siste Tf, tekstobjekt-flagget — og Process nullstiller den tilstanden ved inngang. Kall den én gang per array-medlem, og hver stream etter den første starter uten gjeldende font, så tekst som var helt fint tagget leses som utagget, fontløs støy. PDFlibPas setter sammen først og prosesserer én gang
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;
// Ét Process-kall over hele sammensetningen, aldri ett kall per medlem
Scanner.Process(ContentObjectData(FDoc, PageDict.FindValueByKeyName('Contents')));
Hvilke Form XObjects teller egentlig som ustrukturerte?
Bare de en side virkelig kaller, fra et kallsted utenfor marked content, hvis eget innhold viser tekst. Diagnostikk 10040 håndhever ISO 14289-1 §7.20 ved å registrere tre uavhengige fakta per objektnummer — har tekst, ble kalt, ble kalt inne i marked content — og rapporterer bare snittet av de to første minus det tredje. Hver av de to snarveiene er galt på en måte du ville fraktet: å flagge hvert tekstbærende Form i /Resources straffer et mallbibliotek ingen tegner fra, og å flagge hvert kalt Form straffer vektorlogoer som bærer ingen tekst og trenger ingen tagging. Kallstedet løses etter objektnummer i stedet for etter ressursnavn, siden samme Form rutinemessig nås under forskjellige navn på forskjellige sider. Følgediagnostikken 10041 går gjennom samme sammensatte program for §7.21.8, løser hver tekstvisende operand gjennom fonten i virkeområde og teller kodene som lander på .notdef, som er forbudt uansett tekstgjengivelsesmodus — inkludert den usynlige modusen brukt bak scannede bilder. Hvordan de overlevende Formene skal pakkes er et strukturtre-spørsmål, dekket i artikkelen om å bygge tagget PDF-struktur
Fonter uten FontDescriptor i det hele tatt
En uinnebygget font er legitimt inndata for denne kontrollen, ikke en feiltilstand, og hver hjelper under innebyggingskontrollen må overleve den. Når PDFlibPas ikke finner noen /FontDescriptor, eller en deskriptor uten FontFile, FontFile2 eller FontFile3, registrerer den diagnostikk 10020 — eller 10022 når navnet er ett av Standard 14, som §7.21.4 NOTE 5 ustett nekter å unnta — og fortsetter så gjennom resten av filen. Det er hele poenget med en rapport: en forfatter vil ha hvert funn i én gjennomgang, ikke ett funn per kjøring. Så deskriptor-referansen overlevert til bredde-, cmap-, CharSet- og CIDSet-hjelperne kan være Nil, og hver av dem tester for det ved inngang i stedet for å anta at en tidligere sjekk avbrøt revisjonen. Hvis fiksen er å innebygge det som mangler, finner du mekanikken i notatet om å innebygge manglende fonter i en eksisterende PDF
Å kjøre kontrollen
Ett kall, på en fil du ikke nødvendigvis produserte. TPDFlib.CheckFileCompliance tar en samsvarstest-selektor — 2 for PDF/UA-1 under ISO 14289-1:2014 — og returnerer enten null eller et strengliste-håndtak hvis oppføringer er en numerisk kode, et kolon og en lesbar melding. Font- og innholdsstream-funnene omtalt her opptar 10020 til 10041 i det området, holdt numerisk adskilt fra 00xxx PDF/A-kodene så en blandet logg forblir lesbar. Å sende 1 i Options kortslutter ved første funn, noe du vil ha i en byggport snarere enn i et forfatterverktøy. For et dokument fortsatt åpent i minnet kjører GetPDFUADiagnostics den tilsvarende inspeksjonen uten en rundtur gjennom disk
var
Issues, Count, I: Integer;
begin
// ComplianceTest = 2 velger PDF/UA-1; Options = 0 rapporterer hvert funn
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)); // f.eks. 10037 CIDFontType2 ...
end;
end;
Ingen av delene trenger en eksternal validator-binærfil på maskinen, noe som er forskjellen mellom en sjekk som kjører på hvert bygg og en sjekk som kjører når noen husker det. Samsvars- og diagnostikk-API-ene omtalt her leveres i standard PDFlibPas Delphi PDF Library, hvis produktsside bærer hele diagnostikkodetabellen for PDF/UA-1 ved siden av PDF/A-, PDF/X- og PDF/E-testsuitene