HotPDF วาด emoji สีลงใน PDF ผ่าน THotPDF.DrawRegisteredColorGlyph ซึ่งอ่านข้อมูลสีของฟอนต์ที่ลงทะเบียนด้วย RegisterUnicodeTTF แล้วปล่อยออกมาเป็นกราฟิก PDF แท้: layer ของ COLR v0 เป็นเส้นขอบ glyph ที่เติมสี, paint graph ของ COLR v1 เป็น clip, shading และ blend mode, glyph SVG เป็น Form XObject และ bitmap ของ CBDT หรือ sbix เป็นภาพ อะไรที่แมปแบบ native ไม่ได้ไปตกที่ event OnColorGlyphRasterize แทนที่จะเงียบ ๆ กลายเป็นรูปทรงสีดำ
ประโยคหลังสุดนั่นแหละคือเหตุผลทั้งหมดที่โค้ดชุดนี้ถือกำเนิดมา ฝังฟอนต์ emoji แบบธรรมดาเข้าไปแล้ว viewer จะได้เส้นขอบจาก glyf หรือ CFF เติมด้วยสีเติมปัจจุบันอะไรก็ตามที่ตอนนั้นมี หน้ายิ้มมาถึงในรูปก้อนดำ ธงมาถึงในรูปสี่เหลี่ยม และไม่มีอะไรใน pipeline ร้องเรียน
ทำไม emoji สีถึงพิมพ์ออกมาเป็นเงาดำใน PDF
โปรแกรมฟอนต์ของ PDF ไม่มีแนวคิดเรื่อง color glyph ISO 32000-1 ถือว่า glyph เป็นรูปทรงที่ถูกวาดด้วยสีปัจจุบัน และตารางสีที่ OpenType เพิ่มทีหลัง คือ COLR/CPAL, SVG , CBDT/CBLC กับ sbix ไม่ได้เป็นส่วนของ imaging model ใน PDF viewer จึงไม่มีหน้าที่ต้องอ่านพวกมันจากฟอนต์ที่ฝังมา สีต้องถูกแปลเป็นเนื้อหาหน้าตอน generate ซึ่งเป็นจังหวะที่ producer ยังถือไบต์ฟอนต์และรู้ว่าต้องการ glyph ตัวไหน การแปลนี้ต่างกันไปตามฟอร์แมต และฟอนต์ emoji ในป่าใช้ครบทุกแบบ: เวกเตอร์ซ้อน layer, paint graph แบบ gradient, เอกสาร SVG ที่ฝังมาและ PNG strike HotPDF รายงานผลเป็น THPDFOpenTypeColorFormat ด้วยค่า otcfNone, otcfCOLRv0, otcfCOLRv1, otcfCBDT, otcfSVG กับ otcfSBIX และ probe ฟอนต์ตามลำดับความสำคัญตายตัว: COLR ก่อน แล้ว SVG, CBDT, sbix ตามลำดับ ข้อมูลเวกเตอร์ชนะ bitmap ทุกครั้งที่ฟอนต์มีทั้งคู่ ซึ่งเป็นสิ่งที่คุณต้องการในเอกสารที่อาจถูกซูมหรือพิมพ์
call เดียว ห้าฟอร์แมต: การ resolve และวาด color glyph
THotPDF.GetRegisteredColorGlyphInfo ตอบว่า code point จะเดินเส้นทางไหน และ DrawRegisteredColorGlyph ก็เดินตามทั้งคู่มอง code point ใน character map ของฟอนต์ที่ส่งให้ RegisterUnicodeTTF ล่าสุด ฟอนต์สีจึงต้องเป็นฟอนต์ Unicode ที่ลงทะเบียนอยู่ ณ ตอนเรียก ฟังก์ชันวาดคืน False เมื่อ glyph ไม่มีข้อมูลสีหรือไม่มีเส้นทางไหน render มันได้ แล้วปล่อยเรื่อง fallback ให้คุณ
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, strike bitmap ที่ใกล้ 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
// ไม่มีข้อมูลสี: ย้อนไปใช้เส้นขอบขาวดำ
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;
สองพารามิเตอร์สมควรได้รับความสนใจ PaletteIndex เลือก palette ของ CPAL ฟอนต์ที่แถม palette สำหรับพื้นหลังเข้มมาจึงสลับได้โดยไม่ต้องแตะ glyph TargetPixelsPerEm มีผลเฉพาะกับฟอนต์ bitmap ปล่อยไว้เป็นศูนย์จะ default เป็น Round(FontSize * 96 / 72) ซึ่งเป็นความละเอียดหน้าจอ ตัวอย่างจึงขอ 300 สำหรับ output สำหรับพิมพ์ ขีดจำกัดที่พูดตรง ๆ อยู่ในลายเซ็นของมัน: call นี้รับ code point เดียวและแมปผ่าน cmap เพียงอย่างเดียว ลำดับ ZWJ, modifier skin-tone และธง regional-indicator เป็น ligature ของ GSUB การประกอบมันจึงเป็นปัญหาด้าน shaping แบบเดียวกับที่ครอบไว้ในบทความเรื่อง alternates ของ OpenType GSUB ไม่ใช่สิ่งที่จุดเข้าตัวนี้ทำให้คุณ
COLR v0: layer ของ glyph ที่ซ้อนกันด้วยสีจาก palette
COLR v0 เป็นเคสง่ายและ HotPDF render มันตรง ๆ: glyph ฐานแต่ละตัวลิสต์ layer glyph พร้อม entry สีของ CPAL แต่ละ layer กลายเป็น operation แสดงข้อความธรรมดาหนึ่งตัวที่มีสีเติมของตัวเอง ซ้อนกันตามลำดับในตาราง layer ที่มี alpha ต่ำกว่า 255 ได้ graphics state parameter dictionary ที่มี /ca กับ /CA ตรงกัน (ISO 32000-1 §8.4.5) และ layer glyph ทุกตัวถูกทำเครื่องหมายว่าใช้อยู่ subsetter จึงคงเส้นขอบไว้แม้ไม่มี code point ใดแมปเข้ามันตรง ๆ รายละเอียดหนึ่งที่ทำให้หลายคนสะดุ้ง: index entry ของ palette 0xFFFF หมายถึง "ใช้สี foreground ของข้อความ" ในสเปก OpenType และ HotPDF resolve มันเป็นสีดำ ไม่ใช่สีเติมปัจจุบันของหน้า กับฟอนต์ emoji มันแทบไม่เคยสำคัญ ส่วนฟอนต์ไอคอนที่พึ่ง entry foreground ไว้แต้มสี glyph ให้เช็ก output ก่อนเชื่อว่ามันจะตามสีข้อความของคุณ
HotPDF เปลี่ยน paint graph ของ COLR v1 เป็น operator ของ PDF อย่างไร
ด้วยการ parse ตาราง paint ลงเป็น graph แบบแบนที่มีขอบเขตก่อน แล้วค่อยแมปแต่ละ node เป็น construct ของ PDF glyph ของ COLR v1 ไม่ใช่ list ของ layer แต่เป็น directed acyclic graph ของ paint record ที่ node ใช้ร่วมกันได้ผ่าน PaintColrLayers กับ PaintColrGlyph parser จำกัดที่ 4096 paint node, ความลึก 64 ระดับและ color stop 1024 ตัว และจดทุก node ว่า active หรือ done reference กลับไปหา node ที่ active อยู่ ซึ่งเป็นวงจรที่ฟอนต์เจตนาร้ายสร้างได้จากการใช้ layer ซ้ำ จึงถูกปฏิเสธแทนที่จะเรียกซ้อนลงไป ฐานของ offset คือจุดที่ implementation รอบแรกพลาด offset ของ BaseGlyphPaintRecord อิงจากต้น BaseGlyphList, offset paint ของ LayerList อิงจาก LayerList และ Offset24 ทุกตัวข้างใน paint table อิงจาก paint table ตัวนั้นเอง resolve ทั้งสามต่อฐานเดียวกันแล้ว glyph ที่ถูกต้องสมบูรณ์จะตกการเช็กขอบเขตไปเฉย ๆ หน้าตาเหมือนฟอนต์พังพอดี เมื่อ graph พร้อมแล้ว การแมปเดินตรง ๆ:
PaintGlyphตั้งเส้นขอบ glyph เป็น clip ด้วย text rendering mode 7 (ISO 32000-1 §9.3.6) แล้ววาดลูกของมันข้างใน- paint แบบทึบเติมสี่เหลี่ยมที่ถูกคลิป gradient แนวเส้นตรงกลายเป็น axial shading หลาย stop และ gradient แบบรัศมีกลายเป็น radial shading สองสี (§8.7.4.5)
- sweep gradient ไม่มีของเทียบใน PDF HotPDF จึงประมาณด้วย wedge สีทึบ 96 ชิ้น สุ่มตัวอย่างทีละชิ้นจากเส้นสี
- transform ถูกปล่อยออกมาเป็น
cmconjugate รอบจุดกำเนิด baseline ของ glyph โดยการเลื่อนถูกสเกลด้วยFontSize / UnitsPerEm - โหมด 13 ถึง 27 ของ
PaintCompositeแมปไปยัง blend mode ของ PDF ที่แยกส่วนได้กับแยกไม่ได้ อย่าง/Multiply,/Screenกับ/Luminosity(§11.3.5) ตั้งผ่าน entry/BMของ ExtGState
ขอบเขตถูกบอกไว้ชัด โหมด Porter-Duff 5 ถึง 12 (src_in, xor, plus และเพื่อน) ไม่มี blend mode ใน PDF คู่กัน, extend mode แบบ repeat กับ reflect บน gradient แนวเส้นตรงกับรัศมีไม่ถูกปล่อยออกมา และ gradient ที่ stop แต่ละตัวมี alpha ต่างกันจะไม่ถูกหลอกด้วย opacity ค่าเดียว radial gradient ที่มี stop เกินสองตัวเก็บแค่สีตัวแรกกับตัวสุดท้าย HotPDF เช็ก graph ทั้งก้อนเทียบกับ subset ที่รองรับนี้ก่อนเขียน operator แม้แต่ตัวเดียว glyph ที่ไม่รองรับจึงปล่อยหน้าให้เหมือนเดิมแล้วย้ายไป fallback แบบ raster แทนที่จะทิ้งภาพครึ่ง ๆ กลาง ๆ ไว้
glyph SVG กับ strike bitmap
glyph SVG เดินผ่าน builder ที่มีขอบเขตตัวเดียวกับที่ HotPDF ใช้กับไฟล์ SVG ที่นำเข้า และผลลัพธ์ถูกลงทะเบียนเป็น Form XObject (§8.10) ตรงตามที่เล่าในบทความเรื่อง SVG สู่ Form XObject เอกสารในตาราง SVG อาจถูกบีบอัดแบบ gzip การคลายการบีบอัดวิ่งเป็นช่วง ๆ ละ 8 KB และหยุดทันทีที่ขนาดหลังคลายจะล้ำ 32 MB แทนที่จะคลายก่อนแล้วค่อยตรวจ และ input ที่บีบอัดก็โดนเพดาน 8 MB โปรไฟล์เข้มงวดโดยตั้งใจ: script, ภาพที่ฝังมา, URL ภายนอก, URI data: และ reference ที่ไม่ใช่ local ล้มเหลวแบบปิดสวิตช์ form ถูกสเกลให้ด้านยาวเท่ากับขนาดฟอนต์และยึดที่ baseline ซึ่งแมประบบพิกัดของ SVG ที่ y ไปลง ไปบนแบบ y ขึ้นของ PDF ข้อควรระวังคือ builder ได้รับเอกสาร SVG ทั้งฉบับของ glyph โดยไม่มีการเลือก element glyphNNN ฟอนต์ที่กองหลาย glyph ไว้ในเอกสารใช้ร่วมกันฉบับเดียวจึงสมควรถูกทดสอบก่อนพึ่งพา
ฟอนต์ bitmap เป็นเรื่องของการเลือก strike กับการวางตำแหน่ง สำหรับ CBDT HotPDF เลือกขนาดของ CBLC ที่ ppem แนวตั้งใกล้ TargetPixelsPerEm ที่สุด รับ image format 17, 18 กับ 19 และอ่าน metric ของ format 19 จาก subtable index ของ CBLC เพราะฟอร์แมตนั้นไม่เก็บของตัวเองเลย สำหรับ sbix offset ของ strike อิงจากตาราง ส่วน offset ของ glyph อิงจาก strike และ record dupe ใช้กราฟิกของ glyph อื่นต่อโดยคง offset จุดกำเนิดของตัวเองไว้ ปล่อยให้การเรียกซ้อนเขียนทับจุดกำเนิดชั้นนอกคือภาพถูกเลื่อน payload PNG กับ JPEG ถูก decode ภายใน สเกลด้วย FontSize / PixelsPerEmY แทนที่จะยืดให้เท่าขนาดฟอนต์ และถูกเขียนพร้อม soft mask (§11.6.5.3) ทุกครั้งที่มีพิกเซลสักตัวไม่ทึบเต็ม ส่วน payload TIFF ของ sbix ไม่ถูก decode และไปตกที่ event
เกิดอะไรขึ้นเมื่อ glyph วาดแบบ native ไม่ได้
HotPDF ยิง OnColorGlyphRasterize แล้ววาง bitmap RGBA อะไรก็ตามที่ handler ของคุณคืนมา ถ้าไม่ได้กำหนดอะไรไว้ หรือ handler ปล่อย Handled เป็น false DrawRegisteredColorGlyph คืน False และหน้าคงเดิม event ยิงสำหรับ graph ของ COLR v1 ที่หลุดจาก subset ที่รองรับ, เอกสาร SVG ที่ builder ปลอดภัยปฏิเสธ และ payload bitmap ที่ decoder ภายในอ่านไม่ได้ handler ได้รับฟอร์แมต, ไบต์ฟอนต์ดิบ, asset ที่ดึงออกมา (เอกสาร SVG ซึ่งอาจยังถูก gzip อยู่ หรือไบต์ bitmap กรณี COLR v1 เป็นค่าว่าง), glyph ID, palette และขนาดพิกเซลเป้าหมาย
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 เป็น rasterizer ของคุณ ไม่ใช่ API ของ HotPDF
// มันต้องคืนไบต์ RGBA เป๊ะ Width * Height * 4 ไบต์
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// การเชื่อมสาย
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
HotPDF validate output ของ handler ก่อนแตะหน้า: ขนาดศูนย์, buffer ที่ความยาวไม่เป๊ะเท่า Width * Height * 4 หรือมิติที่ใหญ่พอจะล้นถูกปฏิเสธและ call คืน False fallback แบบ raster ก็ยังเป็น raster emoji ที่ render ทางนี้จึงเสียความคมแบบเวกเตอร์ไป ขอ PixelSize ที่ตรงกับความละเอียด output ของคุณ จับเส้นทางสีคู่กับการเช็กความครอบคลุมตอนวาดจากบทความเรื่องการติดตาม glyph ที่หายไป pipeline ที่รับข้อความผู้ใช้แบบอะไรก็ได้จะรายงานทั้ง glyph ที่หายและ glyph ที่เสียสีได้
ตัว render color glyph, ชุด shaping ของ OpenType และ builder SVG ปลอดภัย ship มาทั้งหมดในHotPDF Delphi PDF component พร้อมใช้สำหรับ Delphi และ C++Builder