HotPDF renderer HTML-tabeller gennem sin HTML5 paged-media-profil med et rigtigt occupancy grid til rowspan og colspan, målte rækkehøjder i stedet for tegnestimat og header-rækker gentaget på hver fortsættelsesside. To situationer får den til at nægte at gentage en header, og at kende dem på forhånd er billigere end senere at debugge en duplikeret celle
Den dokumentklasse, der tvinger dette frem, er den, hvert rapporteringsteam før eller siden shipper: en faktura eller en compliance-rapport, hvor sandhedskilden er HTML, tabellen løber over fire sider, og headeren skal være læsbar på hver af dem. Alt mindre end et rigtigt table layout producerer de to fejl, som læsere lægger mærke til med det samme, en header, der optræder én gang på side ét, og rækker, hvis højder blev gættet ud fra tegnantal
Hvorfor flyttede tabel-funktionaliteten ind i HTML-rendereren?
Fordi alternativet mister rich text, og rich text er grunden til, at indholdet overhovedet er HTML. Den åbenlyse plan ligner genbrug: HotPDF har allerede et layout DOM table-objekt med et ordentligt grid, så bro HTML-parseren ind i det, og få spanning gratis. Problemet er, hvad det table-objekt tegner med. Dets celler bærer tekst og en style, og dets tegnevej emitterer plain text-output, så alt det, HTML'en faktisk indeholdt ud over en font og en farve, links, superscripts, inline-størrelsesændringer, per-run-farve, er væk, inden det når siden
Den retning, der overlever kontakt med rigtige dokumenter, er den omvendte. Flyt tabelmotorens funktioner, occupancy grid, rigtig måling, header-repetition og kolonnevægtning, ind i HTML-rendereren, og lad rich text-rendering ligge, hvor den allerede virker. Det er en større ændring end broen, og det er ændringen, der holder et hyperlink inde i en tabelcelle et hyperlink
Rowspan uden union-find
Spanning-celler skaber atomære rækkegrupper, men closure over de grupper behøver ingen generel disjoint-set-struktur, for occupancy er altid et sammenhængende interval. En celle med rowspan="3", der starter ved række K, optager rækkerne K til K+2 og intet andet, så gruppeinformationen reduceres til en pr.-række slutmarkør
Algoritmen er to linjer intention. Når du placerer en spanning-celle, der starter ved K og slutter ved E, notér GroupEnd[K] := Max(GroupEnd[K], E). Gå derefter rækkerne igennem én gang baglæns og anvend G[R] := G[G[R]], som propagerer hver rækkeslutning baglæns gennem overlappende spans og giver den transitivt lukkede i ét gennemløb. Det, du får, er for hver række den sidste række, der må forblive på samme side som den, hvilket præcis er, hvad pagination-trinnet behøver for at afgøre, hvor et sideskift må falde
Højdefordeling er den anden halvdel. Når en spanning-celle behøver mere lodret plads, end de rækker, den dækker, i øjeblikket giver, går overskuddet til spanlets sidste række, ikke fordelt jævnt på dem. Behandl spanning-celler, efter de almindelige rækkehøjder er på plads, og top så op på hver spanlets sidste række. At fordele overskuddet jævnt virker fairere og giver synligt forkert output: rækker, der kun indeholder korte celler på én linje, blæses op, fordi en eller anden irrelevant celle tre rækker oppe tilfældigvis var høj
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 tager et valgfrit author style sheet som sit andet argument, og dét er, hvor print-reglerne hører hjemme. Hold screen style sheetet ude af det. Profilen er versioneret, og HTML5ProfileMilestones melder, hvilke capability-grupper den aktuelle build implementerer, himParserCascade, himPagedLayout, himTablesForms og himBoundedResources, så en applikation kan degradere bevidst i stedet for at opdage et hul i produktion
Måling og tegning skal stemme eksakt overens
Rækkehøjde er kun korrekt, når koden, der måller wrappede linjer, wrapper dem efter samme regel som koden, der tegner dem. Det lyder åbenlyst og er den mest almindelige enkeltkilde til tabeller, hvis rammer ikke stemmer overens med deres indhold. HotPDF måller med en grådig linjetæller, og den tæller skal matche wrapping-semantikken i rich text-output-vejen i tre specifikke henseender: den bryder kun ved mellemrum, den splitter aldrig et ord, og et ord bredere end kolonnen får en linje for sig selv
Det andet krav er fonten. Måling skal køre med cellens egen font, sat gennem SetFont med det faktiske navn, style-sæt og størrelse før kaldet af breddefunktionen, ikke med hvilken som helst font, der tilfældigvis var aktiv. Fed tekst er rutinemæssigt mere end ti procent bredere end regular ved samme størrelse, hvilket er nok til at forvandle en celle på tre linjer til en på fire. En tabel, hvor header-celler er fede og body-celler ikke er, målt med én font, bliver forkert i præcis de rækker, læsere kigger på først
At få det her rigtigt ændrer, hvad du kan asserte i en test. Den observerbare effekt af præcis måling er linjeafstand, ikke glyfantal: en række på én linje er omkring 20 point høj, mens et tegnestimat for samme indhold forudsiger to linjer og cirka 35. Assert på den lodrette afstand mellem rækkerne. Og husk, at PDF user space har Y stigende opad, så en header, der sidder over en body-række, betyder, at headerens Y-værdi er den større, hvilket er det modsatte af, hvad skærmkoordinat-instinktet skriver
Hvornår nægter HotPDF at gentage en header?
I to tilfælde, som begge ville give synligt forkert output, hvis den gik videre. Det første er en headerblok, der indeholder en spanning-celle, der strækker sig forbi headeren ned i body-rækker. At gentage headeren ville tegne det celleindhold en anden gang på et sted, hvor det ikke længere hører hjemme, så headeren tegnes én gang, og tabellen fortsætter uden den. Det andet er en header højere end 90 procent af den brugbare sidehøjde, hvor repetition ville efterlade næsten ingen plads til data, og tabellen ikke ville komme videre
Begge nægtelser er bevidste og stille af design, for alternativet er værre. Gentager din header sig ikke, og du forventede det, så tjek markuppen for en rowspan, der krydser thead-grænsen, inden du mistænker motoren. Det ene markupmønster står for det meste af overraskelsen
// Kolonnevægte kommer fra markuppen, så print style sheetet er
// stedet at styre dem. Bredde behandles som vægte, ikke som pixels
const
PrintStyleSheet =
'table { width: 100%; }' +
'thead th { font-weight: bold; background: #eee; }' +
'td.amount { text-align: right; }';
// En header-række med en rowspan, der krydser ned i body, undertrykker
// header-repetition. Hold spans inde i én sektion:
// <thead><tr><th rowspan="2">Item</th>...</tr></thead> ok
// <tr><th rowspan="3">Item</th>... spanning ned i tbody, ingen repetition
Kolonnebredder opfører sig som vægte snarere end absolutte mål, hvilket er den adfærd, der holder en tabel brugbar, når indholdet ikke matcher forfatterens estimat. En kolonne deklareret til 30 procent får omtrent 30 procent af den tilgængelige bredde, men fordelingen respekterer den minimale bredde, hver kolonne faktisk behøver, så en smal kolonne med et langt udeligt token flyder ikke lydløst ud over tabelboksen
Hvor det passer ind i en dokumentpipeline
Tabelarbejdet sidder inde i den bredere paged-media-profil, og de pagination-regler, resource budgets og CSS-håndtering, der er beskrevet i HTML5 paged-media-importvejen, gælder uændret for dokumenter, der indeholder tabeller. Starter dine data ikke som HTML, undgår den direkte konstruktionsvej i at bygge tabeller direkte ind i en PDF helt parsinglaget og giver dig samme grid-adfærd gennem en API. Og fordi rækkehøjde i sidste ende afhænger af, hvor linjer bryder, er målingsdiskussionen i tekstjustering og linjebrydning følgestrykket for alle, der tuner tæt tabeloutput
Den genanvendelige lektie her handler slet ikke om tabeller. Når et nyt subsystem behøver en funktion, et gammelt subsystem allerede har, så spørg, hvilken af de to ejer den ting, der er sværest at reimplementere. Grid-aritmetik er et par dusin linjer og flytter let. Rich text-rendering med inline links, superscripts og per-run-styling er det ikke, så gridtet flyttede, og teksten blev. HotPDF shipper begge veje som del af HotPDF Delphi PDF-komponenten, så valget mellem HTML-input og direkte konstruktion er et projektvalg snarere end et biblioteksvalg