Un'emoji faccina sorridente scritta in un PDF con una normale chiamata di testo, usando Segoe UI Emoji come font, esce come una sagoma nera piatta: forma giusta, niente giallo, niente ombreggiatura. Cambiate font con uno emoji CBDT e la stessa chiamata può non disegnare proprio niente, perché quel font conserva le proprie immagini come file PNG e trasporta dati di contorno scarsi o nulli. Copiate la sagoma nera fuori dal viewer e gli appunti contengono ancora U+1F600: il carattere è giusto, solo le tabelle del colore non sono mai state lette
Perché un emoji a colori stampa come sagoma nera in PDF?
Perché un programma di font PDF non ha nessuna nozione di glifo colorato. La ISO 32000-1 tratta ogni glifo come una forma dipinta con il colore di riempimento corrente, e le tabelle colore che OpenType ha aggiunto dopo (COLR/CPAL, SVG , CBDT/CBLC e sbix) non fanno parte del modello di immagine del PDF, così nessun viewer ha motivo di leggerle da un font incorporato. Qualunque colore abbia l'emoji deve essere trasformato in contenuto di pagina mentre il PDF viene scritto, in un punto in cui il produttore tiene ancora i byte del font e sa esattamente quale glifo vuole. Quella traduzione è diversa per ogni formato, e i veri font emoji usano tutte e quattro: livelli vettoriali impilati, grafi di pittura a gradiente, interi documenti SVG e strike PNG
HotPDF fa quella traduzione in THotPDF.DrawRegisteredColorGlyph. Riporta in quale formato un glifo si è risolto come THPDFOpenTypeColorFormat, con i valori otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG e otcfSBIX, e sonda il font in un ordine fisso: COLR, poi SVG, poi CBDT, poi sbix. Un font che porta sia vettori sia bitmap prende quindi la strada vettoriale. Per qualsiasi cosa verrà ingrandita o stampata è l'ordine giusto
Risolvere e disegnare un singolo code point
THotPDF.GetRegisteredColorGlyphInfo vi dice quale strada prenderà un code point; DrawRegisteredColorGlyph la prende. Entrambe cercano il code point nella mappa dei caratteri del font passato più di recente a RegisterUnicodeTTF, quindi il font emoji deve essere il font Unicode registrato al momento della chiamata. La funzione di disegno restituisce False quando il glifo non ha dati a colori o nulla può renderizzarlo, e in quel caso la pagina resta intoccata
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';
// Palette entry 0xFFFF ("text foreground") resolves to this color
Pdf.ColorGlyphForegroundColor := clNavy;
Pdf.BeginDoc;
Pdf.RegisterUnicodeTTF('C:\Windows\Fonts\seguiemj.ttf');
// U+1F600, CPAL palette 0, bitmap strike closest to 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
// No color path: fall back to the monochrome outline
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;
PaletteIndex sceglie una palette CPAL, così un font che spedisce una palette per sfondo scuro si può cambiare senza toccare il glifo. La voce di palette 0xFFFF è speciale nella specifica OpenType: significa "qualunque sia il colore del testo", e HotPDF la riempie da ColorGlyphForegroundColor e ColorGlyphForegroundAlpha (nero e 255 per default). I font emoji la usano raramente. I font di icone sì, e se il vostro tinge i glifi attraverso di essa, impostate la proprietà al colore del vostro testo prima di disegnare
TargetPixelsPerEm conta solo per i font bitmap, e il suo default è il parametro che più vale la pena scavalcare. Lasciato a zero diventa Round(FontSize * 96 / 72), una risoluzione da schermo: 48 pixel per un emoji da 36 punti. Per un font che dichiara strike sbix a 20, 32, 40, 48, 64, 96 e 160 ppem, quel default seleziona lo strike da 48 pixel, e una stampante a 600 dpi deve poi ingrandirlo circa sei volte. Chiedete 300, che è ciò che servono a 36 punti a 600 dpi, e la stessa chiamata prende lo strike 160, il più grande che il font abbia
La firma fissa anche il perimetro. Entra un code point solo e viene mappato attraverso cmap. Le sequenze di famiglia ZWJ, i modificatori di tono della pelle e le bandiere a regional indicator sono legature GSUB, quindi devono prima venire formate in un glifo, il tipo di lavoro trattato in l'articolo sulle alternate GSUB di OpenType
Livelli COLR e grafi di paint come vettori PDF
COLR v0 è il formato semplice. Ogni glifo di base elenca uno stack di glifi di livello, ciascuno con un colore CPAL, e HotPDF riempie il contorno di ogni livello in ordine di tabella. Un livello il cui colore CPAL ha alpha sotto 255 prende il suo /ca, così i livelli traslucidi si fondono con quelli sotto esattamente come intendeva il progettista del font. In un documento ordinario l'intero stack viene poi catturato in un unico gruppo di trasparenza isolato (ISO 32000-1 §11.4). Il gruppo riguarda ciò che accade fuori dal glifo: un'opacità, un blend mode o una soft mask che impostate attorno all'emoji si applica una volta sola al glifo finito, invece di ripetersi su ogni livello e raddoppiarsi dove i livelli si sovrappongono
COLR v1 è un animale diverso: un grafo aciclico diretto di record di paint con gradienti, trasformazioni, clip di glifo annidati e modalità composite, dove i nodi possono essere condivisi attraverso PaintColrLayers e PaintColrGlyph. HotPDF lo analizza prima in un grafo limitato, con tetto a 4096 nodi di paint, 96 livelli di profondità e 1024 color stop, e traccia ogni nodo mentre viene visitato così che un riferimento che torna nel percorso corrente (un ciclo che un font ostile può costruire riutilizzando i livelli) venga respinto invece di essere percorso in ricorsione
Gli offset sono dove i parser COLR v1 scritti a mano di solito si rompono. Gli offset di BaseGlyphPaintRecord sono relativi all'inizio di BaseGlyphList, gli offset di paint di LayerList a LayerList, e ogni Offset24 dentro una tabella di paint a quella tabella di paint stessa. Risolti tutti e tre contro una sola base, glifi perfettamente legali falliscono i controlli dei limiti, il che appare esattamente come un font corrotto. Una volta costruito il grafo, la mappatura verso PDF è per lo più diretta:
PaintGlyphtrasforma il contorno del glifo in un percorso di ritaglio, e il paint figlio riempie l'area ritagliata- I gradienti lineari diventano shading assiali multi-stop e i gradienti radiali diventano shading radiali (§8.7.4.5)
- I gradienti sweep non hanno un tipo di shading PDF, quindi vengono disegnati come 96 spicchi a colore piatto attorno al centro, ciascuno campionato dalla linea di colore
- Le trasformazioni diventano operatori
cm, con le traslazioni scalate diFontSize / UnitsPerEm - Le modalità
PaintCompositeda 13 a 27 si mappano sui blend mode PDF omonimi,/Multiply,/Screen,/Luminositye gli altri (§11.3.5), attraverso una voce/BMin ExtGState
I gradienti con stop traslucidi nascondono un'altra trappola. COLR v1 interpola i colori premoltiplicati per l'alpha, mentre il PDF si aspetta colore diretto e applica l'alpha da sé, così un convertitore che consegna i valori premoltiplicati al PDF fa sì che ogni banda semi-trasparente venga scurita due volte. I colori vanno de-premoltiplicati dopo l'interpolazione e prima di venire scritti
Cosa accade alle modalità composite COLR v1 sotto PDF/A-1 o PDF/X-1a?
Le modalità Porter-Duff in PaintComposite (da 5 a 12: src_in, dest_out, src_atop, xor, plus e i loro fratelli) non hanno un blend mode PDF omonimo, e la lettura ovvia della specifica PDF è che non si possano esprimere. Quella lettura è troppo rigida. Le modalità in/out sono una soft mask di alpha sopra un operando; atop, xor e plus si possono assemblare da forme isolate, alpha di gruppo, maschere di luminosità e aritmetica di blend. HotPDF li costruisce così, quindi questi glifi restano vettoriali
Il costo è che la maggior parte di quelle costruzioni vuole la trasparenza PDF, e PDF/A-1, PDF/X-1a e PDF/X-3 la vietano. Quando PDFACompliance è un valore PDF/A-1 o PDFXCompliance è 'X-1a' o 'X-3', HotPDF controlla il glifo prima di disegnare e instrada al evento raster i casi che non si possono disegnare senza trasparenza: le composite atop, xor e plus, le composite in/out il cui operando mascherato è più di un semplice paint opaco, i paint solidi con alpha parziale, i gradienti con stop traslucidi, e i livelli COLR v0 con alpha sotto 255. Le modalità in e out restano vettoriali quando il lato mascherato è un colore solido o un gradiente semplici, eventualmente avvolti in una clip di glifo o una trasformazione, perché quel caso vuole solo una maschera in scala di grigi opaca. Lo stesso vale per COLR v0 quando StrictVersionLock blocca il file sotto il PDF 1.4
Pdf.PDFACompliance := 'B'; // PDF/A-1b: no transparency allowed
Pdf.BeginDoc;
Pdf.RegisterUnicodeTTF(EmojiFontPath);
if not Pdf.DrawRegisteredColorGlyph(Pdf.CurrentPage, $1F308,
72, 200, EmojiFontName, 36, 0, 300) then
// Without an OnColorGlyphRasterize handler, a translucent
// or composited glyph ends here under PDF/A-1
LogMissingColorGlyph($1F308); // your own logging routine
Se l'archivio deve mostrare gli emoji a colori, puntate a PDF/A-2b invece che a PDF/A-1b. La Parte 2 ammette la trasparenza, ogni costrutto COLR resta vettoriale, e l'handler di ripiego diventa una rete di sicurezza anziché il renderer principale per ogni emoji traslucido. Tenete PDF/A-1 per i flussi di lavoro che lo esigono per nome
Un'impostazione lato lettura sta qui pure. HotPDF composita i gruppi COLR v1 in uno spazio di blend a luce lineare; alcuni renderer perdono i canali molto scuri in tali gruppi e altri ignorano del tutto gli spazi di blend di gruppo, quindi COLRv1DeviceSpaceGroupBlend (default False) lascia che i gruppi si compositino nel normale spazio device. Il compromesso è spiegato in l'articolo sui gradienti repeat e reflect di COLR v1; attivatelo se le tinte scure traslucide spariscono nel viewer che i vostri utenti usano davvero
Glifi SVG e strike bitmap
I glifi SVG passano per lo stesso builder sicuro che HotPDF usa per i file SVG importati, e il risultato viene registrato come Form XObject (§8.10), come descritto in l'articolo da SVG a Form XObject. Il documento nella tabella SVG può essere compresso con gzip. L'input compresso ha un tetto di 8 MB, la decompressione gira a blocchi da 8 KB, e si ferma appena la dimensione espansa supererebbe i 32 MB. Script, foreignObject, URL javascript: e fogli di stile @import fanno sì che il builder rifiuti il documento a prescindere. La form viene scalata così che il suo lato più lungo eguagli la dimensione del font. Una cosa da provare prima della produzione: alcuni font impacchettano molti glifi in un unico documento SVG condiviso, e il builder riceve quell'intero documento per ogni glifo
I font bitmap si riducono alla scelta dello strike e al 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 sottotabella di indice CBLC perché quel formato non ne conserva di proprie. Per sbix vince lo strike più vicino che contiene davvero il glifo; gli offset degli strike sono relativi alla tabella e gli offset dei glifi allo strike. Un record dupe riusa l'immagine di un altro glifo ma tiene i propri offset di origine, e un parser che lascia che la ricerca sovrascriva l'origine esterna sposta l'immagine di lato. I payload PNG e JPEG vengono decodificati internamente, scalati di FontSize / PixelsPerEmY anziché stirati alla dimensione del font, e scritti con una soft mask (§11.6.5.3) ogni volta che qualche pixel è meno che completamente opaco. I payload TIFF in sbix vanno all'evento
Scrivere l'handler di ripiego raster
Quando nessun percorso nativo funziona, HotPDF solleva OnColorGlyphRasterize e piazza la bitmap RGBA che il vostro handler restituisce. Senza handler, o con Handled lasciato a False, DrawRegisteredColorGlyph restituisce False e non viene disegnato nulla. L'handler riceve il formato, i byte del font, l'asset estratto (il documento SVG, eventualmente ancora gzippato, o i byte bitmap; vuoto per COLR), l'ID del glifo, la palette e la dimensione in pixel obiettivo
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 is your rasterizer, not a HotPDF API.
// It must return exactly Width * Height * 4 bytes of RGBA
// with straight (not premultiplied) alpha
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// Wiring
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
HotPDF valida il risultato prima che la pagina lo veda: dimensioni zero, un buffer la cui lunghezza non è esattamente Width * Height * 4, o dimensioni abbastanza grandi da traboccare vengono respinte, e la chiamata restituisce False. Per un font a colori variabile l'handler riceve l'istanza statica ai valori degli assi registrati; se riceve mai il font variabile originale, chiamare GetRegisteredVariableFontAxes su Sender (convertito a THotPDF) restituisce le coordinate a cui renderizzare. PixelSize è lo stesso valore TargetPixelsPerEm, quindi il consiglio sulla risoluzione di stampa vale qui pure
Una pipeline che stampa testo utente arbitrario vuole conoscere due tipi di perdita: i glifi che il font non ha mai avuto, e i glifi che hanno perso il colore. Il tracciamento della copertura al momento del disegno di l'articolo sul tracciamento dei glifi mancanti riporta i primi; un False da DrawRegisteredColorGlyph riporta i secondi
Il rendering di emoji COLR, SVG e bitmap, lo shaping OpenType e il builder SVG sicuro fanno tutti parte dello HotPDF component per Delphi e C++Builder