مقال تقني

HotPDF على Free Pascal: حدود Deflate وAES والمرمزات

يترجم HotPDF ويعمل تحت Free Pascal 3.2.2 مع Lazarus، والملخص الصادق لعملية النقل تلك جملتان. إنشاء المستندات وتحميلها وحفظها وضغطها وفك ضغطها وتشفيرها وفك تشفيرها كلها تعمل على خلفيات بلغة Pascal وحدها، فيمكن لتطبيق Lazarus أن ينتج ويستهلك PDF حقيقيا دون أي اعتمادية C. أما مرمزات الصور الأصلية الاختيارية فلا، لأن كائنات Win64 الجاهزة تستخدم صيغة COFF لا يستطيع أي رابط من روابط Free Pascal استهلاكها، فعلى تلك السلسلة تتحلل نقاط الدخول إلى stubs تفشل بإغلاق

خريطة قدرات HotPDF على Free Pascal: خلفيات deflate وAES والمستندات بلغة Pascal عاملة إلى جانب stubs لمرمزات الصور تفشل بإغلاق
ميزات المستند والضغط والتشفير تعمل على خلفيات بلغة Pascal وحدها، بينما تتحلل مرمزات الصور الأصلية إلى stubs تفشل بإغلاق

كان الانتقال من «يترجم» إلى «يعمل» يتطلب مجموعة إصلاحات محددة، وكل واحد منها فخ سيصطدم به أي قاعدة كود Delphi أخرى تنتقل إلى Free Pascal. وهي تستحق التدوين بالترتيب الذي ألمت به

لماذا لا يثبت ترجمة وحدة شيئا؟

لأن وحدة Pascal يمكن أن تشير إلى رمز لن يفعل شيئا مفيدا أبدا ومع ذلك يرضي المترجم. فحين بنت الوحدات الـ 113 كلها بناء نظيفا تحت Free Pascal، كانت معالجات حاويات الأرشيف تعمل فعلا، تحقق منها اختبار دخان فتح ملف CBZ وحوله إلى PDF. أما تسطيح نماذج XFA فلم يعمل إطلاقا، لأن التسطيح يجب أن يفك ضغط مسار الحزمة /XFA المضغوط وكانت نقطة دخول deflate ما تزال stub. ولا شيء في خرج البناء فرّق بين الحالتين

القاعدة المستخلصة قصيرة. قبل كتابة ملاحظة إصدار تفيد بأن ميزة تعمل على سلسلة أدوات جديدة، اكتب مجسا زمن تشغيل يجرب الميزة من طرف إلى طرف على تلك السلسلة. تغطية الترجمة شرط مسبق، وليست دليلا أبدا. والصورة الأشمل لما تغطيه عملية النقل في ملاحظات دعم Free Pascal وLazarus على Win64

رمي استثناء داخل stub بـ cdecl لا يصل إلى المستدع

تستحق هذه المشكلة قسمها الخاص لأن العرض مضلل إلى هذا الحد. وحدات stub تكشف نقاط دخول C بالطريقة التي تكشف بها مكتبة ساكنة، فيبدو الـ stub هكذا

// يبدو معقولا. وهو ليس كذلك.
function inflate(Strm: Pointer; Flush: Integer): PtrUInt; cdecl;
  public name 'inflate';
begin
  raise ENotSupportedException.Create('codec unavailable');
end;

في Free Pascal لـ Win64 لا ينتقل ذلك الاستثناء إلى المستدع. لا يوجد معالج try..except يراه، لأن فك التراص عبر حد cdecl معلن بهذه الطريقة لا يحمل إطار استثناء Pascal؛ وتنتهي العملية برمز خروج 217. ومن جهة التطبيق لا خطأ ولا رسالة ولا سجل، فقط برنامج يتلاشى. وهذا أسوأ بصرامة من جواب خاطئ، لأن الجواب الخاطئ يمكن التعامل معه

لماذا ينهي الاستثناء المرمى داخل stub بـ cdecl عملية Free Pascal برمز خروج 217 وكيف يصلح ذلك تحصين نقطة الدخول بلغة Pascal
إطار استثناء Pascal لا يستطيع فك التراص عبر حد cdecl، فتموت العملية بصمت؛ والإصلاح يحصن قبل الوصول إلى الـ stub أصلا

الإصلاح المغري هو جعل الـ stub يعيد رمز إخفاق بدلا من ذلك، وفي inflate ذلك صحيح لأن zlib تملك إرجاع خطأ محدد جيدا. وهو خاطئ في العموم: فـ stub لـ jpeg_read_header يعيد صفرا يخبر المستدع بالاستمرار ببنية لم يهيئها أحد. الإصلاح الدائم هو التحصين عند نقطة الدخول بلغة Pascal لا داخل الـ stub ذي الشكل C، مستخدما أي اصطلاح إخفاق تملكه تلك الواجهة أصلا

function TryDecodeJPEG(const Data: TBytes; out Bitmap: TBitmap): Boolean;
begin
{$IFDEF FPC}
  // ارفض قبل الوصول إلى الـ stub أصلا، وباصطلاح الإخفاق الخاص
  // بهذه الواجهة لا باستثناء عبر cdecl
  Bitmap := nil;
  Result := False;
  Exit;
{$ENDIF}
  Result := DecodeJPEGNative(Data, Bitmap);
end;

paszlib ليست zlib، والفرق صنفان من المستندات

تنفيذ deflate بلغة Pascal المتوفر على Free Pascal يتعامل مع تفردين: غلاف zlib وdeflate الخام. وهو لا يتعامل مع تفردي gzip الذي تختاره zlib عبر قيم windowBits من 16 إلى 31، ولا مع وضع الكشف التلقائي الذي تختاره القيم من 32 إلى 47. ويحتاج HotPDF كليهما. مسار استيراد SVG الآمن يطلب 31، واللودر يملك سلم بدائل يطلب 47 عندما يكون تفرّد المسار غامضا. وأغفل أحدهما فستتوقف عائلة كاملة من المستندات عن الفتح، بخطأ فك ترميز يشير إلى المسار لا إلى التفرد المفقود

تغطية windowBits في paszlib مقابل zlib: نطاقا تفردي gzip والكشف التلقائي مفقودان لاستيراد SVG في HotPDF وسلم بدائل اللودر
paszlib تتعامل مع غلاف zlib وdeflate الخام، لكن HotPDF يحتاج أيضا windowBits بقيمتي 31 و47، فيجب أن توفر الطبقة الوسيطة تفردي gzip بنفسها

هناك عدم توافق ثان حاد. سجل z_stream الذي تعلنه paszlib لا يملك تخطيط الذاكرة نفسه الذي يملكه نظيره بلغة C: حقل msg لديه سلسلة قصيرة لا مؤشر، وtotal_in وtotal_out بحجم 64 بت حيث يستخدم ABI بلغة C كلمات الآلة. لا يمكن إذن تمرير سجل مستدع كما هو مباشرة. الترتيب العامل هو إبقاء حالة paszlib خلف مؤشر state الذي يحجزه السجل العام أصلا، ونسخ الحقول العامة للداخل والخارج حول كل نداء. وتُراعى CRC الخاصة بـ gzip ومقطع الطول ذو الثمانية بايتات في طبقة الوساطة نفسها، وهي الموطن الطبيعي لهما لأنها تملك قرار التفرد أصلا

تمرير مصفوفة ديناميكية إلى معامل var غير مصنف النوع

هذا هو العيب الأرجح أنه جاثم في كودك الآن. عندما تمرر مصفوفة ديناميكية إلى معامل var غير مصنف النوع، فإن ما يستلمه المستدع إليه هو عنوان متغير المصفوفة، وهو عنوان مؤشر، لا عنوان الحمولة. فالقراءة إليه تكتب فوق المتغير نفسه وكل ما يجاوره

var
  FBuffer: TBytes;
begin
  SetLength(FBuffer, 65536);

  // خاطئ: يسلّم عنوان متغير FBuffer
  FStream.Read(FBuffer, Length(FBuffer));

  // صحيح: يسلّم عنوان أول بايت من الحمولة
  FStream.Read(FBuffer[0], Length(FBuffer));
end;

في Delphi يبدو الشكل الخاطئ كثيرا وكأنه يعمل، لأن ما يفسده فتحة مكدس مجاورة لا يقرأها شيء بعد ذلك. وفي Free Pascal تتسبب السطر نفسه في خطأ تقسيم عند أول استخدام. وما يجعله عصيا على الرصد بالعين أن المصفوفات الساكنة لا تعاني هذه المشكلة، لأن متغير مصفوفة ساكنة هو حمولته بنفسه، فتكون الصيغتان صحيحتين في الملف نفسه بحسب الإعلان على بعد مئات الأسطر

حاويات ZIP من دون System.Zip

ليس لدى Free Pascal مقابل لوحدة zip في RTL، والبديل المتاح يملك واجهة برمجية مختلفة ولا يدعم التشفير القديم الذي ما تزال صيغ الحاويات الأقدم تستخدمه، فتبين أن قارئا صغيرا داخل المكتبة أقصر من التأقلم معه. وتفصيلان في الصيغة كلفا وقتا ويسهل الخطأ فيهما

أولهما بايت الفحص في ترويسة التشفير. بايته الثاني عشر هو عادة البايت الأعلى من CRC، لكن عندما يضبط البت 3 من علم الأغراض العامة، أي أن الأحجام تعيش في واصف بيانات لاحق ولم يعرف CRC بعد، يأتي بايت الفحص من البايت الأعلى لوقت التعديل بدلا منه. طبق صيغة CRC وحدها فيرفض كل أرشيف كتب بنمط البث كلمة مرور صحيحة. وثانيهما حقل ZIP64 الإضافي: حقوله الثلاثة ذات 64 بت تظهر بترتيب ثابت لكن لا تُكتب إلا عندما يتشبع الحقل المقابل ذو 32 بت، فالقراءة عند إزاحات ثابتة تعمل على الأرشيفات التي اختبرتها وتفشل على التالي. حللها موضعيا مقابل أي حقول 32 بت تشبعت

تسهيل جدير بالمعرفة: مسار فك الضغط في Free Pascal يأخذ وسيط باني ثانيا يتجاوز ترويسة zlib، وهو بالضبط ما تحتاجه مدخلات ZIP لأنها تخزن deflate خاما. ذلك المسار لا يلمس طبقة zlib الوسيطة في المكتبة إطلاقا، فهو غير متأثر بالخلفية C المفقودة

شفافية المحارف الملونة تحت LCL

قراءة قناة ألفا لحرف ملون مصيّر نقطيا هي التفصيل الرسومي الوحيد بلا ترجمة مباشرة. صنف PNG في LCL لا يملك وصولا لخطوط المسح يكشف ألفا، وإسناد PNG إلى صورة نقطية يرميها، فيصل رمز تعبيري ملون معتما تماما ويتألف مع صندوق أسود خلفه. المسار العامل هو صورة الواجهة: أنشئها من PNG، ثم اقرأ البكسلات عبر واصف اللون، متذكرة أن مكوناته بعمق 16 بت وتحتاج إزاحة نزولا بمقدار ثمانية لتصبح بايتات. تلك الواجهة تستخدم أيضا ترتيب صفوف طبيعيا من الأعلى إلى الأسفل، فيجب إزالة الانعكاس Height - 1 - Y الذي يحتاجه كود خطوط المسح في VCL لا نقله

ملاحظتان عن نظام البناء قبل أن تفتح تقرير عيب

يفشل البناء الكامل أحيانا برمز غير معرف ينتهي اسمه بلاحقة $crc وقيمة سداسية عشرية. تلك اللاحقة تحسب من أنواع المعاملات، وتفشل في التطابق عندما يترجم بناء واحد وحدة مقابل نسختي واجهة مختلفتين في المرور نفسه. إعادة تشغيل البناء تزيلها؛ والتوقيع ليس خاطئا

ثانيا، لا تملك Free Pascal 3.2.2 توابع مجهولة، فأي موضع استخدمت فيه المكتبة الإغلاقات لتوصيل مسار متوازي يتخذ بناء Free Pascal بدلا منه بديلا تسلسليا حتميا. المخرجات متطابقة، والمنفذ ليس كذلك؛ فإن كنت تعتمد على تصيير الصفحات بالتوازي فهذا سبب للبقاء على Delphi حاليا، وتصميم المسار موصوف في مقال مسار التصيير المتوازي. ووضع مرمزات الصور هو الموضع الآخر الذي يغير فيه اختيار سلسلة الأدوات القدرة لا السرعة فقط، فعلى نشر Lazarus أن يخطط صيغ صوره وفقا لذلك؛ ومصفوفة السلاسل الحالية في صفحة منتج HotPDF Delphi PDF component