En manglende glyf i en PDF er ikke en feil. Produsenten ber om et tegn den valgte fonten ikke kan mappe, fonten returnerer glyfindeks null, og filen som kommer ut, er strukturelt gyldig, åpner overalt, og viser en tom boks der et navn eller et beløp skulle vært. Ingen i den genererende pipelinen finner det ut. Mottakeren gjør det. HotPDF lukker den sløyfen med TrackUnresolvedGlyphs: slå den på, og teksttegneveien registrerer hvert kodepunkt hvis glyfoppslag løses til indeks null, og utløser OnUnresolvedGlyph én gang per unikt funn med kodepunktet, fonten det feilet på, skriptet det tilhører, og et forslag til fonter som ville dekke det
Deteksjon er halvparten av svaret. Den andre halvdelen er SetFontFallbackChain, som registrerer en ordnet liste av fonter per skript, slik at de vanlige tilfellene løser seg selv og bare ekte gap når handleren din. Sammen gjør de en klasse av defekter som pleide å rapporteres av kunder, om til en byggetidssjekk
Hvorfor kaster en manglende glyf ikke noe?
Fordi ISO 32000 ikke pålegger en produsent noen forpliktelse til å verifisere dekning, og glyfindeks null er en legitim glyf. Den er .notdef, hvis omriss fontdesigneren velger: vanligvis et tomt eller hult rektangel, noen ganger ingenting i det hele tatt. En fremviser som tegner den, oppfører seg korrekt. Tekstekstraksjon kan til og med returnere de riktige tegnene, fordi /ToUnicode-mappingen er skrevet fra kildeteksten snarere enn fra omrissene, så en automatisert rundturssjekk vil gjerne passere et dokument hvis synlige tekst har hull i
Den praktiske konsekvensen er at dekningen må sjekkes i tegneøyeblikket, når biblioteket fortsatt vet hvilket kodepunkt som ble etterspurt og hvilken glyf fonten faktisk tilbød. Etterpå er informasjonen borte
Detektoren må overvåke subset-tilstanden, ikke device context
Det er her den første implementeringen gikk galt, og grunnen er verdt å forstå fordi den gjelder enhver dekningssjekk boltet på en tekstpipeline. HotPDF har to tekstveier. Én sender ut gjennom en registrert Unicode TrueType-font med et in-memory-tegnkart bygget ved registreringstid. Den andre er en legacy GDI-vei som oppretter en fersk device context og fonthåndtak per tegnkjøring
Å dømme dekningen fra GDI-veien er håpløst. Dens mapping er ikke mappingen som ender opp i den sendte innholdsstrømmen, og de to er ikke synkronisert, så en detektor som leser GDI-resultater rapporterer hele det utskrivbare ASCII-området som uløst. Det autoritative svaret bor i den registrerte fonten: tegnkartet som RegisterUnicodeTTF parser, forespurt gjennom GetUnicodeGlyphForCodepoint. Detektoren er derfor gatet på subset-ready-tilstanden, ikke på noen GDI-betingelse, og den kjører rett og slett ikke på dokumenter som aldri registrerte en Unicode-font, noe som er korrekt fordi de dokumentene er begrenset til standardkodingene likevel
En andre felle sitter ved siden av. GDI-familienavnet til en font og PostScript-navnet hentet ut fra fontbinæren ved registrering er forskjellige strenger, og ikke på en måte du kan normalisere: en familie kalt Arial Unicode MS bærer PostScript-navnet ArialMT. Enhver gate skrevet som «er den nåværende valgte fonten den vi registrerte», sammenlignet etter navn, er død kode som aldri utløser. Gate på tilstand, aldri på fontnavn
Ikke test en glyfdetektor med emoji
Det opplagte testtilfellet er et smilende ansikt, og det vil overbevise deg om at detektoren er ødelagt. Vanlige emoji-kodepunkter i astralplanene løses gjennom en private-use-syntesevei som mapper dem direkte til en glyfindeks, så de når aldri den generelle dekningsgrenen. Detektoren oppfører seg korrekt, og testen måler feil vei
Bruk et utildelt kodepunkt i stedet. U+0378 er permanent utildelt i Unicode, så ingen font kan legitimt mappe det, og det trener nøyaktig grenen du vil verifisere. Det skillet mellom «funksjonen er ødelagt» og «testen plukket en input som omgår funksjonen» koster ekte timer, og utildelte kodepunkter er den billigste måten å unngå det på
type
TCoverageAudit = class
private
FFindings: TStringList;
public
procedure Handle(Sender: TObject;
const Info: THPDFUnresolvedGlyphInfo);
property Findings: TStringList read FFindings;
end;
procedure TCoverageAudit.Handle(Sender: TObject;
const Info: THPDFUnresolvedGlyphInfo);
begin
// Utløses én gang per unikt kodepunkt, ikke én gang per forekomst
FFindings.Add(Format('U+%.4X missing in %s (script %d), try: %s',
[Info.CodePoint, String(Info.FontName), Ord(Info.Script),
String(Info.SuggestedFonts)]));
end;
// Koble det inn i en genereringsjobb
Pdf := THotPDF.Create(nil);
try
Pdf.TrackUnresolvedGlyphs := True;
Pdf.OnUnresolvedGlyph := Audit.Handle;
Pdf.RegisterUnicodeTTF('C:\Windows\Fonts\arial.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 720, 0, CustomerName);
Pdf.EndDoc;
if Audit.Findings.Count > 0 then
// La jobben feile snarere enn å sende ut en side med bokser på
raise Exception.Create(Audit.Findings.Text);
finally
Pdf.Free;
end;
Fallback-kjeder er per skript, ikke per font
Grunnen til at fallback er avgrenset etter skript snarere enn etter kildefont, er at dekningsgap klynger seg etter skriftsystem. En latinsk tekstfont mangler Devanagari, thai, Han og emoji, alt på én gang, og erstatningen for hver er en annen font. Å erklære én kjede per skript beskriver derfor den reelle utrullingen: én latinsk font for brødtekst, én CJK-font, én emoji-font, én catch-all
// THPDFFontScript dekker hfsCommon, hfsLatin, hfsGreek, hfsCyrillic,
// hfsHebrew, hfsArabic, hfsIndic, hfsSoutheastAsian, hfsCJK, hfsKana,
// hfsHangul, hfsEmoji og hfsOther
Pdf.SetFontFallbackChain(hfsCJK,
['Microsoft YaHei', 'SimSun', 'Yu Gothic']);
Pdf.SetFontFallbackChain(hfsArabic, ['Segoe UI', 'Arial']);
Pdf.SetFontFallbackChain(hfsEmoji, ['Segoe UI Emoji']);
Pdf.SetFontFallbackChain(hfsOther, ['Arial Unicode MS']);
Fallback og deteksjon er komplementære snarere enn alternativer. Kjeder håndterer dekningen du forutså; detektoren rapporterer dekningen du ikke forutså, noe som på et system som prosesserer vilkårlige kundedata er den interessante halvdelen. Merk at å substituere en font endrer metrikk, så et avsnitt som faller tilbake, kan flyte om; hvis layouten betyr noe, er lukke- og subset-atferden til den substituerte fonten verdt å lese seg opp på i artikkelen om font subset-lukking, og skript som trenger reordregering eller sammenføyning håndteres av shaping-stadiet beskrevet i kompleks skripttekstshaping
Hvordan ettermontere atferd uten å risikere den eksisterende veien
Samme utgivelse la til en legacy kern-tabellfallback for paravstand, og måten den ble avgrenset på, er et mønster verdt å kopiere. Snarere enn å legge et nytt beslutningspunkt til kerninglogikken, henger fallbacken på tidlig-utgang-grenen som allerede fantes for fonter uten GPOS-tabell. En moderne font med GPOS når den aldri, så dens atferd er uendret av konstruksjon snarere enn av testing. Veier som ikke registrerer en Unicode-font produserer to nullforskyvninger, så de er uendret også
Det er den generelle formen av en lavrisikoettermontering i et modent rendringsbibliotek: finn grenen som for tiden produserer ingenting, og legg den nye atferden der. Det konverterer «vi tror dette ikke regreserte noe» til «dette kan ikke ha regresert noe», noe som er en mye bedre ting å si om en tekstmotor andre folks fakturaer går gjennom
Gjør det til en gate, ikke en logg
Dekningsfunn er bare nyttige hvis noe feiler på dem. I en dokumentgenererende tjeneste er den produktive ordningen å holde sporingen på i det nattlige regresjonsjobben mot et korpus av ekte kundenavn, adresser og produktbeskrivelser, og å la jobben feile på ethvert funn. Fordi hendelsen utløses én gang per unikt kodepunkt snarere enn én gang per forekomst, forblir outputen liten nok til å lese selv når et helt skript mangler
I produksjon brukes den samme handleren bedre som telemetri: registrer kodepunktet og fonten, fortsett å servere dokumentet, og la aggregatet fortelle deg hvilket skript som skal legges til distribusjonsfontsettet neste gang. Rendringsatferd for innebygde og substituerte fonter er dekket videre i rendering av innebygde fontglyfer, og den fullstendige egenskapslisten inkludert TrackUnresolvedGlyphs er dokumentert på produktsiden for HotPDF Delphi PDF component