يجمع Delphi و Lazarus نفس Object Pascal، وهذا التشابه السطحي هو بالضبط ما يجعل نقل العارض بينهما خادعًا. تتباعد سلسلتا الأدوات في ثلاثة أماكن تهم عمل PDF: نوع string الأصلي هو UTF-16 في Delphi و UTF-8 في تطبيق LCL؛ و VCL و LCL عبارة عن أطر بصرية مختلفة لها عناصر التحكم، ومربعات الحوار، وتنسيقات تدفق النماذج الخاصة بها؛ ويستهدف الثنائي (binary) لـ Delphi نظام Windows بينما قد يتجه ثنائي FPC إلى Linux أو macOS. لا يظهر أي من هذه الاختلافات في وقت التجميع. سيتم تجميع العارض المبني على مكون PDFium، والذي يشحن إصدارات VCL و LCL من شجرة مصدر واحدة، بشكل نظيف ضمن Lazarus بعد مجموعة من مقايضات أسماء الوحدات وبعض كتل {$IFDEF FPC}. وتصل الإخفاقات لاحقًا، عندما تكشف البيانات الحقيقية والنشر الحقيقي الافتراضات التي كان بناء Delphi يقوم بها بهدوء
وتمثل أربعة من هذه الافتراضات معظم الوقت الضائع: ترميز النص عند حدود واجهة المستخدم، والإغراء بالاحتفاظ بنسختين من النموذج، والطريقة التي يتم بها حل الثنائي لمحرك أصلي في وقت التشغيل، واللحظة التي ينفد فيها تحويل النص إلى كلام (TTS) من المنصة بمجرد اختفاء SAPI. كل منها رخيص في التعامل معه إذا كنت تعرف أنه قادم ومكلف في الملاحقة إذا لم تفعل ذلك
نفس Pascal، بحمولات سلسلة مختلفة
كان نوع string الأصلي لـ Delphi هو UTF-16 منذ عام 2009. يتم تعيين Lazarus و Free Pascal افتراضيًا إلى UTF-8 في تطبيقات LCL. تتحدث API الخاصة بالنص والمواجهة للمكون UTF-16 من خلال نوع WString، والذي بناء FPC أسماء مستعارة له إلى WideString، لذلك فإن كل حد يعبر فيه النص بين واجهة المستخدم لـ LCL ومحرك PDF يمثل نقطة تحويل
تحدث التحويلات تلقائيًا في مهام مباشرة، ولا تحتاج معظم التعليمات البرمجية أبدًا إلى التفكير فيها. هناك عادتان تبعدان أخطاء الترميز. قم بتمرير النص بشكل مستقيم دون معالجة على مستوى البايت: التعليمات البرمجية التي تقطع مصطلح البحث عن طريق إزاحة البايت تعمل في Delphi، حيث يكون Char واحدًا هو وحدة UTF-16 واحدة، ويفسد UTF-8 متعدد البايت في LCL. واختبر باستخدام بيانات غير ASCII من التشغيل الأول. اسم ملف ألماني، أو مصطلح بحث سيريلي (Cyrillic)، أو اسم مؤلف بلكنة في البيانات الوصفية للمستند: تخفي بيانات اختبار ASCII الخالصة كل عيب في الترميز، لأن ASCII هو النطاق الوحيد حيث يتفق UTF-8 و UTF-16 على البايت للحرف. الخلل حقيقي طوال الوقت؛ يحافظ عليه ASCII غير مرئي فقط حتى يفتح عميل في ميونيخ ملفًا لم توسعه من قبل
كتلة شرطية واحدة، وليست شوكة لكل IDE
بعد أول دستة من IFDEFs، تبدأ قاعدة الكود بالشعور وكأنها مشروعان يرتديان مستودعًا واحدًا، ويبدو تشعبها لكل بيئة تطوير متكاملة (IDE) مغريًا. إنها خطوة خاطئة. تنهار الاختلافات الحقيقية في كتلة إعلان مشتركة واحدة، وتضاعف الشوكة تكلفة كل إصلاح خطأ من تلك النقطة فصاعدًا. حافظ على الطبقة الشرطية صغيرة هكذا:
{$IFDEF FPC}
uses
LCLType, Forms, Graphics, Controls;
type
WString = WideString; // component text APIs are UTF-16
TBytes = array of Byte;
{$ELSE}
uses
Winapi.Windows, Vcl.Forms, Vcl.Graphics, Vcl.Controls;
{$ENDIF}
كل ما دون هذه الكتلة يترجم بشكل مماثل في كلا بيئتي IDE. معالجة المستند، والتنقل في الصفحة، واستدعاءات التصيير: تعرض TPdf و TPdfView نفس السطح في إصدارات VCL و LCL، لذا فإن الجزء الأكبر من المتصفح لا يرى أبداً حالة مترجم (compiler condition). إن الحفاظ على هذا النحو هو انضباط بنيوي وليس حيلة ذكية. يعيش منطق PDF المشترك في الوحدات التي لا تسحب أي مربعات حوار أو لوحات خاصة بإطار العمل. تختفي الأشياء القليلة التي تختلف بشكل حقيقي، مثل مربعات حوار الطباعة ومنتقيات الملفات باصطلاحات المنصة الخاصة بها، خلف واجهة رفيعة يتم تنفيذها مرة واحدة لكل إطار عمل. وتصبح كتلة IFDEF هي المكان الوحيد الذي يُسمح فيه باختلاف المنصة في المستقبل بالهبوط، بدلاً من تسريب توجيهات المترجم عبر أربعين وحدة
قم ببناء النموذج في الكود، وليس في اثنين من المصممين
تدفق النماذج (Form streaming) هو المكان الذي تتعفن فيه مشاريع IDE المزدوجة بهدوء. تنفصل .dfm و .lfm اللتان تدعيان وصف نفس النموذج عن بعضهما البعض تدريجياً، خاصية بخاصية، حتى تتصرف العمليتان بشكل مختلف لأسباب لا يستطيع أحد اكتشافها، لأن الملفين ليسا حتى في نفس التنسيق. يتجنب بناء المتصفح في وقت التشغيل المشكلة بأكملها. هناك تسلسل منشئ واحد (constructor sequence)، يتم التحكم في إصداره كشفرة عادية، وهو يُقرأ بالمثل على كلا المنصتين:
procedure TViewerForm.FormCreate(Sender: TObject);
begin
Pdf := TPdf.Create(Self);
PdfView := TPdfView.Create(Self);
PdfView.Parent := Self;
PdfView.Align := alClient;
PdfView.Pdf := Pdf;
PdfView.FitMode := pfmFitWidth;
if ParamCount > 0 then
begin
Pdf.FileName := ParamStr(1);
Pdf.Active := True; // opens the document; PageCount valid after this
end;
end;
الترتيب الدقيق لتلك التعيينات أقل أهمية من السطر الواحد الذي يقوم بالعمل الحقيقي. يربط PdfView.Pdf := Pdf التحكم المرئي بمكون المستند، ومن تلك النقطة يستجيب التنقل عبر الصفحة من خلال PageNumber وسلوك الملاءمة من خلال FitMode بشكل متماثل تحت VCL و LCL. تجدر الإشارة إلى وجود ميزة شاذة واحدة عبر أطر العمل قبل أن يبلغ عنها المستخدم كخلل: إن تعيين Zoom يدويًا يعيد FitMode إلى pfmNone في كلا الإطارين. لذلك إذا كان شريط الأدوات الخاص بك يعامل "عرض الملاءمة" على أنه تفضيل لزج (sticky preference)، فعليك إعادة تعيين وضع الملاءمة بعد أي تكبير/تصغير برمجي، أو يتوقف التفضيل عن الالتصاق بهدوء في المرة الأولى التي يلمس فيها الكود مستوى التكبير/التصغير
الثنائي الذي لم تحذرك منه بيئة التطوير المتكاملة أبدًا
يلتف المكون حول محرك PDFium، والذي يُشحن كثنائي أصلي للمنصة، وهذا الثنائي هو مصدر كل تقرير تقريبًا من تقارير "يعمل في بيئة التطوير المتكاملة (IDE)، يفشل من الاختصار المثبت". ثلاثة قواعد تفسر معظمها. يجب أن تتطابق البتية (Bitness) تمامًا. لا يمكن لملف قابل للتنفيذ 32 بت تحميل مكتبة pdfium 64 بت، والرسالة التي يعيدها نظام التشغيل ("الوحدة النمطية غير موجودة" في بعض إصدارات Windows) تضلل بنشاط، لأن الملف جالس هناك مباشرة بجوار الملف القابل للتنفيذ. حل مسار المكتبة بالنسبة للملف القابل للتنفيذ، وليس أبدًا دليل العمل؛ يختلف تشغيل IDE وتشغيل shell على وجه التحديد في هذه النقطة، وهذا هو السبب في أن الخلل يختفي أثناء التطوير. والتقط حمولة فاشلة قبل فتح المستند الأول، ثم أبلغ عنها بالمسار المتوقع والبنية المكتوبة. تغلق تذكرة الدعم التي تقرأ "ملف PDFium الثنائي 64 بت مفقود في <path>" في دقائق. أما تلك التي تقرأ "توقف العارض عن العمل عند بدء التشغيل" فتتحول إلى أسبوع من الجدل
قم بإصدار ثنائي المحرك جنبًا إلى جنب مع الملف القابل للتنفيذ أثناء وجودك فيه. يتحرك PDFium بسرعة، ويؤدي المثبت الذي يقوم بتحديث التطبيق ولكنه يترك مكتبة قديمة على القرص إلى حدوث أعطال لا يمكن لأي شخص في مكتبك إعادة إنتاجها، للسبب البسيط وهو أن كل جهاز في مكتبك يصادف أنه يحمل الزوج المطابق. عامل المكتبة كجزء من قطعة أثرية البناء (build artifact)، مع نفس المثبت، ونفس ختم الإصدار، ونفس مسار التراجع مثل الملف القابل للتنفيذ الذي يقوم بتحميله
تسجيل المكونات في Lazarus IDE
لا يحتاج إنشاء وقت التشغيل إلى أي تسجيل في وقت التصميم على الإطلاق، وهو الإعداد الأنظف لعارض يبني واجهة المستخدم الخاصة به في التعليمات البرمجية. عندما تريد المكونات الموجودة على لوحة Lazarus لعمل وقت التصميم، قم بتثبيت الحزمة والسماح لوحدة التسجيل المخصصة لها، PDFiumLazReg في Lib/FPC/PDFiumLaz.lpk، بمعالجتها. تم وضع علامة على هذه الوحدة في وقت التصميم عن قصد: فهي تشير إلى واجهات محرر خصائص IDE التي يجب ألا ترتبط أبدًا بالملف القابل للتنفيذ الخاص بشحنك
إذا أخطأت في هذا العرض، فإن الأعراض تتمثل في تطبيق يعتمد لسبب غير مفهوم على حزم بيئة التطوير المتكاملة (IDE)، والذي يظهر كفشل في النشر على أول جهاز عميل لم يتم تثبيت Lazarus عليه مطلقًا
الكلام وقارئات الشاشة خارج Windows
تحويل النص إلى كلام هو الميزة الوحيدة التي تنكسر فيها القصة عبر الأنظمة الأساسية (cross-platform)، وتتحطم في نظام التشغيل، وليس المكون. توجد SAPI، وهي الخلفية المعتادة لـ TTS في Windows، على نظام التشغيل Windows فقط. يحافظ بناء Lazarus الذي لا يزال يستهدف Windows على إخراج SAPI الكامل ونفس السلوك المتوافق مع NVDA الذي كان موجودًا في Delphi الأصلي، لذا فإن النقل من Windows إلى Windows لا يفقد أي شيء هنا، ولا يمكن لمستخدم NVDA التمييز بين البنائين
الهدف الخاص بنظام Linux أو macOS هو مسألة مختلفة. لا يوجد SAPI يمكن استدعاؤه، لذلك يجب إعادة توصيل إخراج الصوت بخدمة كلام أصلية بينما تظل API القراءة أعلاها في مكانها. هذا التقسيم هو الحجة لوضع الكلام خلف واجهة من أول التزام (commit): تحليل ترتيب القراءة ومؤشر تتبع الكلمات هي أمور محايدة للمنصة وتنتقل كما هي، وفقط الطبقة الرقيقة التي تنتج الصوت فعليًا هي التي يجب أن تتغير لكل منصة. تغطي مقالة القارئ الذي يمكن الوصول إليه آلة القراءة هذه بعمق
قائمة مراجعة التكافؤ قبل أن تسمي النقل منتهياً
المرور التالي قد اصطاد تراجعات حقيقية (real regressions)، مدرجة تقريباً بالترتيب الذي تميل فيه الإخفاقات إلى الظهور. افتح مستندًا يحتوي مساره على أحرف غير ASCII. ابحث عن مصطلح بأحرف غير ASCII وأكد على أن النتائج تُبرز المكان الذي يجب أن تكون فيه. تدرب على التمرير بعجلة الماوس، وتحديد السحب، والتنقل في صفحة لوحة المفاتيح في كل مجموعة من عناصر واجهة المستخدم التي تشحنها، لأن معالجة التركيز وسلوك العجلة هما أكثر الزوايا اعتمادًا على مجموعة عناصر واجهة المستخدم في LCL. تحقق من التصيير عند قياس العرض بنسبة 100٪ و 150٪ و 200٪. أخيرًا، قم بتشغيل الإصدار المثبت، وليس بناء IDE، على جهاز لم يكن به IDE مطلقًا، لأن هذا هو الاختبار الوحيد الذي يمارس القرار الثنائي بصدق. كل شيء آخر يمكن أن يمر بينما يفشل هذا بهدوء
تنتقل إنتاجية التصيير بين الإصدارين دون تغيير، لذا فإن نهج التخزين المؤقت (caching) من ذاكرة التخزين المؤقت للتصيير ومقال أداء التكبير/التصيير ينطبق على عارض LCL تمامًا كما هو مكتوب لعارض VCL
لا شيء من هذا يجعل إصدار LCL أقل شأناً. السطح الأساسي متطابق على كلا الجانبين: TPdf، و TPdfView، والتصيير، والنماذج، واستخراج النص، و API لإمكانية الوصول تتصرف بشكل مماثل بغض النظر عن بيئة التطوير المتكاملة (IDE) التي جمعتها. كل فرق يستحق التتبع يكون مرتبطًا بالنظام الأساسي وليس بالإصدار. كلام SAPI مخصص لنظام Windows فقط، وتتبع الحوارات اصطلاحات كل إطار عمل، ويجب أن يتطابق الثنائي (binary) مع البنية التي يتم تحميلها فيه. احصل على حدود الترميز، ونموذج وقت التشغيل، والحل الثنائي بشكل صحيح، وبقية النقل هي العمل الميكانيكي الذي تم التعامل معه بواسطة المترجم (compiler) نيابة عنك
تشحن إصدارات VCL و LCL الموضحة هنا معًا كـ مكون PDFium، مع كود المصدر و API العامة المتطابقة لـ Delphi و C++Builder و Lazarus/FPC