HotPDF dibuja emoji a color en un PDF mediante THotPDF.DrawRegisteredColorGlyph, que lee los datos de color de una fuente registrada con RegisterUnicodeTTF y los emite como gráficos PDF nativos: las capas COLR v0 como contornos de glifo rellenos, los grafos de paint de COLR v1 como clips, shadings y blend modes, los glifos SVG como Form XObjects, y los bitmaps CBDT o sbix como imágenes. Lo que no puede mapear de forma nativa va al evento OnColorGlyphRasterize en lugar de convertirse silenciosamente en una forma negra
Esa última cláusula es toda la razón de que exista este código. 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 esté vigente. La carita 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 sale como una silueta negra en PDF?
Un programa de fuente PDF no conoce la noción de glifos a color. ISO 32000-1 trata un glifo como una forma pintada con el color vigente, y las tablas de color que OpenType agregó después, o sea 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 al contenido de la página en el momento de generar, mientras el productor todavía tiene los bytes de la fuente y sabe qué glifo quiere. Esa traducción difiere por formato, y las fuentes emoji que hay allá afuera usan todas: vectores en capas, grafos de paint con gradientes, documentos SVG incrustados y strikes PNG. HotPDF reporta el resultado como THPDFOpenTypeColorFormat, con los valores otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG y otcfSBIX, y sondea la fuente con una prioridad fija: primero COLR, luego SVG, luego CBDT, luego sbix. Los datos vectoriales le ganan a los bitmaps siempre que la fuente traiga ambos, que es justo lo que usted quiere en un documento que puede ampliarse o imprimirse
Una llamada, cinco formatos: resolver y dibujar un glifo a color
THotPDF.GetRegisteredColorGlyphInfo responde qué camino tomará un code point, y DrawRegisteredColorGlyph lo toma. Ambos buscan el code point en el mapa de caracteres de la fuente pasada más recientemente a RegisterUnicodeTTF, así que la fuente a color tiene que ser la fuente Unicode registrada al momento de la llamada. La función de dibujo devuelve False cuando el glifo no tiene datos de color o ningún camino pudo renderizarlo, y le deja el fallback a usted
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, strike de bitmap 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 selecciona una paleta CPAL, así que una fuente que traiga una paleta para fondo oscuro puede cambiarse sin tocar el glifo. TargetPixelsPerEm solo importa en fuentes bitmap; dejado en cero hace default a Round(FontSize * 96 / 72), una resolución de pantalla, 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 indicadores regionales 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 glifo 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 vuelve una operación común de text-showing con su propio color de relleno, apilada en el orden de la tabla. Una capa con alfa menor que 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 lo mapee 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 del color de relleno vigente de la página. Para fuentes emoji esto rara vez importa; para fuentes de íconos que dependen de la entrada de primer plano para teñir un glifo, revise la salida antes de asumir que seguirá el color de su texto
¿Cómo convierte HotPDF un grafo de paint COLR v1 en operadores PDF?
Parseando las tablas de paint primero en un grafo plano y acotado, y solo entonces mapeando cada nodo a una construcción PDF. Un glifo COLR v1 no es una lista de capas sino un grafo acíclico dirigido de registros de paint, donde los nodos pueden compartirse vía PaintColrLayers y PaintColrGlyph. El parser lo acota a 4096 nodos de paint, 64 niveles de profundidad y 1024 color stops, y sigue cada nodo como activo o terminado, de modo que una referencia a un nodo activo, un ciclo que una fuente maliciosa puede armar reutilizando capas, se rechaza en vez de recursar dentro. 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 misma tabla. Resuelva los tres contra la misma base y glifos perfectamente legales fallan el chequeo de límites, cosa que se ve exactamente como una fuente corrupta. Una vez armado el grafo, el mapeo es directo:
PaintGlyphfija el contorno del glifo como clip con text rendering mode 7 (ISO 32000-1 §9.3.6), y luego pinta a su hijo dentro- Los paints sólidos rellenan un rectángulo recortado; los gradientes lineales se vuelven shadings axiales multi-stop y los radiales, shadings radiales de dos colores (§8.7.4.5)
- Los sweep gradients no tienen equivalente en PDF, así que HotPDF los aproxima con 96 gajos de color plano, cada uno muestreado de la línea de color
- Las transformaciones se emiten como
cm, conjugadas alrededor del origen de la baseline 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 mediante 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 contraparte de blend mode en PDF, los extend modes repeat y reflect en gradientes lineales y radiales no se emiten, y los gradientes cuyos stops llevan valores de alfa distintos no se falsifican con una única opacidad. Los gradientes radiales con más de dos stops conservan solo su primer y último color. HotPDF chequea el grafo completo 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 raster en lugar de dejar medio dibujo tirado
Glifos SVG y strikes de bitmap
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 en la tabla SVG puede venir comprimido con gzip; la descompresión corre en bloques de 8 KB y se detiene apenas el tamaño expandido pasaría de 32 MB, en lugar de inflar primero y chequear después, y la entrada comprimida misma 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. La forma se escala de modo que su lado mayor iguale el tamaño de la fuente y se ancla en la baseline, lo que mapea el sistema de coordenadas SVG con y hacia abajo sobre el de PDF con 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 que usted dependa de ellas
Las fuentes bitmap son una cuestión de elección de strike y de 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 desde 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 offsets de glifo al strike, y un registro dupe reutiliza el gráfico de otro glifo conservando sus propios offsets de origen; dejar que la recursión pise el origen externo desplaza la imagen. Los payloads PNG y JPEG se decodifican internamente, se escalan por FontSize / PixelsPerEmY en lugar de estirarse al tamaño de la 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 se puede dibujar de forma nativa?
HotPDF dispara OnColorGlyphRasterize y coloca el bitmap RGBA que su handler devuelva; si no hay nada asignado, o el handler deja Handled en falso, DrawRegisteredColorGlyph devuelve False y la página queda igual. El evento se dispara para 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;
// Conexión
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
HotPDF valida la salida del handler antes de tocar la página: tamaños en cero, un buffer cuya longitud no sea exactamente Width * Height * 4, o dimensiones lo bastante grandes como para desbordar se rechazan y la llamada devuelve False. Un fallback raster sigue siendo un raster, así que un emoji renderizado así pierde su nitidez vectorial; pida un PixelSize que coincida con su resolución de salida. Combine el camino a color con los chequeos de cobertura al dibujar del artículo de seguimiento de glifos faltantes y un pipeline que procese texto de usuario arbitrario puede reportar tanto glifos faltantes como glifos que perdieron su color
El renderizador de glifos a color, la pila de shaping OpenType y el builder seguro de SVG vienen todos en el HotPDF Delphi PDF component, disponible para Delphi y C++Builder