HotPDF disegna emoji a colori dentro un PDF attraverso THotPDF.DrawRegisteredColorGlyph, che legge i dati colore di un font registrato con RegisterUnicodeTTF e li emette come grafica PDF nativa: layer COLR v0 come contorni di glyph riempiti, grafi di paint COLR v1 come clip, shadings e blend mode, glyph SVG come Form XObject, e bitmap CBDT o sbix come immagini. Tutto ciò che non sa mappare nativamente va all'evento OnColorGlyphRasterize invece di trasformarsi in silenzio in una forma nera
Quella ultima clausola è l'intera ragione per cui questo codice esiste. Includi un font emoji nel modo ordinario e il viewer prende il contorno da glyf o CFF, riempito con qualunque sia il colore di fill corrente. La faccina arriva come chiazza nera, la bandiera come rettangolo, e nella pipeline non si lamenta nessuno
Perché un emoji a colori esce come silhouette nera in PDF?
Un font program PDF non ha nessuna nozione di color glyph. ISO 32000-1 tratta un glyph come una forma dipinta col colore corrente, e le tabelle colore che OpenType ha aggiunto dopo, cioè COLR/CPAL, SVG , CBDT/CBLC e sbix, non fanno parte del modello di imaging PDF, quindi nessun viewer è obbligato a leggerle da un font incluso. Il colore va tradotto in contenuto di pagina al momento della generazione, mentre il producer ha ancora i byte del font e sa quale glyph vuole. Quella traduzione cambia per formato, e i font emoji in giro li usano tutti: vettori a layer, grafi di paint a gradienti, documenti SVG inclusi e strike PNG. HotPDF riporta il risultato come THPDFOpenTypeColorFormat, con i valori otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG e otcfSBIX, e sonda il font in una priorità fissa: prima COLR, poi SVG, poi CBDT, poi sbix. I dati vettoriali battono le bitmap ogni volta che un font porta entrambi, che è ciò che vuoi in un documento che può essere ingrandito o stampato
Una chiamata, cinque formati: risolvere e disegnare un color glyph
THotPDF.GetRegisteredColorGlyphInfo risponde a quale percorso prenderà un code point, e DrawRegisteredColorGlyph lo prende. Entrambe cercano il code point nella character map del font passato più di recente a RegisterUnicodeTTF, quindi il font colore deve essere il font Unicode registrato al momento della chiamata. La funzione di disegno restituisce False quando il glyph non ha dati colore o nessun percorso può renderizzarlo, e lascia a te il fallback
const
FormatNames: array[THPDFOpenTypeColorFormat] of string =
('none', 'COLR v0', 'COLR v1', 'CBDT', 'SVG', 'sbix');
var
Pdf: THotPDF;
Info: THPDFOpenTypeColorGlyphInfo;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'emoji.pdf';
Pdf.BeginDoc;
Pdf.RegisterUnicodeTTF('C:\Windows\Fonts\seguiemj.ttf');
// U+1F600, palette CPAL 0, strike bitmap più vicina a 300 ppem
if Pdf.GetRegisteredColorGlyphInfo($1F600, 0, 300, Info) then
Writeln(Format('GID %d via %s',
[Info.GlyphID, FormatNames[Info.Format]]));
if not Pdf.DrawRegisteredColorGlyph(Pdf.CurrentPage, $1F600,
72, 144, 'Segoe UI Emoji', 36, 0, 300) then
begin
// Nessun dato colore: ripiega sul contorno monocromo
Pdf.CurrentPage.SetFont('Segoe UI Emoji', [], 36, DEFAULT_CHARSET);
Pdf.CurrentPage.TextOut(72, 144, 0, WideString(#$D83D#$DE00));
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Due parametri meritano attenzione. PaletteIndex sceglie una palette CPAL, quindi un font che spedisce una palette per sfondo scuro può essere commutato senza toccare il glyph. TargetPixelsPerEm conta solo per i font bitmap; lasciato a zero fa default a Round(FontSize * 96 / 72), una risoluzione da schermo, ed è per questo che l'esempio chiede 300 per output di stampa. Il limite onesto sta nella firma: la chiamata prende un code point e lo mappa solo attraverso cmap. Le sequenze ZWJ, i modificatori di tono della pelle e le bandiere regional-indicator sono ligature GSUB, quindi comporle è un problema di shaping del tipo coperto in l'articolo sulle alternate OpenType GSUB, non qualcosa che questo punto d'ingresso fa per te
COLR v0: layer di glyph impilati con colori di palette
COLR v0 è il caso semplice e HotPDF lo renderizza direttamente: ogni glyph di base elenca glyph di layer con una voce colore CPAL, e ogni layer diventa una normale operazione text-showing col suo colore di fill, impilata in ordine di tabella. Un layer con alpha sotto 255 riceve un dizionario di graphics state parameter con /ca e /CA corrispondenti (ISO 32000-1 §8.4.5), e ogni glyph di layer viene marcato come usato così il subsetter ne conserva il contorno anche se nessun code point ci mappa direttamente. Un dettaglio sorprende la gente: l'indice di voce di palette 0xFFFF significa «usa il colore di primo piano del testo» nella specifica OpenType, e HotPDF lo risolve in nero anziché nel colore di fill corrente della pagina. Per i font emoji raramente conta; per i font di icone che contano sulla voce di foreground per tingere un glyph, controlla l'output prima di dare per scontato che seguirà il colore del tuo testo
Come trasforma HotPDF un grafo di paint COLR v1 in operatori PDF?
Parsificando prima le tabelle di paint in un grafo piatto e limitato, e solo dopo mappando ogni nodo a un costrutto PDF. Un glyph COLR v1 non è una lista di layer ma un grafo aciclico diretto di record di paint, dove i nodi possono essere condivisi attraverso PaintColrLayers e PaintColrGlyph. Il parser lo limita a 4096 nodi di paint, 64 livelli di profondità e 1024 color stop, e tiene traccia di ogni nodo come attivo o fatto, così un riferimento a un nodo attivo, un ciclo che un font malizioso può costruire riutilizzando i layer, viene rifiutato invece di essere attraversato in ricorsione. Le basi degli offset sono dove una prima implementazione sbaglia. Gli offset di BaseGlyphPaintRecord sono relativi all'inizio di BaseGlyphList, gli offset di paint di LayerList sono relativi a LayerList, e ogni Offset24 dentro una tabella di paint è relativo a quella tabella di paint stessa. Risolvine tre contro la stessa base e glyph perfettamente legali falliscono il controllo dei limiti, con un aspetto identico a quello di un font corrotto. Una volta costruito il grafo, la mappatura è diretta:
PaintGlyphimposta il contorno del glyph come clip con text rendering mode 7 (ISO 32000-1 §9.3.6), poi dipinge il suo figlio dentro- I paint solidi riempiono un rettangolo in clip; i gradienti lineari diventano axial shadings multi-stop e i gradienti radiali diventano radial shadings a due colori (§8.7.4.5)
- I gradienti sweep non hanno equivalente PDF, quindi HotPDF li approssima con 96 spicchi a colore piatto, ognuno campionato dalla linea di colore
- Le trasformazioni vengono emesse come
cm, coniugate attorno all'origine della baseline del glyph, con le traslazioni scalate perFontSize / UnitsPerEm - Le modalità
PaintCompositeda 13 a 27 mappano ai PDF blend mode separabili e non separabili come/Multiply,/Screene/Luminosity(§11.3.5), impostati attraverso una voce/BMdi ExtGState
Il confine è esplicito. Le modalità Porter-Duff da 5 a 12 (src_in, xor, plus e le altre) non hanno controparte come PDF blend mode, le modalità di estensione repeat e reflect sui gradienti lineari e radiali non vengono emesse, e i gradienti i cui stop portano valori di alpha diversi non vengono fintati con una singola opacità. I gradienti radiali con più di due stop conservano solo il primo e l'ultimo colore. HotPDF controlla l'intero grafo contro questo sottoinsieme supportato prima di scrivere un solo operatore, così un glyph non supportato lascia la pagina intatta e passa al fallback raster invece di lasciarsi dietro mezzo disegno
Glyph SVG e strike bitmap
I glyph SVG passano dallo stesso builder limitato che HotPDF usa per i file SVG importati, e il risultato viene registrato come Form XObject (§8.10), esattamente come descritto in l'articolo su SVG verso Form XObject. Il documento nella tabella SVG può essere compresso gzip; la decompressione gira a chunk da 8 KB e si ferma appena la dimensione espansa supererebbe i 32 MB, invece di inflare prima e controllare dopo, e l'input compresso in sé è limitato a 8 MB. Il profilo è restrittivo di proposito: script, immagini incluse, URL esterni, URI data: e riferimenti non locali falliscono chiudendo. La form viene scalata così che il suo lato lungo eguagli la dimensione del font e ancorata sulla baseline, il che mappa il sistema di coordinate y-in basso di SVG su quello y-in alto di PDF. Sappi che il builder riceve l'intero documento SVG del glyph, senza alcuna selezione dell'elemento glyphNNN, quindi i font che impacchettano molti glyph in un documento condiviso meritano un test prima di affidarsi a loro
I font bitmap sono una questione di scelta dello strike e di piazzamento. Per CBDT, HotPDF sceglie la dimensione CBLC il cui ppem verticale è più vicino a TargetPixelsPerEm, accetta i formati immagine 17, 18 e 19, e legge le metriche del formato 19 dalla subtable di indice CBLC perché quel formato non ne memorizza di proprie. Per sbix, gli offset degli strike sono relativi alla tabella e gli offset dei glyph allo strike, e un record dupe riusa la grafica di un altro glyph mantenendo i propri offset di origine; lasciare che la ricorsione sovrascriva l'origine esterna sposta l'immagine. I payload PNG e JPEG vengono decodificati internamente, scalati per FontSize / PixelsPerEmY anziché stirati alla dimensione del font, e scritti con una soft mask (§11.6.5.3) ogni volta che qualche pixel non è del tutto opaco. I payload TIFF di sbix non vengono decodificati e vanno all'evento
Che cosa accade quando un glyph non si può disegnare nativamente?
HotPDF alza OnColorGlyphRasterize e piazza qualunque bitmap RGBA restituisca il tuo handler; se nulla è assegnato, o l'handler lascia Handled false, DrawRegisteredColorGlyph restituisce False e la pagina resta invariata. L'evento scatta per un grafo COLR v1 fuori dal sottoinsieme supportato, un documento SVG che il builder sicuro ha rifiutato, e un payload bitmap che i decoder interni non leggono. L'handler riceve il formato, i byte grezzi del font, l'asset estratto (il documento SVG, forse ancora gzippato, o i byte della bitmap; vuoto per COLR v1), il glyph ID, la palette e la dimensione in pixel di destinazione
type
TEmojiFallback = class
public
procedure Rasterize(Sender: TObject;
Format: THPDFOpenTypeColorFormat; const FontBytes: TBytes;
const AssetData: TBytes; GlyphID: Word;
PaletteIndex, PixelSize: Integer;
out Width, Height: Integer; out RGBA: TBytes;
out Handled: Boolean);
end;
procedure TEmojiFallback.Rasterize(Sender: TObject;
Format: THPDFOpenTypeColorFormat; const FontBytes: TBytes;
const AssetData: TBytes; GlyphID: Word;
PaletteIndex, PixelSize: Integer;
out Width, Height: Integer; out RGBA: TBytes;
out Handled: Boolean);
begin
Width := 0;
Height := 0;
RGBA := nil;
// RenderWithOwnEngine è il tuo rasterizer, non una API HotPDF.
// Deve restituire esattamente Width * Height * 4 byte di RGBA.
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// Cablaggio
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
HotPDF valida l'output dell'handler prima di toccare la pagina: dimensioni zero, un buffer la cui lunghezza non è esattamente Width * Height * 4, o dimensioni abbastanza grandi da andare in overflow vengono rifiutate e la chiamata restituisce False. Un fallback raster resta un raster, quindi un emoji renderizzato così perde la nitidezza vettoriale; chiedi un PixelSize che combaci con la tua risoluzione di output. Accoppia il percorso colore ai controlli di copertura al momento del disegno tratti da l'articolo sul tracciamento dei glyph mancanti e una pipeline che gestisce testo utente arbitrario può riportare sia i glyph mancanti sia i glyph che hanno perso il colore
Il renderer di color glyph, lo stack di shaping OpenType e il builder SVG sicuro sono tutti nella PDF component HotPDF Delphi, disponibile per Delphi e C++Builder