يرسم HotPDF الإيموجي الملون إلى PDF عبر THotPDF.DrawRegisteredColorGlyph، التي تقرأ بيانات اللون لخط مسجل بـ RegisterUnicodeTTF وتطلقها رسوميات PDF أصلية: طبقات COLR v0 كمحيطات حروف ممتلئة، ورسوميات الطلاء COLR v1 كمقصات وتظليلات وأنماط مزج، ورموز SVG بوصفها Form XObjects، وصور CBDT أو sbix النقطية بوصفها صوراً. وما لا تستطيع مطابقته أصلياً يذهب إلى حدث OnColorGlyphRasterize بدل أن يتحول بصمت إلى شكل أسود
ذلك البند الأخير هو السبب الكامل لوجود هذا الكود. ضمّن خط إيموجي بالطريقة العادية وسيعطي العارض المحيط من glyf أو CFF، ممتلئاً بلون التعبئة الحالي أياً كان. يصل الوجه المبتسم بقعة سوداء، والعلم مستطيلاً، ولا شيء في خط الأنابيب يشكو
لماذا يُطبع إيموجي ملون ظلاً أسود في PDF؟
لا يعرف برنامج خط PDF شيئاً عن الرموز الملونة. يعامل ISO 32000-1 الرمز شكلاً يُرسم باللون الحالي، وجداول الألوان التي أضافها OpenType لاحقاً، أي COLR/CPAL و SVG و CBDT/CBLC و sbix، ليست جزءاً من نموذج تصوير PDF، فلا عارض ملزم بقراءتها من خط مضمّن. لا بد أن يترجم اللون إلى محتوى صفحة وقت التوليد، ما دام المنتج ما زال يملك بايتات الخط ويعرف الرمز الذي يريده. ويختلف ذلك الترجمة بحسب التنسيق، وخطوط الإيموجي في الواقع تستعملها كلها: متجهات متراصة، ورسوميات طلاء متدرجة، ومستندات SVG مضمّنة، وضربات PNG. تبلّغ HotPDF النتيجة THPDFOpenTypeColorFormat، بقيم otcfNone و otcfCOLRv0 و otcfCOLRv1 و otcfCBDT و otcfSVG و otcfSBIX، وتفحص الخط بأولوية ثابتة: COLR أولاً، ثم SVG، ثم CBDT، ثم sbix. بيانات المتجهات تتفوق على الصور النقطية كلما حمل الخط كلتيهما، وهو ما تريده في مستند قد يُكبَّر أو يُطبع
استدعاء واحد وخمسة تنسيقات: حل رمز ملون ورسمه
يجيب THotPDF.GetRegisteredColorGlyphInfo عن المسار الذي سيسلكه نقطة كود، و DrawRegisteredColorGlyph تسلكه. كلتاهما تبحثان عن نقطة الكود في خريطة محارف الخط الممرر الأحدث إلى RegisterUnicodeTTF، فيجب أن يكون الخط الملون هو خط Unicode المسجل لحظة الاستدعاء. تعيد دالة الرسم False حين لا بيانات لون للرمز أو لم يقدر أي مسار على عرضه، وتترك البديل لك
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 0، وضربة نقطية أقرب إلى 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 لوحة CPAL، فيمكن تحويل خط يشحن لوحة خلفيات داكنة دون لمس الرمز. و TargetPixelsPerEm لا يهم إلا للخطوط النقطية؛ وتركه صفراً يفترض افتراضياً Round(FontSize * 96 / 72)، وهي دقة شاشة، ولهذا يطلب المثال 300 لمخرجات الطباعة. والحد الصادق يجلس في التوقيع: يأخذ الاستدعاء نقطة كود واحدة ويطابقها عبر cmap وحدها. تسلسلات ZWJ ومعدلات درجات البشرة وأعلام المؤشرات الإقليمية رابطات GSUB، فتأليفها مشكلة تشكيل من نوع مقالة بدائل GSUB في OpenType، وليست شيئاً تفعله نقطة الدخول هذه عنك
COLR v0: طبقات حروف متراصة بألوان لوحة
COLR v0 الحالة البسيطة ويعرضها HotPDF مباشرة: يسرد كل رمز أساس رموز طبقات بمدخل لون CPAL، وتصير كل طبقة عملية عرض نص عادية واحدة بلون تعبئتها الخاص، متراصة بترتيب الجدول. الطبقة ذات ألفا دون 255 تحصل على قاموس معاملات حالة رسومية بـ /ca و /CA مطابقين (§8.4.5 من ISO 32000-1)، ويوسم كل رمز طبقة مستخدماً حتى يبقي المقتطع محيطه رغم أن لا نقطة كود تطابقه مباشرة. تفصيل واحد يفاجئ الناس: فهرس مدخل اللوحة 0xFFFF يعني «استخدم لون مقدمة النص» في مواصفة OpenType، وتحلها HotPDF إلى الأسود لا إلى لون تعبئة الصفحة الحالي. لخطوط الإيموجي نادراً ما يهم ذلك؛ ولخطوط الأيقونات التي تعتمد على مدخل المقدمة لتلوين رمز، افحص المخرجات قبل أن تفترض أنها ستتبع لون نصك
كيف تحول HotPDF رسومية طلاء COLR v1 إلى معاملات PDF؟
بتفكيك جداول الطلاء أولاً إلى رسومية مسطحة محدودة ثم مطابقة كل عقدة إلى بناء PDF بعدها فقط. رمز COLR v1 ليس قائمة طبقات بل رسمية موجَّهة لا دورية من سجلات طلاء، حيث يمكن أن تتشارك العقد عبر PaintColrLayers و PaintColrGlyph. يحدّ المفككها عند 4096 عقدة طلاء و 64 مستوى عمق و 1024 توقف لوني، ويتعقب كل عقدة نشطة أو منجزة حتى تكون الإحالة العائدة إلى عقدة نشطة، دورة يمكن لخط خبيث أن يبنيها من إعادة استخدام الطبقات، تُرفض بدل التكرار داخلها. وقواعد الإزاحة هي حيث يخطئ التنفيذ الأول. إزاحات BaseGlyphPaintRecord نسبية إلى بداية BaseGlyphList، وإزاحات طلاء LayerList نسبية إلى LayerList، وكل Offset24 داخل جدول طلاء نسبي إلى جدول الطلاء ذاك نفسه. حُلّ الثلاثة ضد الأساس نفسه وفشلت رموز سليمة تماماً فحص الحدود، وهو ما يبدو تماماً كخط تالف. وبعد بناء الرسمية تكون المطابقة مباشرة:
- يضبط
PaintGlyphمحيط الرمز مقصّاً بنمط عرض نص 7 (§9.3.6 من ISO 32000-1)، ثم يرسم ابنه داخله - الطلاءات الصلبة تمتلئ مستطيلاً مقصوصاً؛ والتدرجات الخطية تصير تظليلات محورية متعددة التوقفات والتدرجات الشعاعية تظليلات شعاعية بلونين (§8.7.4.5)
- التدرجات الممسوحة لا مقابل لها في PDF، فتقاربها HotPDF بـ 96 إسفيناً بلون مسطح، كل منها معاين من خط اللون
- تطلق التحويلات بـ
cm، مقترنة حول أصل خط أساس الرمز، مع إزاحات مقاسة بـFontSize / UnitsPerEm - أنماط
PaintCompositeمن 13 إلى 27 تطابق أنماط مزج PDF القابلة وغير القابلة للفصل مثل/Multiplyو/Screenو/Luminosity(§11.3.5)، مضبوطة عبر مدخل/BMفي ExtGState
الحد صريح. أنماط Porter-Duff من 5 إلى 12 (src_in و xor و plus وغيرها) لا مقابل لها في أنماط مزج PDF، وأنماط الامتداد repeat و reflect على التدرجات الخطية والشعاعية لا تطلق، والتدرجات التي تحمل توقفاتها قيم ألفا مختلفة لا تُزيَّف بشفافية واحدة. والتدرجات الشعاعية بأكثر من توقفين تحفظ لونيها الأول والأخير فقط. تفحص HotPDF الرسمية كلها ضد تلك المجموعة الجزئية المدعومة قبل كتابة أي معامل، فرمز غير مدعوم يترك الصفحة بلا مساس وينتقل إلى البديل النقطي بدل ترك نصف رسم خلفه
رموز SVG والضربات النقطية
تمر رموز SVG عبر الباني المحدود نفسه الذي تستخدمه HotPDF لملفات SVG المستوردة، وتسجل النتيجة Form XObject (§8.10)، تماماً كما في مقالة التحويل من SVG إلى Form XObject. قد يكون المستند في جدول SVG مضغوطاً gzip؛ تجري إزالة الضغط على قطع 8 KB وتتوقف بمجرد أن يوشك الحجم الموسع على تجاوز 32 MB، بدل النفخ أولاً والفحص بعده، ويحد المدخل المضغوط نفسه عند 8 MB. الملف الشخصي مقيّد عمداً: السكربتات والصور المضمّنة والروابط الخارجية و data: URI والإحالات غير المحلية تفشل انغلاقاً. يُقاس النموذج حتى يساوي ضلعه الأطول حجم الخط ويرسو على خط الأساس، بما يطابق نظام إحداثيات SVG السالب y إلى نظام PDF الموجب y. وانتبه أن الباني يستلم مستند SVG كاملاً للرمز، دون انتقاء عنصر glyphNNN، فالخطوط التي تحشر رموزاً كثيرة في مستند مشترك واحد تستحق الاختبار قبل الاعتماد عليها
الخطوط النقطية مسألة اختيار ضربة وموضع. لـ CBDT تختار HotPDF حجم CBLC الذي ppem العمودي له أقرب إلى TargetPixelsPerEm، وتقبل تنسيقات الصور 17 و 18 و 19، وتقرأ مقاييس التنسيق 19 من الجدول الجزئي لفهرس CBLC لأن ذلك التنسيق لا يخزن شيئاً منها. ولـ sbix تكون إزاحات الضربة نسبية إلى الجدول وإزاحات الرموز نسبية إلى الضربة، وسجل dupe يعيد استخدام صورة رمز آخر مع حفظ إزاحات أصل خاصته؛ وإطلاق التكرار الكتابة فوق الأصل الخارجي يزيح الصورة. حمولات PNG و JPEG تفك داخلياً، وتقاس بـ FontSize / PixelsPerEmY بدل شدّها إلى حجم الخط، وتكتب بقناع ناعم (§11.6.5.3) كلما وُجد بكسل ليس معتماً تماماً. حمولات sbix من نوع TIFF لا تفك وتذهب إلى الحدث
ماذا يحدث حين يستحيل رسم رمز أصلياً؟
ترفع HotPDF OnColorGlyphRasterize وتضع أي صورة نقطية RGBA يعيد معالجك؛ وإن لم يُسند شيء، أو ترك المعالج Handled خاطئاً، تعيد DrawRegisteredColorGlyph False وتبقى الصفحة بلا تغيير. ينطلق الحدث لرسومية COLR v1 خارج المجموعة الجزئية المدعومة، ومستند SVG رفضه الباني الآمن، وحمولة نقطية لا تقرأها المفككات الداخلية. يحصل المعالج على التنسيق، والبايتات الخام للخط، والأصل المستخرج (مستند SVG، ربما ما زال مضغوطاً gzip، أو بايتات الصورة النقطية؛ فارغ لـ COLR v1)، ومعرف الرمز، واللوحة، وحجم البكسل الهدف
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 هو مرسومك النقطي، ليس واجهة من HotPDF.
// يجب أن يعيد بالضبط Width * Height * 4 بايت من RGBA.
Handled := RenderWithOwnEngine(Format, FontBytes, AssetData,
GlyphID, PaletteIndex, PixelSize, Width, Height, RGBA);
end;
// التوصيل
Pdf.OnColorGlyphRasterize := Fallback.Rasterize;
تتحقق HotPDF من ناتج المعالج قبل لمس الصفحة: الأحجام الصفرية، أو مخزن طوله ليس بالضبط Width * Height * 4، أو أبعاد كبيرة بما يكفي للفيضان تُرفض ويعيد الاستدعاء False. البديل النقطي ما زال نقطياً، فإيموجي عُرض هكذا يفقد حدّة متجهاته؛ اطلب PixelSize يطابق دقة إخراجك. اقرن مسار اللون بفحوص تغطية وقت الرسم من مقالة تتبع الرموز المفقودة وسيستطيع خط أنابيب يعالج نص مستخدم اعتباطياً أن يبلّغ عن الرموز المفقودة والرموز التي فقدت لونها معاً
عارض الرموز الملونة ومكدس التشكيل OpenType والباني الآمن لـ SVG كلها تشحن في مكون PDF من HotPDF لـ Delphi، المتاح لـ Delphi و C++Builder