HotPDF menggambar emoji berwarna ke PDF lewat THotPDF.DrawRegisteredColorGlyph, yang membaca data warna font yang didaftarkan dengan RegisterUnicodeTTF dan mengeluarkannya sebagai grafika PDF native: layer COLR v0 sebagai outline glyph terisi, paint graph COLR v1 sebagai clip, shading, dan blend mode, glyph SVG sebagai Form XObject, dan bitmap CBDT atau sbix sebagai gambar. Apa pun yang tak bisa dipetakan secara native diserahkan ke event OnColorGlyphRasterize alih-alih diam-diam berubah menjadi bentuk hitam
Klausul terakhir itulah alasan utama kode ini ada. Tanamkan font emoji dengan cara biasa dan viewer kebagian outline dari glyf atau CFF, diisi dengan warna fill yang kebetulan sedang aktif. Wajah tersenyum datang sebagai gumpalan hitam, bendera sebagai persegi panjang, dan tak ada satu pun di pipeline yang protes
Kenapa emoji berwarna tercetak sebagai siluet hitam di PDF?
Program font PDF tidak mengenal glyph berwarna. ISO 32000-1 memperlakukan glyph sebagai bentuk yang dilukis dengan warna aktif, dan tabel warna yang ditambahkan OpenType belakangan, yaitu COLR/CPAL, SVG , CBDT/CBLC, dan sbix, bukan bagian dari imaging model PDF, jadi tak ada viewer yang wajib membacanya dari font tertanam. Warnanya harus diterjemahkan menjadi konten halaman saat pembuatan, ketika produser masih memegang byte font dan tahu glyph mana yang diinginkannya. Terjemahan itu berbeda per format, dan font emoji di alam liar memakai semuanya: vektor berlapis, paint graph gradien, dokumen SVG tertanam, dan strike PNG. HotPDF melaporkan hasilnya sebagai THPDFOpenTypeColorFormat, dengan nilai otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG, dan otcfSBIX, dan memeriksa font itu dengan prioritas tetap: COLR dulu, lalu SVG, lalu CBDT, lalu sbix. Data vektor mengalahkan bitmap kapan pun font membawa keduanya, dan itulah yang Anda inginkan untuk dokumen yang mungkin di-zoom atau dicetak
Satu panggilan, lima format: meresolusi dan menggambar glyph warna
THotPDF.GetRegisteredColorGlyphInfo menjawab jalur mana yang akan diambil sebuah code point, dan DrawRegisteredColorGlyph yang menjalankannya. Keduanya mencari code point itu di character map font yang terakhir dikirim ke RegisterUnicodeTTF, jadi font warna harus menjadi font Unicode terdaftar pada saat panggilan. Fungsi gambar mengembalikan False ketika glyph tak punya data warna atau tak ada jalur yang bisa merendernya, dan membiarkan fallbacknya pada Anda
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, palet CPAL 0, bitmap strike terdekat ke 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
// Tak ada data warna: mundur ke outline monokrom
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;
Dua parameter layak diperhatikan. PaletteIndex memilih palet CPAL, jadi font yang mengirim palet latar gelap bisa diganti tanpa menyentuh glyph-nya. TargetPixelsPerEm hanya berarti untuk font bitmap; dibiarkan nol ia bawaan Round(FontSize * 96 / 72), resolusi layar, dan itulah kenapa contohnya meminta 300 untuk output cetak. Batas jujurnya ada di signature: panggilan ini mengambil satu code point dan memetakannya lewat cmap saja. Sekuens ZWJ, modifier skin-tone, dan bendera regional-indicator adalah ligatur GSUB, jadi menyusunnya adalah masalah shaping sejenis yang dibahas di artikel alternates GSUB OpenType, bukan sesuatu yang dikerjakan entry point ini untuk Anda
COLR v0: layer glyph bertumpuk dengan warna palet
COLR v0 adalah kasus sederhana dan HotPDF merendernya langsung: setiap glyph dasar mendaftar layer glyph dengan entri warna CPAL, dan setiap layer menjadi satu operasi text-showing biasa dengan warna fill-nya sendiri, bertumpuk dalam urutan tabel. Layer dengan alpha di bawah 255 kebagian dictionary parameter graphics state dengan /ca dan /CA yang cocok (ISO 32000-1 §8.4.5), dan setiap glyph layer ditandai terpakai supaya subsetter mempertahankan outline-nya meski tak ada code point yang memetakan langsung ke sana. Satu detail mengejutkan orang: indeks entri palet 0xFFFF berarti "pakai warna foreground teks" dalam spesifikasi OpenType, dan HotPDF meresolusinya menjadi hitam, bukan warna fill halaman yang sedang aktif. Untuk font emoji ini jarang berarti; untuk icon font yang mengandalkan entri foreground untuk mewarnai glyph-nya, periksa outputnya sebelum mengasumsikan ia akan mengikuti warna teks Anda
Bagaimana HotPDF mengubah paint graph COLR v1 menjadi operator PDF?
Dengan mem-parse tabel paint menjadi graph datar berbatas lebih dulu, dan baru kemudian memetakan setiap node ke konstruksi PDF. Glyph COLR v1 bukan daftar layer melainkan directed acyclic graph berisi paint record, di mana node bisa dipakai bersama lewat PaintColrLayers dan PaintColrGlyph. Parser membatasinya pada 4096 paint node, 64 level kedalaman, dan 1024 color stop, serta melacak setiap node sebagai aktif atau selesai supaya referensi balik ke node yang masih aktif — siklus yang bisa dibangun font jahat dari pemakaian ulang layer — ditolak alih-alih direkursi. Basis offset adalah tempat implementasi pertama tersandung. Offset BaseGlyphPaintRecord relatif terhadap awal BaseGlyphList, offset paint LayerList relatif terhadap LayerList, dan setiap Offset24 di dalam tabel paint relatif terhadap tabel paint itu sendiri. Resolusi ketiganya terhadap basis yang sama membuat glyph yang sepenuhnya legal gagal di pemeriksaan batas, dan itu tampak persis seperti font yang korup. Begitu graph-nya selesai dibangun, pemetaannya langsung:
PaintGlyphmenyetel outline glyph sebagai clip dengan text rendering mode 7 (ISO 32000-1 §9.3.6), lalu melukis anaknya di dalamnya- Paint solid mengisi persegi yang di-clip; gradien linear menjadi axial shading multi-stop dan gradien radial menjadi radial shading dua warna (§8.7.4.5)
- Sweep gradient tak punya padanan PDF, jadi HotPDF mendekatinya dengan 96 wedge berwarna datar, masing-masing disampling dari color line
- Transform dikeluarkan sebagai
cm, dikonjugasi di sekitar origin baseline glyph, dengan translasi diskalakanFontSize / UnitsPerEm - Mode
PaintComposite13 sampai 27 dipetakan ke blend mode PDF separable dan non-separable seperti/Multiply,/Screen, dan/Luminosity(§11.3.5), disetel lewat entri/BMExtGState
Batasnya eksplisit. Mode Porter-Duff 5 sampai 12 (src_in, xor, plus, dan lainnya) tak punya padanan blend mode PDF, extend mode repeat dan reflect pada gradien linear dan radial tidak dikeluarkan, dan gradien yang stop-nya membawa nilai alpha berbeda tidak dipalsukan dengan opacity tunggal. Gradien radial dengan lebih dari dua stop hanya menyimpan warna pertama dan terakhirnya. HotPDF memeriksa seluruh graph terhadap subset yang didukung ini sebelum menulis satu operator pun, jadi glyph yang tak didukung membiarkan halaman tak tersentuh dan beralih ke fallback raster alih-alih menyisakan setengah gambar
Glyph SVG dan bitmap strike
Glyph SVG melewati builder berbatas yang sama yang dipakai HotPDF untuk file SVG impor, dan hasilnya didaftarkan sebagai Form XObject (§8.10), persis seperti dijelaskan di artikel SVG ke Form XObject. Dokumen di tabel SVG bisa terkompresi gzip; dekompresinya berjalan dalam potongan 8 KB dan berhenti begitu ukuran hasil kembangnya akan melewati 32 MB, alih-alih menggelembungkan dulu lalu memeriksa sesudahnya, dan input terkompresinya sendiri dipatok 8 MB. Profilnya sengaja ketat: script, gambar tertanam, URL eksternal, URI data:, dan referensi non-lokal gagal dengan aman. Form-nya diskalakan agar sisi terpanjangnya sama dengan ukuran font dan ditambatkan di baseline, yang memetakan sistem koordinat SVG y-ke-bawah ke sistem y-ke-atas PDF. Perlu disadari builder menerima seluruh dokumen SVG milik glyph itu, tanpa pemilihan elemen glyphNNN, jadi font yang menjejalkan banyak glyph ke satu dokumen bersama layak diuji dulu sebelum Anda mengandalkannya
Font bitmap adalah soal pemilihan strike dan penempatan. Untuk CBDT, HotPDF memilih ukuran CBLC yang ppem vertikalnya paling dekat ke TargetPixelsPerEm, menerima format gambar 17, 18, dan 19, dan membaca metrik format 19 dari subtabel indeks CBLC karena format itu tak menyimpan metriknya sendiri. Untuk sbix, offset strike relatif terhadap tabel dan offset glyph relatif terhadap strike, dan record dupe memakai ulang grafik milik glyph lain sambil mempertahankan offset origin-nya sendiri; membiarkan rekursi menimpa origin terluar menggeser gambarnya. Payload PNG dan JPEG didekode internal, diskalakan dengan FontSize / PixelsPerEmY alih-alih diregangkan ke ukuran font, dan ditulis dengan soft mask (§11.6.5.3) kapan pun ada piksel yang tak sepenuhnya opak. Payload sbix TIFF tak didekode dan diserahkan ke event
Apa yang terjadi ketika glyph tak bisa digambar secara native?
HotPDF menaikkan OnColorGlyphRasterize dan menempatkan bitmap RGBA apa pun yang dikembalikan handler Anda; kalau tak ada yang di-assign, atau handler membiarkan Handled false, DrawRegisteredColorGlyph mengembalikan False dan halamannya tetap tak berubah. Event menyala untuk graph COLR v1 di luar subset yang didukung, dokumen SVG yang ditolak builder aman, dan payload bitmap yang tak dibaca decoder internal. Handler menerima formatnya, byte font mentah, aset hasil ekstraksi (dokumen SVG, mungkin masih ter-gzip, atau byte bitmap; kosong untuk COLR v1), ID glyph, paletnya, dan ukuran piksel targetnya
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 adalah rasterizer Anda, bukan API HotPDF.
// Ia harus mengembalikan tepat Width * Height * 4 byte RGBA.
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// Penyambungan
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
HotPDF memvalidasi output handler sebelum menyentuh halaman: ukuran nol, buffer yang panjangnya tak tepat Width * Height * 4, atau dimensi yang cukup besar untuk overflow ditolak dan panggilannya mengembalikan False. Fallback raster tetaplah raster, jadi emoji yang dirender begini kehilangan ketajaman vektornya; mintalah PixelSize yang cocok dengan resolusi output Anda. Pasangkan jalur warna ini dengan pemeriksaan cakupan saat menggambar dari artikel pelacakan glyph hilang dan pipeline yang menangani teks pengguna sembarangan bisa melaporkan glyph yang hilang sekaligus glyph yang kehilangan warnanya
Renderer glyph warna, shaping stack OpenType, dan builder SVG aman semuanya ikut terkirim di HotPDF Delphi PDF component, tersedia untuk Delphi dan C++Builder