مقال تقني

استيراد EMF في PDFlibPas: قواعد PolyDraw و Polyline و Bezier

يحوّل PDFlibPas، مكتبة PDF من losLab لـ Delphi، سجلات Poly* في EMF إلى مسارات PDF وفق تعريف كل سجل في [MS-EMF]: فسجل ‏32-بت مثل EMR_POLYBEZIER يبدأ من النقطة 0، و polylines تبقى مفتوحة وتُرسم بالحد فقط، و PT_CLOSEFIGURE في EMR_POLYDRAW علم لا نوع نقطة، وكل عدد نقاط يُفحص مقابل حجم السجل. وصلت هذه القواعد عبر v3.539.39 و v3.539.41 و v3.539.43. قبلها كان مخطط تقرير قد يخرج من ImportEMFFromFile بإسفين معبّأ مكان خط اتجاه، أو منحنى Bezier منحرف نحو نقطة تحكم خاطئة، أو مخطط مغلق ناقص ضلعه الأخير. ولا شيء من ذلك رفع خطأ، والقواعد تنطبق على أي محوّل Delphi من EMF إلى PDF أو أي محلل لسجلات GDI

لماذا تخطئ سجلات EMF من نوع Poly* في تحويل PDF؟

تخطئ سجلات Poly* لأن كل واحد منها يحمل جزءاً من معناه خارج نقاطه: هل الشكل مفتوح، وهل يبدأ من الموضع الحالي، وأي قلم وفرشاة يسريان، وأين في السجل تبدأ النقاط. فالـ enhanced metafile تسجيل لنداءات GDI على سياق جهاز (device context)، لذا على المحوّل أن يعيد تشغيل حالة ذلك السياق إضافة إلى الإحداثيات. أما PDF فلا يملك سياق جهاز؛ يملك مساراً، ونقطة حالية داخل ذلك المسار، ومعامل رسم يقرر بين حد (S) وتعبئة (f) والاثنين معاً (B). وكل تعارض بين النموذجين يصبح فرقاً في العرض لا يُعلَن

وعائلة Poly* تأتي بعرضين أيضاً. فكل سجل ‏32-بت مثل EMR_POLYLINE له توأم ذو ‏16-بت مثل EMR_POLYLINE16 يخزن النقاط كأزواج SmallInt. وغالباً ما يسجل GDI الصيغة المضغوطة حين تتسع كل الإحداثيات، لذا قد تبقى معالجات 32-بت في محوّل ما خاطئة لسنوات بينما رسومات الاختبار اليومية لا تصل إليها أبداً. وأسرع تدقيق هو تمرير النقاط نفسها عبر السجلين ومقارنة المسارات الناتجة. والسجلات المشمولة هنا كلها في مجموعة سجلات الرسم في [MS-EMF] ‏(2.3.5 Drawing Record Types)

السجليبدأ منمغلق؟الموضع الحالي
EMR_POLYBEZIERالنقطة 0لالا يُستخدم ولا يُحدَّث
EMR_POLYLINEالنقطة 0لا (قلم فقط)لا يُستخدم ولا يُحدَّث
EMR_POLYLINETOالموضع الحاليلا (قلم فقط)يُستخدم ويُحدَّث
EMR_POLYPOLYLINEأول نقطة في كل polylineلا (قلم فقط)لا يُستخدم ولا يُحدَّث
EMR_POLYDRAWأول PT_MOVETO أو الموضع الحاليفقط حيث يُضبط PT_CLOSEFIGUREيُستخدم ويُحدَّث

أين يبدأ منحنى EMR_POLYBEZIER فعلاً؟

منحنى EMR_POLYBEZIER يبدأ من النقطة 0، والنقاط من الفهرس 1 صعوداً وحدها تُجمَّع بثلاثيات: نقطة تحكم، نقطة تحكم، نقطة نهاية. فسجل بسبع نقاط يرسم مقطعين تكعيبيين: 0 هي البداية، و 1 إلى 3 يشكلان المقطع الأول، و 4 إلى 6 الثاني. معالج 16-بت في PDFlibPas كان يفعل ذلك أصلاً. أما معالج 32-بت كان يبدأ التجميع من النقطة 0، فتُستهلك نقطة البداية كأول نقطة تحكم ويزاح كل مقطع لاحق بمقدار واحد. والمنحنى كان يُرسم، لكنه المنحنى الخطأ. ومنذ v3.539.41 يفتح كلا العرضين المسار بـ m عند النقطة 0 ويصدر c واحدة لكل ثلاثية كاملة بعدها

مخطط PDFlibPas لسجل EMR_POLYBEZIER بسبع نقاط حيث تفتح النقطة صفر المسار بـ m وتشكل النقاط من 1 إلى 3 ومن 4 إلى 6 كل منها مقطع c تكعيبياً، بمقارنة معالج 32-بت المصحح منذ v3.539.41 بالتجميع القديم الذي استهلك نقطة البداية كنقطة تحكم
النقطة 0 هي نقطة البداية، وثلاثيات كاملة بعدها وحدها تصير مقاطع تكعيبية، فPolyBezier بسبع نقاط يُرسم كـ m مع معاملَي c

لمحللك أنت: العدد الذي ليس 1 زائد مضاعف من 3 هو سجل تالف، والنقاط الزائدة في النهاية ينبغي تجاهلها لا خياطتها في منحنى

PolyDraw: ‏PT_CLOSEFIGURE علم لا نوع نقطة

في EMR_POLYDRAW قيمة PT_CLOSEFIGURE ‏(القيمة 1) بت يُدمج مع PT_LINETO ‏(2) أو PT_BEZIERTO ‏(4)، فيمكن أن يكون بايت النوع الصالح 3 أو 5. ونوع النقطة هو البايت بعد إخفاء ذلك البت، والعلم يعني أغلق الشكل بعد المقطع الذي ينتهي عند هذه النقطة. وكان معالج PDFlibPas القديم يطابق البايت مع قيم مفردة في جملة case، فنقاط النوعين 3 و 5 لم تطابق شيئاً وتخطّت كلياً. فمستطيل يُرسم بـ PolyDraw فقد ضلعه المغلق، وثلاثية Bezier آخر نقاطها تحمل العلم فقدت تلك النقطة، وهو ما أخرج كل ثلاثية لاحقة عن الإيقاع

منذ v3.539.39 يُقرأ النوع بوصفه Types[i] and not PT_CLOSEFIGURE، ولا يُصدر الإغلاق إلا بعد مقطع كامل: بعد الخط في PT_LINETO المغلق، وبعد النقطة الثالثة من مجموعة Bezier. والملف التالف الذي يضبط العلم على النقطة الأولى أو الثانية من ثلاثية لا يغلق الشكل مبكراً. وصلحان مرتبطان خرجا في الإصدار نفسه:

  • كل PT_MOVETO في EMR_POLYDRAW16 ذي ‏16-بت كان يعيد بدء المسار كله، فسجل يحمل ثلاثة أشكال أبقى الأخير فقط؛ الآن الحركة الأولى تبدأ المسار والحركات اللاحقة تفتح subpaths
  • سجل PolyDraw لا يبدأ بـ PT_MOVETO يبدأ من الموضع الحالي كما يقول تعريف السجل، بدل كتابة معامل l أو c بلا m سابقة
تشريح PDFlibPas لبايت النوع في EMR_POLYDRAW حيث يكون PT_CLOSEFIGURE هو البت صفر من الأعلام مدمجاً بـ OR مع PT_LINETO أو PT_BEZIERTO، فيجب إخفاء بايتَي النوع الصالحين 3 و 5 بـ and not PT_CLOSEFIGURE قبل التوجيه؛ كانت جملة case القديمة تتخطى البايتين معاً فتفقد الأشكال المغلقة ضلعها الأخير
أخفِ علم الإغلاق قبل التوجيه ولا تصدر الإغلاق إلا بعد خط مكتمل أو ثلاثية Bezier، وإلا أسقط PolyDraw نقاطاً بصمت

لماذا يجب ألا تُعبَّأ polyline في EMF داخل PDF أبداً؟

لا يجب تعبئة polyline في EMF أبداً لأن EMR_POLYLINE و EMR_POLYPOLYLINE أشكال مفتوحة تُرسم بالقلم فقط، وتعبئة مسار مفتوح في PDF تغلقه ضمنياً. فالـ ISO 32000-1 §8.5.3 تنص أن معاملات التعبئة تغلق أي subpath مفتوح قبل رسمه. فمحوّل يصدر B أو f لـ polyline من ثلاث نقاط يرسم مثلثاً معبّأ بلون الفرشاة الحالي: الإسفين المعبّأ تحت خط اتجاه في مخطط. قبل v3.539.41 كانت PDFlibPas تعبّئ عرضَي polyline بالفرشاة، وكان سجل 32-بت يُغلق صراحة أيضاً. اليوم ينتهي كلا العرضين بالحد فقط، ويُحفظ تمييز GDI: ‏Polygon تغلق وتعبّئ، و Polyline لا تفعل ذلك أبداً

مقارنة PDFlibPas لـ polyline على شكل V مفتوح مُصدَّرة من EMR_POLYLINE: المحوّل الصحيح ينهي المسار بمعامل الحد S ويتجاهل الفرشاة المختارة، بينما إصدار f أو B يغلق الـ subpath المفتوح ضمنياً وفق ISO 32000-1 8.5.3 ويرسم خلل الإسفين المعبّأ في المخطط
معامل التعبئة يغلق أي subpath مفتوح قبل الرسم، لذا يجب أن تنتهي polylines بـ S دون h أو f أو B على الـ subpath

PolylineTo يبدأ من الموضع الحالي

EMR_POLYLINETO يرسم من الموضع الحالي عبر كل نقطة في السجل، ويبقى مفتوحاً، ويترك الموضع الحالي عند آخر نقطة. واحتوى المعالج القديم أيضاً على حالة خاصة تطفئ القلم حين تتشارك أول نقطتان إحداثي y، ولم يُعِد أي شيء تشغيله، ففقد كل سجل لاحق في الملف حده. حالة القلم من شأن EMR_SELECTOBJECT و EMR_CREATEPEN؛ ومعالج سجل رسم لا علاقة له بتغييرها. أُزيلت تلك الحالة الخاصة في v3.539.41، وصيغة السجل ذات النقطة الواحدة لم تعد تقرأ بعد نقاطها الخاصة ‏(صُحّح في v3.539.39)

نقاط PolyPolyline تبدأ بعد مصفوفة الأعداد

يخزن EMR_POLYPOLYLINE ذو ‏32-بت أعداداً في nPolys ثم نقاطاً في cptl، وتبدأ النقاط عند إزاحة البايت 32 + nPolys * 4. الفخ في الـ RTL: وحدة Windows تصرّح عن TEMRPolyPolyline بـ aPolyCounts و aptl كمصفوفتَي عنصر واحد، فـ aptl[0] هي النقطة الأولى فقط حين يساوي nPolys ‏1. والكود الذي يفهرس aptl مباشرة يقرأ قيم الأعداد كإحداثيات في كل سجل متعدد الخطوط. وكان معالج PDFlibPas القديم يضبط فحص حدوده على التخطيط الخاطئ نفسه، فتُرفض السجلات متعددة الخطوط الصالحة وترسم ذاتها الأحادية لا شيء. ومنذ v3.539.41 تحدد PDFlibPas موقع مصفوفة النقاط من الإزاحة المحسوبة، بالطريقة التي يفعلها معالج PolyPolygon عندها دائماً، وترسم كل polyline بوصفها subpath مفتوحاً خاصاً بها مع حد واحد في النهاية. وفي v3.539.43 حصل التوأم ذو ‏16-بت على المعاملة نفسها؛ فقد كان يرسم مقطعاً بمقطع، وهو ما كسر وصلات الخطوط وتجاهل NULL_PEN المختار

القلم والفرشاة الافتراضيان، وأقواس المسار

قاعدتا حالة تكملان إصلاحات polyline في v3.539.43:

  • سياق جهاز GDI جديد يكون فيه BLACK_PEN و WHITE_BRUSH مختارين أصلاً، فملف metafile يرسم دون أي EMR_SELECTOBJECT يرسم حدوداً سوداء مع ذلك؛ وكان المحوّل يبدأ بلا قلم ولا تعبئة ويكتب n ‏(إنهاء المسار دون رسم شيء) لتلك السجلات
  • داخل قوس BeginPath / EndPath لا تستخدم Polyline الموضع الحالي ولا تحدّثه، فيجب أن تفتح subpath جديداً عند نقطتها الأولى بدل الاتصال بالشكل السابق، ولا يجوز رسم شيء حتى يُرسم القوس بحد أو يُعبَّأ

بناء ملف EMF اختباري بـ TMetafileCanvas

أسرع طريقة لفحص محوّل ضد هذه القواعد هي تسجيل النداءات الخطرة الثلاثة في enhanced metafile واحد بـ TMetafileCanvas. الرسم أدناه يسجل المنحنيات بفرشاة مجوّفة ثم يختار فرشاة صلبة صفراء لـ polyline عمداً: فالمحوّل الصحيح يجب أن يتجاهل تلك الفرشاة مع polyline، فأي أصفر في PDF الناتج خلل. و PolyDraw ليس لها غلاف في TCanvas، لذا تُستدعى عبر Windows API بمقبض الـ canvas، مستخدمةً بايتَي نوع 3 و 5 لمختبَر علم الإغلاق

uses
  Winapi.Windows, System.Types, Vcl.Graphics;

procedure BuildPolyTestEmf(const FileName: string);
const
  // مربع مغلق (3 = LINETO + CLOSEFIGURE)، ثم شكل Bezier مغلق
  // آخر ثلاثية تحكم فيه تنتهي بـ 5 = BEZIERTO + CLOSEFIGURE
  DrawPts: array[0..7] of TPoint = (
    (X: 300; Y: 40), (X: 380; Y: 40), (X: 380; Y: 120), (X: 300; Y: 120),
    (X: 420; Y: 120), (X: 440; Y: 40), (X: 520; Y: 40), (X: 540; Y: 120));
  DrawTypes: array[0..7] of Byte = (
    PT_MOVETO, PT_LINETO, PT_LINETO, PT_LINETO or PT_CLOSEFIGURE,
    PT_MOVETO, PT_BEZIERTO, PT_BEZIERTO, PT_BEZIERTO or PT_CLOSEFIGURE);
var
  Mf: TMetafile;
  Canvas: TMetafileCanvas;
begin
  Mf := TMetafile.Create;
  try
    Mf.Enhanced := True;
    Mf.Width := 600;
    Mf.Height := 260;
    Canvas := TMetafileCanvas.Create(Mf, 0);
    try
      Canvas.Pen.Color := clNavy;
      Canvas.Pen.Width := 2;
      Canvas.Brush.Style := bsClear;    // حدود فقط للمنحنيات
      // النقطة 0 هي البداية؛ ‏1..3 و ‏4..6 مقطعان تكعيبيان
      Canvas.PolyBezier([Point(20, 120), Point(60, 20), Point(100, 220),
        Point(140, 120), Point(180, 20), Point(220, 220), Point(260, 120)]);
      PolyDraw(Canvas.Handle, DrawPts[0], DrawTypes[0], Length(DrawPts));
      // شكل V مفتوح بفرشاة صلبة مختارة: يُرسم بحد فقط ولا يُغلق
      // إلى مثلث أصفر
      Canvas.Brush.Style := bsSolid;
      Canvas.Brush.Color := clYellow;
      Canvas.Polyline([Point(20, 240), Point(120, 160), Point(220, 240)]);
    finally
      Canvas.Free;   // ينهي التسجيل
    end;
    Mf.SaveToFile(FileName);
  finally
    Mf.Free;
  end;
end;

لأن هذه الإحداثيات تتسع في SmallInt، سيخزن GDI عادة الصيغ ذات ‏16-بت. وللوصول إلى معالجات 32-بت تحتاج منتجاً يكتبها، أو سجلات تبنيها يدوياً. والملفات المبنية يدوياً تأتي بفخها الخاص: ‏TMetafile.LoadFromStream في VCL يعامل الدفق بوصفه EMF فقط حين يكون الطول المتبقي أكبر قطعاً من TEnhMetaHeader ذي ‏108 بايت. فـ EMF مصغّر مكتوب يدوياً بترويسة قصيرة، أو فارغ طوله بالضبط 108 بايت، يُحسب WMF ويرفض برسالة "Metafile is not valid". اكتب دائماً الترويسة كاملة بـ 108 بايت بما فيها حقول الامتداد قبل سجلات الاختبار

استيراد EMF إلى PDF عبر PDFlibPas

تستورد PDFlibPas ملف EMF عبر ImportEMFFromFile أو ImportEMFFromStream، وهما تعيدان معرّف صورة غير صفري عند النجاح و 0 عند الفشل. ‏GeneralOptions = 0 يبقي المسار المتجه الذي تدور حوله هذه المقالة؛ و 1 يحوّل الـ metafile إلى صورة نقطية بدلاً منه. و FontOptions = 1 يضيف خطوط الـ metafile كخطوط TrueType غير مضمّنة. وصيغة الدفق تُرجع الدفق إلى الموضع 0 قبل التحميل، فمرّر دفقاً لا يحوي إلا الـ metafile

uses
  System.SysUtils, PDFlibrary;

procedure EmfToPdf(const EmfFile, PdfFile: WideString);
var
  PDF: TPDFlib;
  ImageID: Integer;
  PageOps: AnsiString;
begin
  PDF := TPDFlib.Create;
  try
    PDF.SetOrigin(1);              // أصل أعلى اليسار لـ DrawImage
    PDF.SetMeasurementUnits(0);    // نقاط
    // ‏FontOptions 1 = إضافة الخطوط كـ TrueType غير مضمّنة
    // ‏GeneralOptions 0 = استيراد متجه و 1 = صورة نقطية
    ImageID := PDF.ImportEMFFromFile(EmfFile, 1, 0);
    if ImageID = 0 then
      raise Exception.Create('The metafile could not be imported');
    PDF.SelectImage(ImageID);
    // في EMF تكون ‏ImageWidth / ImageHeight حجم الإطار بالنقاط
    PDF.DrawImage(36, 36, PDF.ImageWidth, PDF.ImageHeight);

    // الصفحة تستدعي الفورم المستورد فقط: ‏q ... cm /Name Do Q
    PageOps := PDF.GetPageContentToString;
    if Pos(AnsiString(' Do'), PageOps) = 0 then
      raise Exception.Create('Expected a form XObject invocation');

    if PDF.SaveToFile(PdfFile) <> 1 then
      raise Exception.Create('The PDF could not be saved');
  finally
    PDF.Free;
  end;
end;

استيراد EMF متجه يصير form XObject، لذا تعيد GetPageContentToString تسلسل الحفظ والتحويل و Do والاستعادة فقط. فمعاملات m و l و c و h و S الناتجة عن سجلات Poly* تسكن في تدفق الـ form XObject، وهو مضغوط. ولتدقيقها فك ضغط الملف المحفوظ في مفحص كائنات PDF واقرأ تدفق الفورم: في الملف الاختباري أعلاه سترى polyline تنتهي بـ S بلا h قبلها، و h عند كل علم إغلاق في أشكال PolyDraw، ولا f أو B على أي من هذه الـ subpaths. كما يكيّف DrawImage حجم EMF المستورد بالتساوي حسب أصغر Width و Height، فيحفظ الرسم نسبة أبعاده حتى لو لم يطابقها الصندوق الذي تمرره

لأهداف Free Pascal، انظر كيف يُبنى مستورد PDFlibPas المتجه لـ EMF تحت Free Pascal؛ ودلالات السجلات نفسها أينما تُجمَّع المكتبة

كيف يعامل محلل EMF أعداد النقاط القادمة من الملف؟

ينبغي لمحلل EMF أن يعامل كل عدد نقاط كدخل غير موثوق وأن يفحصه مقابل حجم السجل قبل نسخ نقطة واحدة. فـ EnumEnhMetaFile يضمن فقط أن nSize لكل سجل يبقى داخل الملف. وهو لا يفحص أن cptl يوافق nSize، فمعالج ينسخ cptl نقطة بـ Move سيقرأ السجلات التالية، أو بعد نهاية الـ metafile، حين يكون العدد مزوّراً أو تالفاً. ومنذ v3.539.39 تفحص PDFlibPas الترويسة الثابتة زائد العدد مضروباً في بايتات النقطة مقابل nSize في PolyDraw و PolyBezier و PolyBezierTo و Polyline و PolylineTo و Polygon بالعرضين، مع بايت إضافي واحد لكل نقطة لبايتات نوع PolyDraw. وفي سجلات PolyPoly يجب أن يجمع عدد كل شكل إلى ما لا يتجاوز الإجمالي المصرّح به، وتُتخطى الأشكال صفرية النقاط

الفحص نفسه قصير بما يكفي لتنسخه إلى محللك. هذه الصيغة تتحقق من EMR_POLYPOLYLINE ذي ‏32-بت وتعيد مؤشراً إلى مصفوفة نقاطه الحقيقية:

uses
  Winapi.Windows;

// تعيد nil ما لم يحمل السجل فعلاً النقاط التي يصرّح بها.
// النقاط تبدأ بعد مصفوفة الأعداد: عند الإزاحة ‏32 + nPolys * 4 بايت،
// لا عند ‏aptl[0] التي يصرّح عنها الـ RTL كمصفوفة عنصر واحد
function PolyPolylinePoints(Rec: PEnhMetaRecord): PPoint;
var
  P: PEMRPolyPolyline;
  Count: PDWORD;
  PointsOffset, Total: Int64;
  I: Cardinal;
begin
  Result := nil;
  if (Rec^.iType <> EMR_POLYPOLYLINE) or (Rec^.nSize < 32) then
    Exit;
  P := PEMRPolyPolyline(Rec);
  if P^.nPolys = 0 then
    Exit;
  PointsOffset := 32 + Int64(P^.nPolys) * SizeOf(DWORD);
  if PointsOffset + Int64(P^.cptl) * SizeOf(TPoint) > Rec^.nSize then
    Exit;                         // عدد مزوّر أو مقطوع
  Total := 0;
  Count := @P^.aPolyCounts[0];    // مشي بمؤشر: ‏[0..0] يوقظ فحص النطاق
  for I := 1 to P^.nPolys do
  begin
    Inc(Total, Count^);
    Inc(Count);
  end;
  if Total > P^.cptl then
    Exit;                         // الأشكال تدّعي نقاطاً أكثر من الموجود
  Result := PPoint(NativeUInt(Rec) + NativeUInt(PointsOffset));
end;

اختبار اتساع النقاط يجري أولاً، فتُعرف مصفوفة الأعداد داخل السجل قبل أن تمشي عليها الحلقة. والحساب بـ Int64 لأن nPolys * 4 و cptl * 8 محسوبَين بـ 32 بت يمكن أن يلتفّا ويعبّرا المقارنة

مرجع سريع: قواعد Poly* في EMF لتحويل EMF إلى PDF

  • EMR_POLYBEZIER: النقطة 0 هي نقطة البداية؛ تجميع بثلاثيات من النقطة 1؛ أُصلح لسجل 32-بت في v3.539.41
  • EMR_POLYLINE / EMR_POLYPOLYLINE: أشكال مفتوحة، حد بـ S، أبداً h أو f أو B، لأن تعبئة PDF تغلق الـ subpaths المفتوحة
  • EMR_POLYLINETO: ابدأ من الموضع الحالي، ابقَ مفتوحاً، حدّث الموضع الحالي، ولا تمس حالة القلم أبداً
  • سجل ‏32-بت EMR_POLYPOLYLINE: النقاط تبدأ عند البايت 32 + nPolys * 4، لا عند aptl[0]
  • EMR_POLYDRAW: أخفِ PT_CLOSEFIGURE قبل التوجيه، أغلق بعد المقطع المكتمل، وابدأ من الموضع الحالي حين لا تكون النقطة الأولى PT_MOVETO
  • حالة سياق الجهاز الافتراضية هي BLACK_PEN مع WHITE_BRUSH؛ وتحترمها v3.539.43 وما بعدها
  • داخل BeginPath / EndPath تفتح كل polyline الـ subpath الخاص بها ولا يُرسم شيء حتى يُستخدم القوس
  • تحقق من كل cptl / cpts مقابل nSize بحساب ‏64-بت قبل نسخ النقاط
  • ملفات EMF الاختبارية المبنية يدوياً تحتاج الترويسة كاملة بـ 108 بايت، وإلا قرأها TMetafile.LoadFromStream بوصفها WMF

وإن كانت تقاريرك تمر عبر مكوّن مختلف فدلالات السجلات نفسها تسري؛ استيراد HotPDF المتجه لـ EMF و WMF يغطي كيف يحوّل ذلك المكوّن فرشاة التدرج والفرشاة المتشابكة إلى patterns في PDF، و الرسوم المتجهة و shaders والتدرجات في PDFlibPas يغطي رسم الأشكال نفسها مباشرة عبر API المكتبة بدل المرور بـ metafile

‏PDFlibPas v3.539.43 وما بعدها تتضمن كل قاعدة أعلاه. والتفاصيل وتنزيلات التجربة على صفحة منتج PDFlibPas Delphi PDF library