HotPDF esegue il rendering di font PDF incorporati in Delphi senza installare nulla sulla macchina: la pipeline di rendering dei glifi incorporata in HPDFGlyphRender.pas analizza i programmi dei font memorizzati all'interno del PDF stesso — contorni glyf TrueType da FontFile2, charstring CFF Type 2 da FontFile3 e flussi di contenuto di glifi Type 3 — e li riproduce come tracciati vettoriali GDI riempiti. Questo articolo è l'approfondimento sulla fedeltà dei caratteri alla base del rendering di pagine PDF in un TBitmap con HotPDF: quell'articolo copre il renderer nel suo complesso, questo copre come il testo su quelle pagine ottiene le sue forme esatte
Perché un PDF viene renderizzato con rettangoli vuoti al posto del testo?
Rettangoli vuoti (boxes), spazi bianchi o caratteri leggermente errati nel PDF renderizzato indicano quasi sempre che il renderer ha richiesto un font al sistema operativo invece di utilizzare quello incorporato nel file. La lamentela si presenta ogni volta nello stesso modo: il documento appare perfetto sulla macchina che lo ha generato, poi un cliente lo apre su un server pulito o su un desktop aziendale con restrizioni e la fattura in giapponese mostra quadrati vuoti (tofu), oppure un carattere alternativo simile sposta ogni interruzione di riga. I font non sono mai stati presenti su quella macchina — solo all'interno del PDF — e un renderer che si limita alla sostituzione con font di sistema non può visualizzarli. I sotto-set di font (subset) peggiorano le cose: un subset può contenere quaranta glifi con codici di carattere assegnati privatamente a quel singolo file, un'assegnazione che nessun font installato condivide
Perché un PDF viene renderizzato con rettangoli vuoti al posto del testo?
Lo standard ISO 32000-1 §9.9 definisce tre supporti per un programma di font incorporato nel descrittore del font: FontFile contiene un programma originale Type 1, FontFile2 un programma TrueType e FontFile3 un CFF puro (Type1C o CIDFontType0C) o un contenitore OpenType. Una quarta variante, il font Type 3 dello standard ISO 32000-1 §9.6.5, non incorpora alcun file binario — ogni glifo è un piccolo flusso di contenuto PDF eseguito sul posto. I tre supporti differiscono nella matematica dei contorni (B-spline quadratiche rispetto a charstring cubiche o operatori di pagina arbitrari), quindi un renderer fedele necessita di un interprete separato per ciascuno, oltre a un livello di codifica che converta i codici dei caratteri nell'indice del glifo corretto prima che venga elaborato qualsiasi contorno
Come converte HotPDF i contorni TrueType glyf in tracciati GDI?
THPDFEmbeddedTTF in HPDFGlyphRender.pas legge la tabella loca per individuare ogni record di glifo, percorre i contorni glyf punto per punto ed emette un tracciato GDI. Due convenzioni TrueType richiedono una gestione esplicita. In primo luogo, i punti fuori curva consecutivi implicano un punto sulla curva nel loro punto medio, e un contorno i cui punti sono tutti fuori curva inizia nel punto medio tra il suo ultimo e primo punto — saltando una di queste regole i glifi arrotondati sviluppano sfaccettature piatte o collassano. In secondo luogo, le curve TrueType sono curve di Bézier quadratiche mentre la funzione GDI PolyBezierTo accetta curve cubiche, quindi ogni segmento quadratico subisce un incremento esatto del grado anziché essere appiattito in segmenti di linea
// Exact degree elevation: quadratic (P0, Q, P2) -> cubic (P0, C1, C2, P2)
// C1 = P0 + 2/3 (Q - P0), C2 = P2 + 2/3 (Q - P2)
C1.X := P0.X + 2 * (Q.X - P0.X) / 3;
C1.Y := P0.Y + 2 * (Q.Y - P0.Y) / 3;
C2.X := P2.X + 2 * (Q.X - P2.X) / 3;
C2.Y := P2.Y + 2 * (Q.Y - P2.Y) / 3;
// then PolyBezierTo with C1, C2, P2 — geometrically identical curve
L'elevazione del grado è priva di perdite: la curva cubica traccia la stessa identica curva, quindi il contorno renderizzato corrisponde a ciò che un visualizzatore conforme disegna dalla stessa tabella, a qualsiasi livello di zoom. Il lavoro rimanente riguarda il posizionamento. Ciascun glifo viene creato in unità di font (solitamente una griglia di 1000 o 2048 unità per em), e il renderer compone la matrice di scala, la matrice del testo e la matrice di trasformazione corrente in un'unica trasformazione da glifo a dispositivo prima che il tracciato venga riempito. L'ordine è più importante di quanto sembri: componendo le stesse tre matrici al contrario, ogni glifo collassa verso l'origine — una pagina dall'aspetto errato il cui bug effettivo è una singola riga di algebra matriciale
In che modo l'interprete di charstring Type 2 gestisce i font CFF
THPDFEmbeddedCFF fornisce ai programmi FontFile3 un vero interprete di charstring Type 2: analizza le strutture INDEX CFF, il Top DICT e il Private DICT, quindi esegue ciascuna charstring ed emette segmenti di tracciato direttamente a GDI. Un contenitore OpenType (il wrapper OTTO) viene prima rimosso per raggiungere la tabella CFF nuda; i flussi puri CIDFontType0C e Type1C vengono elaborati direttamente. Le charstring sono un linguaggio compatto basato su stack, e tre delle sue convenzioni determinano se l'interprete rimane sincronizzato con il flusso di byte. Il prefisso di larghezza opzionale indica che il primo operatore che svuota lo stack può contenere un operando iniziale aggiuntivo. L'operatore hintmask implica un vstemhm quando gli operandi si trovano ancora nello stack, e il numero di byte di maschera da saltare dipende dal numero di gambi (stem) accumulati — sbagliare questo conteggio anche una sola volta rende errata l'interpretazione di ogni codice operativo successivo. Inoltre, le chiamate di subroutine aggiungono un offset (bias) al loro indice (107, 1131 o 32768 a seconda del numero di subroutine) prima della ricerca, per cui una chiamata senza bias fa riferimento a una subroutine completamente errata
I font CFF con chiavi CID aggiungono un'indirezione che mette in difficoltà le implementazioni più semplici: il codice del carattere seleziona un CID, ma l'indice della charstring è un GID, e il set di caratteri (charset) del font mappa i GID nei CID — quindi il renderer crea la mappatura inversa da CID a GID prima del disegno, e seleziona il Private DICT per ciascun glifo tramite FDSelect per i font che ne contengono diversi. I programmi Type1C identificati da nomi, tipici dei font Type 1 semplici, risolvono invece i codici a un byte tramite la codifica integrata del programma CFF o tramite il meccanismo di codifica a livello PDF descritto di seguito. Una precisazione importante: l'interprete legge gli operatori di hint per mantenere il flusso sincronizzato, ma non esegue l'hinting, un limite trattato alla fine
Cos'è un font Type 3 e come viene disegnato?
Un glifo Type 3 non è affatto un contorno — lo standard ISO 32000-1 §9.6.5 lo definisce come un flusso di contenuto, quindi HotPDF lo renderizza salvando lo stato grafico, componendo la matrice del font, la dimensione del font e la matrice del testo sulla matrice di trasformazione corrente (CTM), ed eseguendo la procedura del glifo tramite lo stesso interprete di operatori che disegna le pagine, con il dizionario /Resources del font stesso attivo. Due dettagli delle specifiche sono importanti per la correttezza. I valori /Widths dei caratteri Type 3 sono espressi nello spazio del glifo anziché nello spazio di testo 1/1000 utilizzato da ogni altro tipo di font, quindi i passi di avanzamento devono passare attraverso la /FontMatrix — i font di codici a barre con una matrice di 0.01 altrimenti si sposterebbero con una distanza errata di un ordine di grandezza. Inoltre, una procedura di glifo che inizia con l'operatore d1 richiede due comportamenti che il renderer applica: il disegno è limitato al bounding box dichiarato e, secondo lo standard ISO 32000-1 §9.6.5.2, il glifo ignora i propri operatori di colore e viene disegnato con il colore di riempimento corrente del chiamante, per cui rg, g, k e i relativi operatori di tratto all'interno della procedura vengono soppressi per la durata di quel glifo. Saltando la regola del colore, un font di codici a barre d1 che dovrebbe essere blu viene disegnato in nero; saltando il ritaglio, un glifo malformato disegna al di fuori della propria area
Come i codici dei caratteri diventano ID glifo
Gli interpreti di contorni costituiscono solo metà del lavoro, perché i byte in una stringa di testo PDF sono codici di caratteri, non indici di glifi, e lo standard ISO 32000-1 dedica due sottoclausole a questa mappatura. Per i font semplici, il paragrafo §9.6.6 prescrive una priorità rigorosa: un array /Differences ha la precedenza sulla codifica di base (WinAnsiEncoding, MacRomanEncoding o StandardEncoding), che a sua volta ha la precedenza sulla mappa interna del programma del font. HotPDF risolve questa catena in una tabella da codice a GID a 256 voci, traducendo i nomi dei glifi in indici di glifi tramite tre percorsi: corrispondenza esatta del set di caratteri all'interno di un programma CFF, nomi numerici gNN/glyphNN considerati come indici letterali, e traduzione nome-Unicode secondo l'Adobe Glyph List seguita da una ricerca nella tabella cmap per i programmi TrueType. Per i font compositi, il paragrafo §9.7 affida la gestione a CIDToGIDMap: il caso comune è /Identity, ma la voce può essere un flusso di coppie big-endian indicizzate per CID — e l'output Unicode di HotPDF utilizza proprio questa forma per i sotto-set compatti, quindi il percorso basato su flussi non è un caso insolito
// /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
Cosa non esegue il rendering incorporato
I limiti meritano di essere esposti chiaramente. L'hinting non viene eseguito — i contorni vengono riempiti così come sono definiti, il che risulta indistinguibile da un output con hinting a 150 DPI e oltre, ma può differire di un pixel rispetto a un rasterizzatore con hinting a dimensioni molto ridotte. I programmi Type 1 originali in FontFile (charstring crittografate eexec) non vengono interpretati, e gli assi dei font variabili OpenType non vengono applicati; in entrambi i casi, così come per un programma di font danneggiato o una tabella glyf priva di una cmap utilizzabile, si ricorre al disegno tramite font di sistema piuttosto che causare un errore della pagina. Lo stesso approccio orientato alla massima fedeltà si applica ad altre parti del renderer — i pattern di sfumatura assiale e radiale ricevono lo stesso trattamento riservato ai gradienti — e la parte di generazione ha le sue peculiarità legate ai font in merito a come EndDoc ordina i sotto-set di font
L'utilizzo della pipeline non richiede alcun codice specifico per i font — ogni meccanismo descritto sopra si attiva automaticamente all'interno della chiamata di rendering della pagina
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;
La conseguenza pratica è quella che interessa maggiormente alla gestione del supporto clienti: un PDF che contiene i propri font viene renderizzato con questi font su un server di build, in un contenitore Windows o sul desktop di un cliente che non ha mai installato quel carattere. La pipeline di rendering dei glifi incorporata viene fornita come parte del Componente HotPDF standard per Delphi e C++Builder — una libreria nativa VCL che copre la creazione, la modifica, l'estrazione del testo e il rendering delle pagine PDF senza dipendenze da DLL esterne