مقاله فنی

Flexbox، CSS Grid و پانویس‌ها در PDF از دلفی

PDF Library for Delphi، HTML را در یک صفحه PDF با چیدمان واقعی دوبعدی رندر می‌کند: display: flex و display: grid اندازه‌گیری و جای‌گذاری می‌شوند نه اینکه به بلوک‌های انباشته تنزل یابند، و پانویس‌ها در پایین جعبه‌ای که ارجاعشان را حمل می‌کند رزرو می‌شوند، با شماره‌گذاری‌ای که در سراسر ستون‌ها و صفحات پیوسته می‌ماند. نقاط ورودی همان‌های آشنا هستند، DrawHTMLTextBox برای یک جعبه واحد و DrawHTMLStory برای جریان چندستونی

این اهمیت دارد زیرا HTML همان روشی است که بیشتر محتوای گزارش اکنون می‌رسد. قالب‌ها توسط افرادی که CSS می‌نویسند تألیف می‌شوند، داشبوردها به‌صورت کارت طراحی می‌شوند، و یک رندرکننده که به‌طور خاموش یک ردیف flex را به چهار بلوک انباشته فرومی‌ریزد، سندی تولید می‌کند که اصلاً شبیه طراحی نیست. تا پیش از وجود این قابلیت، تنها ظرف دوبعدی‌ای که موتور اندازه‌گیری می‌کرد جدول بود، بنابراین هر چیدمان کارتی باید دستی به‌عنوان یک جدول دوباره تألیف می‌شد

در مدل چیدمان چه چیزی تغییر کرد؟

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

Flex و grid هرکدام یک گذر اندازه‌گیری کران‌دار روی فرزندان یک ظرف اضافه می‌کنند، و واژه مهم کران‌دار است. یک ظرف flex تا ۲۵۶ فرزند مستقیم را در یک آرایه ثابت اندازه‌گیری می‌کند. یک grid از یک ماتریس اشغال حداکثر ۶۴ در ۶۴ سلول برای جای‌گذاری خودکار قطعی استفاده می‌کند. آن سقف‌ها وجود دارند تا یک شیوه‌نامه خصمانه یا تولیدشده نتواند بازگشت بی‌کران یا حافظه جای‌گذاری درجه‌دوم را به راه بیندازد، که وقتی HTML از یک قالبی می‌آید که یک مشتری ویرایش می‌کند، یک نگرانی واقعی است

آیتم‌های flex چگونه اندازه خود را می‌گیرند؟

در جهت ردیف، ظرف پایه هر آیتم را همراه با وزن‌های رشد و کوچک‌شدنش جمع می‌زند، سپس فضای باقی‌مانده، مثبت یا منفی، را بر اساس آن وزن‌ها توزیع می‌کند. با flex-wrap، هر خط به‌طور مستقل حل می‌شود، بنابراین ردیفی که به دو خط می‌شکند فضای آزاد را به ازای هر خط اختصاص می‌دهد نه در سراسر کل ظرف. در جهت ستون، همان توزیع محور اصلی در برابر یک ارتفاع صریح یا ارتفاع محتوا اجرا می‌شود

justify-content، align-items، gap و جهت‌های معکوس روی هندسه‌ای عمل می‌کنند که از قبل اندازه‌گیری شده. آن‌ها جعبه‌ها را جابه‌جا می‌کنند؛ هرگز اندازه‌گیری مجدد محتوای آیتم را برنمی‌انگیزند. آن جدایی همان چیزی است که یک داشبورد پیچیده را از اندازه‌گیری چندباره فرزندانش بازمی‌دارد

uses
  PDFlibrary;

var
  Lib: TPDFlib;
  Html, Remainder: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.NewDocument;
    Lib.SetPageSize('A4');
    Lib.NewPage;

    Html :=
      '<div style="display:flex; gap:12px;">' +
      '  <div style="flex:2 1 0; background:#f4f6f8; padding:8px;">' +
      '    <b>Revenue</b><br/>EUR 4,182,300</div>' +
      '  <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
      '    <b>Margin</b><br/>18.4%</div>' +
      '  <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
      '    <b>Backlog</b><br/>92 days</div>' +
      '</div>';

    Remainder := Lib.DrawHTMLTextBox(40, 40, 515, 120, Html);
    if Remainder <> '' then
      Log('content did not fit - carry the remainder to the next box');

    Lib.SaveToFile('dashboard.pdf');
  finally
    Lib.Free;
  end;
end;

مقدار بازگشتی، رشته تداوم است، که همان روشی است که هر نقطه ورودی ترسیم HTML آنچه را که جا نشده گزارش می‌دهد. آن را به جعبه بعدی یا صفحه بعدی بسپارید و جریان از همان‌جایی که متوقف شده ادامه می‌یابد

جای‌گذاری Grid، و یک تراک (track) چه می‌تواند باشد؟

تراک‌های Grid طول‌های ثابت، درصدها، واحد fr، عبارت‌های ساده repeat() و minmax() را می‌پذیرند. جای‌گذاری خودکار به‌طور قطعی ماتریس اشغال را پر می‌کند، بنابراین همان HTML همیشه همان چیدمان را تولید می‌کند. مختصات صریح مجازند همپوشانی کنند، که عمدی است: طراحی‌ای که یک نشان را روی یک کارت لایه می‌کند، قصد را بیان می‌کند، نه یک خطا. وقتی فقط یک محور به‌صراحت داده شده، جای‌گذاری فقط محور دیگر را جست‌وجو می‌کند

آیتم‌هایی که چند ردیف را می‌پوشانند، ارتفاع اندازه‌گیری‌شده خود را به ردیف‌هایی که پوشش می‌دهند بازمی‌گردانند، به‌طور میانگین در سراسر آن‌ها، که یک آیتم بلند پوشاننده را از فشردن یک ردیف واحد بازمی‌دارد درحالی‌که همسایگانش کوتاه باقی می‌مانند:

Html :=
  '<div style="display:grid; grid-template-columns:repeat(3, 1fr); ' +
  '            gap:10px;">' +
  '  <div style="grid-row:span 2; background:#eef;">Site plan</div>' +
  '  <div>Inspector</div>' +
  '  <div>Date</div>' +
  '  <div style="grid-column:2 / span 2;">Findings summary</div>' +
  '</div>';

Remainder := Lib.DrawHTMLTextBox(40, 180, 515, 260, Html);

فرزندان Flex و grid از طریق همان رندرکننده HTML که هر چیز دیگری استفاده می‌کند رندر می‌شوند، که همان ویژگی‌ای است که این قابلیت را قابل‌استفاده می‌کند نه یک دنیای جداگانه. فونت‌ها، آبشار CSS، پیوندها، تصاویر، جدول‌ها و ظرف‌های flex یا grid تودرتوی بیشتر، همگی درون یک آیتم flex دقیقاً همان‌طور که در سطح بالا رفتار می‌کنند، رفتار می‌کنند، و طرح چیدمان بیرونی دستورات نهایی متن و مستطیل را ثبت می‌کند تا ترسیم تکراری از کش اندازه‌گیری موجود دوباره استفاده کند

چرا پانویس‌ها یک مسئله صفحه‌بندی هستند؟

یک پانویس محتوایی نیست که پس از پاراگراف حاوی ارجاعش جریان می‌یابد؛ محتوایی است که باید در پایین همان جعبه‌ای که ارجاعش در آن است ظاهر شود. این ترتیب معمول اندازه‌گیری را وارونه می‌کند، زیرا فضای در دسترس برای متن بدنه اکنون به محتوایی وابسته است که هنوز چیدمان نگرفته

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

در یک جعبه کران‌دار، مساحت یادداشت با یک خط جداکننده بالای آن به پایین سنجاق می‌شود. در اندازه‌گیری بی‌کران، جایی که ارتفاع جعبه‌ای برای سنجاق‌کردن به آن وجود ندارد، مساحت یادداشت بلافاصله پس از بدنه دنبال می‌شود. شماره‌گذاری در یک فیلد گسترش‌یافته روی پشته تداوم حمل می‌شود، بنابراین DrawHTMLTextBox و DrawHTMLStory دنباله را در سراسر ستون‌ها و صفحات درحال‌اجرا نگه می‌دارند، و یک رشته تداوم که پیش از وجود آن فیلد تولید شده همچنان به‌درستی ادامه می‌یابد

// پانویس‌های درون یک استوری چندستونی یک دنباله درحال‌اجرا واحد را نگه می‌دارند
Html := LoadTemplate('chapter.html');    // از نشانگرهای float:footnote استفاده می‌کند
Remainder := Lib.DrawHTMLStory(40, 40, 515, 700,
  2,        // ستون‌ها
  16,       // فاصله میانی (gutter) بر حسب پوینت
  20,       // حداکثر صفحه برای این استوری
  Html);
if Remainder <> '' then
  Log('story exceeded its page budget');

راهنمای عملی برای تألیف‌کنندگان قالب

در سقف‌های مستندشده طراحی کنید. یک ظرف flex با بیش از ۲۵۶ فرزند مستقیم تقریباً همیشه یک جدول داده است که لباس flex پوشیده، و مسیر جدول به‌هرحال آن را بهتر اندازه‌گیری می‌کند. یک grid بزرگ‌تر از ۶۴ در ۶۴ یک صفحه‌گسترده است، و همان توصیه اعمال می‌شود. برای متن بدنه چندستونی، رفتار ستون و خط‌شکنی هجایی که در خط‌شکنی هجایی و ستون‌های متنی متوازن شرح داده شده، تعیین می‌کند جریان درون هر ستون چگونه به نظر می‌رسد

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

جایی که HTML از یک موتور گزارش‌سازی می‌آید نه از قالب‌های دستی‌نوشته‌شده، مسیر داده‌محور در موتور گزارش مجموعه‌داده با این خوب ترکیب می‌شود، و نشانه‌گذاری‌ای را تولید می‌کند که flex و grid سپس آن را می‌آرایند. و وقتی همان محتوا باید دوباره از PDF خارج شود، مسیر صادرات معنایی در صادرات PDF به Markdown و DOCX سفر رفت‌وبرگشت را می‌بندد

چیدمان HTML، تولید گزارش و صادرات معنایی بخشی از یک کتابخانه واحد برای دلفی، C++Builder و Free Pascal هستند؛ فهرست کامل ویژگی‌ها در صفحه PDF Library for Delphi قرار دارد