HotPDF tegner color emoji ind i en PDF gennem THotPDF.DrawRegisteredColorGlyph, som læser colordataene fra en font registreret med RegisterUnicodeTTF og udsender dem som native PDF-grafik: COLR v0-lags som udfyldte glyph-outlines, COLR v1 paint-grafer som clips, shadings og blend modes, SVG-glyphs som Form XObjects og CBDT- eller sbix-bitmaps som billeder. Alt, hvad den ikke kan mappe nativt, går til OnColorGlyphRasterize-eventet i stedet for lydløst at ende som en sort form
Den sidste klausul er hele grunden til, at koden findes. Embedder du en emoji-font på den almindelige måde, får viewer'en outline'en fra glyf eller CFF, udfyldt med, hvad end den aktuelle fyldfarve nu er. Det smilende ansigt ankommer som en sort klump, flaget som et rektangel, og intet i pipelinen brokker sig
Hvorfor printer en color emoji som en sort silhuet i PDF?
Et PDF-fontprogram har intet begreb om color glyphs. ISO 32000-1 behandler en glyph som en form malet med den aktuelle farve, og de colortabeller, OpenType tilføjede senere, nemlig COLR/CPAL, SVG , CBDT/CBLC og sbix, er ikke en del af PDF imaging-modellen, så ingen viewer er forpligtet til at læse dem fra en embedded font. Farven skal oversættes til sideindhold ved genereringstidspunktet, mens producenten stadig har font-bytes og ved, hvilken glyph den vil have. Den oversættelse adskiller sig pr. format, og emoji-fonts derude bruger dem alle: lagdelte vektorer, gradient paint-grafer, embeddede SVG-dokumenter og PNG strikes. HotPDF rapporterer resultatet som THPDFOpenTypeColorFormat med værdierne otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG og otcfSBIX og prober fonten i en fast prioritet: COLR først, derefter SVG, derefter CBDT, derefter sbix. Vektordata vinder over bitmaps, når en font bærer begge, hvilket er, hvad du vil have i et dokument, der kan zoomes eller printes
Ét kald, fem formater: at resolve og tegne en color glyph
THotPDF.GetRegisteredColorGlyphInfo svarer på, hvilken sti et kodepunkt vil tage, og DrawRegisteredColorGlyph tager den. Begge slår kodepunktet op i character map for den font, der senest blev givet til RegisterUnicodeTTF, så color-fonten skal være den registrerede Unicode-font på kaldstidspunktet. Tegnefunktionen returnerer False, når glyphen ikke har colordata, eller ingen sti kunne render den, og overlader fallback til dig
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-palette 0, bitmap strike nærmest 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
// Ingen colordata: falder tilbage til den 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;
To parametre fortjener opmærksomhed. PaletteIndex vælger en CPAL-palette, så en font, der leverer en palette til mørk baggrund, kan skiftes uden at røre glyphen. TargetPixelsPerEm betyder kun noget for bitmap-fonts; sat til nul default'er den til Round(FontSize * 96 / 72), en skærmopløsning, hvilket er grunden til, at eksemplet beder om 300 til print-output. Den ærlige grænse sidder i signaturen: kaldet tager ét kodepunkt og mapper det gennem cmap alene. ZWJ-sekvenser, skin-tone-modifiers og regional-indicator-flags er GSUB-ligaturer, så at komponere dem er et shaping-problem af den slags, der dækkes i artiklen om OpenType GSUB-alternater, ikke noget, dette entry point gør for dig
COLR v0: stablede glyph-lags med palettefarver
COLR v0 er det simple tilfælde, og HotPDF renderer det direkte: hver base-glyph lister lag-glyphs med en CPAL-farve-entry, og hvert lag bliver én almindelig text-showing-operation med sin egen fyldfarve, stakket i tabelrækkefølge. Et lag med alpha under 255 får en graphics state parameter dictionary med matchende /ca og /CA (ISO 32000-1 §8.4.5), og hver lag-glyph markeres som brugt, så subsetteren beholder dens outline, selvom intet kodepunkt mapper til den direkte. Én detalje overrasker folk: palette-entry-indekset 0xFFFF betyder "brug tekstens forgrundsfarve" i OpenType-specifikationen, og HotPDF resolver den til sort i stedet for til sidens aktuelle fyldfarve. For emoji-fonts betyder det sjældent noget; for icon fonts, der læner sig op ad foreground-entryen for at tone en glyph, så tjek outputtet, før du antager, at den følger din tekstfarve
Hvordan omsætter HotPDF en COLR v1 paint-graf til PDF-operatorer?
Ved først at parse paint-tabellerne til en flad, afgrænset graf og først derefter mappe hver node til en PDF-konstruktion. En COLR v1-glyph er ikke en liste af lags, men en directed acyclic graf af paint-records, hvor noder kan deles gennem PaintColrLayers og PaintColrGlyph. Parseren begrænser den til 4096 paint-noder, 64 niveauer af dybde og 1024 color stops og sporer hver node som aktiv eller færdig, så en reference tilbage til en aktiv node — en cyklus, en ondsindet font kan bygge af lag-genbrug — afvises i stedet for at rekurreres ind i. Offset-baserne er, hvor en første implementering går galt. BaseGlyphPaintRecord-offsets er relative til starten af BaseGlyphList, LayerList-paint-offsets er relative til LayerList, og hver Offset24 inde i en paint-tabel er relativ til selve den paint-tabel. Resolver du alle tre mod samme base, fejler fuldt lovlige glyphs bounds-tjekket, hvilket ligner en korrupt font til forveksling. Når grafen er bygget, er mappingen direkte:
PaintGlyphsætter glyph-outlineen som et clip med text rendering mode 7 (ISO 32000-1 §9.3.6) og maler derefter sit child indeni- Solide paints udfylder et clippet rektangel; lineære gradients bliver multi-stop axiale shadings, og radiale gradients bliver tokolorede radiale shadings (§8.7.4.5)
- Sweep gradients har intet PDF-modstykke, så HotPDF approksimerer dem med 96 ensfarvede kilesnit, hver sampleret fra color line
- Transforms udsendes som
cm, konjugeret omkring glyphens baseline-origin, med translationer skaleret medFontSize / UnitsPerEm PaintComposite-modes 13 til 27 mapper til de separable og ikke-separable PDF blend modes som/Multiply,/Screenog/Luminosity(§11.3.5), sat gennem en ExtGState/BM-entry
Grænsen er eksplicit. Porter-Duff-modes 5 til 12 (src_in, xor, plus og resten) har intet PDF blend mode-modstykke, repeat- og reflect-extend-modes på lineære og radiale gradients udsendes ikke, og gradients, hvis stops bærer forskellige alpha-værdier, fakes ikke med én enkelt opacity. Radiale gradients med mere end to stops beholder kun deres første og sidste farver. HotPDF tjekker hele grafen mod dette understøttede subsæt, før en eneste operator skrives, så en ikke-understøttet glyph efterlader siden urørt og går videre til raster-fallback i stedet for at efterlade halvdelen af en tegning
SVG-glyphs og bitmap strikes
SVG-glyphs går gennem den samme afgrænsede builder, som HotPDF bruger til importerede SVG-filer, og resultatet registreres som et Form XObject (§8.10), præcis som beskrevet i artiklen om SVG til Form XObject. Dokumentet i SVG -tabellen kan være gzip-komprimeret; dekomprimeringen kører i 8 KB-chunks og stopper, så snart den ekspanderede størrelse ville overstige 32 MB, i stedet for først at inflate og tjekke bagefter, og det komprimerede input selv er begrænset til 8 MB. Profilen er restriktiv med vilje: scripts, embeddede billeder, eksterne URL'er, data:-URI'er og ikke-lokale referencer fejler lukket. Formen skaleres, så dens længste side svarer til fontstørrelsen, og forankres på baselinjen, hvilket mapper det y-ned SVG-koordinatsystem over på det y-op PDF-system. Vær opmærksom på, at builderen modtager hele SVG-dokumentet for glyphen uden nogen udvælgelse af glyphNNN-elementet, så fonts, der pakker mange glyphs ind i ét delt dokument, er værd at teste, før du læner dig op ad dem
Bitmap-fonts er et spørgsmål om strike-valg og placering. Til CBDT vælger HotPDF den CBLC-størrelse, hvis vertikale ppem er nærmest TargetPixelsPerEm, accepterer image-formaterne 17, 18 og 19 og læser format 19-metrics fra CBLC index subtable, for det format gemmer ingen egne. Til sbix er strike-offsets relative til tabellen og glyph-offsets relative til striken, og en dupe-record genbruger en anden glyphs grafik, mens den beholder sine egne origin-offsets; lader du rekursionen overskrive den ydre origin, flyttes billedet. PNG- og JPEG-payloads dekodes internt, skaleres med FontSize / PixelsPerEmY i stedet for at blive strakt til fontstørrelsen og skrives med en soft mask (§11.6.5.3), når som helst en pixel ikke er fuldt opak. sbix TIFF-payloads dekodes ikke og går til eventet
Hvad sker der, når en glyph ikke kan tegnes nativt?
HotPDF rejser OnColorGlyphRasterize og placerer den RGBA-bitmap, din handler returnerer; er intet tildelt, eller lader handleren Handled være false, returnerer DrawRegisteredColorGlyph False, og siden forbliver uændret. Eventet fyres for en COLR v1-graf uden for det understøttede subsæt, et SVG-dokument, den sikre builder afslog, og en bitmap-payload, de interne decodere ikke læser. Handleren får formatet, de rå font-bytes, det udpakkede asset (SVG-dokumentet, evt. stadig gzippet, eller bitmap-bytes; tomt for COLR v1), glyph-ID'en, paletten og target pixel-størrelsen
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 er din rasterizer, ikke en HotPDF-API.
// Den skal returnere præcis Width * Height * 4 bytes RGBA.
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// Sammenkobling
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
HotPDF validerer handler-outputtet, før siden røres: nul-størrelser, en buffer, hvis længde ikke er præcis Width * Height * 4, eller dimensioner store nok til at overløbe afvises, og kaldet returnerer False. En raster-fallback er stadig en raster, så en emoji renderer på den måde mister sin vektor-skarphed; bed om en PixelSize, der matcher din output-opløsning. Par color-stien med draw-time coverage-tjek fra artiklen om missing glyph-tracking, så kan en pipeline, der håndterer vilkårlig brugertekst, rapportere både manglende glyphs og glyphs, der mistede deres farve
Color glyph-rendereren, OpenType shaping-stakken og den sikre SVG-builder følger alle med i HotPDF Delphi PDF-komponenten, tilgængelig til Delphi og C++Builder