En saknad glyf i en PDF är inte ett fel. Producenten begär ett tecken det valda teckensnittet inte kan mappa, teckensnittet returnerar glyfindex noll, och filen som kommer ut är strukturellt giltig, öppnas överallt och visar en tom ruta där ett namn eller ett belopp borde vara. Ingen i den genererande pipelinen får reda på det. Mottagaren gör det. HotPDF stänger den loopen med TrackUnresolvedGlyphs: slå på den och ritvägen för text spelar in varje kodpunkt vars glyfuppslag resulterar i index noll, och avfyrar OnUnresolvedGlyph en gång per unikt fynd med kodpunkten, teckensnittet den misslyckades på, skriptet den tillhör och ett förslag på teckensnitt som skulle täcka den
Detektering är hälften av svaret. Den andra hälften är SetFontFallbackChain, som registrerar en ordnad lista av teckensnitt per skript, så att de vanliga fallen löser sig själva och bara äkta gap når din hanterare. Tillsammans gör de en klass av defekt som tidigare rapporterades av kunder till en kontroll vid byggtid
Varför kastar en saknad glyf ingenting?
För ISO 32000 ålägger ingen producent skyldigheten att verifiera täckning, och glyfindex noll är en legitim glyf. Det är .notdef, vars kontur teckensnittsdesignern väljer: vanligtvis en tom eller ihålig rektangel, ibland ingenting alls. En visare som ritar den beter sig korrekt. Textextraktion kan till och med returnera de rätta tecknen, eftersom /ToUnicode-mappningen skrivs ur källtexten i stället för ur konturerna, så en automatisk rundturkskontroll passerar glatt ett dokument vars synliga text har hål i
Den praktiska konsekvensen är att täckning måste kontrolleras i stunden av ritningen, när biblioteket fortfarande vet vilken kodpunkt som begärdes och vilken glyf teckensnittet faktiskt erbjöd. Efteråt är informationen borta
Detektorn måste bevaka delmängdstillståndet, inte device context
Här gick den första implementeringen fel, och orsaken är värd att förstå eftersom den gäller varje täckningskontroll som skruvas fast på en textpipeline. HotPDF har två textvägar. En avgår genom ett registrerat Unicode-TrueType-teckensnitt med en teckenkarta i minnet byggd vid registreringstiden. Den andra är ett äldre GDI-spår som skapar en färsk device context och ett teckensnittshandle per teckenkörning
Att bedöma täckning från GDI-spåret är hopplöst. Dess mappning är inte den mappning som hamnar i den avgivna innehållsströmmen, och de två är inte synkroniserade, så en detektor som läser GDI-resultat rapporterar hela det utskrivbara ASCII-intervallet som olöst. Det auktoritativa svaret bor i det registrerade teckensnittet: teckenkartan som RegisterUnicodeTTF parsar, frågad via GetUnicodeGlyphForCodepoint. Detektorn är därför spärrad på delmängdsredo-tillståndet, inte på något GDI-villkor, och den körs helt enkelt inte på dokument som aldrig registrerade ett Unicode-teckensnitt, vilket är korrekt eftersom de dokumenten ändå är begränsade till standardkodningarna
En andra fälla sitter bredvid. GDI-familjenamnet hos ett teckensnitt och PostScript-namnet extraherat ur teckensnittsbinären vid registrering är olika strängar, och inte på ett sätt du kan normalisera: en familj som heter Arial Unicode MS bär PostScript-namnet ArialMT. Varje spärr skriven som ”är det för tillfället valda teckensnittet det vi registrerade”, jämförd per namn, är död kod som aldrig avfyras. Spärra på tillstånd, aldrig på teckensnittsnamn
Testa inte en glyfdetektor med emoji
Det uppenbara testfallet är ett leende ansikte, och det kommer övertyga dig om att detektorn är trasig. Vanliga emoji-kodpunkter i astralplanen löses via en private-use-syntesväg som mappar dem direkt till ett glyfindex, så de når aldrig den allmänna täckningsgrenen. Detektorn beter sig korrekt och testet mäter fel väg
Använd en otilldelad kodpunkt i stället. U+0378 är permanent oallokerad i Unicode, så inget teckensnitt kan legitimt mappa den, och den trär precis grenen du vill verifiera. Den distinktionen mellan ”funktionen är trasig” och ”testet plockade en indata som kringgår funktionen” kostar riktiga timmar, och otilldelade kodpunkter är det billigaste sättet att undvika det
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
// Avfyras en gång per unik kodpunkt, inte en gång per förekomst
FFindings.Add(Format('U+%.4X missing in %s (script %d), try: %s',
[Info.CodePoint, String(Info.FontName), Ord(Info.Script),
String(Info.SuggestedFonts)]));
end;
// Koppla in den i ett genererande jobb
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
// Låt jobbet misslyckas i stället för att leverera en sida med rutor på
raise Exception.Create(Audit.Findings.Text);
finally
Pdf.Free;
end;
Reservkedjor är per skript, inte per teckensnitt
Orsaken till att reserven omfattas per skript i stället för per källteckensnitt är att täckningsgap kluster sig efter skriftsystem. Ett latinskt textteckensnitt saknar devanagari, thai, han och emoji, allt på en gång, och ersättaren för vart och ett är ett annat teckensnitt. Att deklarera en kedja per skript beskriver därför den verkliga distributionen: ett latinskt teckensnitt för brödtext, ett CJK-teckensnitt, ett emoji-teckensnitt, en allt-i-ett
// THPDFFontScript täcker hfsCommon, hfsLatin, hfsGreek, hfsCyrillic,
// hfsHebrew, hfsArabic, hfsIndic, hfsSoutheastAsian, hfsCJK, hfsKana,
// hfsHangul, hfsEmoji och 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']);
Reserv och detektering är komplementära i stället för alternativ. Kedjor hanterar den täckning du förutsåg; detektorn rapporterar den täckning du inte förutsåg, vilket på ett system som bearbetar godtyckliga kunddata är den intressanta hälften. Observera att ett teckensnittsbyte ändrar mått, så ett stycke som faller tillbaka kan flöda om; om layouten betyder något är stängnings- och delmängdsbeteendet hos ersättarteckensnittet värt att läsa på i artikeln om teckensnittsdelmängdsstängning, och skript som behöver omordning eller sammanfogning hanteras av formningssteget som beskrivs i textformning av komplexa skript
Hur man eftermonterar beteende utan att riskera befintlig väg
Samma release lade till en reserv för den äldre kern-tabellen för paravstånd, och sättet den omfattades på är ett mönster värt att kopiera. I stället för att lägga till en ny beslutspunkt till kerninglogiken hänger reserven på den tidiga utgångsgren som redan fanns för teckensnitt utan GPOS-tabell. Ett modernt teckensnitt med GPOS når den aldrig, så dess beteende är oförändrat genom konstruktion i stället för genom testning. Vägar som inte registrerar ett Unicode-teckensnitt producerar två noll-offset, så de är oförändrade också
Det är den allmänna formen av en lågrisk-eftermontering i ett moget renderingsbibliotek: hitta grenen som för tillfället producerar ingenting och lägg det nya beteendet där. Det omvandlar ”vi tror att detta inte regredierade något” till ”detta kan inte ha regredierat något”, vilket är en mycket bättre sak att säga om en textmotor som andras fakturor passerar genom
Gör det till en port, inte en logg
Täckningsfynd är bara användbara om något misslyckas på dem. I en dokumentgenererande tjänst är det produktiva upplägget att hålla spårningen på i det nattliga regressionjobbet mot en corpus av riktiga kundnamn, adresser och produktbeskrivningar, och att låta jobbet misslyckas på vartdera fynd. Eftersom händelsen avfyras en gång per unik kodpunkt i stället för en gång per förekomst förblir utmatningen liten nog att läsa även när ett helt skript saknas
I produktion är samma hanterare bättre använd som telemetri: spela in kodpunkten och teckensnittet, fortsätt servera dokumentet och låt det aggregerade tala om vilket skript som ska läggas till distributionsfontsetet härnäst. Renderingsbeteende för inbäddade och ersatta teckensnitt tas upp vidare i rendering av inbäddade teckensnittsglyfer, och den fullständiga egenskapslistan inklusive TrackUnresolvedGlyphs är dokumenterad på produktsidan för HotPDF Delphi PDF component