HotPDF rendrer HTML-tabeller gjennom HTML5-profilen sin for paged media, med et ekte belegg-rutenett for rowspan og colspan, målte radhøyder i stedet for tegnantallsanslag, og headerrader gjentatt på hver fortsettelsesside. To situasjoner gjør at den takker nei til å gjenta en header, og å kjenne dem på forhånd er billigere enn å feilsøke en duplisert celle senere
Dokumentklassen som tvinger dette frem, er den hvert rapporteringsteam til slutt leverer: en faktura eller en compliance-rapport der sannhetskilden er HTML, tabellen løper over fire sider, og headeren må være lesbar på hver eneste av dem. Alt mindre enn et ekte tabelloppsett gir de to feilene lesere legger merke til umiddelbart: en header som dukker opp én gang på side én, og rader hvis høyder ble gjettet ut fra tegnantall
Hvorfor flyttet tabellfunksjonaliteten inn i HTML-rendereren?
Fordi alternativet mister rik tekst, og rik tekst er grunnen til at innholdet er HTML i det hele tatt. Den åpenbare planen ser ut som gjenbruk: HotPDF har allerede et tabell-objekt i layout-DOM-en med et skikkelig rutenett, så bygg bro fra HTML-parseren inn i det, og spanning kommer gratis. Problemet er hva det tabell-objektet tegner med. Cellene bærer tekst og en stil, og tegneveien produserer ren tekstutdata, så alt HTML-en faktisk inneholdt utover en skrift og en farge, lenker, hevet skrift, størrelsesendringer inne i linjen, farge per tekstsegment, er borte i det den når siden
Retningen som overlever kontakt med ekte dokumenter, er den motsatte. Flytt funksjonaliteten i tabellmotoren, belegg-rutenettet, ekte måling, headergjentakelse og kolonnevektning, inn i HTML-rendereren, og la riktekst-rendringen være der den allerede fungerer. Det er en større endring enn broen, og det er endringen som holder en hyperlenke inne i en tabellcelle en hyperlenke
Rowspan uten union-find
Celler som spenner over rader, skaper atomære radgrupper, men lukkingen over disse gruppene trenger ingen generell disjoint-set-struktur, fordi belegget alltid er et sammenhengende intervall. En celle med rowspan="3" som starter på rad K, opptar rad K til K+2 og ingenting annet, så gruppeinformasjonen reduseres til ett sluttmerke per rad
Algoritmen er to linjer med intensjon. Når du plasserer en spennende celle som starter på K og slutter på E, registrer GroupEnd[K] := Max(GroupEnd[K], E). Gå så gjennom radene én gang baklengs og bruk G[R] := G[G[R]], som propagerer hver radslutt bakover gjennom overlappende spenn og gir den transitive lukkingen i ett gjennomløp. Det du får, er for hver rad den siste raden som må bli på samme side som den, noe som er nøyaktig det pagineringssteget trenger for å bestemme hvor et brudd kan falle
Å fordele høyden er den andre halvdelen. Når en spennende celle trenger mer vertikal plass enn radene den dekker for øyeblikket gir, går overskuddet til siste rad i spannet, ikke fordelt jevnt over dem. Behandle spennende celler etter at ordinære radhøyder er på plass, og fyll så opp siste rad i hvert spenn. Å spre overskuddet jevnt virker rettferdigere og gir synlig feil utdata: rader som bare inneholder korte celler med én linje, blåses opp fordi en eller annen urelatert celle tre rader opp tilfeldigvis var høy
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 tar et valgfritt forfatter-stilark som sitt andre argument, og det er der utskriftsreglene hører hjemme. Hold skjerm-stilarket utenfor. Profilen er versjonert, og HTML5ProfileMilestones rapporterer hvilke funksjonsgrupper gjeldende bygg implementerer, himParserCascade, himPagedLayout, himTablesForms og himBoundedResources, slik at en applikasjon kan degradere bevisst i stedet for å oppdage et hull i produksjon
Måling må være helt enig med tegning
Radhøyde er bare korrekt når koden som måler ombrutte linjer, bryter dem etter samme regel som koden som tegner dem. Dette høres åpenbart ut og er den aller vanligste kilden til tabeller der kantene ikke stemmer med innholdet. HotPDF måler med en grådig linjeteller, og den telleren må matche ombruddssemantikken til riktekst-utdatabanen i tre bestemte henseender: den bryter bare ved mellomrom, den splitter aldri et ord, og et ord bredere enn kolonnen får en linje for seg
Det andre kravet er skriften. Måling må kjøre med cellens egen skrift, satt gjennom SetFont med det faktiske navnet, stilen og størrelsen før breddefunksjonen kalles, ikke med uansett hvilken skrift som tilfeldigvis var aktiv. Fet tekst er rutinemessig mer enn ti prosent bredere enn vanlig ved samme størrelse, noe som er nok til å forvandle en celle med tre linjer til en med fire. En tabell der headerceller er fete og brødtekstceller ikke er det, målt med én skrift, tar feil i nøyaktig de radene leserne ser på først
Å få dette riktig endrer hva du kan påstå i en test. Det observerbare utbyttet av nøyaktig måling er linjeavstand, ikke glyf-antall: en rad med én linje er rundt 20 punkter høy, mens et tegnantallsanslag av samme innhold forutsier to linjer og rundt 35. Test på den vertikale avstanden mellom rader. Og husk at PDFs brukerrom har Y økende oppover, så en header som sitter over en brødtekstrad, betyr at headerens Y-verdi er den større, noe som er motsatt av hva skjermkoordinat-instinktet skriver
Når nekter HotPDF å gjenta en header?
I to tilfeller, som begge ville gitt synlig feil utdata hvis den hadde kjørt videre. Det første er en headerblokk som inneholder en spennende celle som strekker seg forbi headeren og ned i brødtekstradene. Å gjenta headeren ville tegne det celleinnholdet en gang til på et sted det ikke lenger hører hjemme, så headeren tegnes én gang, og tabellen fortsetter uten den. Det andre er en header høyere enn 90 prosent av brukbar sidehøyde, der gjentakelse ville etterlate nesten ingen plass til data, og tabellen ikke ville kommet noe fremover
Begge nektelsene er bevisste og stille ved design, fordi alternativet er verre. Hvis headeren din ikke gjentas og du forventet det, sjekk oppmerkingen for en rowspan som krysser thead-grensen før du mistenker motoren. Det enkelte oppmerkningsmønsteret står for det meste av overraskelsen
// Kolonnevekter kommer fra oppmerkingen, så utskrifts-stilarket er
// stedet å styre dem. Bredder behandles som vekter, ikke som piksler
const
PrintStyleSheet =
'table { width: 100%; }' +
'thead th { font-weight: bold; background: #eee; }' +
'td.amount { text-align: right; }';
// En headerrad som bærer en rowspan som krysser inn i brødteksten,
// undertrykker headergjentakelse. Hold spennene innenfor én seksjon:
// <thead><tr><th rowspan="2">Item</th>...</tr></thead> ok
// <tr><th rowspan="3">Item</th>... spenner inn i tbody, ingen gjentakelse
Kolonnebredder oppfører seg som vekter snarere enn absolutte mål, noe som er atferden som holder en tabell brukbar når innholdet ikke matcher forfatterens anslag. En kolonne deklarert på 30 prosent får omtrent 30 prosent av tilgjengelig bredde, men fordelingen respekterer minimumsbredden hver kolonne faktisk trenger, så en smal kolonne med et langt ubrytelig token flyter ikke stille over tabellboksen
Hvor dette passer inn i en dokumentrørledning
Tabellarbeidet sitter inne i den bredere paged-media-profilen, og pagineringsreglene, ressursbudsjettene og CSS-håndteringen beskrevet i HTML5 paged-media-importveien gjelder uendret for dokumenter som inneholder tabeller. Hvis dataene dine ikke starter som HTML, unngår den direkte konstruksjonsveien i bygging av tabeller rett inn i en PDF parse-laget helt og gir deg samme rutenettatferd gjennom et API. Og siden radhøyde til slutt avhenger av hvor linjer brytes, er målingsdrøftelsen i tekstjustering og linjebrytning følgestykket for alle som finpusser tett tabellutdata
Den gjenbrukbare lærdommen her handler ikke om tabeller i det hele tatt. Når et nytt delsystem trenger en funksjon et gammelt delsystem allerede har, spør hvilket av de to som eier tingen som er vanskeligst å implementere på nytt. Rutenett-aritmetikk er noen titalls linjer og flytter seg lett. Riktekst-rendring med lenker i teksten, hevet skrift og stil per tekstsegment er ikke det, så rutenettet flyttet og teksten ble. HotPDF leverer begge veier som del av HotPDF Delphi PDF-komponent, så valget mellom HTML-inndata og direkte konstruksjon er et prosjektvalg snarere enn et biblioteksspørsmål