Technical Article

الرسم على Canvas في HotPDF مع Delphi: المسارات المتجهة واللون

يرسم HotPDF الرسومات المتجهة عبر بناء مسار على الصفحة الحالية ثم طلب رسمه. لا توجد خطوة bitmap في المنتصف. الخط الذي ترسمه باستخدام MoveTo وLineTo ينتهي على هيئة معاملات مسار PDF داخل مجرى المحتوى، لذلك يبقى متجهًا حقيقيًا: حادًا عند تكبير 50%، وحادًا عند 1600%، وبحجم أصغر بكثير مما تستهلكه النسخة النقطية. بالنسبة إلى المخططات، وخطوط الجداول، ومحاور الرسوم البيانية، وزخارف النماذج، هذا هو المطلوب تمامًا، كما أن واجهة البرمجة خلفه صغيرة بما يكفي لتتعلمها في جلسة واحدة.

كل مساحة الرسم تعيش على THotPDF.CurrentPage. بين BeginDoc وEndDoc تضبط اللون وعرض الخط على كائن الصفحة هذا، وتضع الأشكال الهندسية، ثم تستدعي أحد أوامر الرسم لاعتمادها. وأكثر أربع بدائيات ستستخدمها هي MoveTo وLineTo للمسارات الحرة، وRectangle للمربعات، وCircle للأقراص، وأمرا الرسم Stroke وFill.

نظام الإحداثيات يبدأ من أسفل اليسار

هذه هي النقطة التي تربك كل من يأتي من VCL. كائن TCanvas الذي ترسم عليه عناصر الواجهة يضع نقطة الأصل في الزاوية العلوية اليسرى مع ازدياد Y إلى الأسفل. أما PDF فيفعل العكس. يقيس HotPDF من الزاوية السفلية اليسرى للصفحة بالنقاط، أي 1/72 بوصة، مع ازدياد Y كلما تحركت إلى أعلى. النقطة عند Y := 720 تقع قرب أعلى صفحة US Letter التي يبلغ ارتفاعها 792 نقطة، بينما Y := 50 تقع قرب الأسفل. إذا خرج أول رسم لديك مقلوبًا رأسيًا، فهذه هي السبب: الكود المنقول من رسومات الشاشة يفترض الاتجاه الخطأ ويهبط خارج الحافة السفلية.

وينطبق الاصطلاح نفسه على TextOut، لذلك يشترك النص والأشكال في نموذج ذهني واحد بعد أن تستوعبه. خطط للتخطيط عبر تحديد موضع أسفل كل عنصر، لا أعلاه، وبعدها يتبع الباقي تلقائيًا.

المسارات: MoveTo وLineTo وStroke

المسار المرسوم بالخط هو قلم رُفع ثم وُضع ثم جُرّ. MoveTo يرفع القلم ويحدد نقطة البداية من دون أن يترك أثرًا. وكل LineTo يمد المسار الحالي إلى نقطة جديدة. لا يظهر شيء على الصفحة حتى تستدعي Stroke، وهي ترسم المسار المتراكم باستخدام لون Stroke الحالي وعرض الخط الحالي، ثم تفرغ المسار كي يبدأ MoveTo التالي من جديد.

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'DrawPaths.pdf';
    Pdf.BeginDoc;

    // Line width is in points and applies until you change it.
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.SetRGBStrokeColor(clBlack);

    // A horizontal rule near the top of the page (Y measured from bottom).
    Pdf.CurrentPage.MoveTo(72, 720);
    Pdf.CurrentPage.LineTo(523, 720);
    Pdf.CurrentPage.Stroke;          // commit the path; nothing drew before this

    // A thicker connected polyline: three segments in one path.
    Pdf.CurrentPage.SetLineWidth(3);
    Pdf.CurrentPage.SetRGBStrokeColor(RGB(30, 90, 200));
    Pdf.CurrentPage.MoveTo(72, 640);
    Pdf.CurrentPage.LineTo(172, 690);
    Pdf.CurrentPage.LineTo(272, 620);
    Pdf.CurrentPage.LineTo(372, 680);
    Pdf.CurrentPage.Stroke;

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

تفصيلان يوفران وقتًا حقيقيًا في التصحيح. عرض الخط حالة وليست معاملًا: يحدد SetLineWidth القيمة مرة واحدة، وتستخدمها كل عملية Stroke لاحقة حتى تغيرها من جديد، ولهذا السبب يكون الخط المتعدد المقاطع أعلاه أسمك من الخط الأفقي. كما أن المسار يُعاد ضبطه بعد كل Stroke، لذلك فإن نسيان Stroke يعني أن الهندسة التي رتبتها بعناية لن تُرسم أصلًا. إذا غاب شكل من المخرجات، فاستدعاء الرسم هو أول موضع يجب فحصه.

الإحداثيات بالنقاط، والنقاط يمكن أن تكون كسريّة. يقبل MoveTo وLineTo قيم Single، لذا فخط رفيع عند 0.5 نقطة أو موضع عند 72.25 أمر قانوني وله معنى، وليس مجرد قيمة تُقرب إلى أقرب عدد صحيح. هذه الدقة مهمة في اتجاهين متعاكسين. عرض خط أقل من نحو 0.5 قد يُرسم كأرفع خط ممكن يعتمد على الجهاز، فيختفي على الشاشة ثم يظهر عند الطباعة، لذلك يحتاج الخط المرئي إلى عرض تحدده عمدًا لا إلى العرض الافتراضي. وفي الطرف الآخر، محاذاة خطوط الجداول والشبكات إلى إحداثيات كاملة بالنقاط تمنع الشبكة الكثيفة من أن تبدو غير متساوية قليلًا حيث تُقرب الخطوط المتجاورة بصورة مختلفة. حدد تباعد الشبكة بالنقاط مسبقًا، وتتوارثه بقية البنية.

الأشكال المعبأة واللون

يمكن تعبئة البدائيات المغلقة بدل الاكتفاء برسم حدودها. يأخذ Rectangle موضعًا وحجمًا، ويأخذ Circle مركزًا ونصف قطر، ويُعتمد أي منهما بواسطة Fill الذي يلون الداخل بلون التعبئة الحالي، أو بواسطة Stroke إذا أردت إطارًا فقط. لون التعبئة ولون الحدود حالتان منفصلتان، وتُضبطان بواسطة SetRGBFillColor وSetRGBStrokeColor، وكلاهما يأخذ قيمة TColor واحدة. هذا يعني أنه يمكنك إعادة استخدام ثوابت ألوان Delphi والمساعد RGB مباشرة.

// Rectangle(X, Y, Width, Height): X and Y are the lower-left corner.
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 500, 160, 90);
Pdf.CurrentPage.Fill;

// Circle(X, Y, Radius): X and Y are the center.
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 545, 45);
Pdf.CurrentPage.Fill;

// Outline only: set a stroke color and a width, then Stroke.
Pdf.CurrentPage.SetLineWidth(2);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
Pdf.CurrentPage.Rectangle(72, 400, 160, 60);
Pdf.CurrentPage.Stroke;

انتبه إلى شكل الوسيطات في Rectangle. فهو يعتمد على الموضع ثم الحجم، أي X, Y, Width, Height، وليس ركنين متقابلين. أما TCanvas.Rectangle الذي يعرفه مطورو Delphi فيأخذ (Left, Top, Right, Bottom)، لذلك ستدفعه الذاكرة العضلية إلى إرسال ركن ثانٍ إلى HotPDF بينما هو يتوقع عرضًا وارتفاعًا، فتخرج الصندوق بحجم خاطئ. الزوج (X, Y) هو الزاوية السفلية اليسرى، بما يتفق مع أصل الصفحة. وفي الدائرة يكون (X, Y) هو المركز، بينما الوسيط الثالث هو نصف القطر بالنقاط.

اختيار لون واحد أخطأ فيه المثال الأصلي

كانت نسخة أقدم من هذا المثال تولّد الألوان باستخدام Random($FFFFFF) لكل شكل. يبدو ذلك حيويًا، لكنه ليس التفكير الصحيح في المستندات المولدة. فملف PDF الذي تبنيه من الكود يكون عادة شيئًا تريد اختباره أيضًا، والألوان العشوائية تجعل المخرجات غير قابلة للمقارنة بين تشغيل وآخر: أي فرق بايت-بايت عن ملف معروف الصحة يفشل في كل مرة من دون سبب حقيقي. اختر ألوانًا صريحة. وإذا أردت تنوعًا عبر سلسلة من الأشكال، فاجعله صادرًا من بياناتك أو من مصفوفة ألوان ثابتة، حتى ينتج الإدخال نفسه الملف نفسه دائمًا. الحتمية تستحق أكثر من الطرافة عندما يمر الأثر عبر خط إصدار.

أين يفيد الرسم المتجهي وأين لا يفيد

استدعِ هذه الدوال الخاصة بالمسارات والأشكال عندما تكون الهندسة مولدة: خطوط الشبكة وأعمدة المخطط، والخطوط المرسومة في جدول فاتورة، ومربعات الإشارة في مخطط، وشعار مرسوم بعدة مسارات. كل ذلك يتوسع من دون ضبابية ويضيف أقل قدر ممكن إلى حجم الملف، لأن المستطيل عبارة عن بضع قيم لا آلاف البيكسلات. والجانب الآخر صريح أيضًا. إذا كان ما لديك فعليًا صورة فوتوغرافية أو لقطة شاشة، فارسمها كصورة باستخدام AddImage وShowImage بدلًا من ذلك؛ فمطابقة صورة نقطية باستدعاءات متجهة لا تكسبك شيئًا. المنحنيات المعقدة أيضًا خارج النطاق هنا. البدائيات السابقة هي المقاطع المستقيمة، والمستطيلات، والدوائر، وهي تحمل الغالبية العظمى من أعمال التقارير الحقيقية؛ أما أي شيء يحتاج إلى منحنيات Bezier حرة فهو جزء منفصل من الواجهة البرمجية.

العادة المتبقية الجديرة بالاحتفاظ بها هي التحقق. قد ينجح الرسم المولد على جهازك ويفشل على جهاز العميل، وغالبًا يكون السبب استبدال الخط في أي نص تخلطه أو افتراضًا غير صحيح لحجم الصفحة. افتح الملف النهائي عند عدة مستويات تكبير لتتأكد من بقاء الحواف نظيفة، وتحقق من أن كل شكل يقع داخل صندوق الهوامش الذي قصدته. ومع نظام ألوان حتمي يمكن أتمتة هذا التحقق مقابل ملف PDF مرجعي بدلًا من الاعتماد على العين.

أوامر MoveTo وLineTo وStroke وFill والألوان المعروضة هنا جزء من مكوّن HotPDF الخاص بـ Delphi وC++Builder.