يمثل HotPDF صفحة PDF محملة إلى TBitmap في دلفي من خلال استدعاء واحد: RenderLoadedPageToBitmap(PageIndex, DPI). وتفسر الدالة تدفق محتوى الصفحة وترجع صورة نقطية RGB بمعدل 24 بت مملوكة للمستدعي بالدقة التي تختارها، وهو بالضبط ما يحتاجه شريط الصور المصغرة، أو معاينة الطباعة، أو خط تدفق تصدير PDF إلى صورة. يستعرض هذا المقال واجهة برمجة التطبيقات، ثم يتناول الجزء الذي يفصل بين المصير القابل للاستخدام واللعبة: رسم النصوص من برامج الخطوط المضمنة نفسها بدلاً من خطوط النظام المشابهة
لماذا يعد تمثيل صفحة PDF أصعب من رسم صورة؟
ليست صفحة PDF مجرد صورة، بل هي برنامج: تدفق من المعاملات التي تبني المسارات، وتحدد الخطوط، وتضبط الألوان، وتضع الأشكال الرمزية، وتُنفذ ضد نموذج الرسومات المحدد في ISO 32000-1 §8. ولا يوجد شيء في الملف يوضح شكل أي بكسل. ولإنتاج صورة نقطية، يجب عليك تشغيل هذا البرنامج — والحفاظ على مصفوفة تحويل حالية، ومكدس حالة رسومات لـ q/Q، ومسار قص، ومساحات ألوان للملء والتخطيط — وتنقيط النتيجة. ولهذا السبب فإن "مجرد إظهار الصفحة 3 كصورة" هو مترجم لتدفق المحتوى، وليس تحويلاً لتنسيق الملف
تم بناء مصير HotPDF، الذي تم تقديمه في الإصدار v2.253.0, كست وحدات منفصلة تعكس هذا النموذج: نواة مصفوفة تآلفية لجبر تحويل PDF البنية [a b c d e f]، ومكدس حالة الرسومات، وحلال مساحة الألوان (DeviceRGB, DeviceGray, DeviceCMYK, Indexed)، ومنشئ مسار يربط معاملات مسار PDF بـ GDI، وطبقة مقاييس خط تقرأ مصفوفات /Widths للتقدمات الصحيحة، والمترجم الذي يرسل المعاملات ويوجه الوحدات الخمس الأخرى. وتمر كائنات الصور XObjects عبر نفس مكدس فك التشفير الذي تستخدمه المكتبة للاستخراج، لذا فإن كل مرشح صور يمكن لـ HotPDF فك تشفيره للاستخراج — بما في ذلك صور JPEG 2000 المضغوطة بـ JPXDecode — يظهر أيضًا في المخرجات الممثلة
تمثيل صفحة محملة إلى TBitmap
تأخذ الدالة RenderLoadedPageToBitmap فهرس صفحة يبدأ من الصفر وقيمة DPI، حيث تطابق 72 DPI وحدة مساحة مستخدم PDF واحدة ببكسل واحد. وهي ترجع القيمة nil عند الفشل (فهرس خارج النطاق، موارد مفقودة) بدلاً من إثارة استثناء، بحيث يمكن للعارض تخطي صفحة معطلة والمتابعة. ويمتلك المستدعي الصورة النقطية المرجعة ويجب عليه تحريرها
var
Pdf: THotPDF;
Bmp: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('report.pdf') > 0 then
begin
Bmp := Pdf.RenderLoadedPageToBitmap(0, 144); // page 1 at 144 DPI
if Bmp <> nil then
try
Image1.Picture.Assign(Bmp);
finally
Bmp.Free; // caller owns the bitmap
end;
end;
finally
Pdf.Free;
end;
end;
تقوم معلمة DPI بعمل القياس لكل سيناريو شائع. فيتم تمثيل شريط الصور المصغرة بدقة 36 أو 48 DPI والحصول على صور نقطية صغيرة وسريعة؛ وتطابق المعاينة على الشاشة بدقة 96 أو 144 DPI كثافة العرض النموذجية؛ وينتج مسار التصدير بدقة 300 DPI صورًا بجودة الطباعة. ويتم التعامل مع دوران الصفحة من إدخال /Rotate وقلب أصل /MediaBox (يضع PDF الأصل في الأسفل واليسار، بينما يضعه GDI في الأعلى واليسار) داخل مصفوفة الصفحة إلى الجهاز، لذا فإن صفحة بمقاس US Letter بدقة 72 DPI ترجع كـ 612×792 بكسل بالضبط بالاتجاه الصحيح لأعلى
لماذا تعرض الصور المصغرة لملفات PDF الممثلة أشكالاً رمزية خاطئة؟
تعني الأشكال الرمزية الخاطئة أو التقريبية في مخرجات PDF الممثلة دائمًا تقريبًا أن الممثل يستبدل خط النظام بدلاً من استخدام الخط المضمن في الملف. وقد فعل ممثل HotPDF الأول ذلك بالضبط: فقد جرد بادئة التجزئة من /BaseFont (محولاً ABCDEF+Arial إلى Arial)، وطلب من GDI خط نظام بهذا الاسم، ورسم النص به. بالنسبة لمستند يستخدم Arial أو Times New Roman بالترميز القياسي، تبدو النتيجة قريبة. ولكنه تقريب، ويتعطل في حالات محددة بوضوح
تعد الخطوط المضمنة بالتجزئة هي أسوأ الحالات. فقد يحمل الخط المجزأ الأربعين شكلاً رمزيًا التي يستخدمها المستند بالفعل فقط، مع تعيين رموز الأحرف بترتيب خاص بهذا الملف — فقد يكون الرمز 1 هو "T"، والرمز 2 هو "h"، وهكذا. ولا يعرف خط النظام شيئًا عن هذا التعيين الخاص، لذا فإن النص إما يختفي أو يظهر كأحرف خاطئة تمامًا. وتفشل الترميزات المخصصة، وخطوط الرموز، وخطوط الرموز الشريطية (barcode)، وأي خط غير مثبت على جهاز التمثيل بنفس الطريقة. وينطبق الممثل الذي يتوقف عند استبدال خط النظام صورًا مصغرة يمكن التعرف على أنها الصفحة — حتى تستخدم الصفحة الخطوط التي جعلت التضمين ضروريًا في المقام الأول
تمثيل الأشكال الرمزية المضمنة: الرسم من برنامج الخط نفسه
سد HotPDF هذه الفجوة عبر خمسة إصدارات (من v2.268.0 إلى v2.272.0) من خلال تحليل برامج الخطوط المضمنة وإعادة تشغيل الخطوط الخارجية لأشكالها الرمزية كمسارات متجهة معبأة في GDI. ويأتي النص في الصفحة الممثلة الآن من نفس بيانات الخطوط الخارجية التي يستخدمها عارض متوافق، مما يعني أن الخطوط المجزأة، والترميزات المخصصة، والخطوط غير المثبتة يتم تمثيلها بأشكالها الدقيقة. وقد تم بناء التغطية حسب نوع الخط:
بالنسبة لخطوط Type0/CIDFontType2 التي تحتوي على برنامج TrueType مضمن (FontFile2)، يحلل الممثل جدولي glyf و loca مباشرة: ويتم تحويل المنحنيات التربيعية إلى منحنيات Bézier التكعيبية التي يفهمها GDI، ويتم إعادة بناء نقاط المنحنى الضمنية بين النقاط المتتالية خارج المنحنى، وإعادة تشغيل الأشكال الرمزية المركبة بشكل متكرر. ويتم دعم كلا التخطيطين Identity وتدفق CIDToGIDMap الصريح، وتحترم تقدمات CID إدخالات العرض /W و /DW width entries, بحيث يتقدم نص Identity-H ثنائي البايت بشكل صحيح
تحصل برامج CFF (أي FontFile3، سواء كانت CIDFontType0C، أو Type1C، أو غلاف OpenType) على مترجم كامل لسلسلة رموز الأحرف (charstring) من النوع 2: الخطوط، والمنحنيات، وعائلة flex، وأقنعة التلميح، واستدعاءات الروتين الفرعي المحلية والعالمية مع انحياز الروتين الفرعي الصحيح. وتقوم برامج CFF ذات مفتاح CID بمطابقة رموز الأحرف من خلال مجموعة أحرف الخط، وهو أمر مهم للخطوط المجزأة التي يختلف ترتيب أشكالها الرمزية عن ترتيب CID، ويتم احترام اختيار DICT للخط لكل شكل رمزي من خلال FDArray/FDSelect. وتحل خطوط TrueType البسيطة (غير CID) رموز البايت الواحد من خلال جدول cmap الخاص بالخط المضمن مع سلسلة جداول فرعية قوية — تنسيقات Unicode 4 و 12 أولاً، ثم الجداول الفرعية للرموز مع مرآة الاستخدام الخاص F000، ثم تنسيقات Macintosh القديمة — بينما تحل خطوط Type1 البسيطة من خلال الترميز المدمج لبرنامج CFF
يكتمل المشهد بتحسينين. أولاً، يتم حل قواميس /Encoding للخطوط البسيطة وفقًا للأولوية التي يحددها معيار ISO 32000-1 §9.6.6: تتجاوز مصفوفات /Differences الترميز الأساسي، والذي بدوره يتجاوز خريطة برنامج الخط نفسه — وهو المسار الذي تعتمد عليه سلاسل الأدوات المشتقة من TeX و PostScript، مع حل أسماء الأشكال الرمزية من خلال قائمة Adobe Glyph List، أو مجموعة أحرف CFF، أو جدول TrueType cmap. ثانياً، يتم إعادة تشغيل خطوط Type3، التي تعد أشكالها الرمزية في حد ذاتها تدفقات محتوى صغيرة، من خلال الممثل مع تكوين مصفوفة الخط وحجم الخط ومصفوفة النص؛ ويتم تفسير /Widths لمساحة الأشكال الرمزية من خلال /FontMatrix كما يتطلب ISO 32000-1 §9.6.5، ويتم قص إجراءات الأشكال الرمزية التي تعلن عن مربع إحاطة d1 إليه، بحيث لا يمكن لشكل رمزي مشوه لرمز شريطي (barcode) الرسم خارج خليته. وعندما لا يمكن مطابقة رمز — كبرنامج تالف، أو حرف غير مطابق — يتراجع الممثل إلى الرسم بخط النظام لهذا الشكل الرمزي بدلاً من إسقاط تشغيل النص
كيف تجعل عمليات التمثيل المتكررة سريعة؟
الإجابة التي يشحنها HotPDF هي ذاكرة تخزين مؤقت للصفحات الأكثر استخدامًا مؤخرًا: تحتفظ RenderLoadedPageToBitmapCached بما يصل إلى RenderCacheCapacity من الصفحات الممثلة (الافتراضي 8) والمفهرسة بفهرس الصفحة و DPI، ويؤدي العثور على الصفحة في الذاكرة المؤقتة إلى إرجاع نسخة جديدة مملوكة للمستدعي دون لمس تدفق المحتوى — وهو عادةً أسرع بآلاف المرات من إعادة تفسير الصفحة. ويناسب هذا النمط تطبيقات العرض تمامًا: فالمستخدم الذي يتنقل بين صفحتين، أو حدث تغيير الحجم الذي يعيد طلب نفس الصفحة بنفس دقة DPI، يصل إلى الذاكرة المؤقتة في كل مرة
// Thumbnail strip: first pass renders, scrolling back hits the cache
for I := 0 to ThumbCount - 1 do
begin
Bmp := Pdf.RenderLoadedPageToBitmapCached(I, 48);
if Bmp <> nil then
try
ThumbList.AddThumbnail(I, Bmp);
finally
Bmp.Free;
end;
end;
// After editing a loaded page in place:
Pdf.InvalidateRenderedPageCache; // next render reflects the change
كن صريحًا بشأن فاتورة الذاكرة قبل زيادة السعة. فصفحة بمقاس US Letter بدقة 300 DPI تكون بأبعاد 2550×3300 بكسل، أي حوالي 25 ميجابايت كصورة نقطية بمعدل 24 بت، لذا فإن ثماني صفحات مخزنة مؤقتًا بدقة التصدير تحتجز حوالي 200 ميجابايت. وعند دقة DPI للصور المصغرة، تكلف نفس الإدخالات الثمانية أقل بكثير من ميجابايت واحد. اضبط حجم RenderCacheCapacity لدقة DPI التي تخزن عندها فعليًا، واستدعِ InvalidateRenderedPageCache بعد أي تعديل في المكان — فالذاكرة المؤقتة مفهرسة بالصفحة ودقة DPI فقط، ولا يمكنها رؤية أن المحتوى الأساسي قد تغير. ويؤدي تحميل مستند جديد إلى مسحها تلقائيًا
تعمل ذاكرة تخزين مؤقت ثانية تحت الذاكرة المؤقتة للصفحة: حيث يتم الاحتفاظ بكائنات الصور XObjects التي تم فك تشفيرها في مخزن محدد بالبايت ومحدود بـ ImageCacheMaxBytes (الافتراضي 32 ميجابايت) مع طرد العناصر الأقل استخدامًا مؤخرًا. ويتم فك تشفير الشعار أو ترويسة الرسائل المتكررة في كل صفحة مرة واحدة لكل تحميل للمستند بدلاً من فك تشفيره مرة واحدة لكل معامل Do، مما يقلل وقت التمثيل للنصف تقريبًا للصفحات ذات الصور المشتركة ويسرع تصدير TIFF متعدد الصفحات بنفس المقدار. وتقوم InvalidateRenderedPageCache بمسح هذه الذاكرة المؤقتة أيضًا
ما الذي لا يزال يتم تمثيله بشكل تقريبي
يستهدف الممثل المجموعة الفرعية الشائعة لمستندات PDF، ومن الجدير بالمعرفة أين تقع الحدود. ويتم تمثيل مساحات الألوان المستندة إلى CalRGB و Lab و ICC بشكل تقريبي بدلاً من إدارتها لونيًا — ويتم التعامل مع مساحات ألوان الأجهزة، واللوحات المفهرسة (Indexed)، و عمليات البحث عن ألوان وظائف النوع 0 التي تمت معاينتها، ولكن ملف إنتاج الطباعة الذي يعتمد على مقاصد تمثيل ICC لن يكون دقيقًا من الناحية اللونية. وتعد أنماط التظليل (sh) وأوضاع الدمج خارج نطاق ألفا البسيط خارج نطاق التغطية أيضًا، وتكرار Form XObject محدود العمق كحارس للحلقات المفرغة. وبالنسبة للفواتير والتقارير والعقود والنماذج — وهي الصفحات المكونة من نصوص ومسارات وصور — تكون المخرجات أمينة؛ وبالنسبة لنسخة تصميم مليئة بالتدرجات ومجموعات الشفافية، تعامل مع الصورة النقطية كمعاينة وليست كنسخة نهائية للطباعة
القراءة العملية: إذا كان خط التدفق الخاص بك ينشئ مستندات باستخدام HotPDF أو يستهلك ملفات PDF المعتادة للأعمال، فإن RenderLoadedPageToBitmap يديرها مع أشكال الأشكال الرمزية المضمنة الدقيقة، وتقدمات CID الصحيحة، وهندسة الصفحة الصحيحة. وتعيش التقريبات في زوايا نموذج الرسومات التي نادراً ما تزورها مستندات الأعمال
تشحن RenderLoadedPageToBitmap، ومتغيرها المخزن مؤقتًا، وخط تدفق تمثيل الأشكال الرمزية المضمنة الموضح هنا كجزء من مكون HotPDF لدلفي و C++Builder — وهي مكتبة VCL أصلية بدون تبعيات DLL خارجية، وتغطي إنشاء PDF، وتعديله، واستخراج النصوص، وتمثيل الصفحات في حزمة واحدة