HotPDF vẽ emoji màu vào một PDF qua THotPDF.DrawRegisteredColorGlyph, hàm đọc dữ liệu màu của font đã đăng ký với RegisterUnicodeTTF và phát ra dưới dạng đồ họa PDF thuần: các lớp COLR v0 thành outline glyph tô đặc, đồ thị paint COLR v1 thành clip, shading và blend mode, glyph SVG thành Form XObject, còn bitmap CBDT hay sbix thành ảnh. Thứ gì không map được thuần túy sẽ rơi vào event OnColorGlyphRasterize thay vì lặng lẽ hóa thành một hình đen
Mệnh đề cuối đó chính là toàn bộ lý do code này tồn tại. Nhúng một emoji font theo cách thông thường, trình xem sẽ nhận outline từ glyf hay CFF, tô bằng màu fill đang có lúc đó. Mặt cười đến dưới dạng một cục đen, lá cờ thành hình chữ nhật, và chẳng gì trong pipeline kêu ca
Vì sao một emoji màu in ra thành bóng đen trong PDF?
Một font program PDF không có khái niệm glyph màu. ISO 32000-1 coi glyph là một hình được tô bằng màu hiện tại, còn các bảng màu mà OpenType bổ sung sau này, cụ thể là COLR/CPAL, SVG , CBDT/CBLC và sbix, không thuộc mô hình ảnh của PDF, nên chẳng trình xem nào bắt buộc phải đọc chúng từ font nhúng. Màu phải được dịch thành nội dung trang ngay lúc sinh tài liệu, khi producer còn giữ byte font và biết mình cần glyph nào. Phép dịch ấy khác nhau theo từng định dạng, và các emoji font ngoài đời dùng đủ cả: vector nhiều lớp, đồ thị paint gradient, tài liệu SVG nhúng và các strike PNG. HotPDF báo kết quả dưới dạng THPDFOpenTypeColorFormat, với các giá trị otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG và otcfSBIX, và probe font theo một thứ tự ưu tiên cố định: COLR trước, rồi SVG, rồi CBDT, rồi sbix. Dữ liệu vector thắng bitmap bất cứ khi nào font mang cả hai, và đó chính là điều bạn muốn trong một tài liệu có thể bị phóng to hay in ra
Một lệnh gọi, năm định dạng: phân giải và vẽ một glyph màu
THotPDF.GetRegisteredColorGlyphInfo trả lời một code point sẽ đi đường nào, còn DrawRegisteredColorGlyph đi đường đó. Cả hai tra code point trong character map của font gần nhất được truyền cho RegisterUnicodeTTF, nên color font phải là font Unicode đã đăng ký tại thời điểm gọi. Hàm vẽ trả về False khi glyph không có dữ liệu màu hay chẳng đường nào kết xuất nổi nó, và mặc phần fallback cho bạn
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, bitmap strike gần 300 ppem nhất
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
// Không có dữ liệu màu: quay về outline đơn sắc
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;
Hai tham số đáng chú ý. PaletteIndex chọn một palette CPAL, nên font nào có kèm palette nền tối vẫn có thể đổi mà chẳng đụng tới glyph. TargetPixelsPerEm chỉ có ý nghĩa với font bitmap; để bằng 0 thì mặc định là Round(FontSize * 96 / 72), một độ phân giải màn hình, vì thế ví dụ yêu cầu 300 cho output in ấn. Giới hạn thật nằm trong chữ ký: lệnh gọi nhận một code point và map nó chỉ qua cmap. Chuỗi ZWJ, modifier tông da và các cờ regional-indicator là những ligature GSUB, nên ghép chúng là một bài toán shaping kiểu như bài về các alternate GSUB trong OpenType đã bàn, chứ không phải việc entry point này làm hộ bạn
COLR v0: các lớp glyph xếp chồng với màu palette
COLR v0 là ca đơn giản và HotPDF render thẳng: mỗi base glyph liệt kê các layer glyph với một entry màu CPAL, và mỗi layer trở thành một phép hiển thị văn bản bình thường với màu fill riêng, xếp chồng theo thứ tự bảng. Một layer có alpha dưới 255 nhận một dictionary tham số trạng thái đồ họa với /ca và /CA tương ứng (ISO 32000-1 §8.4.5), và mọi layer glyph được đánh dấu đã dùng để subsetter giữ lại outline của nó dù chẳng code point nào map trực tiếp tới nó. Một chi tiết gây bất ngờ: chỉ số entry palette 0xFFFF nghĩa là "dùng màu foreground của văn bản" trong đặc tả OpenType, và HotPDF phân giải nó thành màu đen thay vì màu fill hiện tại của trang. Với emoji font, điều này hiếm khi quan trọng; với icon font dựa vào entry foreground để nhuộm màu glyph, hãy kiểm tra output trước khi mặc định nó theo màu văn bản của bạn
HotPDF biến một đồ thị paint COLR v1 thành toán tử PDF thế nào?
Bằng cách parse các bảng paint thành một đồ thị phẳng, có chặn trước, rồi mới map từng node sang một cấu trúc PDF. Một glyph COLR v1 không phải là một danh sách layer mà là một đồ thị có hướng không chu trình của các paint record, nơi các node có thể được chia sẻ qua PaintColrLayers và PaintColrGlyph. Bộ parser chặn ở 4096 paint node, 64 tầng sâu và 1024 color stop, và theo dõi mọi node là đang hoạt động hay đã xong để một tham chiếu ngược về node đang hoạt động — chu trình mà một font ác ý có thể dựng từ việc tái dùng layer — bị từ chối thay vì bị đệ quy vào. Các gốc offset là nơi một bản cài đặt đầu tiên hay đi sai. Offset của BaseGlyphPaintRecord tính từ đầu BaseGlyphList, offset paint của LayerList tính từ LayerList, còn mọi Offset24 bên trong một bảng paint tính từ chính bảng paint đó. Giải cả ba theo cùng một gốc, những glyph hoàn toàn hợp lệ sẽ trượt phép kiểm tra biên, trông hệt như một font hỏng. Khi đồ thị đã dựng xong, phần map là trực tiếp:
PaintGlyphđặt outline glyph làm clip với text rendering mode 7 (ISO 32000-1 §9.3.6), rồi vẽ con của nó bên trong clip ấy- Paint đặc tô một hình chữ nhật bị clip; gradient tuyến tính trở thành axial shading nhiều stop và gradient bán kính trở thành radial shading hai màu (§8.7.4.5)
- Gradient sweep chẳng có tương đương trong PDF, nên HotPDF xấp xỉ chúng bằng 96 wedge tô màu phẳng, mỗi miếng được lấy mẫu từ đường màu
- Phép biến đổi được phát dưới dạng
cm, liên hợp quanh gốc baseline của glyph, với phần tịnh tiến được scale theoFontSize / UnitsPerEm PaintCompositemode 13 tới 27 map sang các blend mode PDF phân tách được và không phân tách được như/Multiply,/Screenvà/Luminosity(§11.3.5), đặt qua một entry/BMcủa ExtGState
Ranh giới được nói rõ. Các mode Porter-Duff 5 tới 12 (src_in, xor, plus và các mode còn lại) chẳng có đối tác blend-mode nào trong PDF, các extend mode repeat và reflect trên gradient tuyến tính và bán kính không được phát, còn gradient mà các stop mang giá trị alpha khác nhau không bị giả bằng một opacity duy nhất. Gradient bán kính có hơn hai stop chỉ giữ lại màu đầu và màu cuối. HotPDF đối chiếu toàn bộ đồ thị với tập con được hỗ trợ này trước khi viết một toán tử nào, nên một glyph không được hỗ trợ để nguyên trang và chuyển sang fallback raster thay vì để lại nửa bức vẽ dang dở
Glyph SVG và bitmap strike
Glyph SVG đi qua cùng bộ dựng có chặn mà HotPDF dùng cho các tệp SVG nhập vào, và kết quả được đăng ký thành một Form XObject (§8.10), hệt như bài chuyển SVG thành Form XObject mô tả. Tài liệu trong bảng SVG có thể bị nén gzip; giải nén chạy theo từng khối 8 KB và dừng ngay khi kích thước mở rộng sắp vượt 32 MB, thay vì inflate xong rồi mới kiểm tra, còn đầu vào nén bị chặn ở 8 MB. Profile bị siết là có chủ đích: script, ảnh nhúng, URL ngoài, URI data: và tham chiếu phi cục bộ đều fail closed. Form được scale sao cho cạnh dài hơn bằng cỡ chữ và neo trên baseline, cách ánh xạ hệ tọa độ SVG hướng y-xuống sang hệ PDF hướng y-lên. Lưu ý builder nhận toàn bộ tài liệu SVG của glyph, không chọn phần tử glyphNNN, nên những font nhồi nhiều glyph vào một tài liệu dùng chung đáng được thử trước khi bạn dựa vào
Font bitmap là chuyện chọn strike và đặt chỗ. Với CBDT, HotPDF chọn kích thước CBLC có ppem dọc gần TargetPixelsPerEm nhất, chấp nhận các định dạng ảnh 17, 18 và 19, và đọc metric của format 19 từ subtable chỉ mục CBLC vì định dạng đó không tự lưu metric nào. Với sbix, offset strike tính từ bảng còn offset glyph tính từ strike, và một record dupe tái dùng đồ họa của glyph khác trong khi giữ các offset gốc của riêng nó; để đệ quy ghi đè origin bên ngoài là làm lệch ảnh. Payload PNG và JPEG được giải mã nội bộ, scale theo FontSize / PixelsPerEmY thay vì kéo dãn tới cỡ chữ, và được ghi kèm soft mask (§11.6.5.3) bất cứ khi nào có pixel không hoàn toàn đục. Payload TIFF của sbix không được giải mã mà đi thẳng vào event
Chuyện gì xảy ra khi một glyph không vẽ được thuần túy?
HotPDF bắn OnColorGlyphRasterize và đặt xuống bất kỳ bitmap RGBA nào mà handler của bạn trả về; nếu chẳng gán handler nào, hay handler để Handled là false, DrawRegisteredColorGlyph trả về False và trang giữ nguyên. Event bắn với một đồ thị COLR v1 ngoài tập con được hỗ trợ, một tài liệu SVG mà bộ dựng an toàn từ chối, và một payload bitmap mà các bộ giải mã nội bộ không đọc. Handler nhận được định dạng, byte font thô, tài sản đã trích (tài liệu SVG, có thể vẫn đang gzip, hay byte bitmap; rỗng với COLR v1), glyph ID, palette và kích thước pixel đích
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 là rasterizer của bạn, không phải API HotPDF.
// Nó phải trả về đúng Width * Height * 4 byte RGBA.
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// Nối dây
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
HotPDF xác thực output của handler trước khi đụng vào trang: kích thước bằng 0, một buffer có độ dài không đúng Width * Height * 4, hay kích thước lớn đủ tràn số đều bị từ chối và lệnh gọi trả về False. Fallback raster vẫn là raster, nên một emoji được render kiểu này mất độ sắc vector; hãy xin một PixelSize khớp độ phân giải output của bạn. Ghép đường màu với phép kiểm tra độ phủ lúc vẽ từ bài theo dõi glyph bị thiếu và một pipeline xử lý văn bản người dùng tùy ý có thể báo được cả glyph thiếu lẫn glyph mất màu
Bộ kết xuất glyph màu, shaping stack OpenType và bộ dựng SVG an toàn đều có mặt trong HotPDF Delphi PDF component, có sẵn cho Delphi và C++Builder