Een ontbrekende glyph in een PDF is geen fout. De producent vraagt om een teken dat het gekozen font niet kan mappen, het font geeft glyphindex nul terug, en het bestand dat eruit komt is structureel geldig, opent overal en toont een leeg kader waar een naam of een bedrag hoort te staan. Niemand in de genererende pijplijn merkt het. De ontvanger wel. HotPDF sluit die lus met TrackUnresolvedGlyphs: zet die aan en het tekenpad legt elk codepoint vast waarvan de glyph-opzoek naar index nul resolveert, vuurt OnUnresolvedGlyph één keer per unieke bevinding af met het codepoint, het font waarop het faalde, het script waartoe het behoort en een suggestie van fonts die het zouden dekken
Detectie is de helft van het antwoord. De andere helft is SetFontFallbackChain, dat per script een geordende lijst fonts registreert, zodat de gangbare gevallen zichzelf oplossen en alleen echte gaten uw handler bereiken. Samen maken ze een klasse defecten die klanten vroeger meldden tot een controle op buildtijd
Waarom gooit een ontbrekende glyph niets?
Omdat ISO 32000 een producent geen verplichting oplegt om dekking te verifiëren, en glyphindex nul is een legitieme glyph. Het is .notdef, waarvan de ontwerper van het font de omtrek kiest: gewoonlijk een lege of holle rechthoek, soms helemaal niets. Een viewer die die tekent gedraagt zich correct. Tekstextractie geeft misschien zelfs de juiste tekens terug, omdat de /ToUnicode-mapping uit de brontekst wordt geschreven in plaats van uit de omtrekken, dus een geautomatiseerde round-trip-controle laat graag een document slagen waarvan de zichtbare tekst gaten heeft
De praktische consequentie is dat dekking gecontroleerd moet worden op het moment van tekenen, wanneer de bibliotheek nog weet welk codepoint was aangevraagd en welke glyph het font werkelijk aanbood. Daarna is de informatie weg
De detector moet op de subsettoestand letten, niet op de device context
Dit is waar de eerste implementatie de mist inging, en de reden is het begrijpen waard omdat die geldt voor elke dekkingscontrole die op een tekstpijplijn wordt geschroefd. HotPDF heeft twee tekstpaden. Eén zendt uit via een geregistreerd Unicode TrueType-font met een karakterkaart in het geheugen die bij registratie is opgebouwd. Het andere is een legacy GDI-pad dat per tekenreeks een verse device context en een font-handle aanmaakt
Dekking beoordelen vanuit het GDI-pad is hopeloos. Zijn mapping is niet de mapping die in de uitgezonden contentstream belandt, en de twee zijn niet gesynchroniseerd, dus een detector die GDI-resultaten leest meldt de hele afdrukbare ASCII-reeks als unresolved. Het gezaghebbende antwoord huist in het geregistreerde font: de karakterkaart die RegisterUnicodeTTF parseert, bevraagd via GetUnicodeGlyphForCodepoint. De detector wordt daarom begrensd op de subset-gereedstaat, niet op een GDI-conditie, en die draait simpelweg niet op documenten die nooit een Unicode-font hebben geregistreerd, wat correct is want die documenten zijn toch beperkt tot de standaardcoderingen
Er zit een tweede valkuil naast. De GDI-familienaam van een font en de PostScript-naam die bij registratie uit het fontbinair bestand is gehaald zijn verschillende strings, en niet op een manier die te normaliseren is: een familie genaamd Arial Unicode MS draagt de PostScript-naam ArialMT. Elke afsluiting geschreven als "is het momenteel geselecteerde font het font dat we hebben geregistreerd", vergeleken op naam, is dode code die nooit vuurt. Sluit af op toestand, nooit op fontnamen
Test een glyph-detector niet met emoji
Het voor de hand liggende testgeval is een glimlachend gezicht, en dat zal u overtuigen dat de detector kapot is. Gangbare emoji-codepoints in de astrale vlakken resolven via een pad voor private-use-synthese dat ze rechtstreeks aan een glyphindex mapt, dus ze bereiken de algemene dekkingspoot nooit. De detector gedraagt zich correct en de test meet het verkeerde pad
Gebruik in plaats daarvan een niet-toegewezen codepoint. U+0378 is permanent niet toegekend in Unicode, dus geen enkel font kan het legitiem mappen, en het oefent exact de tak uit die u wilt verifiëren. Dit onderscheid tussen "de functie is kapot" en "de test koos een input die de functie omzeilt" kost echte uren, en niet-toegewezen codepoints zijn de goedkoopste manier om dat te vermijden
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
// Vuurt één keer per uniek codepoint, niet één keer per voorkomen
FFindings.Add(Format('U+%.4X missing in %s (script %d), try: %s',
[Info.CodePoint, String(Info.FontName), Ord(Info.Script),
String(Info.SuggestedFonts)]));
end;
// Koppeling in een genererende taak
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
// Laat de taak falen in plaats van een pagina vol kaders te verschepen
raise Exception.Create(Audit.Findings.Text);
finally
Pdf.Free;
end;
Fallbackketens zijn per script, niet per font
De reden dat fallback per script en niet per bronfont wordt afgebakend, is dat dekkingsgaten zich clusteren per schrift. Een Latijns tekstfont mist Devanagari, Thai, Han en emoji, allemaal tegelijk, en de vervanger voor elk is een ander font. Eén keten per script declareren omschrijft daarom de echte uitrol: één Latijns font voor bodytekst, één CJK-font, één emoji-font, één vang-alles
// THPDFFontScript covers hfsCommon, hfsLatin, hfsGreek, hfsCyrillic,
// hfsHebrew, hfsArabic, hfsIndic, hfsSoutheastAsian, hfsCJK, hfsKana,
// hfsHangul, hfsEmoji and 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 en detectie vullen elkaar aan in plaats van elkaar te vervangen. Ketenlenen de dekking af die u had voorzien; de detector meldt de dekking die u niet had voorzien, en op een systeem dat willekeurige klantgegevens verwerkt is dat de interessante helft. Let op dat het vervangen van een font metrieken verandert, dus een alinea die terugvalt kan opnieuw breken; als de lay-out er toe doet, is het sluitings- en subsetgedrag van het vervangende font het nalezen waard in het artikel over font subset closure, en scripts die herordening of verbinding nodig hebben worden afgehandeld door de shaping-fase die wordt beschreven in complex script text shaping
Gedrag retrofitten zonder het bestaande pad te riskeren
Dezelfde release voegde een legacy kern-tabel-fallback voor paarafstand toe, en de manier waarop die werd afgebakend is een patroon om te kopiëren. In plaats van een nieuw beslissingspunt aan de kerning-logica toe te voegen, hangt de fallback aan de early-exit-tak die al bestond voor fonts zonder GPOS-tabel. Een modern font met GPOS komt er nooit, dus zijn gedrag is onveranderd door constructie in plaats van door testen. Paden die geen Unicode-font registreren produceren twee nul-offsets, dus die blijven ook onveranderd
Dat is de algemene vorm van een retrofit met laag risico in een volwassen renderingbibliotheek: vind de tak die momenteel niets produceert en zet het nieuwe gedrag daar. Die maakt van "wij geloven dat dit niets heeft teruggedraaid" een "dit kan niets hebben teruggedraaid", en dat is een veel betere uitspraak over een tekst-engine waar andermans facturen doorheen lopen
Maak er een afsluiting van, geen log
Dekkingsbevindingen zijn alleen nuttig als er iets op faalt. In een documentgenererende dienst is de productieve opzet tracking aan laten staan in de nachtelijke regressietaak tegen een corpus van echte klantnamen, adressen en productomschrijvingen, en de taak te laten falen op elke bevinding. Omdat het event één keer per uniek codepoint vuurt in plaats van één keer per voorkomen, blijft de uitvoer klein genoeg om te lezen, zelfs wanneer een heel script ontbreekt
In productie is dezelfde handler beter als telemetrie te gebruiken: leg het codepoint en het font vast, blijf het document dienen, en laat het totaal u vertellen welk script u het volgende aan de uitrolfontset moet toevoegen. Het rendergedrag van ingebedde en vervangen fonts wordt verder behandeld in ingebedde fontglyphs renderen, en de volledige eigenschappenlijst inclusief TrackUnresolvedGlyphs staat gedocumenteerd op de productpagina van de HotPDF Delphi PDF component