HotPDF desenează emoji color într-un PDF prin THotPDF.DrawRegisteredColorGlyph, care citește datele de culoare ale unui font înregistrat cu RegisterUnicodeTTF și le emite ca grafică PDF nativă: straturile COLR v0 ca contururi de glife umplute, grafurile de paint COLR v1 ca clip-uri, shading-uri și blend modes, glifele SVG ca Form XObjects, iar bitmapurile CBDT sau sbix ca imagini. Orice nu poate fi mapat nativ merge către evenimentul OnColorGlyphRasterize în loc să se transforme în tăcere într-o formă neagră
Claeza din urmă e tot motivul pentru care există codul acesta. Încorporați un font emoji pe calea obișnuită și vizualizatorul primește conturul din glyf sau CFF, umplut cu orice culoare de umplere curentă se întâmplă să existe. Fața zâmbitoare sosește ca o pată neagră, steagul ca un dreptunghi, și nimic din pipeline nu se plânge
De ce un emoji color se tipărește ca o siluetă neagră în PDF?
Un program de font PDF n-are noțiune de glife color. ISO 32000-1 tratează o glifă ca o formă pictată cu culoarea curentă, iar tabelele de culoare pe care OpenType le-a adăugat ulterior, anume COLR/CPAL, SVG , CBDT/CBLC și sbix, nu fac parte din modelul de imagistică PDF, deci niciun vizualizator nu e obligat să le citească dintr-un font încorporat. Culoarea trebuie tradusă în conținut de pagină în momentul generării, când producătorul încă deține octeții fontului și știe ce glifă vrea. Traducerea aceea diferă per format, iar fonturile emoji din sălbăticie le folosesc pe toate: vectori stratificați, grafuri de paint cu degradee, documente SVG încorporate și strikes PNG. HotPDF raportează rezultatul ca THPDFOpenTypeColorFormat, cu valorile otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG și otcfSBIX, și sondează fontul într-o prioritate fixă: întâi COLR, apoi SVG, apoi CBDT, apoi sbix. Datele vectoriale câștigă în fața bitmapurilor ori de câte ori un font cară ambele, exact ce vreți într-un document care poate fi mărit sau tipărit
Un apel, cinci formate: rezolvarea și desenarea unei glife color
THotPDF.GetRegisteredColorGlyphInfo răspunde la întrebarea ce cale va lua un code point, iar DrawRegisteredColorGlyph o parcurge. Ambele caută code point-ul în harta de caractere a fontului pasat cel mai recent spre RegisterUnicodeTTF, deci fontul color trebuie să fie fontul Unicode înregistrat în momentul apelului. Funcția de desen întoarce False când glifa n-are date de culoare sau nicio cale nu a putut să o randeze și lasă fallback-ul în seama voastră
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-ul cel mai apropiat 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
// Fără date de culoare: revin pe conturul monocrom
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;
Două parametri merită atenție. PaletteIndex alege o paletă CPAL, deci un font care livrează o paletă pentru fundal întunecat poate fi comutat fără să atingă glifa. TargetPixelsPerEm contează doar pentru fonturile bitmap; lăsat pe zero, are implicit Round(FontSize * 96 / 72), o rezoluție de ecran, motiv pentru care exemplul cere 300 pentru output tipărit. Limita onestă stă în semnătură: apelul primește un singur code point și îl mapează doar prin cmap. Secvențele ZWJ, modificatorii de nuanță a pielii și steagurile regional-indicator sunt ligaturi GSUB, deci compunerea lor e o problemă de shaping de genul celei acoperite în articolul despre alternatele OpenType GSUB, nu ceva ce face acest punct de intrare pentru voi
COLR v0: straturi de glife suprapuse cu culori din paletă
COLR v0 e cazul simplu și HotPDF îl randează direct: fiecare glifă de bază listează glife de strat cu o intrare de culoare CPAL, iar fiecare strat devine o operație obișnuită de afișare a textului, cu propria culoare de umplere, suprapuse în ordinea din tabelă. Un strat cu alpha sub 255 primește un dicționar de parametri de stare grafică cu /ca și /CA potrivite (ISO 32000-1 §8.4.5), iar fiecare glifă de strat e marcată ca folosită, ca subsetter-ul să îi păstreze conturul chiar dacă niciun code point nu îi corespunde direct. Un detaliu surprinde pe mulți: indexul de intrare de paletă 0xFFFF înseamnă „folosește culoarea de prim-plan a textului” în specificația OpenType, iar HotPDF îl rezolvă în negru, nu în culoarea de umplere curentă a paginii. Pentru fonturi emoji contează rar; pentru fonturi de iconițe care se bazează pe intrarea de prim-plan ca să înteze o glifă, verificați rezultatul înainte să presupuneți că va urma culoarea textului vostru
Cum transformă HotPDF un graf de paint COLR v1 în operatori PDF?
Prin parsarea tabelelor de paint într-un graf plat, mărginit, și abia apoi maparea fiecărui nod pe un construct PDF. O glifă COLR v1 nu e o listă de straturi, ci un graf aciclic orientat de înregistrări de paint, în care nodurile pot fi partajate prin PaintColrLayers și PaintColrGlyph. Parser-ul îl plafonează la 4096 de noduri de paint, 64 de niveluri de adâncime și 1024 de color stops și ține evidența fiecărui nod ca activ sau terminat, astfel încât o referință înapoi spre un nod activ, un ciclu pe care un font răuvoitor îl poate construi din reutilizarea de straturi, e respinsă în loc să fie recursată. Bazele de offset sunt locul în care o primă implementare greșește. Offset-urile BaseGlyphPaintRecord sunt relative la începutul lui BaseGlyphList, offset-urile de paint din LayerList sunt relative la LayerList, iar fiecare Offset24 dintr-o tabelă de paint e relativ la tabla de paint respectivă. Rezolvați toate trei față de aceeași bază și glife perfect legale pică la verificarea de margini, ceea ce arată exact ca un font corupt. Odată construit graful, maparea e directă:
PaintGlyphsetează conturul glifei ca clip cu modul de randare a textului 7 (ISO 32000-1 §9.3.6), apoi pictează copilul lui în interiorul lui- Painct-urile solide umplu un dreptunghi decupat; degradeele liniare devin shading-uri axiale multi-stop, iar cele radiale devin shading-uri radiale în două culori (§8.7.4.5)
- Degradeele sweep n-au echivalent PDF, deci HotPDF le aproximează cu 96 de sectoare cu culoare plată, fiecare eșantionat din linia de culoare
- Transformările sunt emise ca
cm, conjugate în jurul originii baseline-ului glifei, cu translațiile scalate cuFontSize / UnitsPerEm - Modurile
PaintComposite13 până la 27 se mapează pe modurile de blend PDF separabile și non-separabile precum/Multiply,/Screenși/Luminosity(§11.3.5), setate printr-o intrare ExtGState/BM
Granița e explicită. Modurile Porter-Duff 5 până la 12 (src_in, xor, plus și restul) nu au echivalent de blend-mode PDF, modurile de extindere repeat și reflect pe degradeele liniare și radiale nu sunt emise, iar degradeele ale căror stops cară valori de alpha diferite nu sunt falsificate cu o singură opacitate. Degradeele radiale cu mai mult de două stops păstrează doar prima și ultima culoare. HotPDF verifică tot graful împotriva acestui subansamblu suportat înainte să scrie un singur operator, deci o glifă nesuportată lasă pagina neatinsă și trece la fallback-ul raster în loc să lase în urmă jumătate de desen
Glife SVG și strikes bitmap
Glifele SVG trec prin același builder mărginit pe care HotPDF îl folosește pentru fișierele SVG importate, iar rezultatul e înregistrat ca Form XObject (§8.10), exact cum descrie articolul despre SVG în Form XObject. Documentul din tabela SVG poate fi comprimat gzip; decomprimarea rulează în chunk-uri de 8 KB și se oprește imediat ce dimensiunea extinsă ar trece de 32 MB, în loc să facă întâi inflația și abia apoi verificarea, iar input-ul comprimat e el însuși plafonat la 8 MB. Profilul e restrictiv din principiu: scripturile, imaginile încorporate, URL-urile externe, URI-urile data: și referințele non-local eșuează închis. Form-ul e scalat astfel încât latura lui mai lungă să egaleze dimensiunea fontului și ancorat pe baseline, ceea ce mapează sistemul de coordonate SVG cu y în jos pe cel PDF cu y în sus. Rețineți că builder-ul primește tot documentul SVG al glifei, fără nicio selecție a elementului glyphNNN, deci fonturile care împachetează multe glife într-un document partajat merită testate înainte să vă bazați pe ele
Fonturile bitmap sunt o chestiune de alegere a strike-ului și de plasare. Pentru CBDT, HotPDF alege dimensiunea CBLC al cărei ppem vertical e cel mai apropiat de TargetPixelsPerEm, acceptă formatele de imagine 17, 18 și 19 și citește metricile formatului 19 din subtabela de index CBLC, pentru că acel format nu stochează al lui. Pentru sbix, offset-urile de strike sunt relative la tabelă, iar offset-urile de glifă relative la strike, iar o înregistrare dupe refolosește grafica altei glife păstrându-și propriile offset-uri de origine; lăsarea recurgerii să suprascrie originea exterioară deplasează imaginea. Payload-urile PNG și JPEG sunt decodate intern, scalate cu FontSize / PixelsPerEmY în loc să fie întinse la dimensiunea fontului, și scrise cu un soft mask (§11.6.5.3) ori de câte ori vreun pixel nu e complet opac. Payload-urile sbix TIFF nu sunt decodate și merg la eveniment
Ce se întâmplă când o glifă nu poate fi desenată nativ?
HotPDF declanșează OnColorGlyphRasterize și plasează orice bitmap RGBA întoarce handler-ul vostru; dacă nu e asignat nimic, sau handler-ul lasă Handled fals, DrawRegisteredColorGlyph întoarce False și pagina rămâne neschimbată. Evenimentul se declanșează pentru un graf COLR v1 în afara subansamblului suportat, un document SVG refuzat de builder-ul sigur și un payload bitmap pe care decodoarele interne nu îl citesc. Handler-ul primește formatul, octeții bruti ai fontului, asset-ul extras (documentul SVG, eventual încă gzip-uit, sau octeții bitmap; gol pentru COLR v1), GlyphID-ul, paleta și dimensiunea de pixel țintă
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 este rasterizatorul vostru, nu un API HotPDF.
// Trebuie să întoarcă exact Width * Height * 4 octeți de RGBA.
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// Cablarea
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
HotPDF validează rezultatul handler-ului înainte să atingă pagina: dimensiuni zero, un buffer a cărui lungime nu e exact Width * Height * 4 sau dimensiuni suficient de mari cât să facă overflow sunt respinse, iar apelul întoarce False. Un fallback raster rămâne tot un raster, deci un emoji randat așa își pierde ascuțimea vectorială; cereți un PixelSize care să se potrivească cu rezoluția output-ului vostru. Împerecheați calea color cu verificările de acoperire la desen din articolul despre urmărirea glifelor lipsă, iar un pipeline care procesează text de utilizator arbitrar poate raporta și glifele lipsă și glifele care și-au pierdut culoarea
Renderer-ul de glife color, stiva de shaping OpenType și builder-ul SVG sigur se livrează în HotPDF Delphi PDF component, disponibil pentru Delphi și C++Builder