Teknik Makale

HotPDF'de Rowspan Izgaraları ve Yinelenen Tablo Başlıkları

HotPDF, HTML tablolarını HTML5 paged-media profili üzerinden çizer: rowspan ve colspan için gerçek bir doluluk ızgarası, karakter sayma tahmini yerine ölçülmüş satır yükseklikleri ve her devam sayfasında yinelenen başlık satırları kullanır. İki durumda başlık yinelemeyi reddeder ve bunları baştan bilmek, sonra mükerrer bir hücreyi ayıklamaktan daha ucuzdur

Bunu dayatan belge sınıfı, her raporlama ekibinin er ya da son çıkardığı belgedir: kaynağın gerçeği HTML olan, tablonun dört sayfa boyunca aktığı ve başlığın her sayfada okunabilir olması gereken bir fatura ya da uyumluluk raporu. Gerçek bir tablo yerleşiminden aşağısı, okuyucunun hemen fark ettiği iki hatayı üretir: birinci sayfada bir kez görünen başlık ve karakter sayısından tahmin edilmiş satır yükseklikleri

Tablo yeteneği HTML renderer'a neden taşındı?

Çünkü alternatif zengin metni kaybeder ve içerik baştan zaten HTML'dir, onun nedeni de zengin metindir. Bariz plan yeniden kullanım gibi görünür: HotPDF'in kuralı dolu bir ızgarası olan layout DOM tablo nesnesi zaten vardır; HTML çözümleyicisini ona köprüleyin ve kapsamı bedavaya alın. Sorun, o tablo nesnesinin neyle çizdiğidir. Hücreleri metin ve stil taşır, çizim yolu düz metin çıktısı üretir; böylece HTML'in font ve renk ötesinde gerçekten içerdiği her şey, linkler, üst simgeler, satır içi boyut değişimleri, run başına renk, sayfaya ulaştığında kaybolmuştur

Gerçek belgelerle temasta hayatta kalan yön, tersidir. Tablo motorunun yeteneklerini (doluluk ızgarası, gerçek ölçüm, başlık yinelemesi ve sütun ağırlıklandırması) HTML renderer'a taşıyın ve zengin metin render'ını zaten çalıştığı yerde bırakın. Bu, köprüden daha büyük bir değişikliktir ve tablo hücresi içindeki bir hyperlink'i hyperlink yapan da bu değişikliktir

Union-find olmadan rowspan

Kapsayan hücreler atomik satır grupları oluşturur ama bu gruplar üzerindeki kapanış, genel bir disjoint-set yapısına ihtiyaç duymaz; çünkü doluluk her zaman bitişik bir aralıktır. K satırında başlayan rowspan="3" hücresi, K'dan K+2'ye kadarki satırları kaplar, başka hiçbir şeyi; böylece grup bilgisi satır başı bir bitiş işaretine indirgenir

Algoritma iki satırlık bir niyettir. K'da başlayıp E'de biten kapsayan bir hücre yerleştirirken GroupEnd[K] := Max(GroupEnd[K], E) kaydedin. Sonra satırları bir kez ters yönde yürüyüp G[R] := G[G[R]] uygulayın; bu, her satır bitişini üst üste binen span'lar üzerinden geriye yayar ve geçişli kapanışı tek geçişte verir. Elde ettiğiniz şey, her satır için onunla aynı sayfada kalmak zorunda olan son satırdır; sayfalama adımının bir kırılımın nereye düşebileceğine karar vermek için ihtiyacı olan da tam olarak budur

Yüksekliği dağıtmak öbür yarısıdır. Kapsayan bir hücre, kapladığı satırların şu an sağladığından daha fazla dikey alana ihtiyaç duyduğunda fazlalık, satırlara eşit yayılmaz; span'ın son satırına gider. Sıradan satır yükseklikleri belirlendikten sonra kapsayan hücreleri işleyin, ardından her span'ın son satırını tamamlayın. Fazlalığı eşit dağıtmak daha adil görünür ve bariz biçimde yanlış çıktı üretir: yalnızca kısa tek satırlık hücreler içeren satırlar, üç satır yukarıdaki alakasız bir hücre tesadüfen uzun olduğu için şişer

Satır 2'de başlayan rowspan 3'lü bir hücrenin satır 2 ile 4'ü tek bir atomik dikdörtgen olarak kapladığı HotPDF HTML tablo ızgarası; yanında tek bir ters yürüyüşün ürettiği, satır 2, 3 ve 4'ü aynı sayfaya bağlayan satır başı grup bitiş G değerleri
Kapsayan doluluk her zaman bitişik bir aralıktır; bu yüzden satır başı bitiş işaretleri ve tek bir ters yürüyüş union-find yerine geçer ve sayfalamanın bir kırılımın nereye düşebileceğini tam olarak bilmesini sağlar
var
  Pdf: THotPDF;
  Importer: THPDFHTMLImporter;
  Stats: THPDFHTMLImportStatistics;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'audit-report.pdf';
    Pdf.BeginDoc;
    Importer := THPDFHTMLImporter.Create(Pdf);
    try
      Importer.Margin := 48;
      Importer.BaseFontName := 'Arial';
      Importer.BaseFontSize := 10;
      Importer.MaxDOMNodes := 200000;
      Importer.MaxLayoutOperations := 2000000;
      if Importer.RenderHTML5(SourceHtml, PrintStyleSheet) then
      begin
        Stats := Importer.Statistics;
        Writeln('tables ', Stats.TableCount,
                '  page breaks ', Stats.PageBreakCount);
      end;
    finally
      Importer.Free;
    end;
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

RenderHTML5, ikinci argüman olarak isteğe bağlı bir author style sheet alır; baskı kuralları oraya aittir. Ekran style sheet'ini dışarıda tutun. Profil sürümlüdür ve HTML5ProfileMilestones, güncel derlemenin hangi yetenek gruplarını uyguladığını bildirir: himParserCascade, himPagedLayout, himTablesForms ve himBoundedResources; böylece bir uygulama, boşluğu üretimde keşfetmek yerine bilinçli biçimde düşük moda geçebilir

Ölçüm, çizimle tam olarak örtüşmek zorundadır

Satır yüksekliği, ancak kıvrılan satırları ölçen kod, onları çizen kodla aynı kuralla kıvırdığında doğrudur. Bariz gibi görünür ve kenarlıkları içeriğiyle hizalanmayan tabloların tek en yaygın kaynağıdır. HotPDF açgözlü bir satır sayacıyla ölçer ve o sayacın, zengin metin çıktı yolunun kıvırma semantiğiyle üç özgül noktada eşleşmesi gerekir: yalnızca boşluklarda kırar, hiçbir zaman bir kelimeyi bölmez ve sütundan geniş bir kelime kendi başına bir satır alır

İkinci gereklilik fonttur. Ölçüm, hücrenin kendi fontuyla çalışmalıdır: genişlik işlevini çağırmadan önce SetFont ile gerçek ad, stil seti ve boyut ayarlanmış olarak; aktif tesadüf eden hangi fontla değil. Bold metin, aynı boyutta regular'dan rutin olarak yüzde ondan fazla geniştir ve üç satırlık hücreyi dört satırlık hücreye çevirmeye yeter. Başlık hücreleri bold, gövde hücreleri bold olmayan bir tablo tek fontla ölçülürse, tam olarak okuyucunun ilk baktığı satırlarda yanlış olur

Bunu doğru yapmak, testte neyi doğrulayabileceğinizi değiştirir. Hassas ölçümün gözlenebilir etkisi satır aralığıdır, glif sayıları değil: tek satırlık bir satır yaklaşık 20 punto yüksekliğindedir, aynı içerik için karakter sayma tahmini ise iki satır ve kabaca 35 öngörür. Satırlar arasındaki dikey mesafeye doğrulama kurun. Ve PDF kullanıcı uzayında Y'nin yukarı doğru arttığını unutmayın; gövde satırının üstünde duran bir başlık, başlık Y değerinin daha büyük olduğu anlamına gelir, bu da ekran koordinatı içgüdüsünün yazacağıyla terstir

HotPDF bir başlığı yinelemeyi ne zaman reddeder?

İki durumda; her ikisinde de devam edilseydi bariz biçimde yanlış çıktı oluşurdu. Birincisi, başlıktan geçip gövde satırlarına uzanan kapsayan bir hücre içeren başlık bloğudur. Başlığı yinelemek, o hücre içeriğini artık ait olmadığı konumda ikinci kez çizerdi; bu yüzden başlık bir kez çizilir ve tablo onsuz devam eder. İkincisi, kullanılabilir sayfa yüksekliğinin yüzde 90'ından uzun bir başlıktır; yineleme veriye neredeyse hiç yer bırakmazdı ve tablo ilerleme kaydetmezdi

HTML tablo başlıklarının sayfa kırılımlarında yinelenmesi için HotPDF karar akışı: rowspan'ı gövde satırlarına taşan bir başlık bir kez çizilir, kullanılabilir sayfa yüksekliğinin yüzde 90ından uzun bir başlık bir kez çizilir ve diğer her başlık her devam sayfasında yinelenir
İki red kasıtlıdır: gövdede kapsayan hücresi olan ya da sayfanın çoğunu dolduran bir başlığı yinelemek, içeriği artık ait olmadığı yere çizer ya da veriye yer bırakmaz

Her iki red de tasarım gereği kasıtlı ve sessizdir; çünkü alternatif daha kötüdür. Başlığınız yinelenmiyorsa ve yinelenmesini bekliyorsanız, motordan kuşkulanmadan önce markup'ta thead sınırını geçen bir rowspan olup olmadığına bakın. Sürprizlerin çoğunu tek başına o markup kalıbı açıklar

// Sütun ağırlıkları markup'tan gelir; onları kontrol edecek yer baskı
// style sheet'idir. Genişlikler piksel değil ağırlık olarak ele alınır
const
  PrintStyleSheet =
    'table { width: 100%; }' +
    'thead th { font-weight: bold; background: #eee; }' +
    'td.amount { text-align: right; }';

// Gövdeye taşan rowspan taşıyan bir başlık satırı, başlık yinelemesini
// bastırır. Span'ları tek bölüm içinde tutun:
//   <thead><tr><th rowspan="2">Item</th>...</tr></thead>  tamam
//   <tr><th rowspan="3">Item</th>...  tbody'ye taşıyor, yineleme yok

Sütun genişlikleri mutlak ölçü yerine ağırlık gibi davranır; içerik yazarın tahminiyle örtüşmediğinde tabloyu kullanılabilir tutan davranış budur. Yüzde 30 ilan edilmiş bir sütun, kullanılabilir genişliğin kabaca yüzde 30'unu alır ama dağıtım, her sütunun gerçekten ihtiyaç duyduğu asgari genişliğe saygı duyar; böylece bölünemeyen uzun bir token taşıyan dar bir sütun, tablo kutusunu sessizce taşmaz

Bunlar bir belge işlem hattında nereye oturur?

Tablo işi, daha geniş paged-media profilinin içinde durur ve HTML5 paged-media içe aktarım yolu makalesinde anlatılan sayfalama kuralları, kaynak bütçeleri ve CSS idaresi, tablo içeren belgeler için de değişmeden geçerlidir. Veriniz HTML olarak başlamıyorsa, tabloları doğrudan PDF'e kurma makalesindeki doğrudan inşa yolu çözümleme katmanını tümüyle atlar ve aynı ızgara davranışını bir API üzerinden verir. Satır yüksekliği sonuç olarak satırların nerede kırıldığına bağlı olduğu için, metin iki yana hizalama ve satır kırma makalesindeki ölçüm tartışması, yoğun tabular çıktı ayarlayan herkesin yardımcı metnidir

Buradaki yeniden kullanılabilir ders aslında tablolarla ilgili değil. Yeni bir alt sistemin, eski bir alt sistemin zaten sahip olduğu bir yeteneğe ihtiyacı olduğunda, ikisinden hangisinin yeniden yazılması en zor olan şeye sahip olduğunu sorun. Izgara aritmetiği birkaç düzine satırdır ve kolay taşınır. Satır içi linkler, üst simgeler ve run başına stil taşıyan zengin metin render'ı değildir; bu yüzden ızgara taşındı, metin yerinde kaldı. HotPDF her iki yolu da HotPDF Delphi PDF bileşeni kapsamında sunar; böylece HTML girdisi ile doğrudan inşa arasındaki seçim, kütüphane değil proje kararıdır