كل سلسلة مرئية في مستند 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 واسع عندما تخلط اللغات
الوسيط angle يدور حول نقطة الارتكاز
الوسيط الثالث هو الذي تتركه أغلب الشيفرات على الصفر إلى الأبد. مرّر قيمة غير صفرية، فتدور السلسلة عكس اتجاه عقارب الساعة حول نقطة ارتكازها الخاصة عند (X, Y)، أي الزاوية العلوية اليسرى للنص، بذلك العدد من الدرجات. نقطة الارتكاز نفسها لا تتحرك، لذلك يضع الإحداثي نفسه التسمية الأفقية ونظيرها المدور؛ الذي يتغير فقط هو اتجاه تقدم glyphs
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 لأعلى من الحافة السفلية، فيكتب السطر الأول قرب أسفل الصفحة ويقضي عشر دقائق يتساءل لماذا يبدأ التقرير من الأسفل
عامل التخطيط كحسابات ضد نقاط ارتكاز مسماة، لا كعمود من الأرقام السحرية. هامش أيسر، و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، لذلك تضبطها، ثم ترسم، ثم تعيدها كما كانت
// وسّع التباعد بين حروف عنوان قصير ليتمدّد على 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 بدلًا من تجميعه عند المسافات
إعادة الضبط هي جوهر الانضباط هنا. فالتباعد، مثل الخط، جزء من حالة الرسم الخاصة بالصفحة، والحالة تبقى حتى تغيرها. إذا زدت التباعد في عنوان ونسيته على الصفر، فكل فقرة أسفله ترث هذا التمدد، ويبدو ذلك خطأً خفيًا يصعب تحديده، يمر في مراجعة سريعة ويفشل في مراجعة دقيقة. العادة الموثوقة هي أن تضبط قيمة التباعد، وترسم السلسلة التي تحتاجها، ثم تعيدها إلى الصفر في السطر التالي، حتى لا يضطر كود لاحق إلى معرفة ما فعله القسم السابق
التحقق من المخرجات في مكان فشلها الفعلي
يفشل تخطيط النص على الجهاز الثاني لا الأول، لذلك فإن الفحوص المهمة تحدث بعيدًا عن مكتبك. افتح الملف المولّد على نظام لا يملك مجموعة خطوط المطور لديك، وتأكد من أن الوجوه المضمّنة ما زالت تُرسم، بما في ذلك اللاتيني ذو العلامات، وأي نصوص غير لاتينية، وعلامات الترقيم، في تمريرة واحدة بدلًا من الاكتفاء بفحص عيّنات سهلة. حدّد بعض الأسطر وانسخها لتتأكد من أن النص نص حقيقي لا outlines، وهذا مهم منذ اللحظة التي يدخل فيها البحث أو الاستخراج ضمن النطاق. ومرّر إلى التخطيط بيانات تمثيلية، مثل أطول تسمية ألمانية وأعرض رقم، لا عنصرًا نائبًا مرتبًا، لأن السلسلة التي تتجاوز الحقل هي دائمًا السلسلة التي لم تكتبها يدك. وإذا كان لا بد أن تقع الصفحة فوق نموذج مطبوع مسبقًا، فاطبع عينة واحدة أو حوّلها إلى raster وضعها فوق الأصل؛ لأن انحراف baseline بمقدار ربع مليمتر غير مرئي على الشاشة وواضح على الورق
إذا لم تكن قد كتبت صفحة واحدة بعد، فابدأ بـ مثال Hello World من HotPDF، فهو يجهز المستند والخط ونظام الإحداثيات الذي يبدأ من أعلى اليسار والذي تعتمد عليه كل النقاط السابقة. واستدعاءات TextOut وSetFont والتباعد المعروضة هنا جزء من مكوّن HotPDF الخاص بـ Delphi وC++Builder