HotPDF dibuja emoji a color en un PDF a través de THotPDF.DrawRegisteredColorGlyph, que lee los datos de color de una fuente registrada con RegisterUnicodeTTF y los emite como gráficos PDF nativos: capas COLR v0 como contornos de glifo rellenos, grafos de paint COLR v1 como clips, shadings y blend modes, glifos SVG como Form XObjects, y bitmaps CBDT o sbix como imágenes. Todo lo que no puede mapear de forma nativa va al evento OnColorGlyphRasterize en vez de convertirse silenciosamente en una silueta negra
Esa última cláusula es toda la razón de que este código exista. Incruste una fuente emoji de la manera corriente y el visor recibe el contorno de glyf o CFF, relleno con el color de relleno que haya en ese momento. La cara sonriente llega como una mancha negra, la bandera como un rectángulo, y nada en el pipeline se queja
¿Por qué un emoji a color se imprime como silueta negra en PDF?
Un programa de fuente PDF no conoce los glifos de color. ISO 32000-1 trata un glifo como una forma pintada con el color actual, y las tablas de color que OpenType añadió después, a saber COLR/CPAL, SVG , CBDT/CBLC y sbix, no son parte del modelo de imagen de PDF, así que ningún visor está obligado a leerlas de una fuente incrustada. El color tiene que traducirse a contenido de página en el momento de generar, mientras el producer todavía tiene los bytes de la fuente y sabe qué glifo quiere. Esa traducción cambia según el formato, y las fuentes emoji de la realidad usan todos: vectores por capas, grafos de pintura con degradados, documentos SVG incrustados y strikes PNG. HotPDF informa del resultado como THPDFOpenTypeColorFormat, con los valores otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG y otcfSBIX, y sondea la fuente en una prioridad fija: COLR primero, luego SVG, luego CBDT, luego sbix. Los datos vectoriales ganan a los bitmaps cuando la fuente lleva ambos, que es lo que usted quiere en un documento que puede hacerse zoom o imprimirse
Una llamada, cinco formatos: resolver y dibujar un glifo de color
THotPDF.GetRegisteredColorGlyphInfo responde qué camino tomará un code point, y DrawRegisteredColorGlyph lo recorre. Ambas buscan el code point en el character map de la fuente pasada más recientemente a RegisterUnicodeTTF, así que la fuente de color tiene que ser la fuente Unicode registrada en el momento de la llamada. La función de dibujo devuelve False cuando el glifo no tiene datos de color o ningún camino puede renderizarlo, y le deja a usted el 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, paleta CPAL 0, bitmap strike más cercano 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
// Sin datos de color: caer al 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;
Dos parámetros merecen atención. PaletteIndex elige una paleta CPAL, así que una fuente que trae una paleta de fondo oscuro puede cambiarse sin tocar el glifo. TargetPixelsPerEm solo importa en fuentes bitmap; a cero hace default a Round(FontSize * 96 / 72), una resolución de pantalla, que es la razón por la que el ejemplo pide 300 para salida de impresión. El límite honesto está en la firma: la llamada toma un code point y lo mapea solo vía cmap. Las secuencias ZWJ, los modificadores de tono de piel y las banderas de regional-indicator son ligaduras GSUB, así que componerlas es un problema de shaping del tipo que cubre el artículo de alternates GSUB de OpenType, no algo que este punto de entrada haga por usted
COLR v0: capas de glifos apiladas con colores de paleta
COLR v0 es el caso simple y HotPDF lo renderiza directamente: cada glifo base lista glifos de capa con una entrada de color CPAL, y cada capa se convierte en una operación de texto corriente con su propio color de relleno, apilada en orden de tabla. Una capa con alfa por debajo de 255 recibe un diccionario de parámetros de estado gráfico con /ca y /CA correspondientes (ISO 32000-1 §8.4.5), y cada glifo de capa se marca como usado para que el subsetter conserve su contorno aunque ningún code point mapee a él directamente. Un detalle sorprende a la gente: el índice de entrada de paleta 0xFFFF significa «use el color de primer plano del texto» en la especificación OpenType, y HotPDF lo resuelve a negro en lugar de al color de relleno actual de la página. Para fuentes emoji eso rara vez importa; para fuentes de iconos que dependen de la entrada de foreground para teñir un glifo, revise la salida antes de dar por hecho que seguirá el color de su texto
¿Cómo convierte HotPDF un grafo de paint COLR v1 en operadores PDF?
Parseando primero las tablas de paint a un grafo plano y acotado, y solo después mapeando cada nodo a una construcción PDF. Un glifo COLR v1 no es una lista de capas sino un grafo dirigido acíclico de registros de paint, donde los nodos pueden compartirse vía PaintColrLayers y PaintColrGlyph. El parser lo limita a 4096 nodos de paint, 64 niveles de profundidad y 1024 color stops, y lleva la cuenta de cada nodo como activo o terminado, de modo que una referencia hacia atrás a un nodo activo, un ciclo que una fuente maliciosa puede montar con reutilización de capas, se rechaza en vez de recursarse. Las bases de los offsets son donde una primera implementación se equivoca. Los offsets de BaseGlyphPaintRecord son relativos al inicio de BaseGlyphList, los offsets de paint de LayerList son relativos a LayerList, y cada Offset24 dentro de una tabla de paint es relativo a esa tabla de paint misma. Resuelva los tres contra la misma base y glifos perfectamente legales fallan el chequeo de límites, que se ve exactamente como una fuente corrupta. Una vez construido el grafo, el mapeo es directo:
PaintGlyphfija el contorno del glifo como clip con modo de renderizado de texto 7 (ISO 32000-1 §9.3.6), y luego pinta a su hijo dentro- Los paints sólidos rellenan un rectángulo recortado; los degradados lineales se convierten en shadings axiales multi-stop y los radiales en shadings radiales de dos colores (§8.7.4.5)
- Los degradados sweep no tienen equivalente PDF, así que HotPDF los aproxima con 96 cuñas de color plano, cada una muestreada de la línea de color
- Las transformaciones se emiten como
cm, conjugadas alrededor del origen de la línea base del glifo, con traslaciones escaladas porFontSize / UnitsPerEm - Los modos 13 a 27 de
PaintCompositese mapean a los blend modes PDF separables y no separables como/Multiply,/Screeny/Luminosity(§11.3.5), fijados vía una entrada/BMde ExtGState
El límite es explícito. Los modos Porter-Duff 5 a 12 (src_in, xor, plus y el resto) no tienen equivalente como blend mode PDF, los modos de extensión repeat y reflect en degradados lineales y radiales no se emiten, y los degradados cuyos stops llevan valores de alfa distintos no se falsifican con una única opacidad. Los degradados radiales con más de dos stops conservan solo su primer y último color. HotPDF comprueba el grafo entero contra este subconjunto soportado antes de escribir un solo operador, así que un glifo no soportado deja la página intacta y pasa al fallback rasterizado en lugar de dejar medio dibujo tirado
Glifos SVG y bitmap strikes
Los glifos SVG pasan por el mismo builder acotado que HotPDF usa para archivos SVG importados, y el resultado se registra como Form XObject (§8.10), exactamente como describe el artículo de SVG a Form XObject. El documento de la tabla SVG puede estar comprimido con gzip; la descompresión corre en bloques de 8 KB y se para en cuanto el tamaño expandido pasaría de 32 MB, en lugar de inflar primero y comprobar después, y la entrada comprimida en sí tiene un tope de 8 MB. El perfil es restrictivo a propósito: scripts, imágenes incrustadas, URLs externas, URIs data: y referencias no locales fallan cerrado. El form se escala para que su lado mayor iguale el tamaño de fuente y se ancla en la línea base, lo que mapea el sistema de coordenadas SVG con la y hacia abajo al de PDF con la y hacia arriba. Tenga presente que el builder recibe el documento SVG completo del glifo, sin selección del elemento glyphNNN, así que las fuentes que empaquetan muchos glifos en un documento compartido merecen una prueba antes de fiarse de ellas
Las fuentes bitmap son una cuestión de elección de strike y colocación. Para CBDT, HotPDF elige el tamaño CBLC cuyo ppem vertical esté más cerca de TargetPixelsPerEm, acepta los formatos de imagen 17, 18 y 19, y lee las métricas del formato 19 de la subtabla de índice CBLC porque ese formato no guarda ninguna propia. Para sbix, los offsets de strike son relativos a la tabla y los de glifo relativos al strike, y un registro dupe reutiliza el gráfico de otro glifo conservando sus propios offsets de origen; dejar que la recursión sobreescriba el origen exterior desplaza la imagen. Los payloads PNG y JPEG se decodifican internamente, se escalan por FontSize / PixelsPerEmY en vez de estirarse al tamaño de fuente, y se escriben con soft mask (§11.6.5.3) siempre que algún píxel no sea totalmente opaco. Los payloads TIFF de sbix no se decodifican y van al evento
¿Qué pasa cuando un glifo no puede dibujarse de forma nativa?
HotPDF lanza OnColorGlyphRasterize y coloca el bitmap RGBA que su handler devuelva; si nada está asignado, o el handler deja Handled en false, DrawRegisteredColorGlyph devuelve False y la página queda como está. El evento se dispara por un grafo COLR v1 fuera del subconjunto soportado, un documento SVG que el builder seguro rechazó, y un payload bitmap que los decodificadores internos no leen. El handler recibe el formato, los bytes crudos de la fuente, el asset extraído (el documento SVG, posiblemente aún gzipeado, o los bytes del bitmap; vacío para COLR v1), el ID del glifo, la paleta y el tamaño de píxel objetivo
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 es su rasterizador, no una API de HotPDF.
// Debe devolver exactamente Width * Height * 4 bytes de RGBA.
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// Cableado
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
HotPDF valida la salida del handler antes de tocar la página: tamaños cero, un buffer cuya longitud no es exactamente Width * Height * 4, o dimensiones lo bastante grandes como para desbordar se rechazan y la llamada devuelve False. Un fallback rasterizado sigue siendo un raster, así que un emoji renderizado así pierde su nitidez vectorial; pida un PixelSize que iguale su resolución de salida. Empareje el camino de color con chequeos de cobertura en el momento de dibujar del artículo de seguimiento de glifos ausentes y un pipeline que maneja texto de usuario arbitrario puede informar tanto de glifos ausentes como de glifos que perdieron su color
El renderizador de glifos de color, la pila de shaping OpenType y el builder seguro de SVG llegan todos en el componente PDF HotPDF para Delphi, disponible para Delphi y C++Builder