HotPDF rendert HTML-tabellen via zijn HTML5 paged-media-profiel met een echt bezettingsgrid voor rowspan en colspan, gemeten rijhoogten in plaats van schattingen op tekenaantal, en koprijen die op elke vervolgpagina worden herhaald. Twee situaties maken dat hij weigert een kop te herhalen, en die van tevoren weten is goedkoper dan later een gedupliceerde cel debuggen
De documentcategorie die dit afdwingt, is degene die elk rapportageteam uiteindelijk uitlevert: een factuur of compliance-rapport waarin de source of truth HTML is, de tabel over vier pagina's loopt, en de kop op elke pagina leesbaar moet zijn. Alles wat minder is dan een echte table layout levert de twee fouten op die lezers meteen opmerken: een kop die één keer op pagina één verschijnt en rijen waarvan de hoogten uit tekenaantallen zijn gegokt
Waarom verhuisde de tabelcapaciteit naar de HTML-renderer?
Omdat het alternatief rich text verliest, en rich text is nu eenmaal de reden dat de inhoud HTML is. Het voor de hand liggende plan lijkt op hergebruik: HotPDF heeft al een layout DOM table-object met een fatsoenlijk grid, dus brug de HTML-parser daarheen en krijg spanning gratis. Het probleem is waarmee dat table-object tekent. Zijn cellen dragen tekst en een stijl, en zijn tekenpad produceert platte tekstoutput, dus alles wat de HTML werkelijk bevatte buiten een font en een kleur, links, superscripts, inline grootteveranderingen, per-run kleur, is weg tegen de tijd dat het de pagina bereikt
De richting die contact met echte documenten overleeft, is de omgekeerde. Verhuis de capaciteiten van de tabelengine, het bezettingsgrid, echte meting, kopherhaling en kolomweging, naar de HTML-renderer, en laat rich-text rendering waar die al werkt. Dat is een grotere wijziging dan de brug, en het is de wijziging die een hyperlink in een tabelcel een hyperlink laat blijven
Rowspan zonder union-find
Spans vormen atomaire rijgroepen, maar de afsluiting over die groepen heeft geen algemene disjoint-set-structuur nodig, want de bezetting is altijd een aaneengesloten interval. Een cel met rowspan="3" die bij rij K begint, bezet rij K tot en met K+2 en niets anders, dus de groepsinformatie reduceert tot een eindmarkering per rij
Het algoritme is twee regels intentie. Plaatst u een cel die begint bij K en eindigt bij E, noteer dan GroupEnd[K] := Max(GroupEnd[K], E). Loop daarna de rijen één keer achterstevoren en pas G[R] := G[G[R]] toe, wat elk rij-einde achterwaarts door overlappende spans propageert en de transitieve afsluiting in één pas oplevert. Wat u krijgt is, voor elke rij, de laatste rij die op dezelfde pagina moet blijven, en dat is precies wat de pagineringsstap nodig heeft om te besluiten waar een break mag vallen
Hoogte verdelen is de andere helft. Als een cel meer verticale ruimte nodig heeft dan de rijen die hij beslaat nu bieden, gaat het overschot naar de laatste rij van de span, niet gelijkmatig verdeeld. Verwerk de spanning-cells nadat de gewone rijhoogten vastliggen en vul daarna de slotrij van elke span aan. Het overschot gelijkmatig verdelen lijkt eerlijker en levert zichtbaar foutieve output op: rijen die alleen korte cellen van één regel bevatten, worden opgeblazen omdat een ongerelateerde cel drie rijen hoger toevallig lang was
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 neemt een optionele author style sheet als tweede argument, en daar horen printregels thuis. Houd de screen style sheet erbuiten. Het profiel is geversioneerd, en HTML5ProfileMilestones rapporteert welke capability-groepen de huidige build implementeert, himParserCascade, himPagedLayout, himTablesForms en himBoundedResources, zodat een applicatie bewust kan degraderen in plaats van in productie een gat te ontdekken
Meting moet exact overeenkomen met het tekenen
Rijhoogte is alleen correct als de code die omgebroken regels meet ze ombreekt volgens dezelfde regel als de code die ze tekent. Dit klinkt vanzelfsprekend en is toch de allergewoonlijkste bron van tabellen waarvan de randen niet aansluiten op de inhoud. HotPDF meet met een greedy-regelteller, en die teller moet op drie specifieke punten matchen met de wrapping-semantiek van het rich-text-outputpad: hij breekt alleen op spaties, hij splijt nooit een woord, en een woord breder dan de kolom krijgt een eigen regel
De tweede eis is het font. De meting moet draaien met het font van de cel zelf, gezet via SetFont met de werkelijke naam, stijlset en grootte vóór het aanroepen van de breedtefunctie, niet met wat er toevallig actief was. Vette tekst is doorgaans ruim tien procent breder dan regular op dezelfde grootte, en dat is genoeg om een cel van drie regels er een van vier te maken. Een tabel waarin kopcellen vet zijn en bodycellen niet, gemeten met één font, zit fout in precies de rijen die lezers het eerst bekijken
Als dit goed zit, verandert dat wat u in een test kunt asserten. Het waarneembare effect van accurate meting is regelspatiëring, niet glyph-aantallen: een rij van één regel is ongeveer 20 punten hoog, terwijl een schatting op tekenaantal voor dezelfde inhoud twee regels en zo'n 35 voorspelt. Assert op de verticale afstand tussen rijen. En onthoud dat Y in PDF user space omhoog toeneemt, dus een kop die boven een bodyrij zit betekent dat de kop-Y-waarde de grotere is, het omgekeerde van wat schermcoördinaat-instinct schrijft
Wanneer weigert HotPDF een kop te herhalen?
In twee gevallen, die beide zichtbaar foutieve output zouden opleveren als hij doorging. Het eerste is een kopblok met een cel die voorbij de kop doorloopt in de bodyrijen. De kop herhalen zou die celinhoud een tweede keer tekenen op een plek waar hij niet meer thuishoort, dus de kop wordt één keer getekend en de tabel gaat zonder hem verder. Het tweede is een kop die hoger is dan 90 procent van de bruikbare paginahoogte; herhaling zou bijna geen ruimte voor data overlaten en de tabel zou geen vooruitgang boeken
Beide weigeringen zijn bewust en vanzelfsprekend stil, want het alternatief is erger. Als uw kop niet herhaalt terwijl u dat verwachtte, zoek dan in de markup naar een rowspan die de thead-grens oversteekt voordat u de engine verdenkt. Dat ene markup-patroon verklaart het grootste deel van de verrassing
// Kolomgewichten komen uit de markup, dus de print style sheet is de plek om ze
// te sturen. Breedtes worden als gewichten behandeld, niet als pixels
const
PrintStyleSheet =
'table { width: 100%; }' +
'thead th { font-weight: bold; background: #eee; }' +
'td.amount { text-align: right; }';
// Een koprij met een rowspan die de body inloopt, onderdrukt kopherhaling.
// Houd spans binnen één sectie:
// <thead><tr><th rowspan="2">Item</th>...</tr></thead> ok
// <tr><th rowspan="3">Item</th>... loopt in tbody, geen herhaling
Kolombreedtes gedragen zich als gewichten in plaats van absolute maten, en dat is het gedrag dat een tabel bruikbaar houdt als de inhoud niet aan de schatting van de auteur voldoet. Een kolom die op 30 procent is gedeclareerd krijgt ruwweg 30 procent van de beschikbare breedte, maar de verdeling respecteert de minimale breedte die elke kolom werkelijk nodig heeft, dus een smalle kolom met een lang onbreekbaar token overloopt niet stilletjes de tabelbox
Waar dit past in een documentpipeline
Het tabelwerk zit binnen het bredere paged-media-profiel, en de pagineringsregels, resourcebudgetten en CSS-behandeling uit het HTML5 paged-media-importpad gelden ongewijzigd voor documenten met tabellen. Begint uw data niet als HTML, dan omzeilt de route van directe constructie in tabellen rechtstreeks in een PDF bouwen de parsellaag volledig en geeft dezelfde gridgedrag via een API. En omdat rijhoogte uiteindelijk afhangt van waar regels breken, is de meetdiscussie in text justification en regelafbreking het kompaanartikel voor wie dichte tabeloutput afstelt
De herbruikbare les hier gaat helemaal niet over tabellen. Als een nieuw subsysteem een capaciteit nodig heeft die een oud subsysteem al heeft, vraag dan welke van de twee de eigenaar is van het deel dat het lastigst opnieuw te implementeren is. Grid-rekenkunde is een paar tientallen regels en verhuist makkelijk. Rich-text rendering met inline links, superscripts en per-run styling niet, dus het grid verhuisde en de tekst bleef. HotPDF levert beide paden als onderdeel van de HotPDF Delphi PDF component, zodat de keuze tussen HTML-invoer en directe constructie een projectbeslissing is in plaats van een library-beslissing