HotPDF zet kleur-emoji in een PDF via THotPDF.DrawRegisteredColorGlyph, dat de colordata leest van een font dat bij RegisterUnicodeTTF is geregistreerd en die als native PDF-grafiek uitzendt: COLR v0-lagen als gevulde glyph-outlines, COLR v1-paint-grafen als clips, shadings en blend modes, SVG-glyphs als Form XObjects, en CBDT- of sbix-bitmaps als images. Wat hij niet native kan mappen gaat naar het event OnColorGlyphRasterize in plaats van geruisloos in een zwart silhouet te veranderen
Die laatste clausule is de hele reden dat deze code bestaat. Embedt u een emoji-font op de gewone manier, dan krijgt de viewer de outline uit glyf of CFF, gevuld met wat de huidige vulkleur toevallig is. Het lachende gezicht komt binnen als zwarte klodder, de vlag als rechthoek, en nergens in de pipeline klaagt iemand
Waarom verschijnt een kleur-emoji als zwart silhouet in een PDF?
Een PDF-fontprogramma heeft geen notie van kleurglyphs. ISO 32000-1 behandelt een glyph als een vorm die met de huidige kleur wordt geschilderd, en de kleurtabellen die OpenType later toevoegde, namelijk COLR/CPAL, SVG , CBDT/CBLC en sbix, maken geen deel uit van het PDF-imagingmodel, dus geen enkele viewer is verplicht ze uit een embedded font te lezen. De kleur moet bij het genereren worden vertaald naar pagina-inhoud, op het moment dat de producent nog de fontbytes heeft en weet welke glyph hij wil. Die vertaling verschilt per formaat, en emoji-fonts in het wild gebruiken ze allemaal: gelaagde vectoren, gradient-paint-grafen, embedded SVG-documenten en PNG-strikes. HotPDF meldt het resultaat als THPDFOpenTypeColorFormat, met de waarden otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG en otcfSBIX, en sondert het font in een vaste prioriteit: eerst COLR, dan SVG, dan CBDT, dan sbix. Vectordata wint het van bitmaps zodra een font allebei heeft, en dat is wat u wilt in een document dat misschien wordt ingezoomd of geprint
Eén aanroep, vijf formats: een kleurglyph oplossen en tekenen
THotPDF.GetRegisteredColorGlyphInfo antwoordt welk pad een codepunt zal nemen, en DrawRegisteredColorGlyph neemt het. Beide zoeken het codepunt op in de character map van het font dat het laatst aan RegisterUnicodeTTF werd meegegeven, dus het kleurfont moet op het moment van de aanroep het geregistreerde Unicode-font zijn. De tekenfunctie geeft False terug wanneer de glyph geen colordata heeft of geen pad hem kon renderen, en laat de fallback aan u over
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, CPAL-palet 0, bitmap strike het dichtst bij 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
// Geen colordata: terugvallen op de 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;
Twee parameters verdienen aandacht. PaletteIndex kiest een CPAL-palet, dus een font met een palet voor donkere achtergronden kan worden gewisseld zonder de glyph aan te raken. TargetPixelsPerEm telt alleen voor bitmapfonts; op nul gelaten valt hij terug op Round(FontSize * 96 / 72), een schermresolutie, en daarom vraagt het voorbeeld 300 voor printuitvoer. De eerlijke grens zit in de signatuur: de aanroep neemt één codepunt en mapt het uitsluitend via cmap. ZWJ-reeksen, skin-tone-modifiers en regional-indicator-vlaggen zijn GSUB-ligaturen, dus ze samenstellen is een shaping-probleem van het soort dat in het artikel over OpenType GSUB-alternates wordt behandeld, niet iets wat dit entry point voor u doet
COLR v0: gestapelde glyph-lagen met paletkleuren
COLR v0 is het eenvoudige geval en HotPDF rendert hem rechtstreeks: elke base glyph somt layer-glyphs op met een CPAL-kleurenentry, en elke laag wordt één gewone text-showing-operatie met zijn eigen vulkleur, gestapeld in tabelvolgorde. Een laag met alpha onder 255 krijgt een graphics state parameter dictionary met matchende /ca en /CA (ISO 32000-1 §8.4.5), en elke layer-glyph wordt als gebruikt gemarkeerd zodat de subsetter zijn outline houdt ook al mapt geen enkel codepunt er rechtstreeks naartoe. Eén detail verrast mensen: de palet-entry-index 0xFFFF betekent "use the text foreground color" in de OpenType-specificatie, en HotPDF lost die op naar zwart in plaats van naar de huidige vulkleur van de pagina. Voor emoji-fonts maakt dat zelden uit; bij icon fonts die op de foreground-entry leunen om een glyph te tinten, controleert u de uitvoer voordat u ervan uitgaat dat hij uw tekstkleur volgt
Hoe vertaalt HotPDF een COLR v1-paint-graaf naar PDF-operators?
Door eerst de paint-tabellen te parsen naar een vlakke, begrensde graaf en pas daarna elk knooppunt naar een PDF-constructie te mappen. Een COLR v1-glyph is geen lijst van lagen maar een gerichte acyclische graaf van paint-records, waarin knooppunten via PaintColrLayers en PaintColrGlyph gedeeld kunnen worden. De parser capteert hem op 4096 paint-knooppunten, 64 diepteniveaus en 1024 color stops, en houdt elk knooppunt bij als actief of klaar, zodat een verwijzing terug naar een actief knooppunt, een cyclus die een kwaadwillend font uit laaghergebruik kan bouwen, wordt geweigerd in plaats van in gerecursed. De offset-bases zijn waar een eerste implementatie de mist in gaat. BaseGlyphPaintRecord-offsets zijn relatief aan het begin van BaseGlyphList, paint-offsets in LayerList zijn relatief aan LayerList, en elke Offset24 binnen een paint-tabel is relatief aan die paint-tabel zelf. Los je alle drie tegen dezelfde basis op dan zakken volstrekt legale glyphs door de bounds-check, wat er precies uitziet als een corrupt font. Is de graaf eenmaal gebouwd, dan is de mapping rechttoe rechtaan:
PaintGlyphzet de glyph-outline als clip met text rendering mode 7 (ISO 32000-1 §9.3.6) en schildert daarna zijn kind erin- Solide paints vullen een afgeknipte rechthoek; lineaire gradients worden multi-stop axiale shadings en radiale gradients worden tweekleurige radiale shadings (§8.7.4.5)
- Sweep-gradients hebben geen PDF-equivalent, dus HotPDF benadert ze met 96 egale gekleurde taarten, elk gesampled van de kleurenlijn
- Transformaties worden als
cmuitgezonden, geconjugeerd rond de glyph-baseline-oorsprong, met translaties geschaald doorFontSize / UnitsPerEm PaintComposite-modi 13 tot 27 mappen op de scheidbare en niet-scheidbare PDF-blend modes zoals/Multiply,/Screenen/Luminosity(§11.3.5), gezet via een ExtGState-/BM-entry
De grens staat expliciet vast. Porter-Duff-modi 5 tot 12 (src_in, xor, plus en de rest) hebben geen PDF-tegenhanger als blend mode, de extend-modes repeat en reflect bij lineaire en radiale gradients worden niet uitgezonden, en gradients waarvan de stops verschillende alpha-waarden dragen worden niet geveinsd met één enkele opacity. Radiale gradients met meer dan twee stops houden alleen hun eerste en laatste kleur over. HotPDF toetst de hele graaf aan deze ondersteunde subset voordat er ook maar één operator wordt geschreven, dus een niet-ondersteunde glyph laat de pagina onaangetast en schuift door naar de raster-fallback in plaats van een halve tekening achter te laten
SVG-glyphs en bitmap-strikes
SVG-glyphs gaan door dezelfde begrensde builder die HotPDF voor geïmporteerde SVG-bestanden gebruikt, en het resultaat wordt geregistreerd als Form XObject (§8.10), precies zoals beschreven in het artikel over SVG naar Form XObject. Het document in de SVG -tabel mag gzip-gecomprimeerd zijn; decompressie verloopt in chunks van 8 KB en stopt zodra de uitgebreide grootte 32 MB zou passeren, in plaats van eerst op te blazen en daarna te controleren, en de gecomprimeerde invoer zelf is gecapteerd op 8 MB. Het profiel is met opzet restrictief: scripts, embedded images, externe URL's, data:-URI's en niet-lokale referenties falen gesloten. De form wordt geschaald zodat zijn langste zijde gelijk is aan de fontgrootte en verankerd op de baseline, wat het y-omlaag SVG-coördinatensysteem op het y-omhoog PDF-systeem mapt. Wél weet dat de builder het hele SVG-document voor de glyph ontvangt, zonder selectie van het glyphNNN-element, dus fonts die veel glyphs in één gedeeld document proppen zijn het testen waard voordat u erop vertrouwt
Bitmapfonts zijn een kwestie van strikekeuze en plaatsing. Voor CBDT kiest HotPDF de CBLC-grootte waarvan de verticale ppem het dichtst bij TargetPixelsPerEm zit, accepteert imageformaten 17, 18 en 19, en leest de metrics van formaat 19 uit de CBLC-indexsubtabel omdat dat formaat er geen eigen opslaat. Voor sbix zijn strike-offsets relatief aan de tabel en glyph-offsets aan de strike, en een dupe-record hergebruikt de graphic van een andere glyph terwijl hij zijn eigen origin-offsets houdt; laat je de recursie de buitenste origin overschrijven dan schuift de image. PNG- en JPEG-payloads worden intern gedecodeerd, geschaald met FontSize / PixelsPerEmY in plaats van uitgerekt naar de fontgrootte, en weggeschreven met een soft mask (§11.6.5.3) zodra enige pixel niet volledig dekkend is. sbix-TIFF-payloads worden niet gedecodeerd en gaan naar het event
Wat gebeurt er wanneer een glyph niet native kan worden getekend?
HotPDF vuurt OnColorGlyphRasterize af en plaatst de RGBA-bitmap die uw handler teruggeeft; is er niets toegewezen, of laat de handler Handled op false staan, dan geeft DrawRegisteredColorGlyph False terug en blijft de pagina onveranderd. Het event gaat af voor een COLR v1-graaf buiten de ondersteunde subset, een SVG-document dat de veilige builder weigerde, en een bitmap-payload die de interne decoders niet lezen. De handler krijgt het formaat, de ruwe fontbytes, de geëxtraheerde asset (het SVG-document, mogelijk nog gegzipt, of de bitmapbytes; leeg voor COLR v1), de glyph-ID, het palet en de doelpixelgrootte
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 uw rasterizer, geen HotPDF-API.
// Hij moet exact Width * Height * 4 bytes RGBA teruggeven.
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// Aansluiting
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
HotPDF valideert de uitvoer van de handler voordat hij de pagina aanraakt: nulgroottes, een buffer waarvan de lengte niet exact Width * Height * 4 is, of dimensies groot genoeg om te overlopen worden geweigerd en de aanroep geeft False terug. Een raster-fallback blijft een raster, dus een emoji die zo wordt gerenderd verliest zijn vectorscherpte; vraag een PixelSize die bij uw uitvoerresolutie past. Koppel het kleurpad aan de draw-time-dekkingscontroles uit het artikel over het volgen van ontbrekende glyphs en een pipeline die willekeurige gebruikerstekst verwerkt kan zowel ontbrekende glyphs als glyphs die hun kleur verloren melden
De kleurglyph-renderer, de OpenType-shaping-stack en de veilige SVG-builder zitten allemaal in de HotPDF Delphi PDF component, beschikbaar voor Delphi en C++Builder