Technical Article

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, 740, 0, 'Quarterly Report');        // 24pt regular

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

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

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

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

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

HotPDF TextOut page showing Arial, Times New Roman, and Courier New rendered with regular, bold, and italic styles across several character sets

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

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

Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);

// A vertical axis label down the left margin: 90 degrees reads bottom-to-top.
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');

// A diagonal DRAFT watermark across the page body.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');

// Column headers tilted 60 degrees so long labels fit a narrow table.
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');

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

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

الإحداثيات هي الجزء الذي ينجو من المراجعة أو يفشل فيها بصمت. يقيس HotPDF من الزاوية السفلية اليسرى للصفحة مع ازدياد Y إلى أعلى، بالنقاط بمعدل 72 نقطة في البوصة. صفحة US Letter قياسها 612 × 792 نقطة، وA4 قياسها 595 × 842. لذلك فإن هامشًا علويًا بوصة واحدة في Letter يضع baseline الأول قرب Y = 792 ناقص 72 ناقص حجم الخط، لا عند رقم صغير قريب من أعلى الصفحة. أما من يأتي من إحداثيات الشاشة حيث يزداد Y إلى الأسفل بدءًا من الصفر، فيكتب السطر الأول خارج الحافة السفلية ويقضي عشر دقائق يتساءل أين ذهب.

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

const
  LeftMargin = 72;        // 1 inch in
  TopBaseline = 720;       // first line, ~1 inch down on Letter
  Leading = 16;            // vertical step between lines
var
  Y: Single;
  Line: string;
begin
  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Y := TopBaseline;
  for Line in ReportLines do
  begin
    Pdf.CurrentPage.TextOut(LeftMargin, Y, 0, Line);
    Y := Y - Leading;
    if Y < 72 then            // bottom margin reached
    begin
      Pdf.AddPage;
      Pdf.CurrentPage.SetFont('Arial', [], 11);  // font resets on a new page
      Y := TopBaseline;
    end;
  end;
end;

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

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

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

// Letter-space a short heading so it stretches across a rule.
Pdf.CurrentPage.SetCharacterSpacing(4);
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(72, 740, 0, 'S U M M A R Y');
Pdf.CurrentPage.SetCharacterSpacing(0);   // reset before normal body text

// Open up the gaps between words on a single wide line.
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Name        Department        Extension');
Pdf.CurrentPage.SetWordSpacing(0);

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

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

HotPDF TextOut page comparing horizontal text scaling, character spacing, word spacing, and fill versus stroke rendering modes

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

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

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