Artikel Teknis

Flexbox, CSS Grid, dan Footnote dalam PDF dari Delphi

PDF Library for Delphi merender HTML ke dalam sebuah halaman PDF dengan layout dua dimensi sungguhan: display: flex dan display: grid diukur dan ditempatkan alih-alih didegradasi menjadi blok bertumpuk, dan footnote dicadangkan di bagian bawah kotak yang membawa referensinya, dengan penomoran yang tetap berkelanjutan lintas kolom dan halaman. Entry point-nya adalah yang sudah familiar, DrawHTMLTextBox untuk sebuah kotak tunggal dan DrawHTMLStory untuk flow multi-kolom

Ini penting karena HTML adalah cara sebagian besar konten laporan datang sekarang. Template ditulis oleh orang-orang yang menulis CSS, dashboard dirancang sebagai card, dan sebuah renderer yang secara diam-diam meruntuhkan sebuah baris flex menjadi empat blok bertumpuk menghasilkan dokumen yang sama sekali tidak menyerupai desainnya. Sampai kapabilitas ini ada, satu-satunya container dua dimensi yang diukur engine ini adalah table, sehingga setiap layout card harus ditulis ulang sebagai table secara manual

Apa yang berubah dalam model layout?

Loop utama sebelumnya mempertahankan satu line box dan maju ke bawah halaman. Model itu menangani konten inline dan blok bertumpuk dengan sempurna dan tidak dapat menyatakan sebuah container yang child-nya diukur relatif satu sama lain. Table adalah satu-satunya pengecualian, dengan pengukuran dua pass-nya sendiri

Flex dan grid masing-masing menambahkan sebuah pass pengukuran terbatas atas child sebuah container, dan kata pentingnya adalah terbatas. Sebuah flex container mengukur hingga 256 child langsung ke dalam sebuah array tetap. Sebuah grid menggunakan matriks okupansi maksimal 64 kali 64 cell untuk penempatan otomatis yang deterministik. Batas atas itu ada agar sebuah stylesheet yang jahat atau yang dihasilkan tidak dapat memicu rekursi tak terbatas atau memori penempatan kuadratik, sebuah kekhawatiran nyata ketika HTML-nya berasal dari sebuah template yang diedit pelanggan

Bagaimana item flex mendapatkan ukurannya

Pada arah baris, container menjumlahkan basis setiap item beserta bobot grow dan shrink-nya, lalu mendistribusikan ruang sisa, positif atau negatif, sesuai bobot tersebut. Dengan flex-wrap, setiap baris diselesaikan secara independen, sehingga sebuah baris yang pecah menjadi dua baris menetapkan ruang bebas per baris alih-alih di seluruh container. Pada arah kolom, distribusi main-axis yang sama berjalan terhadap sebuah tinggi eksplisit atau tinggi konten

justify-content, align-items, gap, dan arah terbalik beroperasi pada geometri yang sudah diukur. Mereka memindahkan kotak; mereka tidak pernah memicu pengukuran ulang konten item. Pemisahan itulah yang mencegah sebuah dashboard kompleks mengukur child-nya berkali-kali

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;

Nilai baliknya adalah string kelanjutan, yaitu cara setiap entry point penggambaran HTML melaporkan apa yang tidak muat. Serahkan itu ke kotak berikutnya atau halaman berikutnya dan flow-nya berlanjut dari tempat ia berhenti

Penempatan grid, dan apa yang bisa menjadi sebuah track

Track grid menerima panjang tetap, persentase, unit fr, ekspresi repeat() sederhana, dan minmax(). Penempatan otomatis mengisi matriks okupansi secara deterministik, sehingga HTML yang sama selalu menghasilkan susunan yang sama. Koordinat eksplisit diizinkan tumpang tindih, dan itu disengaja: sebuah desain yang melapiskan sebuah badge di atas sebuah card sedang menyatakan maksud, bukan sebuah error. Ketika hanya satu sumbu yang diberikan secara eksplisit, penempatan mencari sumbu yang lain saja

Item yang membentang beberapa baris menyumbangkan tinggi terukurnya kembali ke baris-baris yang dicakupnya, dirata-ratakan di antara mereka, yang mencegah sebuah item tinggi yang membentang memepetkan satu baris tunggal sementara tetangganya tetap pendek:

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

Child flex dan grid dirender melalui renderer HTML yang sama seperti segala sesuatu lainnya, yang merupakan properti yang membuat fitur ini dapat digunakan alih-alih menjadi dunia terpisah. Font, cascade CSS, link, gambar, table, dan container flex atau grid bersarang lebih lanjut semuanya berperilaku di dalam sebuah item flex persis seperti di level teratas, dan rencana layout terluar mencatat perintah teks dan rectangle final sehingga penggambaran berulang menggunakan kembali cache pengukuran yang sudah ada

Mengapa footnote merupakan masalah pagination?

Sebuah footnote bukan konten yang mengalir setelah paragraf yang mengandung referensinya; ia adalah konten yang harus muncul di bagian bawah kotak yang sama dengan referensinya. Itu membalik urutan pengukuran biasa, karena ruang yang tersedia untuk teks body kini bergantung pada konten yang belum di-layout

Renderer karena itu mengukur note tersebut ketika bertemu dengan referensinya, dan mengurangi area note dari budget tinggi body dari kotak terbatas saat ini. Jika referensi, teks body sejauh ini, dan note tidak dapat semuanya muat, marker footnote dan segala sesuatu setelahnya berpindah bersama-sama ke dalam string kelanjutan. Aturan itulah yang mencegah dua kegagalan klasik: sebuah note yang menimpa teks body, dan sebuah note yang terdampar pada sebuah halaman sementara referensinya berada di halaman sebelumnya

Dalam sebuah kotak terbatas, area note dipatri di bagian bawah dengan sebuah garis pemisah di atasnya. Dalam pengukuran tak terbatas, di mana tidak ada tinggi kotak untuk dipatri, area note mengikuti langsung setelah body. Penomoran dibawa dalam sebuah field ekstensi pada continuation stack, sehingga DrawHTMLTextBox dan DrawHTMLStory menjaga urutan tetap berjalan lintas kolom dan halaman, dan sebuah string kelanjutan yang dihasilkan sebelum field itu ada tetap dilanjutkan dengan benar

// Footnote di dalam sebuah story multi-kolom menjaga satu urutan yang berjalan
Html := LoadTemplate('chapter.html');    // menggunakan marker float:footnote
Remainder := Lib.DrawHTMLStory(40, 40, 515, 700,
  2,        // kolom
  16,       // gutter dalam point
  20,       // maksimum halaman untuk story ini
  Html);
if Remainder <> '' then
  Log('story exceeded its page budget');

Panduan praktis untuk penulis template

Rancanglah di dalam batas atas yang terdokumentasi. Sebuah flex container dengan lebih dari 256 child langsung hampir selalu adalah sebuah tabel data yang berdandan flex, dan jalur table mengukurnya lebih baik lagipula. Sebuah grid lebih besar dari 64 kali 64 adalah sebuah spreadsheet, dan saran yang sama berlaku. Untuk teks body multi-kolom, perilaku kolom dan hyphenation dijelaskan dalam hyphenation dan kolom teks seimbang mengatur bagaimana flow-nya tampak di dalam setiap kolom

Ukur sebelum Anda menggambar ketika sebuah layout harus muat. GetHTMLTextHeight melaporkan tinggi yang dibutuhkan sebuah lebar tertentu, yang merupakan cara murah untuk memutuskan antara satu layout dan yang lain sebelum berkomitmen ke tinta. Dan perlakukan string kelanjutan yang tidak kosong sebagai hal normal alih-alih pengecualian: itu adalah mekanisme yang membuat konten panjang berhalaman, bukan sinyal error

Ketika HTML berasal dari sebuah mesin laporan alih-alih dari template yang ditulis tangan, jalur yang digerakkan dataset dalam mesin laporan dataset berpadu baik dengan ini, menghasilkan markup yang kemudian diatur flex dan grid. Dan ketika konten yang sama juga harus keluar dari PDF lagi, jalur ekspor semantik dalam mengekspor PDF ke Markdown dan DOCX menutup perjalanan pulang-pergi

Layout HTML, generasi laporan, dan ekspor semantik adalah bagian dari satu library untuk Delphi, C++Builder, dan Free Pascal; daftar fitur lengkapnya ada di halaman PDF Library for Delphi