PDFlibPas risolve i caratteri che il font selezionato non può disegnare cercando in una catena di fallback di face installate, cluster per cluster, preservando lo shaping e l'ordine dei run bidirezionali. Lo attivi con SetAutomaticFontFallback, estendi la catena con AddFontFallback, e solo i font di fallback effettivamente usati per l'output vengono incorporati nel file
Il problema che risolve è uno che ogni generatore di documenti incontra la prima volta che il nome di un cliente arriva in una scrittura che il font del template non aveva mai previsto. Il fallimento è silenzioso, ed è ciò che lo rende costoso
Perché il testo non supportato svanisce invece di generare un errore?
Perché PDF non ha alcun concetto di font che non può disegnare un carattere. Un font semplice mappa i codici byte su nomi di glifo tramite una codifica; un font composito mappa i codici tramite una CMap su indici di glifo. Chiedi un glifo che la face non contiene e ottieni l'indice di glifo zero, .notdef, che la maggior parte delle face disegna come niente o come un riquadro vuoto. Il file è strutturalmente valido, l'operatore di testo è ben formato, e la pagina viene renderizzata. È solo vuota dove dovrebbe esserci il nome
Nulla in ISO 32000-1 richiede che un producer se ne accorga. Un generatore che scrive testo senza controllare la copertura produce un PDF tecnicamente conforme che ha perso contenuto in silenzio, e la perdita emerge sullo schermo di un cliente settimane dopo. Ecco perché la funzionalità di fallback e il report dei glifi mancanti vengono distribuiti insieme: risolvere ciò che può essere risolto è solo metà del lavoro, e segnalare ciò che non ha potuto essere risolto è l'altra metà
Il fallback avviene per cluster, non per code point
La granularità è il dettaglio che separa un'implementazione funzionante da una plausibile. Il testo non è una sequenza di caratteri indipendenti. Una sillaba devanagari, un'emoji con un modificatore di tono della pelle, una lettera base con segni combinanti: ciascuno è un cluster che deve essere renderizzato da un solo font, perché le decisioni di shaping al suo interno dipendono da tabelle in quella face
PDFlibPas risolve i cluster, quindi un cluster coperto da una face di fallback viene disegnato interamente da quella face. Dividere a metà cluster e disegnare metà dal font primario e metà da un fallback produrrebbe un risultato tecnicamente presente ma visibilmente rotto, il che è probabilmente peggio del vuoto da cui si era partiti. Anche l'ordine dei run viene preservato, quindi un fallback dentro un run da destra a sinistra non riordina il testo circostante; lo stesso meccanismo è alla base del layout verticale descritto in scrittura verticale per giapponese e cinese
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutomaticFontFallback(1);
// Ordine di ricerca: vince la prima corrispondenza, quindi metti le face più generiche per ultime
Lib.AddFontFallback('Microsoft YaHei'); // Cinese semplificato
Lib.AddFontFallback('Meiryo'); // Giapponese
Lib.AddFontFallback('Segoe UI Symbol');
Lib.AddFontFallback('Segoe UI Emoji');
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_REPORT);
Lib.AddTrueTypeFont('Arial', 1); // 1 = embed the face
Lib.SetTextSize(11);
Lib.DrawText(72, 720, 'Invoice for 北京示例科技有限公司');
Lib.DrawText(72, 700, 'Delivery status: on time');
Lib.SaveToFile('invoice.pdf');
finally
Lib.Free;
end;
end;
Ordina la catena deliberatamente. La risoluzione prende la prima face che copre il cluster, quindi un font pan-Unicode generico messo per primo vincerà quasi tutto e le tue face specifiche per script scelte con cura non verranno mai consultate. Metti per prime le face specifiche e per ultima quella generica
Report o abort: quale fallimento vuoi?
SetMissingGlyphPolicy accetta PDF_MISSING_GLYPH_REPORT, il valore predefinito compatibile, oppure PDF_MISSING_GLYPH_ABORT. Con la policy report l'operazione di testo procede, i code point non risolvibili vengono scartati come prima, e ciascuno viene registrato. Con la policy abort l'operazione di testo viene rifiutata prima che venga scritto qualunque contenuto e LastErrorCode viene impostato a 521
Scegli in base a cosa serve il documento. Un batch di report interni dovrebbe continuare a renderizzare e registrare le lacune, perché un report leggermente incompleto oggi batte nessun report. Un contratto legalmente vincolante, una fattura, o qualunque cosa porti un nome dovrebbe interrompersi, perché un carattere scartato in silenzio nel nome di una parte è un difetto che vuoi scoprire nel tuo processo, non in una controversia. La policy abort fallisce prima della scrittura, così non resta alcun content stream a metà formato
var
Lib: TPDFlib;
Report: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_ABORT);
// ... build the document ...
if Lib.DrawText(72, 660, CustomerName) <> 1 then
if Lib.LastErrorCode = PDFLIB_ERROR_MISSING_GLYPH then
begin
Report := Lib.GetMissingGlyphReportJSON;
// {"valid":false,"policy":1,"eventCount":1,"events":[
// {"sequence":1,"documentIndex":0,"page":1,"utf16Index":12,
// "codePoint":21271,"unicode":"U+5317","fontName":"Arial",
// "fontType":"TrueType","operation":"DrawText"}]}
EscalateToOperator(Report);
end;
finally
Lib.Free;
end;
end;
Il report è deliberatamente leggibile da macchina e limitato. Ogni evento porta la pagina, l'indice UTF-16 dentro la stringa, il code point sia in forma numerica sia in forma U+XXXX, il font che è stato selezionato, il suo tipo e l'operazione che ha incontrato il problema, così un ticket di supporto può nominare il carattere esatto invece di descrivere un sintomo. Il tracker mantiene i 256 eventi più recenti, il che è sufficiente per diagnosticare un documento ed è abbastanza piccolo perché un'esecuzione patologica non possa trasformare la diagnostica in un problema di memoria
Misurazione e disegno devono concordare
La misurazione della larghezza usa le stesse decisioni di fallback cluster-aware del disegno. Sembra ovvio ed è la cosa che la maggior parte dei livelli di fallback fatti in casa sbaglia: mettono una toppa sul percorso di disegno, lasciano la misurazione sul font primario, e ogni casella di testo, allineamento a destra e colonna di tabella finisce per essere calcolato da larghezze che non corrispondono a ciò che è stato renderizzato
Poiché entrambi i percorsi condividono la risoluzione, una stringa misurata prima del disegno occupa la larghezza con cui è stata misurata, run di fallback inclusi. Questo è ciò che rende sicuro attivare il fallback globalmente invece che solo nei punti che hai verificato a mano
Viene incorporato solo ciò che hai usato
I font di fallback vengono incorporati in modo lazy: una face nella catena che non ha mai risolto un cluster non contribuisce in nulla all'output. Un documento che contiene un carattere cinese e 5.000 caratteri latini non porta una face CJK completa; porta ciò che il passaggio di subsetting ha prodotto per quel singolo glifo, il che è il comportamento descritto in ottimizzazione delle dimensioni del file e font subsetting
Questa pigrizia rende economico configurare una catena ampia. Registra le face di cui il tuo set di documenti potrebbe avere bisogno in ogni locale che servi, e ogni singolo PDF paga solo per ciò che ha effettivamente usato. Per i documenti che non hai generato tu, dove le face mancanti sono già dentro un file esistente, il percorso di riparazione è diverso ed è trattato in incorporare font mancanti in un PDF esistente
Un avvertimento sul deployment vale la pena dichiararlo chiaramente: il fallback si risolve rispetto alle face installate sulla macchina che esegue il codice. Un server senza font CJK installati non ha nulla a cui ricorrere, e il report te lo dirà già al primo documento invece che dopo il primo reclamo. Distribuisci i font da cui dipendi, e verifica la licenza per la loro incorporazione
PDFlibPas è una libreria PDF per Delphi, C++Builder e Lazarus con interfacce DLL e ActiveX corrispondenti, quindi le API di fallback e dei glifi mancanti sono disponibili anche da chiamanti non Pascal. La documentazione completa è sulla pagina della libreria PDF Delphi PDFlibPas