Quando un PDF non incorpora un font, il componente HotPDF renderizza quel testo con un font Windows installato scelto da HPDFMapBaseFontToSystem: decodifica il nome /BaseFont, toglie la parte di stile, prova diverse grafie finché GDI conferma che la famiglia è installata, misura le larghezze standard 14 mancanti da font metricamente compatibili, e converte i codici a byte singolo in Unicode prima di disegnare. Ognuno di questi passi esiste perché la versione ingenua falliva su file reali. Il renderer di pagine RenderLoadedPageToBitmap se la cava bene con i programmi incorporati; questa è la storia dei font che nel file non ci sono affatto
Perché GDI disegna in silenzio il typeface sbagliato per un font non incorporato?
GDI non riporta mai un font mancante: passa a CreateFontIndirect un nome di carattere che non conosce e seleziona tranquillamente un sostituto, spesso un altro serif senza peso bold. Il renderer primitivo passava il nome PDF quasi verbatim, quindi TimesNewRoman,Bold, TimesNewRomanPS-BoldMT e SegoeUI-Semibold non facevano match con niente e uscivano in qualunque cosa GDI scegliesse. I nomi possono essere peggio. ISO 32000-1 §7.3.5 consente a un nome di scrivere qualsiasi byte come #xx, e i producer CJK spellano di routine i nomi dei font come byte UTF-8 escapati o di code page legacy; prima della v2.766.69 gli escape stessi diventavano il nome del carattere. HPDFMapBaseFontToSystem ora decodifica prima gli escape, restituisce una sequenza di byte UTF-8 valida come suoi caratteri, e legge gli altri byte alti nella code page di sistema
Tagliare lo stile è dove le euristiche mordono. Una virgola chiude sempre la famiglia (Arial,Bold dà Arial), ma un trattino lo fa solo quando la parola dopo è uno stile: Bold, Italic, Oblique, Regular, Roman, Medium, Light, Black, Heavy, Semi, Demi, Thin, Extra, Ultra o Condensed. Quella regola tiene MS-Mincho intero mentre trasforma Calibri-Light in Calibri. HotPDF poi prova le grafie della famiglia, seguite dalle grafie della famiglia con un suffisso PSMT, MT o PS tolto. Dalla v2.768.18 quelle grafie coprono ogni scelta di spazi nei punti in cui una parola può iniziare: prima di una maiuscola che segue una minuscola (MyriadPro diventa Myriad Pro), all'ultima maiuscola di una corsa seguita da una minuscola (UIGothic), e dopo una MS iniziale (MSPGothic), dalla forma completamente spaziata fino al nome così com'è scritto; oltre quattro di questi punti si provano solo la forma completamente spaziata e il nome unito. La spaziatura non può essere applicata alla cieca, perché Windows tiene alcune parole unite: SimSun è installato con esattamente quella grafia, mentre MicrosoftYaHei, MicrosoftJhengHei e MSPGothic appartengono a Microsoft YaHei, Microsoft JhengHei e MS PGothic. Prima della v2.768.18 il mapper metteva uno spazio prima di ogni maiuscola interna, quindi MicrosoftYaHei veniva cercato come Microsoft Ya Hei e mai trovato. Un candidato conta come installato quando CreateFontIndirect seguito da GetTextFace restituisce il nome richiesto o, dalla v2.768.18, quando la tabella name del font selezionato lo elenca come family, full o typographic family name in qualsiasi lingua; la risposta viene messa in cache per nome, quindi i documenti con molti font non installati non interrogano più Windows per ogni nome a ogni pagina
Dato che la funzione di mappatura è pubblica nell'unità HPDFRenderFontMetrics, un report di preflight può mostrare con quale famiglia installata renderizzerà ogni font non incorporato, usando l'enumerazione dei font che THotPDF espone già per i documenti caricati:
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;
Come misura HotPDF i font standard 14 senza /Widths?
HotPDF misura gli avanzamenti mancanti sul font installato con le stesse metriche, perché ISO 32000-1 §9.6.2.2 consente ai font standard 14 di omettere /Widths e la libreria non spedisce tabelle AFM. Arial porta le metriche di Helvetica, Times New Roman porta Times e Courier New porta Courier, quindi HPDFMeasureBaseFontWidths crea il carattere corrispondente a lfHeight = -1000 e chiama GetCharWidth32W; a quell'altezza il risultato è già nelle unità 1/1000 em che le larghezze PDF usano. Il renderer prima converte ogni codice in Unicode attraverso /Encoding, /BaseEncoding e /Differences, con default StandardEncoding. Export SVG ed estrazione del testo beccavano un'altra trappola: un font Type 1 standard senza alcun /Encoding produceva un decoder senza informazioni di codifica, l'export SVG non lo registrava mai, e ogni larghezza misurata andava sprecata. Fornire l'implicita StandardEncoding l'ha sistemato, purché sia marcata come codifica predefinita; instradarla lungo il percorso dei nomi CMap decodifica ogni codice come 0 e ogni larghezza segue
Bold, italic e un off-by-one nei flag del font descriptor
La voce /Flags di un font descriptor numera i propri bit da 1, non da 0, quindi ForceBold è il bit 19 ($40000) e Italic è il bit 7 ($40), per ISO 32000-1 Tabella 123. Il vecchio codice testava $20000, che è il bit 18, SmallCap. L'errore è sopravvissuto dalla v2.345.0 alla v2.766.53 perché /FontDescriptor è quasi sempre un riferimento indiretto e il costruttore di font leggeva solo oggetti diretti, quindi l'intero ramo dei flag non girava mai, e la stessa cecità ignorava /Widths 12 0 R e componeva il testo a un avanzamento di ripiego di 500 unità. Quando la v2.766.53 ha iniziato a risolvere i riferimenti indiretti attraverso il renderer, il bit è stato corretto nella stessa modifica, altrimenti ogni carattere maiuscoletto avrebbe improvvisamente reso in bold:
const
// ISO 32000-1 Tabella 123 conta le posizioni dei bit da 1
FD_ITALIC = $00040; // bit 7
FD_SMALLCAP = $20000; // bit 18, non un peso
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;
Perché i font CJK con un CMap UCS2 prendono le larghezze sbagliate?
Il testo CJK con un CMap UCS2 predefinito disegna i glyph giusti ma la spaziatura sbagliata quando un renderer tratta il codice come il CID, perché /W è indicizzata per CID, non per codice carattere. Con STSong-Light e UniGB-UCS2-H, il codice per caso eguaglia il valore Unicode, quindi GDI disegna i caratteri corretti e il bug si nasconde negli avanzamenti: le lettere minuscole arrivano come codici 97 e oltre, cadono fuori da una voce /W come [1 95 500], e prendono tutte la larghezza di default /DW di 1000. Dalla v2.766.56 il renderer HotPDF legge i codici attraverso gli intervalli codespace del CMap (ISO 32000-1 §9.7.6.2) e li mappa in CID prima di cercare le larghezze. Vengono usate solo le tabelle UCS2 e UTF16 integrate e gli stream CMap incorporati; un'approssimazione identity per qualcosa come GBK-EUC-H sembrerebbe soltanto supportata mentre produce output sbagliato, quindi il renderer non finge
Perché i caratteri accentati diventano punti interrogativi su Windows cinese?
I codici a byte singolo non devono mai arrivare alle funzioni GDI ANSI («A»), perché GetGlyphOutlineA e GetGlyphIndicesA interpretano i byte nella code page di sistema mentre TextOutA usa il set di caratteri del font selezionato. Su un sistema cinese, il byte $A9 di Arial (il simbolo del copyright in Windows-1252) diventava un byte iniziale GBK e si renderizzava come «?», una trappola dentro cui è caduto dritto il percorso outline unhinted aggiunto nella v2.766.83. La v2.767.3 chiede al font realizzato il proprio set di caratteri con GetTextCharset, lo converte in una code page attraverso TranslateCharsetInfo, passa il byte per MultiByteToWideChar e chiama le funzioni W; i font symbol usano U+F000 più il codice. Le codifiche che non sono d'accordo con Windows-1252 — /Differences, StandardEncoding, MacRomanEncoding — vengono mappate in Unicode prima che qualsiasi font di sistema le veda
Quali sono i limiti del disegno con font di sistema?
Il rendering con font di sistema è un'approssimazione, e il componente HotPDF è onesto su dove si ferma. Prima della v2.768.18 il controllo di installazione confrontava solo il nome che GetTextFace restituisce, e su Windows localizzato quella funzione riporta il nome della famiglia nella lingua di sistema, quindi Microsoft YaHei su Windows cinese o Yu Mincho su Windows giapponese era giudicato mancante e disegnato con un sostituto GDI; dalla v2.768.18 un carattere che torna sotto altro nome viene cercato anche nella tabella name del font, e tali font vengono trovati. La compatibilità metrica è garantita solo per le famiglie Helvetica, Times e Courier; Symbol mappa su Symbol e ZapfDingbats su Wingdings, che è un cerotto più che un match. Il preflight qui sopra vede anche solo i font nel dizionario /Resources di ogni pagina, non quelli referenziati dall'interno dei form XObjects. Quando un codice ancora non si può disegnare, il tracciamento dei glyph irrisolti al momento del disegno lo segnala, che è un segnale migliore che guardare le miniature
La correzione durable sta sul lato di chi compone. HotPDF stesso scrive con FontEmbedding impostato a True per default, sostituendo un Arial incorporato anche quando il codice chiama SetFont con Helvetica, e il testo incorporato passa per il renderer dei glyph dei font incorporati invece che per una qualsiasi delle congetture qui sopra. Una guardia economica per i file in arrivo è avvisare prima del rendering quando una famiglia mappata non è nella lista dei font dello schermo:
// VCL: Screen.Fonts elenca i nomi delle famiglie installate (unità Forms)
function MissingSystemFamily(const BaseFont: AnsiString): Boolean;
begin
Result := Screen.Fonts.IndexOf(HPDFMapBaseFontToSystem(BaseFont)) < 0;
end;
Per il componente completo, inclusi rendering di pagine, estrazione del testo e font subsetting sul lato scrittura, vedi la pagina di prodotto di HotPDF Delphi PDF component