HotPDF renderar HTML-tabeller genom sin HTML5 paged-media-profil med ett verkligt upptagsrutnät för rowspan och colspan, uppmätta radhöjder i stället för teckenräkningsuppskattningar, och huvudrader upprepade på varje fortsättningssida. Två situationer får den att avstå från att upprepa ett huvud, och att känna till dem i förväg är billigare än att felsöka en duplicerad cell senare
Dokumentklassen som tvingar fram det här är den varje rapporteringsteam förr eller senare levererar: en faktura eller en efterlevnadsrapport där källan till sanningen är HTML, tabellen löper över fyra sidor, och huvudet måste vara läsbart på varenda en. Allt mindre än en verklig tabellayout ger de två felen läsare märker direkt, ett huvud som visas en gång på sida ett och rader vars höjder gissats från teckenantal
Varför flyttades tabellförmågan in i HTML-renderaren?
Därför att alternativet tappar rich text, och rich text är anledningen till att innehållet är HTML från början. Den uppenbara planen ser ut som återanvändning: HotPDF har redan ett layout-DOM-tabellobjekt med ett ordentligt rutnät, så broa HTML-parsaren in i det och få spanning gratis. Problemet är vad det tabellobjektet ritar med. Dess celler bär text och en stil, och dess ritningsväg avger vanlig textutmatning, så allt HTML:en faktiskt innehöll bortom ett teckensnitt och en färg, länkar, upphöjd text, inline-storleksändringar, färg per körning, är borta när det når sidan
Riktningen som överlever kontakten med riktiga dokument är den omvända. Flytta tabellmotorns förmågor, upptagsrutnätet, verklig mätning, huvudupprepning och kolumnvikter, in i HTML-renderaren, och låt rich-text-renderingen stanna där den redan fungerar. Det är en större ändring än bron, och det är ändringen som håller en hyperlänk inuti en tabellcell en hyperlänk
Rowspan utan union-find
Spannande celler skapar atomära radgrupper, men stängningen över de grupperna behöver ingen allmän disjoint-set-struktur, eftersom upptaget alltid är ett sammanhängande intervall. En cell med rowspan="3" som startar vid rad K upptar rad K till K+2 och inget annat, så gruppinformationen reduceras till en slutmarkör per rad
Algoritmen är två rader avsikt. När du placerar en cell som spänner från K till E, registrera GroupEnd[K] := Max(GroupEnd[K], E). Gå sedan igenom raderna en gång i bakvänd ordning och tillämpa G[R] := G[G[R]], vilket propagerar varje radslut bakåt genom överlappande spann och ger den transitiva stängningen i ett enda pass. Det du får är, för varje rad, den sista rad som måste stanna på samma sida som den, vilket är precis vad pagineringssteget behöver för att avgöra var en brytning får falla
Att fördela höjd är den andra halvan. När en spännande cell behöver mer vertikalt utrymme än de rader den täcker för närvarande ger, går överskottet till spännets sista rad, inte jämnt fördelat över dem. Behandla de spännande cellerna efter att vanliga radhöjder ställts, och fyll sedan på varje spans sista rad. Att sprida överskottet jämnt verkar rättvisare och ger synligt felaktig utmatning: rader som bara innehåller korta celler med en enda textrad blåses upp för att någon orelaterad cell tre rader upp råkade vara hög
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 en valfri författarstilmall som sitt andra argument, och det är där utskriftsreglerna hör hemma. Håll skärmstilmallen borta från den. Profilen är versionsmärkt, och HTML5ProfileMilestones rapporterar vilka förmågegrupper det aktuella bygget implementerar, himParserCascade, himPagedLayout, himTablesForms och himBoundedResources, så en applikation kan degradera medvetet i stället för att upptäcka ett tomrum i produktion
Mätningen måste överensstämma med ritningen, exakt
Radhöjd är bara korrekt när koden som mäter radbrutna rader bryter dem efter samma regel som koden som ritar dem. Det låter uppenbart och är den enskilt vanligaste källan till tabeller vars kanter inte stämmer med innehållet. HotPDF mäter med en girig radräknare, och den räknaren måste matcha radbrytningssmantiken hos rich-text-utdatavägen i tre specifika avseenden: den bryter endast vid mellanslag, den delar aldrig ett ord, och ett ord bredare än kolumnen får en egen rad
Det andra kravet är typsnittet. Mätningen måste köras med cellens eget teckensnitt, satt via SetFont med det faktiska namnet, stilset och storlek innan breddfunktionen anropas, inte med vilket typsnitt som råkade vara aktivt. Fet text är rutinmässigt mer än tio procent bredare än vanlig vid samma storlek, vilket räcker för att förvandla en treradscell till en fyrradscell. En tabell där huvudceller är feta och brödtextceller inte är det, mätt med ett enda teckensnitt, blir fel i exakt de rader läsare tittar på först
Att få det här rätt ändrar vad du kan hävda i ett test. Den observerbara effekten av korrekt mätning är radavståndet, inte glyfantal: en rad med en enda textrad är omkring 20 punkter hög medan en teckenräkningsuppskattning av samma innehåll förutsäger två rader och ungefär 35. Hävda om det vertikala avståndet mellan rader. Och kom ihåg att PDF:s användarrymd har Y ökande uppåt, så ett huvud som sitter ovanför en brödrad betyder att huvudets Y-värde är det större, vilket är det motsatta av vad skärmkoordinatinstinkt skriver
När vägrar HotPDF upprepa ett huvud?
I två fall, vilka båda skulle ge synligt felaktig utmatning om den gick vidare. Det första är ett huvudblock som innehåller en spännande cell som sträcker sig förbi huvudet in i brödrader. Att upprepa huvudet skulle rita det cellinnehållet en andra gång på en plats där det inte längre hör hemma, så huvudet ritas en gång och tabellen fortsätter utan det. Det andra är ett huvud högre än 90 procent av den användbara sidhöjden, där upprepning skulle lämna nästan inget utrymme för data och tabellen inte skulle göra något framåtskridande
Båda avslagen är medvetna och tysta av design, eftersom alternativet är värre. Om ditt huvud inte upprepas och du förväntade dig det, kontrollera uppmärkningen efter en rowspan som korsar thead-gränsen innan du misstänker motorn. Det enskilda uppmärkningsmönstret står för det mesta av överraskningen
// Kolumnvikter kommer från uppmärkningen, så utskriftsstilmallen är
// platsen att styra dem. Bredder behandlas som vikter, inte som pixlar
const
PrintStyleSheet =
'table { width: 100%; }' +
'thead th { font-weight: bold; background: #eee; }' +
'td.amount { text-align: right; }';
// En huvudrad som bär en rowspan som korsar in i brödtexten undertrycker
// huvudupprepning. Håll spann inom en sektion:
// <thead><tr><th rowspan="2">Item</th>...</tr></thead> ok
// <tr><th rowspan="3">Item</th>... spann in i tbody, ingen upprepning
Kolumnbredder beter sig som vikter snarare än absoluta mått, vilket är beteendet som håller en tabell användbar när innehållet inte matchar författarens uppskattning. En kolumn deklarerad till 30 procent får ungefär 30 procent av den tillgängliga bredden, men fördelningen respekterar den minimibredd varje kolumn faktiskt behöver, så en smal kolumn med en lång odelbar token svämmar inte tyst över tabellrutan
Var detta passar in i en dokumentpipeline
Tabellarbetet sitter inuti den bredare paged-media-profilen, och pagineringsreglerna, resursbudgeterna och CSS-hanteringen som beskrivs i HTML5-importvägen för paged media gäller oförändrade för dokument som innehåller tabeller. Om din data inte börjar som HTML undviker den direkta konstruktionsvägen i att bygga tabeller rakt in i en PDF parsningslagret helt och ger dig samma rutnätsbeteende via ett API. Och eftersom radhöjd i slutändan beror på var rader bryts är mätningsdiskussionen i textjustering och radbrytning följeslagaren för alla som trimmar tät tabellutmatning
Den återanvändbara lärdomen här handlar inte om tabeller alls. När ett nytt delsystem behöver en förmåga ett gammalt delsystem redan har, fråga vilket av de två som äger det som är svårast att återimplementera. Rutnätsaritmetik är några dussin rader och flyttas lätt. Rich-text-rendering med inline-länkar, upphöjd text och stil per körning är det inte, så rutnätet flyttade och texten stannade. HotPDF skickar båda vägarna som del av HotPDF Delphi PDF component, så valet mellan HTML-indata och direkt konstruktion är ett projektbeslut snarare än ett biblioteksbeslut