مقال تقني

HotPDF TextOut في Delphi: الحجم، النمط، الدوران، والتباعد

كل سلسلة مرئية في مستند HotPDF تصل عبر استدعاء واحد: TextOut(X, Y, angle, Text). يستخدم مثال Hello World هذا الاستدعاء في أبسط صورة، مع تعيين الخط مرة واحدة وترك الوسيطات الأربع على القيم الافتراضية المناسبة. وبعد الصفحة الأولى يحمل الاستدعاء نفسه عبء التخطيط كله. الوسيط الثالث يدير دوران السلسلة. والخط المضبوط قبله مباشرة يحدد الحجم والنمط. أما الزوج X وY، المقاس من زاوية الصفحة بالنقاط، فهو الفاصل الوحيد بين تقرير نظيف ونص يتداخل أو يُقص أو يهبط سطرًا أدنى على طابعة شخص آخر. هنا يثبت TextOut قيمته، وهنا لا تعود القيم الافتراضية كافية

من المفيد تثبيت التوقيع في الذهن قبل أي شيء آخر: X وY من نوع Single بالنقاط، وangle من نوع Extended بالدرجات، وText من نوع WideString، لذلك يمر Unicode من دون استدعاء منفصل. وهناك overload ثانٍ يأخذ PWORD مع طول عندما تكون رموز glyphs موجودة لديك مسبقًا، لكن بالنسبة إلى السلاسل العادية فصيغة WideString هي الخيار المعتاد

الحجم والنمط يأتيان من SetFont، لا من TextOut

لا يملك TextOut معاملًا للحجم. فالحجم، والوزن، والميل، كلها تعيش في استدعاء SetFont الذي يسبق السلسلة، وتبقى سارية حتى يستبدلها SetFont آخر. هذه هي الحقيقة الواحدة التي تفسر معظم ارتباك اليوم الأول: يخرج السطر عريضًا لأن استدعاءً قبل ثلاثة أسطر ضبط [fsBold] ولم يمحُه أحد

Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 72, 0, 'Quarterly Report');         // 24pt regular

Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 100, 0, 'Revenue');                 // 12pt bold

Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 118, 0, 'figures in thousands');    // 11pt italic

Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsItalic], 10);
Pdf.CurrentPage.TextOut(72, 136, 0, '  +18.4% YoY');            // styles combine

الوسيط الثاني مجموعة من TFontStyles، لذلك فإن [fsBold, fsItalic] يعني عريضًا مائلًا، بينما [] يعني عاديًا. الحجم بالنقاط، وهي الوحدة نفسها المستخدمة للإحداثيات، ما يجعل التباعد الرأسي سهل الفهم: فالسطر بحجم 12 نقطة يحتاج إلى خطوة رأسية بنحو 14 إلى 16 نقطة كي يتنفس، لذلك يُعد زيادة Y بمقدار 14 لكل سطر بداية معقولة للتباعد. لا يوجد انتقال تلقائي إلى السطر التالي. أنت تحسب موضع كل سطر بنفسك، وهذا متعب في الفقرة لكنه دقيق في النموذج، حيث يقع كل حقل عند إحداثي ثابت

هناك ملاحظتان عمليتان عن اسم الخط. يُحل الاسم مقابل الخطوط المثبتة على جهاز البناء، وما يعيده النظام هو ما يُضمّن في الملف، لذلك فإن الاسم الذي يعمل على سطح مكتبك والاسم الذي يعمل على خادم البناء ليسا بالضرورة الشكل نفسه. كما يجب أن يغطي الخط النصوص الموجودة في السلسلة. فالنص السيريلي أو CJK تحت وجه لاتيني فقط يظهر كمربعات glyph مفقودة من دون خطأ، ولهذا تعمد صفحة Hello World إلى وجه Unicode واسع عندما تخلط اللغات

صفحة TextOut في HotPDF تُظهر Arial و Times New Roman و Courier New مصيَّرة بأنماط عادية وغامقة ومائلة عبر عدة مجموعات محارف

الوسيط angle يدور حول نقطة الارتكاز

الوسيط الثالث هو الذي تتركه أغلب الشيفرات على الصفر إلى الأبد. مرّر قيمة غير صفرية، فتدور السلسلة عكس اتجاه عقارب الساعة حول نقطة ارتكازها الخاصة عند (X, Y)، أي الزاوية العلوية اليسرى للنص، بذلك العدد من الدرجات. نقطة الارتكاز نفسها لا تتحرك، لذلك يضع الإحداثي نفسه التسمية الأفقية ونظيرها المدور؛ الذي يتغير فقط هو اتجاه تقدم glyphs

مخطط هندسة دوران TextOut في HotPDF: مقاطع عند 0 و45 و90 درجة تدور عكس عقارب الساعة حول مرساة ثابتة، مع أمثلة استدعاء TextOut في Delphi لتسميات الكعب والعلامات المائية ورؤوس الأعمدة المائلة
تدير الزاوية غير الصفرية المقطع عكس عقارب الساعة حول مرساته الثابتة عند الزاوية العلوية اليسرى للنص، وخطوط الأساس المشتركة تزيح X تماماً كما تزيح الأسطر الأفقية Y
Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);

// label لمحور رأسي على طول الهامش الأيسر: تُقرأ 90 درجة من الأسفل إلى الأعلى
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');

// علامة DRAFT مائية قطرية عبر متن الصفحة
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');

// عناوين الأعمدة مائلة 60 درجة لتناسب labels الطويلة جدولًا ضيقًا
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');

تُستخدم 90 درجة عادةً للسطر العمودي على جانب مخطط أو لعنوان على الحافة. والميل الأكثر حدة مثل 60 درجة أعلاه يتولى رؤوس الأعمدة، وهي الحيلة التي تسمح لتسمية عريضة بالجلوس فوق عمود ضيق من دون أن تتسرب إلى جيرانها، بينما تناسب 45 درجة علامة مائية قطرية. لا يغير الدوران طريقة تفسير نقطة الارتكاز، وهذا ما يربك الناس: فالسلسلة عند 90 درجة تبدأ من (X, Y) وتكبر إلى أعلى من هناك، لذلك فإن توسيط التسمية المدورة يعني تعديل نقطة الارتكاز لا تعديل الزاوية. وعندما تشترك عدة سلاسل مدورة في baseline واحد، امنحها Y نفسه وحرّك X، تمامًا كما تحرك Y في الأسطر الأفقية المتراكبة

وضع الإحداثيات من دون تخمين

الإحداثيات هي الجزء الذي ينجو من المراجعة أو يفشل فيها بصمت. تقيس واجهة الرسم في HotPDF من الزاوية العلوية اليسرى للصفحة مع ازدياد Y نحو الأسفل، بالنقاط بمعدل 72 نقطة في البوصة عند القيمة الافتراضية Resolution البالغة 72؛ ويحوّل THPDFPage.YProjection كل Y إلى فضاء مستخدم PDF، الذي يتدرج من الأسفل إلى الأعلى، بطرحه من ارتفاع الصفحة. وافتراضيًا فإن Y الذي تمرره إلى TextOut هو الحافة العلوية للنص، ويجلس baseline بمقدار صعود واحد تحته؛ واستدعِ TextFromBaseLine(True) على الصفحة إذا كنت تفضل تموضع baselines. صفحة US Letter قياسها 612 × 792 نقطة، وA4 قياسها 595 × 842. لذلك فإن هامشًا علويًا بوصة واحدة في Letter يضع سطرك الأول عند Y = 72، لا عند رقم كبير قرب 792. أما من يأتي من إحداثيات PDF الخام حيث يزداد Y لأعلى من الحافة السفلية، فيكتب السطر الأول قرب أسفل الصفحة ويقضي عشر دقائق يتساءل لماذا يبدأ التقرير من الأسفل

مخطط صفحة US Letter يوضح حساب مواضع الأسطر بهامش أيسر وسطر بداية علوي و Leading ثابت يتدرج نحو حد للهامش السفلي، بجوار حلقة التخطيط بالثوابت المسماة مع حماية AddPage اليدوية وإعادة ضبط SetFont
تحوّل المراسي المسماة عموداً من الأرقام السحرية إلى حساب: زد Y الجاري بمقدار Leading في كل سطر واحرس الهامش السفلي بنفسك عبر AddPage

عامل التخطيط كحسابات ضد نقاط ارتكاز مسماة، لا كعمود من الأرقام السحرية. هامش أيسر، وY جارٍ تزيده لكل سطر، وتباعد ثابت، كلها تحول كتلة التسمية إلى حلقة قصيرة بدلًا من جدار من القيم الحرفية:

const
  LeftMargin = 72;        // 1 inch in
  TopLine = 72;            // السطر الأول، بوصة واحدة إلى الأسفل (Y يزداد نحو الأسفل)
  BottomLimit = 720;       // 792 - 72: بوصة واحدة فوق قاع Letter
  Leading = 16;            // المسافة الرأسية بين الأسطر
var
  Y: Single;
  Line: string;
begin
  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Y := TopLine;
  for Line in ReportLines do
  begin
    Pdf.CurrentPage.TextOut(LeftMargin, Y, 0, Line);
    Y := Y + Leading;
    if Y > BottomLimit then    // تم الوصول إلى الهامش السفلي
    begin
      Pdf.AddPage;
      Pdf.CurrentPage.SetFont('Arial', [], 11);  // تُعاد تهيئة font في صفحة جديدة
      Y := TopLine;
    end;
  end;
end;

حارس كسر الصفحة هو السطر الذي ينساه الجميع أولًا ويضرب الجميع بقوة. لا توجد طبقة flow layout تحت TextOut. إذا تجاوزت هامش الأسفل، يستمر النص في الرسم داخل الهامش، وخارج الصفحة، وفي لا شيء، من دون تحذير. لذلك تراقب Y بنفسك، وتستدعي AddPage عندما يعبر الحد، ثم تعيد ضبط Y الجاري. واستدعاء SetFont بعد AddPage ليس حشوًا اختياريًا: الخط الحالي لا يصمد عبر كسر الصفحة، وأول سلسلة في الصفحة الجديدة ستخرج بوجه العرض الافتراضي في العارض إذا أهملت ذلك

تباعد الأحرف والكلمات من أجل الملاءمة والمحاذاة

أحيانًا تكون السلسلة صحيحة لكن بعرض خاطئ: عنوان يجب أن يمتد فوق خط ثابت، أو كود يجب أن يبدو بأرقام أكثر هواء، أو عمود يحتاج إلى دفع قيمه قليلًا كي تصطف. يحمل PDF معاملين لحالة النص لهذا الغرض، وهما تباعد الأحرف (Tc، أي مساحة إضافية بعد كل glyph) وتباعد الكلمات (Tw، أي مساحة إضافية عند كل مسافة)، وكلاهما يُعبَّر عنه بوحدات مساحة النص غير المقياس، أي عمليًا بالنقاط عند حجم الخط الحالي. هذه حالة وليست معاملات لـ TextOut، لذلك تضبطها، ثم ترسم، ثم تعيدها كما كانت

HotPDF: تباعد المحارف Tc يوزع مساحة إضافية بالتساوي بعد كل محرف في SUMMARY مقابل تباعد الكلمات Tw الذي يجمعها عند محارف الفراغ فقط، موصولًا بدورة الضبط والرسم وإعادة الضبط التي تمنع حالة التباعد من التسرب إلى الفقرات اللاحقة
ينشر Tc تعديله على كل محرف بينما يعمل Tw على محرف المسافة وحده، وإعادة الضبط في السطر التالي تُبقي حالة الرسم نظيفة
// وسّع التباعد بين حروف عنوان قصير ليتمدّد على rule
Pdf.CurrentPage.SetCharacterSpacing(4);
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(72, 72, 0, 'S U M M A R Y');
Pdf.CurrentPage.SetCharacterSpacing(0);   // أعد الضبط قبل نص body العادي

// افتح الفجوات بين الكلمات في سطر واحد عريض
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 100, 0, 'Name        Department        Extension');
Pdf.CurrentPage.SetWordSpacing(0);

يؤثر تباعد الكلمات فقط في حرف المسافة (code 32)، وله نتيجة تستحق المعرفة: فهو لا يفعل شيئًا داخل سلسلة CJK لا تحتوي على مسافات ASCII، ويتصرف بشكل غريب مع النص المرمّز كفهارس glyphs لا كبايتات. بالنسبة إلى الجداول اللاتينية فهو الطريقة الرخيصة لتوسيع الفراغات من دون إعادة كتابة السلسلة. أما تباعد الأحرف فهو الأداة الأفضل لعنوان يجب أن يبلغ عرضًا محددًا، لأنه يوزع التعديل بالتساوي على كل glyph بدلًا من تجميعه عند المسافات

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

صفحة TextOut في HotPDF تقارن تغيير مقياس النص الأفقي وتباعد المحارف وتباعد الكلمات وأوضاع التصيير بالتعبئة مقابل الرسم بالحدود

التحقق من المخرجات في مكان فشلها الفعلي

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

إذا لم تكن قد كتبت صفحة واحدة بعد، فابدأ بـ مثال Hello World من HotPDF، فهو يجهز المستند والخط ونظام الإحداثيات الذي يبدأ من أعلى اليسار والذي تعتمد عليه كل النقاط السابقة. واستدعاءات TextOut وSetFont والتباعد المعروضة هنا جزء من مكوّن HotPDF الخاص بـ Delphi وC++Builder