Odborný článok

Mriežky rowspan a opakované hlavičky tabuliek v HotPDF

HotPDF vykresľuje HTML tabuľky cez svoj HTML5 paged-media profil s použitím skutočnej mriežky obsadenia pre rowspan a colspan, nameraných výšok riadkov namiesto odhadov z počtu znakov a hlavičkových riadkov opakovaných na každej pokračovacej strane. Dve situácie ho prinútia odmietnuť opakovanie hlavičky a poznať ich dopredu je lacnejšie než neskôr ladiť zdvojenú bunku

Trieda dokumentov, ktorá to vynúti, je tá, ktorú nakoniec vydá každý reportingový tím: faktúra alebo súladová správa, kde zdrojom pravdy je HTML, tabuľka beží cez štyri strany a hlavička musí byť čitateľná na každej z nich. Čokoľvek menej než skutočný tabuľkový layout vyprodukuje dve zlyhania, ktoré čitatelia všimnú okamžite: hlavičku, ktorá sa objaví raz na prvej strane, a riadky, ktorých výšky boli uhádnuté z počtov znakov

Prečo sa schopnosť tabuliek presunula do HTML renderera?

Pretože alternatíva stráca rich text a rich text je presne ten dôvod, prečo je obsah vôbec HTML. Zjavný plán vyzerá ako recyklácia: HotPDF už má tabuľkový objekt layout DOMu s poriadnou mriežkou, takže stačí premostiť HTML parser do neho a spanning dostanete zadarmo. Problém je v tom, čím ten tabuľkový objekt kreslí. Jeho bunky nesú text a štýl a jeho kresliaca cesta vydáva čistý textový výstup, takže všetko, čo HTML reálne obsahovalo nad rámec fontu a farby, odkazy, horné indexy, zmeny veľkosti v riadku, farbu po častiach, je preč, kým sa to dostane na stranu

Smer, ktorý prežije kontakt so skutočnými dokumentmi, je opačný. Premiestnite schopnosti tabuľkového enginu, mriežku obsadenia, skutočné meranie, opakovanie hlavičiek a váhovanie stĺpcov, do HTML renderera a nechajte vykresľovanie rich textu tam, kde už funguje. To je väčšia zmena než most a je to zmena, vďaka ktorej hyperlink vo vnútri tabuľkovej bunky zostáva hyperlinkom

Rowspan bez union-find

Spanujúce bunky tvoria atomické skupiny riadkov, ale uzáver nad týmito skupinami nepotrebuje všeobecnú štruktúru disjunktných množín, pretože obsadenie je vždy súvislý interval. Bunka s rowspan="3" začínajúca na riadku K obsadzuje riadky K až K+2 a nič iné, takže informácia o skupine sa redukuje na koncovú značku pre každý riadok

Algoritmus sú dva riadky úmyslu. Keď umiestnite spanujúcu bunku, ktorá začína na K a končí na E, zaznamenajte GroupEnd[K] := Max(GroupEnd[K], E). Potom prejdite riadky raz odzadu a aplikujte G[R] := G[G[R]], čo propaguje každý koniec riadku dozadu cez prekrývajúce sa spanu a dáva tranzitívny uzáver v jednom prechode. Výsledkom je, že pre každý riadok poznáte posledný riadok, ktorý musí zostať na tej istej strane ako on, a presne to potrebuje krok stránkovania na rozhodnutie, kde môže padnúť zlom

Rozdeľovanie výšky je druhá polovica. Keď spanujúca bunka potrebuje viac zvislého priestoru, než jej riadky aktuálne dávajú, prebytok ide do posledného riadku spanu, nerozdeľuje sa rovnomerne. Spracujte spanujúce bunky až po tom, čo sa urobia obyčajné výšky riadkov, a potom doplňte posledný riadok každého spanu. Rovnomerné rozdelenie prebytku sa javí spravodlivejšie a produkuje viditeľne zlý výstup: riadky obsahujúce len krátke jedoriadkové bunky nafúknu, pretože nejaká nesúvisiaca bunka tri riadky vyššie bola náhodou vysoká

Mriežka HTML tabuľky HotPDF, kde jedna bunka s rowspan 3 začínajúca na riadku 2 obsadzuje riadky 2 až 4 ako jediný atomický obdĺžnik, vedľa hodnôt koncov skupín G[R] pre jednotlivé riadky vyrobených jedným spätným prechodom ukazujúcim riadky 2, 3 a 4 viazané na tú istú stranu
Spanujúce obsadenie je vždy súvislý interval, takže koncové značky po riadkoch a jeden spätný prechod nahrádzajú union-find a hovoria stránkovaniu presne, kde môže padnúť zlom
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 berie voliteľný autorový style sheet ako druhý argument a presne tam patria pravidlá pre tlač. Screen style sheet nechajte mimo. Profil je verzovaný a HTML5ProfileMilestones hlási, ktoré skupiny schopností aktuálny build implementuje: himParserCascade, himPagedLayout, himTablesForms a himBoundedResources, takže aplikácia môže degradovať zámerne namiesto toho, aby diery objavila vo výrobe

Meranie sa musí presne zhodovať s kreslením

Výška riadku je správna len vtedy, keď kód merajúci zalamované riadky zalamuje podľa toho istého pravidla ako kód, ktorý ich kreslí. Znie to zjavne a je to jediný najčastejší zdroj tabuliek, ktorých okraje nelignujú s obsahom. HotPDF meria chamtivým počítadlom riadkov a toto počítadlo sa musí zhodovať so sémantikou zalamovania rich-text výstupnej cesty v troch konkrétnych ohľadoch: láme len na medzerách, nikdy nerozdelí slovo a slovo širšie než stĺpec dostane vlastný riadok

Druhou požiadavkou je font. Meranie musí bežať s vlastným fontom bunky, nastaveným cez SetFont so skutočným menom, sadou štýlov a veľkosťou pred volaním šírkovej funkcie, nie s tým fontom, ktorý sa práve náhodou aktivoval. Tučný text býva pri rovnakej veľkosti pravidelne viac než desať percent širší než obyčajný, čo stačí na to, aby trojriadková bunka bola štvoriadková. Tabuľka, kde sú hlavičkové bunky tučné a telové nie, meraná jedným fontom, bude zlá presne v riadkoch, na ktoré sa čitatelia pozerajú prvé

Urobiť to správne mení to, čo môžete v teste tvrdiť. Pozorovateľný efekt presného merania je riadkovanie, nie počty glyfov: jedoriadkový riadok je vysoký približne 20 bodov, kým odhad z počtu znakov toho istého obsahu predpovedá dva riadky a zhruba 35. Tvrďte zvislú vzdialenosť medzi riadkami. A pamätajte, že PDF user space má Y rastúce nahor, takže hlavička sediaca nad telovým riadkom znamená, že Y hodnota hlavičky je tá väčšia, čo je opak toho, čo napíše inštinkt z obrazovkových súradníc

Kedy HotPDF odmietne hlavičku opakovať?

V dvoch prípadoch, z ktorých oba by pri pokračovaní vyprodukovali viditeľne zlý výstup. Prvým je blok hlavičky obsahujúci spanujúcu bunku, ktorá presahuje za hlavičku do telových riadkov. Opakovanie hlavičky by nakreslilo obsah tej bunky druhýkrát na mieste, kam už nepatrí, takže hlavička sa nakreslí raz a tabuľka pokračuje bez nej. Druhým je hlavička vyššia než 90 percent použiteľnej výšky strany, kde by opakovanie nechalo takmer žiadny priestor pre dáta a tabuľka by nepostúpila

Rozhodovací tok HotPDF pre opakovanie hlavičiek HTML tabuliek cez zlomy strán: hlavička, ktorej rowspan prekračuje do telových riadkov, sa nakreslí raz, hlavička vyššia než 90 percent použiteľnej výšky strany sa nakreslí raz a každá ďalšia hlavička sa opakuje na každej pokračovacej strane
Odmietnutia sú dve a zámerné: opakovanie hlavičky, ktorá vlastní spanujúcu telovú bunku alebo vyplní väčšinu strany, by nakreslilo obsah tam, kam už nepatrí, alebo by nechalo žiadny priestor pre dáta

Oba odmietnutia sú zámerné a podľa návrhu tiché, pretože alternatíva je horšia. Ak sa vaša hlavička neopakuje, hoci ste čakali, že sa bude, skontrolujte v markupu rowspan prekračujúci hranicu thead, skôr než začnete podozrievať engine. Tento jediný markup vzor vysvetľuje väčšinu prekvapení

// Váhy stĺpcov pochádzajú z markupu, takže print style sheet je miesto,
// kde sa dajú riadiť. Šírky sa berú ako váhy, nie ako pixely
const
  PrintStyleSheet =
    'table { width: 100%; }' +
    'thead th { font-weight: bold; background: #eee; }' +
    'td.amount { text-align: right; }';

// Hlavičkový riadok nesúci rowspan prekračujúci do tela potlačí
// opakovanie hlavičky. Držte spanu v jednej sekcii:
//   <thead><tr><th rowspan="2">Item</th>...</tr></thead>  ok
//   <tr><th rowspan="3">Item</th>...  span do tbody, bez opakovania

Šírky stĺpcov sa správajú ako váhy, nie ako absolútné merania, a to je správanie, ktoré udrží tabuľku použiteľnú, keď obsah nesedí s odhadom autora. Stĺpec deklarovaný na 30 percent dostane zhruba 30 percent dostupnej šírky, ale rozdeľovanie rešpektuje minimálnu šírku, ktorú každý stĺpec skutočne potrebuje, takže úzky stĺpec držiaci dlhý nezalomiteľný token nepretečie poticho tabuľkový box

Kam sa to celé hodí do dokumentového pipeline

Tabuľková práca sedí vo vnútri širšieho paged-media profilu a pravidlá stránkovania, rozpočty resources a spracovanie CSS popísané v článku importná cesta HTML5 paged-media platia nezmenene aj pre dokumenty obsahujúce tabuľky. Ak vaše dáta nezačínajú ako HTML, cesta priamej konštrukcie v článku tabuľky stavite priamo do PDF obíde parsing vrstvu celkom a dá vám rovnaké mriežkové správanie cez API. A keďže výška riadku v konečnom dôsledku závisí od toho, kde sa riadky lámú, diskusia o meraní v článku zarovnávanie textu a lámanie riadkov je sprievodný kúsok pre každého, kto ladí hustý tabuľkový výstup

Znovu použiteľné ponaučenie odtiaľ sa vôbec netýka tabuliek. Keď nový subsystém potrebuje schopnosť, ktorú starý subsystém už má, spýtajte sa, ktorý z dvoch vlastní vec, ktorá sa najťažšie reimplementuje. Mriežková aritmetika je pár desiatok riadkov a presťahuje sa ľahko. Vykresľovanie rich textu s inline odkazmi, hornými indexmi a štýlovaním po častiach nie, takže sa presťahovala mriežka a text zostal. HotPDF dodáva obe cesty ako súčasť HotPDF Delphi PDF komponentu, takže voľba medzi HTML vstupom a priamou konštrukciou je rozhodnutie projektu, nie knižnice