HotPDF dessine des emoji couleur dans un PDF via THotPDF.DrawRegisteredColorGlyph, qui lit les données couleur d'une police enregistrée avec RegisterUnicodeTTF et les émet en graphiques PDF natifs : couches COLR v0 comme contours de glyphes remplis, graphes de paint COLR v1 comme clips, shadings et blend modes, glyphes SVG comme Form XObjects, et bitmaps CBDT ou sbix comme images. Tout ce qu'il ne peut pas cartographier nativement part vers l'événement OnColorGlyphRasterize au lieu de se transformer silencieusement en forme noire
Cette dernière phrase est toute la raison d'être de ce code. Embarquez une police emoji de la façon ordinaire et le lecteur récupère le contour depuis glyf ou CFF, rempli de la couleur de remplissage courante, quelle qu'elle soit. Le sourire arrive en tas de noir, le drapeau en rectangle, et rien dans le pipeline ne se plaint
Pourquoi un emoji couleur imprime-t-il en silhouette noire dans un PDF ?
Un programme de police PDF n'a aucune notion de glyphes couleur. ISO 32000-1 traite un glyphe comme une forme peinte avec la couleur courante, et les tables de couleur ajoutées plus tard par OpenType, à savoir COLR/CPAL, SVG , CBDT/CBLC et sbix, ne font pas partie du modèle d'imagerie PDF, donc aucun lecteur n'est obligé de les lire dans une police embarquée. La couleur doit être traduite en contenu de page au moment de la génération, tant que le producteur tient encore les octets de la police et sait quel glyphe il veut. Cette traduction diffère selon le format, et les polices emoji dans la nature utilisent tous : vecteurs en couches, graphes de paint à dégradés, documents SVG embarqués et strikes PNG. HotPDF rapporte le résultat sous forme de THPDFOpenTypeColorFormat, avec les valeurs otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG et otcfSBIX, et sonde la police dans une priorité fixe : COLR d'abord, puis SVG, puis CBDT, puis sbix. Les données vectorielles gagnent contre les bitmaps dès qu'une police porte les deux, ce que vous voulez dans un document appelé à être zoomé ou imprimé
Un appel, cinq formats : résoudre et dessiner un glyphe couleur
THotPDF.GetRegisteredColorGlyphInfo répond au chemin qu'empruntera un point de code, et DrawRegisteredColorGlyph l'emprunte. Tous deux cherchent le point de code dans la table de caractères de la police la plus récemment passée à RegisterUnicodeTTF, donc la police couleur doit être la police Unicode enregistrée au moment de l'appel. La fonction de dessin renvoie False quand le glyphe n'a pas de données couleur ou qu'aucun chemin n'a pu le rendre, et vous laisse le repli
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 la plus proche de 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
// Pas de données couleur : retomber sur le contour monochrome
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;
Deux paramètres méritent l'attention. PaletteIndex choisit une palette CPAL, si bien qu'une police qui livre une palette pour fond sombre peut être commutée sans toucher au glyphe. TargetPixelsPerEm ne compte que pour les polices bitmap ; laissé à zéro il vaut Round(FontSize * 96 / 72) par défaut, une résolution d'écran, et c'est pourquoi l'exemple demande 300 pour une sortie d'impression. La limite honnête est dans la signature : l'appel prend un seul point de code et le cartographie via le seul cmap. Les séquences ZWJ, les modificateurs de teint de peau et les drapeaux à indicateurs régionaux sont des ligatures GSUB, donc les composer est un problème de shaping du genre traité dans l'article sur les alternates GSUB d'OpenType, pas quelque chose que ce point d'entrée fait pour vous
COLR v0 : couches de glyphes empilées avec les couleurs de palette
COLR v0 est le cas simple et HotPDF le rend directement : chaque glyphe de base liste des glyphes de couche avec une entrée de couleur CPAL, et chaque couche devient une opération ordinaire de show-text avec sa propre couleur de remplissage, empilée dans l'ordre de la table. Une couche avec alpha sous 255 reçoit un dictionnaire de paramètres d'état graphique avec /ca et /CA assortis (ISO 32000-1 §8.4.5), et chaque glyphe de couche est marqué utilisé pour que le subsetter garde son contour même si aucun point de code ne pointe vers lui directement. Un détail surprend : l'index d'entrée de palette 0xFFFF signifie « utiliser la couleur de premier plan du texte » dans la spécification OpenType, et HotPDF le résout en noir plutôt qu'en couleur de remplissage courante de la page. Pour les polices emoji, cela compte rarement ; pour les polices d'icônes qui comptent sur l'entrée de premier plan pour teinter un glyphe, vérifiez la sortie avant de supposer qu'elle suivra la couleur de votre texte
Comment HotPDF transforme-t-il un graphe de paint COLR v1 en opérateurs PDF ?
En analysant d'abord les tables de paint en un graphe plat et borné, et seulement ensuite en cartographiant chaque nœud en construction PDF. Un glyphe COLR v1 n'est pas une liste de couches mais un graphe acyclique dirigé d'enregistrements de paint, où les nœuds peuvent être partagés via PaintColrLayers et PaintColrGlyph. L'analyseur le plafonne à 4096 nœuds de paint, 64 niveaux de profondeur et 1024 arrêts de couleur, et suit chaque nœud comme actif ou fait, pour qu'une référence vers un nœud actif — un cycle qu'une police malveillante peut bâtir avec la réutilisation de couches — soit rejetée au lieu d'être récursorée. Les bases d'offset sont là où une première implémentation se trompe. Les offsets BaseGlyphPaintRecord sont relatifs au début de BaseGlyphList, les offsets de paint de LayerList sont relatifs à LayerList, et chaque Offset24 dans une table de paint est relatif à cette table de paint elle-même. Résolvez les trois contre la même base et des glyphes parfaitement légaux échouent au contrôle de bornes, ce qui ressemble exactement à une police corrompue. Une fois le graphe bâti, la cartographie est directe :
PaintGlyphpose le contour du glyphe comme clip avec le mode de rendu de texte 7 (ISO 32000-1 §9.3.6), puis peint son enfant dedans- Les paints unis remplissent un rectangle clippé ; les dégradés linéaires deviennent des shadings axiaux multi-arrêts et les dégradés radiaux des shadings radiaux à deux couleurs (§8.7.4.5)
- Les dégradés sweep n'ont pas d'équivalent PDF, donc HotPDF les approche avec 96 secteurs de couleur plate, chacun échantillonné sur la ligne de couleur
- Les transformations sont émises en
cm, conjuguées autour de l'origine de la ligne de base du glyphe, avec des translations mises à l'échelle parFontSize / UnitsPerEm - Les modes
PaintComposite13 à 27 se cartographient vers les blend modes PDF séparables et non séparables comme/Multiply,/Screenet/Luminosity(§11.3.5), posés via une entrée/BMd'ExtGState
La frontière est explicite. Les modes Porter-Duff 5 à 12 (src_in, xor, plus et les autres) n'ont pas d'équivalent en blend mode PDF, les modes d'extension repeat et reflect des dégradés linéaires et radiaux ne sont pas émis, et les dégradés dont les arrêts portent des valeurs alpha différentes ne sont pas simulés avec une opacité unique. Les dégradés radiaux à plus de deux arrêts ne gardent que leur première et dernière couleurs. HotPDF confronte tout le graphe à ce sous-ensemble supporté avant d'écrire un seul opérateur, si bien qu'un glyphe non supporté laisse la page intacte et passe au repli raster au lieu de laisser derrière lui un demi-dessin
Glyphes SVG et strikes bitmap
Les glyphes SVG passent par le même constructeur borné que HotPDF utilise pour les fichiers SVG importés, et le résultat est enregistré comme Form XObject (§8.10), exactement comme décrit dans l'article SVG vers Form XObject. Le document de la table SVG peut être compressé en gzip ; la décompression tourne par blocs de 8 Ko et s'arrête dès que la taille décompressée franchirait 32 Mo, au lieu de décomprimer d'abord et vérifier ensuite, et l'entrée compressée elle-même est plafonnée à 8 Mo. Le profil est restrictif à dessein : scripts, images embarquées, URLs externes, URIs data: et références non locales échouent fermé. La forme est mise à l'échelle pour que son plus long côté égale la taille de police et ancrée sur la ligne de base, ce qui fait correspondre le système de coordonnées SVG y-vers-le-bas à celui du PDF y-vers-le-haut. Sachez que le constructeur reçoit tout le document SVG du glyphe, sans sélection de l'élément glyphNNN, donc les polices qui entassent beaucoup de glyphes dans un document partagé méritent un test avant que vous vous y fiez
Les polices bitmap sont une affaire de choix de strike et de placement. Pour CBDT, HotPDF choisit la taille CBLC dont le ppem vertical est le plus proche de TargetPixelsPerEm, accepte les formats d'image 17, 18 et 19, et lit les métriques du format 19 depuis la sous-table d'index CBLC parce que ce format n'en stocke aucune lui-même. Pour sbix, les offsets de strike sont relatifs à la table et les offsets de glyphes à la strike, et un enregistrement dupe réutilise le graphique d'un autre glyphe tout en gardant ses propres offsets d'origine ; laisser la récursion écraser l'origine externe décale l'image. Les charges PNG et JPEG sont décodées en interne, mises à l'échelle par FontSize / PixelsPerEmY plutôt qu'étirées à la taille de police, et écrites avec un soft mask (§11.6.5.3) dès qu'un pixel n'est pas complètement opaque. Les charges TIFF de sbix ne sont pas décodées et partent vers l'événement
Que se passe-t-il quand un glyphe ne peut pas être dessiné nativement ?
HotPDF déclenche OnColorGlyphRasterize et pose le bitmap RGBA que votre handler renvoie ; si rien n'est affecté, ou si le handler laisse Handled à false, DrawRegisteredColorGlyph renvoie False et la page reste inchangée. L'événement se déclenche pour un graphe COLR v1 hors du sous-ensemble supporté, un document SVG que le constructeur sûr a refusé, et une charge bitmap que les décodeurs internes ne lisent pas. Le handler reçoit le format, les octets bruts de la police, l'actif extrait (le document SVG, éventuellement encore gzipé, ou les octets du bitmap ; vide pour COLR v1), l'ID du glyphe, la palette et la taille cible en pixels
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 est votre rasterizer, pas une API HotPDF.
// Il doit renvoyer exactement Width * Height * 4 octets de RGBA.
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// Câblage
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
HotPDF valide la sortie du handler avant de toucher à la page : tailles nulles, tampon dont la longueur n'est pas exactement Width * Height * 4, ou dimensions assez grandes pour déborder sont rejetés et l'appel renvoie False. Un repli raster reste un raster, donc un emoji rendu ainsi perd sa netteté vectorielle ; demandez un PixelSize qui colle à votre résolution de sortie. Accouplez le chemin couleur aux contrôles de couverture au dessin tirés de l'article de suivi des glyphes manquants et un pipeline qui gère du texte utilisateur arbitraire peut signaler à la fois les glyphes manquants et les glyphes qui ont perdu leur couleur
Le moteur de rendu de glyphes couleur, la pile de shaping OpenType et le constructeur SVG sûr sont tous livrés dans le composant PDF HotPDF pour Delphi, disponible pour Delphi et C++Builder