مقال تقني

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

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

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

نظام الإحداثيات أصله الزاوية العلوية اليسرى مثل TCanvas

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

وتحكم القاعدة نفسها TextOut، حيث يكون Y الذي تمرره افتراضيًا هو الحافة العلوية للنص، لذا يتشارك النص والأشكال نموذجًا ذهنيًا واحدًا. خطط للتخطيط بتحديد موضع أعلى كل عنصر ويتبع الباقي. والاستثناءات هي الاستدعاءات التي تخاطب ملف PDF لا سطح الرسم: فمضبطات صناديق الصفحة مثل SetCropBox، وعائلة StampLoadedPageText وStampLoadedPageImage التي تختم مستندًا محمَّلًا مسبقًا، تأخذ إحداثيات فضاء مستخدم PDF بأصل في الزاوية السفلية اليسرى

مقارنة بين VCL TCanvas وواجهة الرسم على صفحة HotPDF، كلتاهما تقيس من الزاوية العلوية اليسرى مع نمو Y نحو الأسفل، بينما يكتب YProjection النقطة نفسها قرب أعلى صفحة US Letter كـ y = 720 في فضاء مستخدم PDF التصاعدي من الأسفل
يرسم HotPDF في الإطار العلوي الأيسر نفسه الذي يستخدمه TCanvas، لذا فإن بقعة بوصة واحدة تحت أعلى صفحة 612 في 792 تساوي Y = 72 في كليهما، ويكتبها YProjection في الملف كـ y = 720 بفضاء مستخدم PDF

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

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

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

    // عرض الخط بالنقاط ويسري حتى تغيّره
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.SetRGBStrokeColor(clBlack);

    // خط أفقي قرب أعلى الصفحة (Y يُقاس من الأعلى)
    Pdf.CurrentPage.MoveTo(72, 72);
    Pdf.CurrentPage.LineTo(523, 72);
    Pdf.CurrentPage.Stroke;          // ثبّت المسار؛ لم يُرسم شيء قبل هذا

    // خط متعدد القطع متصل وأسمك: ثلاث قطع في مسار واحد
    Pdf.CurrentPage.SetLineWidth(3);
    Pdf.CurrentPage.SetRGBStrokeColor(RGB(30, 90, 200));
    Pdf.CurrentPage.MoveTo(72, 152);
    Pdf.CurrentPage.LineTo(172, 102);
    Pdf.CurrentPage.LineTo(272, 172);
    Pdf.CurrentPage.LineTo(372, 112);
    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 مباشرة

نموذج المسار في HotPDF: تبني MoveTo و LineTo و Rectangle و Circle مسارًا حاليًا غير مرئي في الذاكرة، ولا يرسمه إلا تثبيت Stroke أو Fill أو FillAndStroke باستخدام حالة الرسوميات الدائمة من لون التحديد ولون التعبئة وعرض الخط قبل مسح المخزن المؤقت
تتراكم الهندسة بصمت في المسار الحالي حتى يثبّتها معامل رسم بلون التحديد ولون التعبئة وعرض الخط المخزَّنة — واستدعاء رسم منسي يترك الشكل غير مرسوم
// Rectangle(X, Y, Width, Height): X و Y هما الزاوية العلوية اليسرى
Pdf.CurrentPage.SetRGBFillColor(RGB(220, 60, 60));
Pdf.CurrentPage.Rectangle(72, 202, 160, 90);
Pdf.CurrentPage.Fill;

// Circle(X, Y, Radius): X و Y هما المركز
Pdf.CurrentPage.SetRGBFillColor(clNavy);
Pdf.CurrentPage.Circle(420, 247, 45);
Pdf.CurrentPage.Fill;

// التحديد فقط: اضبط لون تحديد وعرضًا، ثم Stroke
Pdf.CurrentPage.SetLineWidth(2);
Pdf.CurrentPage.SetRGBStrokeColor(clBlack);
Pdf.CurrentPage.Rectangle(72, 332, 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 تبنيه من كود هو عادةً شيء تريد اختباره أيضًا، وألوان التعبئة العشوائية تجعل المخرجات مستحيلة المقارنة من تشغيل إلى آخر: يفشل فرق بايت مقابل بايت مع ملف معروف الصحة في كل مرة، بلا سبب حقيقي. اختر ألوانًا صريحة. وحين تريد تنوعًا عبر سلسلة من الأشكال، قُده من بياناتك أو من مصفوفة لوحة ألوان ثابتة، بحيث ينتج المدخل نفسه الملف نفسه دائمًا. فالحتمية تساوي أكثر من الجدّة حين يمر الناتج عبر خط أنابيب إصدار

تجميع العناصر الأولية معًا: صندوق تعليق

كل عنصر أولي بسيط بمفرده؛ ويظهر المكسب حين تتركب حفنة منها في شيء يحتاجه تقرير فعليًا. فصندوق التعليق، أي الصندوق المشروح الذي يشير إلى شكل ويفسّره، يستخدم كل ما غُطّي حتى الآن: مستطيل معبّأ بإطار، وخط مؤشر محدَّد، ونقطة لتثبيت المؤشر، ونص موضوع داخل الصندوق باستخدام الإحداثيات ذاتها ذات الأصل العلوي الأيسر التي تستخدمها الأشكال. وتكسب FillAndStroke مكانها هنا، إذ ترسم الداخل والتحديد لمسار واحد في تثبيت واحد بدلًا من بناء المستطيل مرتين

تشريح صندوق تعليق مجمَّع من عناصر HotPDF الأولية: مستطيل مثبَّت بـ FillAndStroke، وخط مؤشر محدَّد من MoveTo و LineTo، ونقطة تثبيت معبّأة، وأشرطة تسمية TextOut تتشارك شبكة الأصل العلوي الأيسر نفسها مع الأشكال
أربعة تثبيتات تبني صندوق التعليق — ترسم FillAndStroke اللوحة والحافة معًا، ويعيد المؤشر والنقطة استخدام حالة التحديد والتعبئة، وكل إزاحة تسمية حساب بسيط نسبةً إلى زاوية الصندوق (90, 122)
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'Callout.pdf';
    Pdf.BeginDoc;

    // 1. الصندوق: تعبئة باهتة زائد إطار مرئي، مسار واحد، تثبيت واحد
    //    Rectangle هو الزاوية العلوية اليسرى زائد الحجم، Y يُقاس من الأعلى
    Pdf.CurrentPage.SetRGBFillColor(RGB(255, 244, 214));   // لوحة كهرمانية باهتة
    Pdf.CurrentPage.SetRGBStrokeColor(RGB(180, 130, 40));  // حافة أغمق
    Pdf.CurrentPage.SetLineWidth(1);
    Pdf.CurrentPage.Rectangle(90, 122, 240, 70);
    Pdf.CurrentPage.FillAndStroke;

    // 2. المؤشر: قطعة محدَّدة واحدة من حافة الصندوق نزولًا
    //    نحو الشيء المشروح
    Pdf.CurrentPage.SetLineWidth(1.5);
    Pdf.CurrentPage.MoveTo(90, 177);        // الحافة اليسرى للصندوق
    Pdf.CurrentPage.LineTo(66, 246);
    Pdf.CurrentPage.Stroke;

    // 3. نقطة معبّأة تثبّت المؤشر عند هدفه
    Pdf.CurrentPage.SetRGBFillColor(RGB(180, 130, 40));
    Pdf.CurrentPage.Circle(64, 250, 3);
    Pdf.CurrentPage.Fill;

    // 4. التسمية، موضوعة نسبةً إلى الزاوية العلوية اليسرى للصندوق
    //    يتشارك النص والأشكال نظام إحداثيات واحدًا، لذا فإن الإزاحات
    //    حساب بسيط نسبةً إلى (90, 122)؛ وكل Y هو حافة علوية لنص
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 10);
    Pdf.CurrentPage.TextOut(102, 134, 0, 'Check this total');
    Pdf.CurrentPage.SetFont('Arial', [], 9);
    Pdf.CurrentPage.TextOut(102, 152, 0, 'The rounding rule changed in the');
    Pdf.CurrentPage.TextOut(102, 164, 0, 'June release; verify against v2.1');

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

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

أين يؤتي الرسم المتجهي ثماره، وأين لا

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

المنحنيات والتقطيع والشفافية باختصار

تمدّ المنحنيات الحرة آلية المسار نفسها التي لديك بالفعل. تُلحق CurveToC(X1, Y1, X2, Y2, X3, Y3) قطعة Bezier تكعيبية من النقطة الحالية إلى (X3, Y3)، منحنيةً نحو نقطتي التحكم، وتغطي النسختان المختصرتان CurveToV وCurveToY الحالات التي تتطابق فيها إحدى نقطتي التحكم مع نقطة طرفية. ويمكن للمسار أن يمزج قطع LineTo وCurveToC بحرية قبل أن تثبّته Stroke أو Fill واحدة، وهكذا تُبنى الزوايا المستديرة وخطوط الرسوم البيانية الناعمة

والتحديدات المقطّعة حالة، تمامًا مثل عرض الخط. تبدّل SetDash([3, 3], 0) كل تحديد لاحق إلى نمط ثلاث نقاط تشغيل وثلاث نقاط إيقاف، إذ تحدد المصفوفة أطوال مقاطع التشغيل/الإيقاف بالنقاط ويضبط الوسيط الثاني طور بداية الدورة؛ وتعيد NoDash القلم إلى خط متصل. اضبطه، وحدّد خطوط الشبكة التي تريده، وأعد ضبطه قبل الخط المتصل التالي، وإلا أصاب التقطيع بهدوء كل ما يليه

وتمر الشفافية عبر حالة رسوميات مسمّاة لا عبر وسيط لون، لأن ألفا في PDF خاصية لقاموس حالة الرسوميات. سجّل واحدة على المستند بـ RegisterExtGState، ممرّرًا ألفا تعبئة وألفا تحديد بين 0 و1، ثم طبّق الاسم الذي تُعيده بـ CurrentPage.SetGraphicsState؛ وترسم التعبئات والتحديدات من تلك النقطة فصاعدًا بالعتامة المسجَّلة. إنها طقوس أثقل من ضابطات الألوان، وتستحق ذلك في أول مرة يضطر فيها شريط تمييز إلى الجلوس فوق نص من دون إخفائه

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

واستدعاءات MoveTo وLineTo وStroke وFill والألوان المعروضة هنا جزء من HotPDF Delphi Component لـ Delphi و C++Builder