يمكن لثلاثة مُنقِّطات أن تقرأ ملف PDF نفسه وتختلف حول ما يقوله. فالمحرك المدمج في PDF Library for Delphi هو الذي يُشحن بلا ملفات إضافية ويصيّر كل شيء بكفاءة، ولهذا يستحق الخانة الافتراضية. ويجلب Cairo خط أنابيب مختلفًا للشفافية وتنعيم الحواف، ويميل إلى أن يكون ما يلجأ إليه الناس حين تخرج الأقنعة الناعمة أو أنماط المزج خاطئة في مكان آخر. ويحمل PDFium كود التصيير الخاص بـ Chrome، لذا فإن صفحة تبدو صحيحة في متصفح تبدو عادةً صحيحة تحت PDFium أيضًا، بثمن DLL ضخم ومعمارية بتات يصرّ على مطابقتها. ولا أحد من الثلاثة صحيح بالمطلق. فالصحة تخص كل مستند على حدة، والطريقة الصادقة الوحيدة لمعرفة أي محرك يتعامل مع مجموعة ملفات معيّنة هي تمرير تلك المجموعة عبر كل منها
هذه هي الحجة لمعاملة المحرك كخيار وقت تشغيل لا خيار وقت بناء. وتضع PDF Library for Delphi، مكتبة PDF لـ Delphi و C++Builder من losLab، الثلاثة جميعًا خلف سطح تصيير واحد بحيث يكلّف القرار عددًا صحيحًا واحدًا بدلًا من تفريعة كود. وما تبقى يتلخص في الاختيار بينها بأمان، وتأكيد أي المحركات يحملها ثنائي منشور فعليًا، ومنع حالة التصيير من تسميم المهمة التالية بهدوء
ثلاثة مُنقِّطات خلف سطح استدعاء واحد
ترقّم المكتبة محركاتها. المحرك 1 هو المُصيِّر المدمج، الافتراضي، بخيارات تنعيم GDI+ على Windows. والمحرك 2 هو Cairo والمحرك 3 هو PDFium، وكلاهما يُختار في وقت التشغيل عبر SelectRenderer. ويُحمَّل المحركان الخارجيان من ملفات DLL تزوّد مساراتها بـ SetCairoFileName وSetPDFiumFileName قبل اختيارهما. وأيًّا كان المحرك النشط، يمر العمل عبر الاستدعاءات نفسها: RenderPageToFile وRenderPageToStream وRenderDocumentToFile. وتبديل المحركات يحرّك رقمًا واحدًا؛ ولا يلاحظ بقية كود التصيير لديك شيئًا أبدًا
ويمتد نموذج الوجهات إلى ما هو أبعد بكثير من الصور النقطية. فصنف المُصيِّر يستهدف أيضًا ملفات التعريف (WMF و EMF و EMF+) و EPS وسياقات الأجهزة المباشرة والطابعات و HTML5، ولا يظهر Cairo و PDFium كوجهتين إضافيتين إلا حين جرى تجميعهما داخل البناء. والإخراج النقطي هو حيث تتباين المحركات الثلاثة بأوضح صورة، لذا فهو ما تستخدمه الأمثلة هنا
لا تفترض أبدًا وجود محرك: اسبر عند بدء التشغيل
Cairo و PDFium ميزتان تخضعان للتجميع الشرطي، ما يعني أن ثنائيًا قد يُبنى من دونهما بالكامل. وحين يحدث ذلك، فإن طلب المحرك 2 أو 3 لا يرفع أي شيء. تُعيد SelectRenderer ببساطة قيمة غير المعرّف الذي طلبته، والكود الذي يتجاهل القيمة المُعادة يواصل التصيير بأي محرك كان نشطًا بالفعل. والدفاع هو مسبار عند بدء التشغيل يطلب من كل محرك أن يعرّف بنفسه ويسجّل الإجابة:
function ProbeEngines(PDF: TPDFlib): string;
begin
Result := 'built-in'; // المحرك 1 موجود دائمًا
if (PDF.SetCairoFileName('cairo.dll') = 1) and (PDF.SelectRenderer(2) = 2) then
Result := Result + ', cairo';
if (PDF.SetPDFiumFileName('pdfium.dll') = 1) and (PDF.SelectRenderer(3) = 3) then
Result := Result + ', pdfium';
PDF.SelectRenderer(1); // استعد الافتراضي قبل العمل الفعلي
end;
شغّل ذلك المسبار مرة واحدة عند بدء التشغيل واكتب نتيجته في السجل بجانب كل مهمة تصيير. فالسؤال الأكثر شيوعًا على الإطلاق حين يُبلغ عميل عن اختلاف في التصيير هو أي المحركات يملكها تثبيته فعليًا، وإجابة من سطر واحد جالسة في السجل تحسمه من دون جلسة سطح مكتب بعيد. وثمة أثر جانبي مفيد: إذا أعادت SetPDFiumFileName نفسها القيمة 0، فأنت تعرف بالفعل أن DLL هو المشكلة (مسار خاطئ، أو معمارية بتات خاطئة، أو اعتمادية مفقودة) لا ثنائيًا مجمَّعًا من دون دعم PDFium، لأن استدعاء المسار لم يحل شيئًا قبل أن تعمل SelectRenderer أصلًا
عشرة تنسيقات إخراج خلف عدد صحيح واحد في Options
يختار المعامل Options في استدعاءات التصيير ترميز الإخراج: 0 هو BMP، و1 JPEG، و2 WMF، و3 EMF، و4 EPS، و5 PNG، و6 GIF، و7 TIFF، و8 EMF+، و9 HTML5. و PNG (5) هو الافتراضي المعقول للمعاينات وصور الصفحات الأرشيفية. أما JPEG (1)، مقترنًا بـ SetJPEGQuality، فهو الخيار الأفضل للمسحوحات الفوتوغرافية حيث يهم حجم الملف أكثر من الحواف الحادة
ويخفي تنسيق واحد متطلبًا بشأن الدفق الهدف. فمسار BMP يكتب بيانات الصورة أولًا، ثم يعود بالمؤشر إلى الإزاحة 0x26 لترقيع حقول الدقة في الترويسة. وجّه ذلك إلى دفق أمامي فقط، أو غلاف ضغط أو مقبس شبكة، فيفشل الاستدعاء بطريقة تُقرأ كعطل في المحرك وليست كذلك. وحين يتعذر تجنّب هدف غير قابل للبحث، صيّر PNG بدلًا من ذلك، أو مرّر BMP عبر دفق ذاكرة وانسخه إلى الأمام ما إن يكتمل
DPI الذي تمرّره ليس DPI الذي تحصل عليه
يأخذ كل استدعاء تصيير وسيط DPI، لكن الدقة التي تحصل عليها فعليًا هي تلك القيمة مضروبة في مقياس التصيير العام. تبدأ SetRenderScale عند 1.0، وما إن تغيّرها حتى يسري المعامل الجديد بصمت على كل تصيير لاحق على تلك النسخة:
PDF.SetRenderScale(2.0); // كل تصيير لاحق يُضاعَف
PDF.RenderPageToFile(150, 1, 5, 'p1.png'); // فعليًا 300 DPI
PDF.SetRenderScale(1.0); // أعد الضبط، وإلا وصلت مصغّراتك ضخمة
وتنطبق اللزوجة نفسها على SetRenderCropType وإعداد جودة JPEG. ففي خدمة تنتج مصغّرات ومعاينات وصورًا بدقة الطباعة من نسخة واحدة مشتركة، هذه الإعدادات المتبقية هي ما يقف فعليًا وراء تذكرة "المصغّرات صارت فجأة 40 ميغابايت" التي تظهر بين الحين والآخر. وثمة مخرجان نظيفان: أعد ضبط الحالة ذات الصلة في مستهل كل عملية، أو خصّص نسخة منفصلة لكل ملف تعريف إخراج بحيث لا يتسرب شيء بينها
ضبط المحرك الافتراضي قبل اللجوء إلى غيره
حصة مفاجئة من طلبات "نحتاج إلى محرك مختلف" تتبين أنها مشكلات إعدادات في تنكّر. فالمُصيِّر المدمج يكشف سلوك التنعيم لديه عبر SetGDIPlusOptions وعائلة SetRenderOptions الأوسع، وتتيح لك SetGDIPlusFileName توجيهه إلى وقت تشغيل GDI+ محدد حين تشحن بيئة نشر نسخة غير معتادة. الرسوم الخطية المسننة عند DPI منخفض، والنص الضبابي في المصغّرات، والتدرج المتشرّط عبر التدرجات اللونية: كلها تستجيب لتلك المقابض، وتدويرها لا يكلّف شيئًا في المثبّت. أما إضافة Cairo أو PDFium، على النقيض، فتعني شحن مزيد من ملفات DLL، وتتبّع نسخة ثانية أو ثالثة لمعمارية البتات، وتحمّل واجب تحديثها
لذا فإن لشكوى الجودة ترتيبًا طبيعيًا للعمليات. أعد إنتاجها أولًا عند DPI العميل ومقياسه بالضبط، إذ يتبخر الفرق نصف الوقت ما إن يتطابقا. جرّب خيارات التنعيم في المحرك المدمج بعد ذلك. وعندها فقط ضع الصفحة جنبًا إلى جنب عبر المحركات مع تثبيت كل متغير آخر: صيّرها إلى PNG عبر المحركات 1 و2 و3 عند DPI متطابق وأرفق الثلاثة. وعادةً يتفق اثنان من الثلاثة، وتخبرك تلك الأغلبية ما إذا كان الشاذ هو المستند يُفسَّر بطريقة مختلفة أم توقّعك المرجعي نفسه منحرف. وثلاث صور ملموسة تحسم نزاع "يُصيَّر بشكل خاطئ" أسرع بكثير من فقرة من الصفات
سلسلة احتياطية تشرح نفسها
ما إن يستقر السبر وانضباط الحالة، تصبح السلسلة الاحتياطية نفسها قصيرة. ويعتمد اكتشاف الفشل على LastRenderError، التي تحمل نص رسالة المحرك نفسه لأحدث تصيير وتكون فارغة حين نجح التصيير:
procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
PDF.SelectRenderer(1); // المدمج أولًا
PDF.RenderPageToFile(200, Page, 5, OutFile); // 5 = PNG
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('built-in', Page, PDF.LastRenderError);
if PDF.SelectRenderer(3) = 3 then // PDFium كاحتياطي ثقيل
begin
PDF.RenderPageToFile(200, Page, 5, OutFile);
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('pdfium', Page, PDF.LastRenderError);
end;
raise Exception.CreateFmt('Page %d failed on all available engines', [Page]);
end;
نقطتا تصميم لهما وزن هنا. تسجّل السلسلة سبب حدوث كل تبديل، لأن سطر سجل يقرأ "هذه الصفحة تعود احتياطيًا إلى PDFium منذ الإصدار 3.7" إشارة تراجع تريدها تتّجه في المراقبة لا أن تضيع. وترتيب الاحتياط نفسه سياسة تستحق الاختيار لكل حمل عمل. فالمحرك المدمج يُنشر من دون ملفات DLL إضافية، ما يجعله المحاولة الأولى الصحيحة في معظم التثبيتات، بينما المستندات المثقلة بمجموعات الشفافية أو التظليل غير المعتاد هي السبب المعتاد لأن يوصّل فريق محركًا بديلًا أصلًا. ولا يوجد محرك هو الأسرع عمومًا، وهذا هو المغزى الكامل من الاختيار لكل استدعاء: قِس أداء كل منها مقابل عيّنة من مستنداتك الحقيقية عند DPI الحقيقي لديك، وراجع ذلك القياس كلما تغيّرت ملفات DLL للمحركات أو مزيج المستندات. ومجموعة الملفات تكسب الجدال في كل مرة
ما بعد الصفحات المفردة: دفعات TIFF وسياقات الأجهزة الحية
جاران لاستدعاءات كل صفحة يكملان صندوق الأدوات. تصيّر RenderAsMultipageTIFFToFile تعبير نطاق صفحات مباشرة في TIFF متعدد الصفحات، وهو الشكل الطبيعي للتسليم الأرشيفي إلى أنظمة إدارة المستندات التي تسبق PDF. وترسم RenderPageToDC مباشرة على سياق جهاز Windows لعناصر المعاينة، محكومةً بثلاثيها الخاص من الإعدادات اللزجة (SetRenderDCOffset وSetRenderDCErasePage إضافةً إلى نوع القص) التي تحتاج إلى انضباط إعادة الضبط نفسه الذي يحتاجه معامل المقياس. وتحمل معاينة الشاشة وتصيير مسار الطباعة من الفخاخ الخاصة بهما ما يكفي لتبرير مقال مخصص، مرتبط أدناه
إلى أين تذهب بعد ذلك
عادة واحدة تستحق أن تحملها معك: لأن SelectRenderer تسري على كل استدعاء لاحق على النسخة، يمكن إعادة محاولة صفحة عنيدة واحدة على محرك آخر بينما يبقى بقية المستند على الافتراضي. ولرسم المعاينة واختيار الطابعة ومعالجة DevMode، تابع مع مقال معاينة الطباعة وسياق الجهاز. وحين تغذّي عمليات التصيير خط أنابيب عالي الحجم فوق ملفات ضخمة جدًا، يقترن النهج القائم على المقابض في دليل الوصول المباشر طبيعيًا بتصيير كل صفحة عبر DARenderPageToFile
وتُفصَّل حزم المحركات والتنسيقات المدعومة والبناءات التجريبية على صفحة منتج PDF Library for Delphi