HotPDF renderar inbäddade PDF-typsnitt i Delphi utan att installera något på datorn: den inbyggda renderingspipelinen för glyfer i HPDFGlyphRender.pas tolkar typsnittsprogrammen som lagras inuti själva PDF-filen — TrueType-glyf-konturer från FontFile2, CFF Typ 2-charstrings från FontFile3 samt Typ 3-glyf-innehållsströmmar — och spelar upp dem som fyllda GDI-vektorbanor. Den här artikeln är en djupdykning i typsnittsprecision bakom rendering av PDF-sidor till en TBitmap med HotPDF: den artikeln täcker renderaren som helhet, medan denna täcker hur texten på dessa sidor får sina exakta former
Fyrkanter, tomma ytor eller subtilt felaktiga tecken i renderade PDF-utdata beror nästan alltid på att renderaren efterfrågade ett typsnitt från operativsystemet istället för att använda det som var inbäddat i filen. Klagomålet uppstår på samma sätt varje gång: dokumentet ser perfekt ut på datorn där det skapades, men när en kund öppnar det på en ren server eller ett låst skrivbord visar den japanska fakturan tomma fyrkanter, eller så gör ett ersatt typsnitt att radbrytningarna flyttas. Typsnitten fanns aldrig på den datorn — endast inuti PDF-filen — och en renderare som stannar vid att ersätta typsnitt sätter systemtypsnitt kan inte visa dem. Delsmängds-typsnitt (subset fonts) gör det ännu värre: en delmängd kan innehålla fyrtio glyfer under teckenkoder som tilldelats privat för just den filen, en tilldelning som inget installerat typsnitt delar
Typsnittsbehållare i PDF
ISO 32000-1 §9.9 definierar tre behållare för ett inbäddat typsnittsprogram i typsnittsbeskrivningen: FontFile innehåller ett ursprungligt Typ 1-program, FontFile2 ett TrueType-program och FontFile3 en ren CFF (Type1C eller CIDFontType0C) eller en OpenType-wrapper. En fjärde variant, Typ 3-typsnittet enligt ISO 32000-1 §9.6.5, bäddar inte in något binärt överhuvudtaget — varje glyf är en liten PDF-innehållsström som exekveras på plats. De tre behållarna skiljer sig åt i sin konturmatematik (kvadratiska B-splines kontra kubiska charstrings kontra godtyckliga sidoperatorer), så en noggrann renderare behöver en separat tolk för varje, plus ett kodningsskikt som omvandlar teckenkoder till rätt glyfindex innan någon kontur rörs
Hur konverterar HotPDF TrueType-glyfkonturer till GDI banor?
THPDFEmbeddedTTF i HPDFGlyphRender.pas läser tabellen loca för att lokalisera varje glyfpost, stegar igenom glyf-konturerna punkt för punkt och skickar ut en GDI-bana. Två TrueType-konventioner kräver explicit hantering. För det första innebär på varandra följande off-curve-punkter en on-curve-punkt vid deras mittpunkt, och en kontur vars punkter alla är off-curve börjar vid mittpunkten av dess sista och första punkter — om man hoppar över någon av reglerna blir rundade glyfer kantiga eller faller samman. För det andra är TrueType-kurvor kvadratiska Bézier-kurvor medan GDI:s PolyBezierTo tar kubiska, så varje kvadratiskt segment upphöjs exakt i gradtal istället för att plattas till till linjesegment
Gradtalsupphöjning är förlustfri: den kubiska kurvan följer den identiska kurvan, så den renderade konturen matchar vad en kompatibel visare ritar från samma tabell, vid valfri zoom. Det återstående arbetet är placering. Varje glyf är skapad i typsnittsenheter (vanligtvis ett rutnät med 1000 eller 2048 enheter per em), och renderaren sätter samman skalningsmatrisen, textmatrisen och den aktuella transformationsmatrisen till en transformationsmatris från glyf till enhet innan banan fylls. Ordningsföljden är viktigare än den verkar: sätter man samman samma tre matriser baklänges faller varje glyf samman mot nollpunkten – en felaktig sida vars faktiska bugg är en enda rad med matrisalgebra
Hur tolken för Typ 2-charstrings hanterar CFF-typsnitt
THPDFEmbeddedCFF ger FontFile3-program en äkta Typ 2-charstring-tolk: den tolkar CFF INDEX-strukturerna, Top DICT och Private DICT, exekverar sedan varje charstring och skickar bansegment direkt till GDI. En OpenType-wrapper (behållaren OTTO) skalas av först för att nå den rena CFF-tabellen; nakna CIDFontType0C- och Type1C-strömmar konsumeras direkt. Charstrings är ett kompakt stack-språk, och tre av dess konventioner avgör om tolken förblir synkroniserad med byte-strömmen. Det valfria breddprefixet innebär att den första stack-rensande operatorn kan bära en extra inledande operand. Operatorn hintmask innebär en vstemhm när operander fortfarande finns på stacken, och antalet maskbyte att hoppa över beror på det ackumulerade stamantalet (stem count) — räknar man fel på antalet en enda gång kommer varje efterföljande opkod att feltolkas. Dessutom lägger subrutinanrop till en bias till sitt index (107, 1131 eller 32768 beroende på antalet subrutiner) före uppslagning, vilket gör att ett anrop utan bias landar på helt fel subrutin
CID-kodad CFF lägger till en indirektion som kan ställa till det för enkla implementeringer: teckenkoden väljer ett CID, men charstring-indexet är ett GID, och typsnittets teckenuppsättning mappar GID till CID — så renderaren bygger den omvända mappningen från CID till GID före ritning, och väljer Private DICT per glyf via FDSelect för typsnitt som innehåller flera. Namnkodade Type1C-program, den vanliga bäraren för enkla Typ 1-typsnitt, löser istället enbyteskoder via CFF-programmets inbyggda kodning eller via kodningsmekanismen på PDF-nivå som beskrivs härnäst. En ärlig varning: tolken läser hint-operatorer för att hålla strömmen synkroniserad men utför inte hinting, en begränsning som diskuteras i slutet
Vad är ett Typ 3-typsnitt och hur ritas det?
En Typ 3-glyf är inte en kontur överhuvudtaget — ISO 32000-1 §9.6.5 definierar den som een innehållsström, så HotPDF renderar den genom att trycka grafiktillståndet på stacken, sätta samman typsnittsmatrisen, typsnittsstorleken och textmatrisen till CTM, och exekvera glyfproceduren via samma operator-tolk som ritar sidor, med typsnittets egna /Resources i omfång. Två specifikationsdetaljer är viktiga för korrektheten. Typ 3 /Widths uttrycks i glyfutrymme snarare än det 1/1000-textutrymme som alla andra typsnittstyper använder, så framsteg måste passera genom /FontMatrix — streckkodstypsnitt med en 0,01-matris stegar annars fel med en storleksordning. Dessutom ger en glyfprocedur som inleds med operatorn d1 vad renderaren upprätthåller: ritningen klipps till den deklarerade begränsningsrutan, och enligt ISO 32000-1 §9.6.5.2 ignorerar glyfen sina egna färgoperatorer och ritar med anroparens aktuella fyllnadsfärg, så att rg, g, k och deras linje-motsvarigheter inuti proceduren undertrycks under glyfens varaktighet. Hoppar man över färgregeln blir ett d1-streckkodstypsnitt som stämplats i blått av sidan svart; hoppar man över klippningen ritar en felaktig glyf utanför sin cell
Hur teckenkoder blir till glyf-ID:n
Konturtolkar är bara halva jobbet, eftersom byten i en PDF-textsträng är teckenkoder, inte glyfindex, och ISO 32000-1 ägnar två underparagrafer åt mappningen. För enkla typsnitt föreskriver §9.6.6 en strikt prioritet: en /Differences-array åsidosätter baskodningen (WinAnsiEncoding, MacRomanEncoding, eller StandardEncoding), som i sin tur åsidosätter typsnittsprogrammets egna mappning. HotPDF löser upp den kedjan till en 256-posters tabell för kod till GID, och översätter glyfnamn till glyfindex via tre vägar: exakt matchning av teckenuppsättning inuti ett CFF-program, numeriska gNN/glyphNN-namn som tas som bokstavliga index samt Adobe Glyph List-översättning från namn till Unicode följt av en cmap-uppslagning för TrueType-program. För sammansatta typsnitt (composite fonts) sätter §9.7 CIDToGIDMap i täten: det vanligaste fallet är /Identity, men posten kan vara en ström av big-endian-par indexerade efter CID — och HotPDF:s egna Unicode-utdata använder exakt den strömformen för kompakta delmängder, så strömvägen är inget exotiskt särfall
// /CIDToGIDMap as a stream: big-endian Word pairs indexed by CID
if 2 * CID + 1 <= High(MapBytes) then
GID := (MapBytes[2 * CID] shl 8) or MapBytes[2 * CID + 1]
else
GID := 0; // out of range maps to .notdef
När en TrueType-cmap-uppslagning krävs, går HotPDF igenom en fallback-kedja snarare än att förlita sig på en enda undertabell: Windows Unicode-undertabeller (format 4, sedan format 12 för kompletterande plan) kommer först, därefter (3,0)-symbolundertabellen med sin F000-konvention för privata användningsområden speglad till den låga byten — anledningen till att ett symboltypsnitt som Wingdings svarar på vanliga ASCII-koder — och sedan de äldre formaten 6 och 0. Format 2-undertabeller tolkas avsiktligt inte: de mappar äldre multibyte-kodtabeller som Shift-JIS och Big5, inte Unicode, och moderna CJK-typsnitt innehåller ändå alltid en format 4- eller format 12-undertabell. Kod som inte överlever någon av dessa vägar faller tillbaka på GDI-ritning för den enskilda glyfen, så att ett tecken som inte kan mappas endast försämrar en glyf, inte hela textsekvensen
Begränsningar i renderingen
Begränsningarna är värda att nämnas tydligt. Hinting exekveras inte — konturer fylls som de skapades, vilket inte kan skiljas från hintad utskrift av 150 DPI och högre men kan skilja sig med en pixel från en hintad rastererare vid mycket små storlekar. Ursprungliga Typ 1-program i FontFile (eexec-krypterade charstrings) tolkas inte, och OpenType-variabla typsnittsaxlar tillämpas inte. Båda fallen faller, precis som ett skadat typsnittsprogram eller en glyf-tabell utan någon användbar cmap, tillbaka på systemtypsnittsritning snarare än att orsaka fel på sidan. Samma precision-först-metod sträcker sig till andra delar av renderaren — axiella och radiella skuggningsmönster får samma behandling som gradienter förtjänar — och genereringssidan har sin egen historia om typsnittssubtiliteter i hur EndDoc ordnar typsnittsdelmängder
Att använda pipelinen kräver ingen typsnittsspecifik kod överhuvudtaget — varje mekanism ovan aktiveras automatiskt inuti sidrenderingens anrop
var
Pdf: THotPDF;
Bmp: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('invoice-embedded-fonts.pdf') > 0 then
begin
// Embedded TrueType, CFF, and Type 3 fonts render from the
// file itself — nothing needs to be installed on this machine
Bmp := Pdf.RenderLoadedPageToBitmap(0, 144);
if Bmp <> nil then
try
Bmp.SaveToFile('page1.bmp');
finally
Bmp.Free;
end;
end;
finally
Pdf.Free;
end;
end;
Den praktiska konsekvensen är den som din support-inkorg bryr sig om: en PDF som bär på sina egna typsnitt renderas med dessa typsnitt på en byggserver, i en Windows-container eller på en kunds skrivbord som aldrig har sett typsnittet. Den inbäddade renderingspipelinen för glyfer levereras som en del av HotPDF Component för Delphi och C++Builder — ett inbyggt VCL-bibliotek som täcker PDF-skapande, redigering, textextraktion och sidrendering utan externa DLL-beroenden