أول فاتورة يعرضها أي فريق تقريبًا باستخدام مكتبة PDF تخرج خاطئة بالطريقة نفسها: نص الترويسة يجلس على الحافة السفلى من الصفحة، وكل سطر لاحق يصعد إلى أعلى. لا يوجد شيء مكسور. يضع فضاء مستخدم PDF، حسب ISO 32000-1 §8.3، نقطة الأصل في الزاوية اليسرى السفلى مع ازدياد Y إلى أعلى، وهو عكس Canvas في GDI الذي ظل مطور VCL يرسم عليه لسنوات. يعرض HotPDF، مكتبة losLab لتوليد PDF في Delphi وC++Builder، نموذج الإحداثيات هذا مباشرة، لذلك فإن الدقائق الخمس التي تقضيها في استيعابه الآن توفر إعادة كتابة تخطيط لاحقًا. تسير هذه المقالة عبر بدائيات الإخراج التي يحتاجها مولد تقارير فعليًا: النص الموضوع بدقة، والخطوط التي تنجو من النشر، ووضع الصور، والرسم المتجهي
وضع النص ونقطة الأصل اليسرى السفلى
الاستدعاء المركزي لكائن الصفحة هو TextOut(X, Y, Angle, Text). يحدد X وY موضع النص بالنقاط من الزاوية اليسرى السفلى، ويديره Angle بالدرجات، وهذه هي طريقة أختام DRAFT وCOPY القطرية من دون أي آلية إضافية. الاصطلاح الذي يحافظ على حدس مطوري VCL هو حساب Y كارتفاع الصفحة ناقص المسافة من الأعلى:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice-0001.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(50, 792 - 50, 0, 'INVOICE'); // 50pt from top of Letter
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 792 - 70, 0, 'Date: 2026-06-11');
Pdf.CurrentPage.TextOut(300, 400, 45, 'COPY'); // rotated stamp
Pdf.AddPage; // CurrentPage now points here
Pdf.CurrentPage.SetFont('Arial', [], 10); // font state does not carry over
Pdf.CurrentPage.TextOut(50, 742, 0, 'Page 2 detail rows');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
سلوكان حالويان في ذلك المثال يسببان معظم عيوب الصفحة الثانية. يعيد AddPage توجيه CurrentPage إلى الصفحة الجديدة، لذلك يصبح أي مرجع خزّنته إلى كائن الصفحة السابقة قديمًا لأغراض الرسم. كما أن اختيار الخط لكل صفحة: استدع SetFont مرة أخرى بعد كل AddPage، وإلا فلن يستخدم أول TextOut على الصفحة الجديدة الخط الذي تظن أنه سيستخدمه. يجب أن يتعامل حلقة التقرير مع "صفحة جديدة" و"إعادة إنشاء حالة النص" كوحدة واحدة
خطوط موجودة على الخادم لا على سطح مكتبك فقط
إخفاقات الخطوط إخفاقات نشر. جهاز التطوير لديه خط الشركة مثبتًا؛ أما حساب خدمة Windows على مضيف الإنتاج فلا يملكه، والمخرج يستبدل بصمت. النمط الدفاعي هو تحميل الخطوط من ملفات يتحكم بها المثبت بدل الثقة بمجلد خطوط نظام التشغيل، واستدعاء تسجيل Unicode في HotPDF يفعل ذلك بالضبط:
Pdf.RegisterUnicodeTTF('C:\ProgramData\MyApp\Fonts\NotoSans.ttf');
Pdf.CurrentPage.SetFont('NotoSans', [], 12);
Pdf.CurrentPage.TextOut(50, 700, 0, WideString('Łódź — Ünïcode test ✓'));
لاحظ أن TextOut يستقبل WideString مباشرة، لذلك تمر بيانات العملاء التي تحتوي على أي شيء يتجاوز صفحة الشيفرة المحلية، وهذا عمليًا يعني كل بيانات العملاء، عبر الاستدعاء نفسه مثل أثاث تقرير ASCII، بشرط أن يغطي الخط المحدد المحارف. كما تتطلب خطوط Unicode المضمّنة أن يكون المستند PDF 1.5 أو أحدث، لذلك احتفظ بهذا الحد الأدنى للإصدار في ذهنك إذا كان مطلب آخر يثبتك على إصدار أقدم. بالنسبة إلى النصوص التي تحتاج تشكيلًا لا مجرد lookup للمحارف، وخصوصًا العربية والعبرية، يغطي مقالنا عن تشكيل نصوص complex script باستخدام HotPDF خط الأنابيب المخصص للاتجاه من اليمين إلى اليسار
للحالة النادرة التي لا يستطيع فيها أي ملف خط تمثيل ما تحتاجه، مثل علامات MICR أو الرموز الملكية، يدعم HotPDF خطوط Type 3 عبر RegisterType3Font وAddType3Glyph، حيث يكون كل glyph تدفق محتوى صغيرًا تعرفه أنت. إنها أداة متخصصة، لكنها أفضل من شحن رموز كعمل فني في مئات الصور الصغيرة
الصور: الوسيطان الأوسطان عرض وارتفاع لا زاوية مقابلة
يفصل HotPDF بين تسجيل الصورة ووضعها. يقرأ AddImage كائن TBitmap أو TJPEGImage مرة واحدة، وفك artwork PNG إلى bitmap أولًا، ثم يرجع فهرسًا؛ أما ShowImage فيضع ذلك الفهرس بعدد المرات المطلوب. التوقيع هو الجزء الذي يجب قراءته مرتين:
var
Png: TPngImage;
Logo: TBitmap;
LogoIdx: Integer;
begin
Png := TPngImage.Create;
Logo := TBitmap.Create;
try
Png.LoadFromFile('brand-logo.png');
Logo.Assign(Png); // decode PNG to a bitmap
LogoIdx := Pdf.AddImage(Logo, icFlate); // lossless for flat-color art
finally
Logo.Free;
Png.Free;
end;
// (Index, X, Y, Width, Height, Angle) — not (X1, Y1, X2, Y2)
Pdf.CurrentPage.ShowImage(LogoIdx, 50, 700, 120, 40, 0);
end;
الرقمان بعد الموضع هما عرض وارتفاع، لا زاوية مقابلة، والوسيط الأخير زاوية دوران. الشيفرة المكتوبة على افتراض X1/Y1/X2/Y2 تنتج شعارات ممتدة على معظم الصفحة، وهو عيب واضح في المخرج ومحيّر في المصدر. كذلك فإن KeepImageAspectRatio يبدأ بالقيمة True، لذلك يضيف الصندوق غير المتطابق letterbox بدل التشويه؛ اضبطه على False فقط عندما يكون التمديد مقصودًا فعلًا
كما أن فصل التسجيل عن الوضع مهم للأداء والحجم: يضمّن AddImage بيانات bitmap مرة واحدة، وكل ShowImage بالفهرس نفسه يعيد استخدام الكائن المضمن نفسه. تشغيل كشوف من 500 صفحة يستدعي AddImage في كل صفحة للشعار نفسه يضمّن الشعار 500 مرة؛ والتشغيل نفسه الذي يسجل مرة واحدة ويعيد استخدام الفهرس يضمّنه مرة واحدة. خزّن الفهارس في قاموس صغير مفهرس بمسار الأصل ولن تظهر المشكلة
حجم الملف يعيش هنا أيضًا. يجب أن يمر المحتوى الفوتوغرافي عبر ترميز JPEG، مرر icJpeg إلى AddImage واضبط JpegQuality قرب 85، لأن الخاصية تبدأ بالقيمة 100، وهذا نظيف بصريًا للمرفقات الممسوحة والصور بجزء من الحجم غير الفاقد. أبق PNG للعمل الفني ذي الألوان المسطحة مثل الشعارات والمخططات، حيث تكون آثار ringing في JPEG مرئية ويكون ضغط Flate فعالًا أصلًا. تشغيل كشوف يضمّن صورة واحدة في كل صفحة بالإعدادات الخطأ يشحن gigabytes؛ والتشغيل نفسه عند JPEG 85 يشحن عُشر ذلك من دون أي شكوى من العين
القواعد والصناديق والتظليل ببدائيات المسار
لا تحتاج خطوط الجداول وصناديق المجاميع إلى صور أصلًا؛ فالبدائيات المتجهية تنتج مخرجًا أنظف عند أي تكبير وتكلفتها شبه معدومة في حجم الملف. النموذج هو بناء المسار ثم معامل تلوين:
// Horizontal rule under the table header
Pdf.CurrentPage.SetLineWidth(0.75);
Pdf.CurrentPage.MoveTo(50, 660);
Pdf.CurrentPage.LineTo(545, 660);
Pdf.CurrentPage.Stroke;
// Shaded totals box: X, Y, width, height
Pdf.CurrentPage.SetRGBFillColor(RGB(235, 235, 235));
Pdf.CurrentPage.Rectangle(395, 120, 150, 40);
Pdf.CurrentPage.Fill;
انضباط الترتيب هو نفسه في تدفقات محتوى PDF الخام: اضبط حالة الطلاء، وابن المسار، ثم استدع Stroke أو Fill. المسار الذي لا يُطلى يختفي ببساطة، وهذا هو التفسير المعتاد عندما "لا يظهر" خط. يستقبل SetRGBFillColor قيمة TColor واحدة، لذلك تعمل ثوابت VCL، مثل clNavy وclBlack، مباشرة، وتتبع Rectangle اصطلاح العرض والارتفاع نفسه المستخدم في وضع الصور. تستحق hairlines تنبيهًا واحدًا: عروض الخطوط الأقل من نحو نصف نقطة تبدو أنيقة على الشاشة وقد تختفي تمامًا على طابعة مكتبية بدقة 600 dpi، لذلك يمثل 0.75pt حدًا أدنى معقولًا لخطوط الجداول التي يجب أن تنجو على الورق
تقسيم الصفحات مقابل بيانات حقيقية لا بيانات عينة
تكشف الأعمدة الرقمية عادة أخرى جديرة بالبناء مبكرًا: حاذِ المبالغ على حافتها اليمنى بحساب موضع X من حد العمود الأيمن وعرض القيمة المعروضة، بدل حشو السلاسل بمسافات. لا تصطف المسافات إلا في الخطوط أحادية العرض، والتقارير المالية لا تُضبط أبدًا بخطوط أحادية العرض. نسّق القيم عبر إجراءات Delphi الحساسة للّغة مثل FormatFloat قبل القياس، حتى يكون فاصل الآلاف الذي تتوقعه لغة العميل هو نفسه الذي قست عرضه
مجموعة بيانات العرض فيها عشرة صفوف قصيرة؛ أما الإنتاج ففيه عميل يبلغ اسم شركته 140 حرفًا وكشف يضم 4,000 بند. تتعقب حلقة تقرير متينة مؤشر Y إلى أسفل، وتطرح ارتفاع كل صف، وتكسر إلى صفحة جديدة عندما يتجاوز المؤشر الهامش السفلي، مع تذكر أن "إلى أسفل" يعني تناقص Y في نظام الإحداثيات هذا. ضع معالجة كسر الصفحة في مكان واحد، وأعد إصدار SetFont وأعد رسم الترويسة الجارية داخله، وستختفي عيوب off-by-one-page. إذا كانت تقاريرك يجب أن تفي أيضًا بمتطلبات الأرشفة أو إتاحة الوصول، فإن خيارات التوليد المتخذة هنا، من الخطوط المضمنة، والإخراج الموسوم، وفضاءات اللون، هي بالضبط ما تقيده المعايير؛ راجع دليل HotPDF إلى PDF/A وPDF/X وPDF/UA قبل أن يتصلب القالب
الأسئلة الشائعة
لماذا يظهر نصي في أسفل الصفحة؟
نقطة أصل PDF هي الزاوية اليسرى السفلى مع ازدياد Y إلى أعلى. حوّل المواضع المقاسة من الأعلى باستخدام PageHeight - Offset، أو صمّم شيفرة التخطيط حول نقطة الأصل اليسرى السفلى منذ البداية
لماذا يكون الخط خطأ في الصفحة 2 وصحيحًا في الصفحة 1؟
اختيار الخط لا ينتقل بين الصفحات، وAddPage يبدّل CurrentPage إلى الصفحة الجديدة. استدع SetFont بعد كل AddPage قبل أول TextOut
كيف أحافظ على حجم الملف معقولًا مع كثير من الصور المضمنة؟
مرر icJpeg إلى AddImage واضبط JpegQuality قريبًا من 85 للمحتوى الفوتوغرافي؛ واحجز icFlate غير الفاقد للشعارات وفن الخطوط ذي الألوان المسطحة. سجّل كل صورة مميزة مرة واحدة باستخدام AddImage وأعد استخدام الفهرس
مرجع المنتج
كل استدعاء في هذه المقالة يأتي مع HotPDF Component لـ Delphi وC++Builder، الذي يوثق API النص والخط والصورة والرسم كاملة إلى جانب ميزات النماذج والتشفير والتوقيع المغطاة في مواضع أخرى من هذه المدونة