Når en PDF ikke indlejrer en font, renderer HotPDF-komponenten den tekst med en installeret Windows-font valgt af HPDFMapBaseFontToSystem: den afkoder /BaseFont-navnet, skærer stil-delen af, prøver flere stavemåder, indtil GDI bekræfter, at familien er installeret, måler manglende standard 14-bredder fra metrik-kompatible fonts og konverterer én-byte-koder til Unicode før tegning. Hvert af de trin findes, fordi den naive version fejlede på rigtige filer. RenderLoadedPageToBitmap-siderenderen håndterer indlejrede programmer fint; dette er historien om de fonts, der slet ikke er i filen
Hvorfor tegner GDI i stilhed den forkerte skrifttype for en ikke-indlejret font?
GDI rapporterer aldrig en manglende font: giv CreateFontIndirect et face-navn, den ikke kender, og den vælger i stilhed en erstatning, ofte en anden serif uden en bold-vægt. Den tidlige renderer gav PDF-navnet næsten ordret videre, så TimesNewRoman,Bold, TimesNewRomanPS-BoldMT og SegoeUI-Semibold matchede ingenting og kom ud i, hvad GDI nu valgte. Navne kan være værre end det. ISO 32000-1 §7.3.5 lader et navn skrive enhver byte som #xx, og CJK-producenter staver rutinemæssigt fontnavne som escaped UTF-8 eller legacy code page-bytes; før v2.766.69 blev escapes selv til face-navnet. HPDFMapBaseFontToSystem afkoder nu escapes først, returnerer en gyldig UTF-8 bytesekvens som sine tegn og læser alle andre høje bytes i systemets kodepage
At skære stilen af er der, hvor heuristikkerne bider. Et komma ender altid familien (Arial,Bold giver Arial), men en bindestreg gør det kun, når ordet efter den er en stil: Bold, Italic, Oblique, Regular, Roman, Medium, Light, Black, Heavy, Semi, Demi, Thin, Extra, Ultra eller Condensed. Den regel holder MS-Mincho hel, mens Calibri-Light bliver til Calibri. HotPDF prøver derefter stavemåder af familien, efterfulgt af stavemåder af familien med et PSMT-, MT- eller PS-suffiks fjernet. Siden v2.768.18 dækker de stavemåder hvert valg af mellemrum de steder, et ord kan begynde: før et versal, der følger efter et minuskel (MyriadPro bliver Myriad Pro), ved det sidste versal i et løb, som et minuskel følger (UIGothic), og efter et indledende MS (MSPGothic), fra den fuldt mellemrumslagte form ned til navnet som skrevet; ud over fire sådanne steder prøves kun den fuldt mellemrumslagte og det sammensatte navn. Mellemrum kan ikke anvendes blindt, for Windows holder nogle ord sammen: SimSun er installeret under netop den stavemåde, mens MicrosoftYaHei, MicrosoftJhengHei og MSPGothic hører til Microsoft YaHei, Microsoft JhengHei og MS PGothic. Før v2.768.18 satte mapperen et mellemrum foran hvert indre versal, så MicrosoftYaHei blev slået op som Microsoft Ya Hei og aldrig fundet. En kandidat tæller som installeret, når CreateFontIndirect efterfulgt af GetTextFace returnerer det ønskede navn, eller, siden v2.768.18, når den valgte fonts name-tabel lister den som en familie, fuldt eller typografisk familienavn på ethvert sprog; svaret caches pr. navn, så dokumenter med mange fonts, der ikke er installeret, ikke længere afprøver Windows for hvert navn på hver side
Fordi mappingfunktionen er offentlig i HPDFRenderFontMetrics-uniten, kan en preflight-rapport vise, hvilken installeret familie hver ikke-indlejret font renderes med, ved hjælp af font-enumereringen, som THotPDF allerede eksponerer for indlæste dokumenter:
uses
HPDFDoc, HPDFRenderFontMetrics;
procedure ListSystemFontMappings(Pdf: THotPDF; Log: TStrings);
var
Page, I: Integer;
Info: THPDFLoadedFontInfo;
begin
for Page := 0 to Pdf.LoadedPageCount - 1 do
for I := 0 to Pdf.GetLoadedFontCount(Page) - 1 do
if Pdf.GetLoadedFontInfo(Page, I, Info) and not Info.IsEmbedded then
Log.Add(Format('page %d /%s %s -> %s',
[Page + 1, string(Info.ResourceName), string(Info.FontName),
HPDFMapBaseFontToSystem(Info.FontName)]));
end;
Hvordan måler HotPDF standard 14-fonts, der ikke har /Widths?
HotPDF måler de manglende fremskridt på den installerede font med samme metrik, for ISO 32000-1 §9.6.2.2 tillader standard 14-fonts at udelade /Widths, og biblioteket sender ingen AFM-tabeller med. Arial bærer Helvetica-metrik, Times New Roman bærer Times, og Courier New bærer Courier, så HPDFMeasureBaseFontWidths opretter det matchende face ved lfHeight = -1000 og kalder GetCharWidth32W; ved den højde er resultatet allerede i de 1/1000 em-enheder, PDF-bredder bruger. Rendereren omsætter først hver kode til Unicode gennem /Encoding, /BaseEncoding og /Differences med StandardEncoding som default. SVG-eksport og tekstudtræk rammer endnu en fælde: en standard Type 1-font helt uden /Encoding gav en dekoder uden encoding-information, SVG-eksporten registrerede den aldrig, og hver målt bredde gik ubrugt hen. At levere den underforståede StandardEncoding fikset det, forudsat at den er markeret som en foruddefineret encoding; at sende den ned ad CMap-navne-stien afkoder hver kode som 0, og hver bredde følger med
Bold, kursiv og en off-by-one i font descriptor-flaggene
/Flags-indslagget i en font descriptor nummererer sine bits fra 1, ikke 0, så ForceBold er bit 19 ($40000), og Italic er bit 7 ($40), ifølge ISO 32000-1 tabel 123. Den gamle kode testede $20000, som er bit 18, SmallCap. Fejlen overlevede fra v2.345.0 til v2.766.53, fordi /FontDescriptor næsten altid er en indirekte reference, og font-byggeren kun læste direkte objekter, så hele flags-grenen kørte aldrig, og samme blindhed ignorerede /Widths 12 0 R og satte tekst op med et fallback-fremskridt på 500 enheder. Da v2.766.53 begyndte at opløse indirekte referencer gennem rendereren, skulle biten rettes i samme ændring, ellers ville hver small-caps-face pludselig være renderet bold:
const
// ISO 32000-1 tabel 123 tæller bitpositioner fra 1
FD_ITALIC = $00040; // bit 7
FD_SMALLCAP = $20000; // bit 18, ikke en vægt
FD_FORCEBOLD = $40000; // bit 19
procedure ApplyDescriptorFlags(Flags: Integer; var LF: TLogFont);
begin
if (Flags and FD_FORCEBOLD) <> 0 then
LF.lfWeight := FW_BOLD;
if (Flags and FD_ITALIC) <> 0 then
LF.lfItalic := 1;
end;
Hvorfor får CJK-fonts med et UCS2 CMap de forkerte bredder?
CJK-tekst med et foruddefineret UCS2 CMap tegner de rigtige glyffer, men den forkerte afstand, når en renderer behandler koden som CID, for /W indekseres efter CID, ikke efter tegnkode. Med STSong-Light og UniGB-UCS2-H svarer koden tilfældigvis til Unicode-værdien, så GDI tegner de korrekte tegn, og fejlen gemmer sig i fremskridtene: minuskler ankommer som koder 97 og op, falder uden for et /W-indslag som [1 95 500] og får alle standardbredden /DW på 1000. Siden v2.766.56 læser HotPDF-rendereren koder gennem CMap'ens codespace-intervaller (ISO 32000-1 §9.7.6.2) og mapper dem til CIDs, før bredder slås op. Kun de indbyggede UCS2- og UTF16-tabeller og indlejrede CMap-streams bruges; en identitets-approksimation for noget som GBK-EUC-H ville bare ligne understøttelse, mens den producerede forkert output, så rendereren lader som ingenting
Hvorfor bliver accenterede tegn til spørgsmålstegn på kinesisk Windows?
Én-byte-koder må aldrig nå ANSI-("A")-GDI-funktionerne, for GetGlyphOutlineA og GetGlyphIndicesA fortolker bytes i systemets kodepage, mens TextOutA bruger den valgte fonts tegnsæt. På et kinesisk system blev Arial-byten $A9 (copyright-tegnet i Windows-1252) til en GBK-lead-byte og renderet som "?", en fælde, som stien med unhinted outlines tilføjet i v2.766.83 gik direkte i. v2.767.3 spørger den realiserede font om dens tegnsæt med GetTextCharset, konverterer det til en kodepage gennem TranslateCharsetInfo, kører byten gennem MultiByteToWideChar og kalder W-funktionerne; symbolfonts bruger U+F000 plus koden i stedet. Encodings, der er uenige med Windows-1252 — /Differences, StandardEncoding, MacRomanEncoding — mappes til Unicode, før nogen systemfont ser dem
Hvad er grænserne for at tegne med systemfonts?
Rendering med systemfonts er en approksimation, og HotPDF-komponenten er ærlig omkring, hvor den stopper. Før v2.768.18 sammenlignede installeringstjekket kun det navn, GetTextFace returnerer, og på lokaliseret Windows rapporterer den funktion familienavnet på systemets sprog, så Microsoft YaHei på kinesisk Windows eller Yu Mincho på japansk Windows blev bedømt manglende og tegnet i en GDI-erstatning; siden v2.768.18 slås et face, der kommer tilbage under et andet navn, også op i fontens name-tabel, og sådanne fonts findes. Metrik-kompatibilitet er kun garanteret for Helvetica-, Times- og Courier-familierne; Symbol mappes til Symbol, og ZapfDingbats til Wingdings, hvilket er en nødløsning snarere end et match. Preflighten ovenfor ser også kun fonts i hver sides /Resources-dictionary, ikke dem, der refereres indefra fra form XObjects. Når en kode stadig ikke kan tegnes, rapporterer draw-time unresolved glyph tracking det, hvilket er et bedre signal end at kigge thumbnails efter
Den holdbare fix ligger på forfattersiden. HotPDF skriver selv med FontEmbedding sat til True som standard og erstatter med en indlejret Arial, selv når koden kalder SetFont med Helvetica, og indlejret tekst går gennem den indlejrede font glyph-renderer i stedet for noget af gætteriet ovenfor. En billig vagt for indkommende filer er at advare før rendering, når en mappet familie ikke er i listen over skærmfonts:
// VCL: Screen.Fonts lister installerede familienavne (Forms-uniten)
function MissingSystemFamily(const BaseFont: AnsiString): Boolean;
begin
Result := Screen.Fonts.IndexOf(HPDFMapBaseFontToSystem(BaseFont)) < 0;
end;
For hele komponenten, inklusive side-rendering, tekstudtræk og font subsetting på skrivesiden, se HotPDF Delphi PDF component-produktsiden