مقاله فنی

TextOut کامپوننت HotPDF در دلفی: اندازه، سبک، چرخش، و فاصله‌گذاری

هر رشته (string) قابل مشاهده‌ای در یک سند HotPDF از طریق یک فراخوانی به دست می‌آید: TextOut(X, Y, angle, Text). مثال "سلام دنیا" (Hello World) از آن در ساده‌ترین شکل ممکن استفاده می‌کند، جایی که فونت یک بار تنظیم شده و چهار آرگومان در مقادیر پیش‌فرض معقول رها شده‌اند. پس از آن صفحه اول، همان چهار آرگومان تمام وزن چیدمان (layout) را به دوش می‌کشند. آرگومان سوم متن را می‌چرخاند. تنظیم فونت درست قبل از آن، اندازه و سبک را تعیین می‌کند. و جفت مختصات X, Y، که از گوشه صفحه بر حسب نقطه (points) اندازه‌گیری می‌شود، تنها چیزی است که بین یک گزارش تمیز و متنی که روی هم می‌افتد (overlaps)، بریده می‌شود (clips)، یا روی چاپگر شخص دیگری یک خط پایین‌تر جابجا می‌شود، قرار دارد. اینجاست که TextOut ارزش خود را نشان می‌دهد و مقادیر پیش‌فرض دیگر کافی نیستند

امضای تابع (signature) ارزش آن را دارد که قبل از هر چیز دیگری در ذهن تثبیت شود: X و Y از نوع Single و بر حسب نقطه هستند، angle (زاویه) یک Extended بر حسب درجه است، و Text یک WideString است، بنابراین کاراکترهای یونیکد (Unicode) بدون نیاز به فراخوانی جداگانه از آن عبور می‌کنند. یک اضافه‌بار (overload) دوم، یک PWORD به اضافه یک طول (length) را دریافت می‌کند برای زمانی که از قبل کدهای شکل کاراکترها (glyph codes) را در اختیار دارید، اما برای رشته‌های معمولی، فرم WideString همان چیزی است که به سراغ آن می‌روید

اندازه و سبک از SetFont می‌آیند، نه از TextOut

متد TextOut هیچ پارامتری برای اندازه ندارد. اندازه، ضخامت (weight)، شیب (slant)، همه اینها در فراخوانی SetFont که قبل از متن می‌آید قرار دارند، و تا زمانی که SetFont بعدی جایگزین آن نشود به قوت خود باقی می‌ماند. این تنها واقعیتی است که بیشتر سردرگمی‌های روز اول را توضیح می‌دهد: یک خط بولد (bold) ظاهر می‌شود زیرا سه فراخوانی قبل‌تر، چیزی [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] به معنای پررنگ ایتالیک و [] به معنای ساده (plain) است. اندازه بر حسب نقطه (points) است، همان واحدی که برای مختصات استفاده می‌شود، که استدلال در مورد فاصله‌گذاری عمودی را آسان می‌کند: یک خط 12-نقطه‌ای به طور تقریبی به 14 تا 16 نقطه فاصله عمودی (vertical step) برای نفس کشیدن نیاز دارد، بنابراین کاهش Y به میزان 14 نقطه در هر خط یک فاصله پایه (leading) معقول برای شروع است. هیچ پیشروی خودکار خطی (automatic line advance) وجود ندارد. شما خودتان هر خط مبنا (baseline) را محاسبه می‌کنید، که برای یک پاراگراف خسته‌کننده است اما برای یک فرم، که هر فیلد در یک مختصات ثابت قرار دارد، دقیق است

دو نکته عملی در مورد نام فونت. این نام در برابر فونت‌های نصب شده روی دستگاه بیلد (build machine) ارزیابی (resolved) می‌شود، و هر چیزی که سیستم‌عامل بازگرداند همان چیزی است که در فایل تعبیه (embedded) می‌شود، بنابراین نامی که روی دسکتاپ شما ارزیابی می‌شود و نامی که روی یک سرور بیلد ارزیابی می‌شود تضمینی نیست که همان فونت (face) یکسان باشند. و فونت باید اسکریپت‌های (scripts) موجود در رشته را پوشش دهد. اجرای یک متن سیریلیک (Cyrillic) یا CJK تحت یک فونت صرفاً لاتین، بدون هیچ خطایی به صورت کادرهای از دست رفته (missing-glyph boxes) رندر می‌شود، که این دلیلی است که صفحه "سلام دنیا" هنگامی که زبان‌ها را با هم ترکیب می‌کند به سراغ یک فونت یونیکد گسترده می‌رود

صفحه TextOut کامپوننت HotPDF نشان‌دهنده Arial، Times New Roman، و Courier New رندر شده با سبک‌های معمولی، بولد، و ایتالیک در میان چندین مجموعه کاراکتر

آرگومان زاویه، به دور لنگرگاه (anchor) می‌چرخد

آرگومان سوم همان چیزی است که بیشتر کدها برای همیشه روی صفر باقی می‌گذارند. انتقال یک مقدار غیرصفر و متن بر خلاف جهت عقربه‌های ساعت به دور لنگرگاه (X, Y) خود، یعنی پایین-چپ متن، به همان میزان درجه می‌چرخد. خود لنگرگاه حرکت نمی‌کند، بنابراین همان مختصاتی که یک برچسب افقی را قرار می‌دهد، همتای چرخانده شده آن را نیز قرار می‌دهد؛ تنها جهتی که کاراکترها در آن پیش می‌روند تغییر می‌کند

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');

نود درجه حالت رایجی است، یک برچسب که در کنار یک نمودار یا عنوان شیرازه (spine title) بالا می‌رود. چهل و پنج درجه، سرصفحه‌های ستون شیب‌دار را کنترل می‌کند، ترفندی که اجازه می‌دهد یک برچسب عریض روی یک ستون باریک قرار بگیرد بدون اینکه به ستون‌های همسایه‌اش سرریز کند. چرخش نحوه تفسیر لنگرگاه (anchor) را تغییر نمی‌دهد، چیزی که افراد را به اشتباه می‌اندازد: یک متن چرخیده شده 90 درجه هنوز از (X, Y) شروع می‌شود و از آنجا به سمت بالا رشد می‌کند، بنابراین برای وسط‌چین کردن یک برچسب چرخیده شده شما لنگرگاه را تنظیم می‌کنید، نه زاویه را. وقتی چندین متن چرخیده شده دارای یک خط مبنای (baseline) مشترک هستند، به آنها Y یکسان بدهید و X را با یک فاصله مشخص جلو ببرید (step X)، دقیقاً به همان روشی که برای خطوط افقی روی هم قرار گرفته (stacked) برای Y این کار را انجام می‌دادید

قرار دادن مختصات بدون حدس زدن

مختصات بخشی هستند که در بررسی کد جان سالم به در می‌برند یا در سکوت شکست می‌خورند. HotPDF از گوشه پایین-چپ صفحه، با Y رو به بالا، بر حسب نقاط (points) با اندازه 72 در هر اینچ، اندازه‌گیری می‌کند. یک صفحه US Letter برابر 612 در 792 نقطه است؛ A4 برابر 595 در 842 است. بنابراین، یک اینچ حاشیه بالایی روی کاغذ Letter باعث می‌شود اولین خط مبنای (baseline) شما نزدیک به Y = 792 منهای 72 منهای اندازه فونت قرار گیرد، نه در یک عدد کوچک نزدیک بالای صفحه. هر کسی که از مختصات صفحه‌نمایش می‌آید، جایی که Y از صفر به سمت پایین رشد می‌کند، اولین خط را از لبه پایین خارج می‌نویسد و ده دقیقه صرف این می‌کند که به دنبال آن بگردد تا ببیند کجا رفته است

با چیدمان (layout) به عنوان محاسباتی نسبت به لنگرگاه‌های نام‌گذاری شده (named anchors) رفتار کنید تا یک ستون از اعداد جادویی (magic numbers). یک حاشیه چپ، یک خط مبنای در حال اجرا که به ازای هر خط آن را کاهش می‌دهید، و یک فاصله پایه (leading) ثابت، یک بلوک از برچسب‌ها را به جای دیواری از مقادیر ثابت (literals)، به یک حلقه کوتاه تبدیل می‌کند:

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;

محافظ صفحه (page-break guard) خطی است که همه اول از همه فراموش می‌کنند و بیشترین ضربه را در عمل وارد می‌کند. هیچ چیدمان شناوری (flow layout) در زیر TextOut وجود ندارد. از حاشیه پایین (bottom margin) فراتر بروید و متن به رسم خود در حاشیه (gutter)، خارج از صفحه، و به هیچ‌کجا، بدون هیچ هشداری ادامه می‌دهد. بنابراین شما خودتان مراقب Y هستید، وقتی از مرز عبور می‌کند AddPage را فراخوانی می‌کنید و خط مبنا (baseline) را بازنشانی می‌کنید. SetFont پس از AddPage یک حاشیه اختیاری نیست: فونت فعلی از شکستگی صفحه جان سالم به در نمی‌برد، و اگر از این خط بپرید، اولین متن در صفحه جدید با فونت پیش‌فرض نمایشگر ظاهر می‌شود

فاصله‌گذاری کاراکتر و کلمه برای تناسب و تراز کردن (alignment)

گاهی اوقات یک رشته (string) صحیح است اما عرض آن اشتباه است: یک سربرگ (header) که باید سراسر یک خط‌کشی ثابت را در بر بگیرد، کدی که باید با ارقامی دارای فاصله بیشتر (airier digits) خوانده شود، ستونی که مقادیر آن برای تراز شدن نیاز به جابجایی (nudged) دارند. فرمت PDF دو عملگر وضعیت-متن (text-state operators) را برای این کار حمل می‌کند، فاصله‌گذاری کاراکتر (Tc، فضای اضافی اضافه شده پس از هر شکل کاراکتر یا glyph) و فاصله‌گذاری کلمه (Tw، فضای اضافی اضافه شده در هر کاراکتر فاصله)، و هر دو در واحدهای فضای متنی بدون مقیاس (unscaled text-space units) بیان می‌شوند، که به طور موثر همان نقاط (points) در اندازه فونت فعلی هستند. آنها یک وضعیت (state) هستند، نه آرگومان‌هایی برای 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);

فاصله‌گذاری کلمه (Word spacing) تنها روی کاراکتر فاصله (کد 32) عمل می‌کند، که پیامدی دارد که دانستن آن ارزش دارد: این عمل هیچ کاری در داخل یک متن CJK که هیچ فاصله ASCII ندارد انجام نمی‌دهد، و تعامل عجیبی با متنی دارد که به عنوان اندیس‌های شکل کاراکتر (glyph indices) به جای بایت (bytes) کدگذاری شده است. برای خروجی جدولی لاتین (Latin tabular output) این راهی ارزان برای گسترده کردن شکاف‌ها بدون تایپ مجدد رشته است. فاصله‌گذاری کاراکتر (Character spacing) ابزار بهتری برای یک عنوان است که باید به عرض مشخصی (target width) برسد، زیرا تنظیمات را به طور مساوی در بین تمام کاراکترها پخش می‌کند به جای اینکه آن را در فاصله‌ها جمع کند

بازنشانی (reset) کردن تمام آن چیزی است که باید رعایت کنید. فاصله‌گذاری، مانند فونت، بخشی از وضعیت رسم صفحه (page's drawing state) است، و وضعیت تا زمانی که آن را تغییر دهید پایدار می‌ماند. فاصله حروفی (Letter-space) یک عنوان را تنظیم کنید و فراموش کنید که آن را صفر کنید، در این صورت هر پاراگراف در پایین، کشیدگی (stretch) را به ارث می‌برد، که به عنوان یک اشتباه ظریف و دشوار برای یافتن خوانده می‌شود که در یک تصحیح معمولی جان سالم به در می‌برد و در یک تصحیح دقیق شکست می‌خورد. عادت قابل اعتماد این است که یک مقدار فاصله‌گذاری تعیین کنید، متنی را که به آن نیاز دارد رسم کنید، و در خط بعدی آن را به صفر برگردانید، بنابراین هیچ کد بعدی نیاز ندارد بداند که یک بخش قبلی چه کاری انجام داده است

صفحه TextOut در HotPDF که مقیاس‌بندی افقی متن، فاصله‌گذاری کاراکتر، فاصله‌گذاری کلمه، و حالت‌های رندر پر کردن در برابر خط دور (fill versus stroke) را با هم مقایسه می‌کند

بررسی خروجی در جایی که واقعاً می‌شکند

چیدمان متن (Text layout) در دستگاه دوم شکست می‌خورد، نه در دستگاه اول، بنابراین بررسی‌هایی که اهمیت دارند دور از میز شما اتفاق می‌افتند. فایل تولید شده را روی سیستمی بدون مجموعه فونت‌های نصب شده برنامه‌نویس خود باز کنید و تأیید کنید که فونت‌های تعبیه شده (embedded faces) هنوز هم رندر می‌شوند، شامل لاتین آکسان‌دار (accented Latin)، هر اسکریپت غیر لاتینی، و علائم نگارشی، و این کار را در یک گذر انجام دهید تا اینکه به بررسی تصادفی کاراکترهای آسان اکتفا کنید. چند خط را انتخاب کرده و کپی کنید تا تأیید کنید که متن واقعاً متن است و نه خطوط دور (outlines)، که این امر در لحظه‌ای که جستجو یا استخراج در دستور کار قرار می‌گیرد اهمیت پیدا می‌کند. چیدمان را با داده‌های نماینده، با طولانی‌ترین برچسب آلمانی و عریض‌ترین شماره تغذیه کنید، نه یک متن جایگزین (placeholder) مرتب، زیرا متنی که از یک فیلد سرریز می‌کند همیشه متنی است که شما با دست آن را تایپ نکرده‌اید. و اگر صفحه باید روی یک فرم از پیش چاپ شده (preprinted form) قرار گیرد، یک نمونه چاپ یا رندر (rasterize) کنید و آن را با نسخه اصلی مقایسه کنید؛ یک چهارم میلی‌متر انحراف خط مبنا روی صفحه‌نمایش نامرئی است اما روی کاغذ کاملاً مشهود است

اگر هنوز حتی یک صفحه ننوشته‌اید، با مثال Hello World کامپوننت HotPDF شروع کنید، که سند، فونت و سیستم مختصات پایین-چپ (bottom-left) را که هر چیزی در بالا به آن وابسته است را راه‌اندازی می‌کند. فراخوانی‌های TextOut، SetFont و فاصله‌گذاری که در اینجا نشان داده شده‌اند، بخشی از کامپوننت HotPDF برای دلفی و C++Builder هستند