Teknik Makale

Delphi'den PDF'de Flexbox, CSS Grid ve Dipnotlar

PDF Library for Delphi, HTML'yi gerçek iki boyutlu bir yerleşimle bir PDF sayfasına render eder: display: flex ve display: grid, yığılmış bloklara indirgenmek yerine ölçülür ve yerleştirilir; dipnotlar ise referanslarını taşıyan kutunun altında, sütunlar ve sayfalar boyunca kesintisiz kalan numaralandırmayla ayrılır. Giriş noktaları tanıdık olanlardır: tek bir kutu için DrawHTMLTextBox ve çok sütunlu akış için DrawHTMLStory

Bu önemlidir, çünkü artık çoğu rapor içeriği HTML olarak geliyor. Şablonlar, CSS yazan kişiler tarafından oluşturuluyor, kontrol panelleri kartlar olarak tasarlanıyor ve bir flex satırını sessizce dört yığılmış bloğa çökerten bir render edici, tasarıma hiç benzemeyen bir belge üretiyor. Bu yetenek var olana kadar, motorun ölçtüğü tek iki boyutlu kapsayıcı tabloydu, bu yüzden her kart yerleşiminin elle bir tablo olarak yeniden yazılması gerekiyordu

Yerleşim modelinde ne değişti?

Önceki ana döngü tek bir satır kutusu tutuyor ve sayfa boyunca aşağı ilerliyordu. Bu model, satır içi içeriği ve yığılmış blokları kusursuz bir şekilde ele alır ve çocukları birbirine göre boyutlandırılan bir kapsayıcıyı ifade edemez. Kendi iki geçişli ölçümüyle tablolar tek istisnaydı

Flex ve grid, her biri bir kapsayıcının çocukları üzerinde sınırlı bir ölçüm geçişi ekler ve önemli sözcük sınırlıdır. Bir flex kapsayıcısı, sabit bir diziye en fazla 256 doğrudan çocuk ölçer. Bir grid, belirlenimci otomatik yerleştirme için en fazla 64 x 64 hücrelik bir işgal matrisi kullanır. Bu tavanlar, kötü niyetli veya üretilmiş bir stil sayfasının sınırsız özyinelemeyi veya karesel yerleştirme belleğini tetikleyememesi için vardır; bu, HTML bir müşterinin düzenlediği bir şablondan geldiğinde gerçek bir endişedir

Flex öğeleri boyutlarını nasıl alır?

Satır yönünde, kapsayıcı her öğenin tabanını (basis), büyüme ve küçülme ağırlıklarıyla birlikte toplar, ardından kalan boşluğu — pozitif veya negatif — bu ağırlıklara göre dağıtır. flex-wrap ile her satır bağımsız olarak çözülür, bu yüzden iki satıra bölünen bir satır, boş alanı tüm kapsayıcı genelinde değil satır başına atar. Sütun yönünde ise aynı ana eksen dağıtımı, ya açık bir yükseklik ya da içerik yüksekliğine karşı çalışır

justify-content, align-items, gap ve ters yönler, zaten ölçülmüş geometri üzerinde çalışır. Kutuları taşırlar; hiçbir zaman öğe içeriğinin yeniden ölçülmesini tetiklemezler. Bu ayrım, karmaşık bir kontrol panelinin çocuklarını birkaç kez ölçmesini önleyen şeydir

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;

Dönüş değeri, her HTML çizim giriş noktasının neyin sığmadığını bildirme yolu olan devam dizesidir (continuation string). Bunu bir sonraki kutuya veya bir sonraki sayfaya geçirin, akış durduğu yerden devam eder

Grid yerleştirme ve bir izin (track) ne olabileceği

Grid izleri (track), sabit uzunlukları, yüzdeleri, fr birimini, basit repeat() ifadelerini ve minmax()'ı kabul eder. Otomatik yerleştirme, işgal matrisini belirlenimci bir şekilde doldurur, bu yüzden aynı HTML her zaman aynı düzenlemeyi üretir. Açık koordinatların çakışmasına izin verilir ve bu kasıtlıdır: bir kartın üzerine bir rozet katmanlayan bir tasarım, bir hatayı değil bir amacı ifade eder. Yalnızca bir eksen açıkça verildiğinde, yerleştirme yalnızca diğer ekseni arar

Birkaç satırı kaplayan öğeler, ölçülen yüksekliklerini kapladıkları satırlara, aralarında ortalanarak geri katkıda bulunur; bu, uzun bir kaplayan öğenin, komşularını kısa bırakırken tek bir satırı sıkıştırmasını önler:

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 ve grid çocukları, ayrı bir dünya yerine özelliği kullanılabilir kılan özellik olan, diğer her şeyle aynı HTML render edici üzerinden render edilir. Yazı tipleri, CSS basamağı (cascade), bağlantılar, görüntüler, tablolar ve daha fazla iç içe geçmiş flex veya grid kapsayıcıları, bir flex öğesinin içinde tam olarak üst düzeyde davrandıkları gibi davranır ve dış yerleşim planı, tekrarlanan çizimin mevcut ölçüm önbelleğini yeniden kullanması için son metin ve dikdörtgen komutlarını kaydeder

Dipnotlar neden bir sayfalama sorunu?

Bir dipnot, referansını içeren paragraftan sonra akan bir içerik değildir; referansıyla aynı kutunun altında görünmesi gereken bir içeriktir. Bu, olağan ölçüm sırasını tersine çevirir, çünkü gövde metni için kullanılabilir alan artık henüz yerleştirilmemiş içeriğe bağlıdır

Bu yüzden render edici, referansla karşılaştığında notu ölçer ve not alanını geçerli sınırlı kutunun gövde yüksekliği bütçesinden çıkarır. Referans, o ana kadarki gövde metni ve not hepsi sığmıyorsa, dipnot işaretçisi ve ondan sonraki her şey birlikte devam dizesine geçer. Bu kural, iki klasik hatayı önleyen şeydir: gövde metninin üzerine baskı yapan bir not ve referansı önceki sayfada olan, mahsur kalmış bir not

Sınırlı bir kutuda, not alanı üstünde bir ayırıcı çizgiyle alta sabitlenir. Sabitlenecek bir kutu yüksekliğinin olmadığı sınırsız ölçümde, not alanı gövdenin hemen ardından gelir. Numaralandırma, devam yığınındaki bir uzantı alanında taşınır, bu yüzden DrawHTMLTextBox ve DrawHTMLStory, sırayı sütunlar ve sayfalar boyunca çalışır tutar ve bu alan var olmadan önce üretilmiş bir devam dizesi de yine de doğru bir şekilde devam eder

// Çok sütunlu bir story içindeki dipnotlar tek bir çalışan sıra tutar
Html := LoadTemplate('chapter.html');    // float:footnote işaretçilerini kullanır
Remainder := Lib.DrawHTMLStory(40, 40, 515, 700,
  2,        // sütunlar
  16,       // punto cinsinden oluk (gutter)
  20,       // bu story için maksimum sayfa
  Html);
if Remainder <> '' then
  Log('story exceeded its page budget');

Şablon yazarları için pratik kılavuz

Belgelenmiş tavanlar içinde tasarlayın. 256'dan fazla doğrudan çocuğu olan bir flex kapsayıcısı neredeyse her zaman flex kostümü giymiş bir veri tablosudur ve tablo yolu onu zaten daha iyi ölçer. 64 x 64'ten büyük bir grid bir elektronik tablodur ve aynı tavsiye geçerlidir. Çok sütunlu gövde metni için, tireleme ve dengelenmiş metin sütunları yazısında anlatılan sütun ve tireleme davranışı, akışın her sütunun içinde nasıl göründüğünü yönetir

Bir yerleşimin sığması gerektiğinde çizmeden önce ölçün. GetHTMLTextHeight, belirli bir genişliğin ihtiyaç duyacağı yüksekliği bildirir; bu, mürekkebi taahhüt etmeden önce bir yerleşimle diğeri arasında karar vermenin ucuz yoludur. Ve boş olmayan bir devam dizesini istisnai değil normal olarak ele alın: bu, uzun içeriğin sayfalanma mekanizmasıdır, bir hata sinyali değil

HTML, elle yazılmış şablonlardan değil bir rapor motorundan geldiğinde, veri kümesi rapor motoru yazısındaki veri kümesi odaklı yol bununla iyi bir şekilde birleşir; flex ve grid'in ardından düzenleyeceği işaretlemeyi üretir. Ve aynı içeriğin PDF'den tekrar çıkması gerektiğinde, PDF'yi Markdown ve DOCX'e dışa aktarma yazısındaki anlamsal dışa aktarma yolu gidiş-dönüşü tamamlar

HTML yerleşimi, rapor üretimi ve anlamsal dışa aktarma, Delphi, C++Builder ve Free Pascal için tek bir kütüphanenin parçasıdır; eksiksiz özellik listesi Delphi için PDF Library sayfasındadır