مقاله فنی

تراز کامل (Justification) متن PDF در دلفی با HotPDF

تراز کامل (Full justification) طرح‌بندی‌ای است که باعث می‌شود یک ستون متن در هر دو لبه چپ و راست تراز شود، ظاهری که از یک کتاب چاپ شده یا یک گزارش رسمی انتظار دارید. توصیف آن آسان است اما به طرز شگفت‌آوری احتمال اشتباه در آن زیاد است، زیرا پاسخ به این سوال که "فضای اضافی کجا می‌رود" برای انگلیسی با ژاپنی یکسان نیست، و همچنین به این دلیل که روش ساده‌لوحانه برای اندازه‌گیری هر خط، یک صفحه سریع را به یک صفحه کند تبدیل می‌کند. HotPDF از طریق یک فراخوانی طرح‌بندی جعبه‌ای، ترازسازی حساس به خط را در اختیار شما قرار می‌دهد و در زیر این فراخوانی، یک اصلاح عملکردی کلاسیک قرار دارد که ارزش درک کردن را دارد

این مقاله هر دو مورد را بررسی می‌کند. اول، قانون تایپوگرافی که تعیین می‌کند فضای خالی برای خطوط دارای فاصله بین کلمات در مقابل خطوط بدون فاصله چگونه توزیع شود. دوم، تغییر در اندازه‌گیری که هزینه ترازسازی در هر صفحه را تقریباً هشتاد برابر کاهش داد بدون اینکه تفاوت قابل مشاهده‌ای در خروجی ایجاد کند. اگر اسناد را در حجم بالا تولید می‌کنید و می‌خواهید مانند حروف‌چینی واقعی خوانده شوند نه خروجی با فاصله ثابت که برای پر کردن کشیده شده است، هر دو مورد اهمیت دارند

تراز کامل در واقع به چه چیزی نیاز دارد

یک خط متن که با عرض طبیعی خود کشیده می‌شود، تقریباً هرگز به لبه راست ستون خود نمی‌رسد. همیشه یک باقیمانده (فضای خالی) بین جایی که آخرین گلیف به پایان می‌رسد و مرز ستون وجود دارد. تراز به چپ، این فضای خالی را در سمت راست باقی می‌گذارد. تراز به راست آن را به سمت چپ منتقل می‌کند. مرکزچین آن را تقسیم می‌کند. تراز کامل آن را با پهن کردن خود خط تا زمانی که هر دو لبه به جعبه برسند، از بین می‌برد و تنها راه درست برای انجام این کار این است که گلیف‌ها را از داخل از هم دور کنید

قانونی که ترازسازی خوب را از بد متمایز می‌کند این است که فضای خالی را کجا قرار می‌دهید. خطی که کلمات را با فاصله بین آنها می‌نویسد، مانند انگلیسی و بقیه خانواده لاتین، در هر فاصله بین کلمات دارای درزهای طبیعی است. پهن کردن این فاصله‌ها برای چشم نامرئی است زیرا خوانندگان از قبل می‌پذیرند که فاصله‌های کلمات متفاوت است. خطی که بدون فاصله بین کلمات می‌نویسد، مانند نویسه‌های هان چینی، کانای ژاپنی یا هانگول کره‌ای، چنین درزهایی ندارد. در آنجا فضای خالی باید به طور مساوی بین گلیف‌های مجاور پخش شود، اصلی که حروف‌چین‌های ژاپنی به آن kintou-waritsuke (فاصله‌گذاری یکنواخت) می‌گویند. اعمال کشش فاصله کلمات به سبک لاتین روی یک خط CJK، یا چپاندن تمام فضای خالی در تنها جایی که به طور تصادفی یک خط CJK دارای فاصله است، باعث ایجاد رودخانه‌ها و شکاف‌هایی می‌شود که نشانه خروجی آماتور است

چگونه HotPDF تصمیم می‌گیرد که فضا کجا برود

ابزار HotPDF این تصمیم را برای هر فاصله (گپ) می‌گیرد، نه برای هر خط. هنگامی که یک خط را تراز می‌کند، روی هر جفت گلیف مجاور حرکت می‌کند و بررسی می‌کند که آیا مرز قابل کششی بین آنها قرار دارد یا خیر. یک مرز زمانی قابل کشش است که در هر دو طرف یک فاصله یا تب وجود داشته باشد (حالت لاتین)، یا زمانی که هر دو طرف نویسه‌های CJK قابل شکستن باشند (حالت فاصله‌گذاری یکنواخت). این ابزار آن مرزها را می‌شمارد، فضای خالی خط را به طور مساوی بین آنها تقسیم می‌کند و آن سهم را به هر فاصله واجد شرایط اضافه می‌کند

نتیجه به طور طبیعی به دست می‌آید. یک خط انگلیسی فقط در فواصل بین کلمات خود مرزهای قابل کشش دارد، بنابراین تمام فضای خالی در آنجا قرار می‌گیرد و کلمات از هم باز می‌شوند در حالی که حروف درون هر کلمه فاصله طبیعی خود را حفظ می‌کنند. یک خط هان یا کانا تقریباً بین هر جفت گلیف دارای مرز قابل کشش است، بنابراین فضای خالی به طور مساوی در کل خط توزیع می‌شود، دقیقاً همان فاصله‌گذاری یکنواخت بین گلیف‌ها که این خطوط نیاز دارند. خطی که یک کلمه طولانی لاتین بدون فاصله داخلی است، اصلاً مرز قابل کششی ندارد، بنابراین HotPDF به جای اینکه کلمه را حرف به حرف از هم جدا کند، آن را در عرض طبیعی خود رها می‌کند. همین منطق، ترکیب خطوط لاتین و CJK در یک خط را بدون نیاز به حالت‌بندی خاص مدیریت می‌کند، زیرا تصمیم‌گیری برای هر مرز به صورت محلی انجام می‌شود

یک مرز عمداً در همه جا مستثنی می‌شود. موقعیت بعد از گلیف نهایی یک خط هرگز به عنوان یک فاصله در نظر گرفته نمی‌شود، زیرا کشش در آنجا فقط باعث ایجاد مجدد یک باقیمانده در سمت راست می‌شود که دقیقاً نقطه مقابل ترازسازی است

چرا خط آخر رها می‌شود

خط نهایی یک پاراگراف خاص است و اشتباه در آن، رایج‌ترین باگ ترازسازی است. خط آخر یک پاراگراف معمولاً کوتاه است، اغلب تنها چند کلمه است و کشیدن آن تا عرض کامل ستون، این کلمات را در سراسر صفحه به یک ردیف پراکنده و شکسته تبدیل می‌کند. تایپوگرافی صحیح، خط آخر را در عرض طبیعی خود به صورت تراز به چپ رها می‌کند (در زبان‌های راست‌به‌چپ، تراز به راست)

ابزار HotPDF خط پایانی را از روی موقعیت تشخیص می‌دهد. همانطور که متن را به خطوط می‌شکند، می‌داند چه زمانی خطی که به تازگی جدا کرده است به انتهای رشته ارائه‌شده می‌رسد. آن خط نهایی با تراز ساده (بدون کشش) خروجی داده می‌شود و عرض طبیعی خود را حفظ می‌کند. تمام خطوط قبل از آن از هر دو لبه تراز می‌شوند. شکستگی‌های سخت خطوط (Enter) که در متن می‌نویسید نیز به همان صورت که نوشته شده‌اند رعایت می‌شوند، بنابراین یک خط کوتاه عمدی نیز هرگز کشیده نمی‌شود. خواننده یک بلوک مستطیلی تمیز از متن را می‌بیند که خط آخر آن به طور طبیعی به پایان می‌رسد، چیزی که چشم انتظار آن را دارد

هزینه اندازه‌گیری که ترازسازی را کند می‌کرد

برای تراز کردن یک خط باید عرض دقیق آن را بدانید و باید میزان پیشروی هر گلیف را بدانید تا بتوانید فضای اضافی را به دقت قرار دهید. اولین پیاده‌سازی این اعداد را به روش واضحی به دست آورد. کل خط را با یک کوئری کامل عرض یونیکد اندازه‌گیری کرد، سپس پیشوند پس از پیشوند را اندازه‌گیری کرد تا میزان پیشروی هر گلیف را با تفاوت‌گیری بازیابی کند. برای یک خط با N گلیف، این یعنی N+1 فراخوانی به موتور اندازه‌گیری، و هر فراخوانی یک رفت‌وبرگشت کامل GDI است که از سیستم عامل می‌خواهد متن را شکل داده و اندازه‌گیری کند و پاسخ را برگرداند

در هر خط این ارزان به نظر می‌رسد. اما در سراسر یک صفحه اینطور نیست. یک صفحه A4 متراکم از متن اصلی را در نظر بگیرید، تقریباً چهل و پنج خط که هر کدام حدود هشتاد کاراکتر دارند. با N+1 رفت‌وبرگشت در هر خط، این حدود 81 رفت‌وبرگشت برای هر خط و تقریباً 3645 برای کل صفحه است، که تقریباً همه آنها صرف اندازه‌گیری مجدد متنی می‌شود که موتور لحظاتی پیش به آن نگاه کرده بود. در یک کار دسته‌ای (batch) که هزاران صفحه تولید می‌کند، این سربار بر زمان طرح‌بندی مسلط می‌شود و هر رفت‌وبرگشت از مرز بین پردازش شما و زیرسیستم گرافیکی عبور می‌کند

یک فراخوانی به جای N به علاوه یک

این اصلاح، نوعی تغییر است که کوچک به نظر می‌رسد اما نتیجه بزرگی به همراه دارد. GDI از قبل می‌تواند عرض کل یک رشته و موقعیت هر گلیف را در یک کوئری واحد گزارش دهد. HotPDF این را از طریق GetWideCharAdvances ارائه می‌دهد، که آرایه‌ای را با پیشروی طبیعی هر گلیف، از جمله کرنینگ (kerning)، پر می‌کند و عرض کل را در یک فراخوانی به جای N+1 برمی‌گرداند. روال ترازسازی (که به صورت داخلی _HPDFEmitJustifiedWideLine است)، یک بار تمام پیشروی‌ها را درخواست می‌کند، فضای خالی را محاسبه می‌کند، آن را در سراسر مرزهای قابل کشش توزیع می‌کند و خط را صادر می‌کند

برای همان صفحه A4، اندازه‌گیری در هر خط از حدود 81 رفت‌وبرگشت به یک کاهش می‌یابد، بنابراین صفحه از حدود 3645 رفت‌وبرگشت به حدود 45 کاهش می‌یابد که نزدیک به یک کاهش هشتاد برابری است. خروجی بایت به بایت یکسان است، زیرا هیچ چیز در مورد اندازه‌گیری تغییر نکرده جز تعداد دفعاتی که درخواست شده است. همان موتور GDI، همان متریک‌های فونت، و همان کرنینگ اعداد یکسانی را تغذیه می‌کنند. فقط تعداد رفت‌وبرگشت‌ها کاهش یافته است. زمانی که یک اندازه‌گیری از قبل درست است، بهینه‌سازی صحیح این است که از درخواست مکرر آن دست برداریم، نه اینکه آن را تقریب بزنیم

خط چگونه به صفحه می‌رسد

پس از تسهیم فضای خالی، HotPDF خط را با ExtTextOut و یک آرایه پیشروی به ازای هر گلیف، یعنی آرایه Dx صادر می‌کند. هر ورودی فاصله از مبدا یک گلیف تا گلیف بعدی است، که پیشروی طبیعی آن گلیف به علاوه سهم آن از فضای خالی زمانی است که یک مرز قابل کشش به دنبال آن باشد. این به طور مستقیم روی مدل تصویرسازی PDF نگاشت می‌شود. متن موقعیت‌یابی شده با عملگر TJ نوشته می‌شود، آرایه‌ای که اجرای گلیف‌ها را با تنظیمات افقی صریح ترکیب می‌کند، و مقادیر Dx دقیقاً به همان تنظیمات تبدیل می‌شوند. به همین دلیل است که فضای اضافی بین گلیف‌ها در موقعیت‌های دقیق زیر-نقطه‌ای (sub-point) قرار می‌گیرد و با کاراکترهای فاصله‌گذار (padding) جعل نمی‌شود، و به همین دلیل است که اگر یک ابزار پایین‌دستی آن را دوباره بخواند، یک خط تراز شده HotPDF به درستی اندازه‌گیری می‌شود

شما خودتان برای پاراگراف‌های تراز شده، ExtTextOut را فراخوانی نمی‌کنید. نقطه ورود WideTextOutBox است که یک رشته یونیکد را در یک جعبه می‌پیچد و ترازی را که درخواست می‌کنید اعمال می‌کند. این متد متن را به خطوطی که در عرض جعبه قرار می‌گیرند تقسیم می‌کند، هر خط را در طول ارتفاع جعبه قرار می‌دهد و تعداد کاراکترهایی را که توانسته است قبل از اتمام فضای عمودی جا دهد، برمی‌گرداند. تراز توسط enum ترازسازی (justification enum) انتخاب می‌شود

type
  THPDFJustificationType = (jtLeft, jtCenter, jtRight, jtJustify);

سه مورد اول کاملاً واضح هستند: تراز به چپ، مرکزچین و تراز به راست. مورد چهارم، jtJustify، همان تراز کامل از هر دو لبه است که در اینجا توضیح داده شد، و این مقداری است که WideTextOutBox می‌خواند تا فاصله‌گذاری حساس به خط را فعال کند

تراز کردن یک پاراگراف در عمل

یک مثال کامل، سندی ایجاد می‌کند، فونتی را تنظیم می‌کند و پاراگرافی را با تراز کامل در یک جعبه می‌ریزد. همین کد، متن‌های لاتین و CJK را بدون تغییر هیچ پرچمی (flag) تراز می‌کند، زیرا حساسیت به خط در لایه‌های زیرین API قرار دارد

uses
  HPDFDoc;

procedure JustifyParagraph;
var
  Pdf: THotPDF;
  Body: WideString;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'Justified.pdf';
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', 11);

    Body :=
      'Full justification spreads the slack on each filled line so both ' +
      'edges meet the column, while the last line keeps its natural width. ' +
      'For scripts with word gaps the space lands between words; for ' +
      'scripts without them it spreads evenly between glyphs.';

    // X, Y, LineSpacing, BoxWidth, BoxHeight, Text, Align
    Pdf.CurrentPage.WideTextOutBox(72, 72, 4, 380, 240, Body, jtJustify);

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

برای رسم همان بلوک به صورت تراز به چپ، مرکزچین یا تراز به راست، تنها آرگومان نهایی را به jtLeft، jtCenter یا jtRight تغییر دهید. شکستن خطوط، قرارگیری خط و مقدار بازگشتی یکسان باقی می‌ماند. عرض اندازه‌گیری شده که هر چهار مسیر را هدایت می‌کند از GetWideTextWidth می‌آید، کوئری عرض حساس به یونیکد که یک WideString را به درستی اندازه‌گیری می‌کند در حالی که اندازه‌گیری بایت به بایت قدیمی‌تر اندازه هر چیزی فراتر از لاتین-1 را به اشتباه محاسبه می‌کرد، و این همان چیزی است که باعث می‌شود جعبه از همان ابتدا متن‌های CJK و جفت‌های جایگزین (surrogate-pair) را در جای مناسب بشکند

ترازسازی، یک لایه از پشته بزرگترِ شکل‌دهی متن است. زمانی که یک خط حاوی خطوطی است که گلیف‌های خود را تغییر ترتیب می‌دهند یا به هم می‌پیوندند، تصمیمات مربوط به فاصله‌گذاری در اینجا، بر روی کار توصیف شده در مقاله ما در مورد شکل‌دهی متن خطوط پیچیده قرار می‌گیرند، و زمانی که یک فونت دارای متغیرهای تایپوگرافی است که می‌خواهید انتخاب کنید، به مقاله نحوه استفاده از متغیرهای سبکی GSUB در OpenType مراجعه کنید. همه این موارد در کامپوننت HotPDF برای دلفی و C++Builder، در کنار سایر APIهای گسترده‌تر متن، طرح‌بندی و سند که در سراسر این وبلاگ پوشش داده شده است، عرضه می‌شود