HotPDF rend les tableaux HTML via son profil HTML5 paged-media en utilisant une vraie grille d'occupation pour le rowspan et le colspan, des hauteurs de lignes mesurées plutôt qu'estimées au nombre de caractères, et des lignes d'en-tête répétées sur chaque page de continuation. Deux situations le font renoncer à répéter un en-tête, et les connaître à l'avance coûte moins cher que de déboguer plus tard une cellule dupliquée
La classe de documents qui impose cela est celle que toute équipe de reporting finit par livrer : une facture ou un rapport de conformité où la source de vérité est du HTML, où le tableau court sur quatre pages, et où l'en-tête doit rester lisible sur chacune. Moins qu'une vraie mise en page de tableau produit les deux défauts que les lecteurs repèrent immédiatement, un en-tête qui n'apparaît qu'une fois en page une et des lignes dont la hauteur a été devinée au nombre de caractères
Pourquoi la capacité tableau a-t-elle déménagé dans le moteur HTML ?
Parce que l'alternative perd le texte riche, et que le texte riche est la raison pour laquelle le contenu est en HTML à l'origine. Le plan évident ressemble à de la réutilisation : HotPDF a déjà un objet table du DOM de mise en page avec une vraie grille, donc on bridge l'analyseur HTML dedans et on obtient le spanning gratuitement. Le problème est avec quoi cet objet table dessine. Ses cellules portent du texte et un style, et son chemin de dessin émet du texte brut, donc tout ce que le HTML contenait réellement au-delà d'une police et d'une couleur, liens, exposants, changements de taille en ligne, couleur par segment, est perdu au moment d'atteindre la page
La direction qui survit au contact des vrais documents est l'inverse. Déplacer les capacités du moteur de tableau, la grille d'occupation, la vraie mesure, la répétition d'en-tête et la pondération des colonnes, dans le moteur HTML, et laisser le rendu de texte riche là où il fonctionne déjà. C'est un changement plus grand que le pont, et c'est celui qui garde un lien hypertexte dans une cellule de tableau un lien hypertexte
Le rowspan sans union-find
Les cellules en span créent des groupes de lignes atomiques, mais la clôture sur ces groupes n'a besoin d'aucune structure générale d'ensembles disjoints, parce que l'occupation est toujours un intervalle contigu. Une cellule avec rowspan="3" commençant à la ligne K occupe les lignes K à K+2 et rien d'autre, donc l'information de groupe se réduit à un marqueur de fin par ligne
L'algorithme tient en deux lignes d'intention. Quand vous placez une cellule en span qui commence à K et finit à E, enregistrez GroupEnd[K] := Max(GroupEnd[K], E). Puis parcourez les lignes une fois à rebours en appliquant G[R] := G[G[R]], ce qui propage chaque fin de ligne en arrière à travers les spans qui se chevauchent et produit la clôture transitive en une seule passe. Ce que vous obtenez, c'est pour chaque ligne la dernière ligne qui doit rester sur la même page qu'elle, exactement ce dont l'étape de pagination a besoin pour décider où une coupure peut tomber
La répartition de hauteur est l'autre moitié. Quand une cellule en span a besoin de plus d'espace vertical que ce que les lignes qu'elle couvre offrent actuellement, le surplus va à la dernière ligne du span, pas réparti uniformément. Traitez les cellules en span après que les hauteurs de lignes ordinaires sont réglées, puis complétez la dernière ligne de chaque span. Répartir le surplus uniformément paraît plus juste et produit une sortie visiblement fausse : des lignes qui ne contiennent que des cellules courtes à une seule ligne se retrouvent gonflées parce qu'une cellule sans rapport trois lignes plus haut s'est trouvée grande
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 prend une feuille de style d'auteur optionnelle en second argument, et c'est là que les règles d'impression ont leur place. Gardez la feuille de style d'écran hors de là. Le profil est versionné, et HTML5ProfileMilestones signale quels groupes de capacités la construction courante implémente, himParserCascade, himPagedLayout, himTablesForms et himBoundedResources, si bien qu'une application peut dégrader délibérément au lieu de découvrir un trou en production
La mesure doit concorder avec le dessin, exactement
La hauteur de ligne n'est correcte que si le code qui mesure les lignes reconduites les reconduit selon la même règle que le code qui les dessine. Cela sonne évident et c'est la source la plus courante de tableaux dont les bordures ne s'alignent pas avec le contenu. HotPDF mesure avec un compteur de lignes glouton, et ce compteur doit correspondre à la sémantique de reconduction du chemin de sortie texte riche sur trois points précis : il ne coupe qu'aux espaces, il ne scinde jamais un mot, et un mot plus large que la colonne obtient une ligne pour lui seul
La seconde exigence est la police. La mesure doit s'exécuter avec la police propre à la cellule, posée via SetFont avec le nom réel, le jeu de styles et la taille avant d'appeler la fonction de largeur, pas avec la police qui se trouvait active. Le texte en gras est couramment plus de dix pour cent plus large que le regular à taille égale, ce qui suffit à transformer une cellule de trois lignes en cellule de quatre. Un tableau où les cellules d'en-tête sont en gras et pas celles du corps, mesuré avec une seule police, sera faux précisément sur les lignes que les lecteurs regardent en premier
Bien faire cela change ce que vous pouvez affirmer dans un test. L'effet observable d'une mesure précise est l'interligne, pas le compte de glyphes : une ligne à une seule ligne de texte fait environ 20 points de haut alors qu'une estimation au nombre de caractères du même contenu prédit deux lignes et environ 35. Affirmez sur la distance verticale entre les lignes. Et rappelez-vous que l'espace utilisateur PDF a Y croissant vers le haut, donc un en-tête assis au-dessus d'une ligne de corps signifie que la valeur Y de l'en-tête est la plus grande, l'inverse de ce que l'instinct des coordonnées d'écran écrit
Quand HotPDF refuse-t-il de répéter un en-tête ?
Dans deux cas, qui produiraient tous deux une sortie visiblement fausse s'il allait de l'avant. Le premier est un bloc d'en-tête contenant une cellule en span qui déborde de l'en-tête vers les lignes du corps. Répéter l'en-tête dessinerait ce contenu une seconde fois à une position où il n'appartient plus, donc l'en-tête est dessiné une fois et le tableau continue sans lui. Le second est un en-tête plus haut que 90 pour cent de la hauteur de page utilisable, où la répétition ne laisserait presque aucune place aux données et le tableau ne progresserait plus
Les deux refus sont délibérés et silencieux par conception, parce que l'alternative est pire. Si votre en-tête ne se répète pas et que vous vous y attendiez, inspectez le balisage pour un rowspan qui traverse la frontière du thead avant de suspecter le moteur. Ce seul schéma de balisage explique la plupart des surprises
// Les pondérations de colonnes viennent du balisage, donc la feuille de style d'impression
// est l'endroit pour les contrôler. Les largeurs sont traitées comme des pondérations, pas comme des pixels
const
PrintStyleSheet =
'table { width: 100%; }' +
'thead th { font-weight: bold; background: #eee; }' +
'td.amount { text-align: right; }';
// Une ligne d'en-tête qui porte un rowspan débordant dans le corps
// supprime la répétition d'en-tête. Gardez les spans dans une section :
// <thead><tr><th rowspan="2">Item</th>...</tr></thead> ok
// <tr><th rowspan="3">Item</th>... déborde dans le tbody, pas de répétition
Les largeurs de colonnes se comportent comme des pondérations plutôt que comme des mesures absolues, ce qui est le comportement qui garde un tableau utilisable quand le contenu ne correspond pas à l'estimation de l'auteur. Une colonne déclarée à 30 pour cent reçoit environ 30 pour cent de la largeur disponible, mais la répartition respecte la largeur minimale dont chaque colonne a réellement besoin, si bien qu'une colonne étroite portant un jeton long insécable ne déborde pas silencieusement de la boîte du tableau
Où cela se situe dans un pipeline de documents
Le travail sur les tableaux s'inscrit dans le profil paged-media plus large, et les règles de pagination, les budgets de ressources et la gestion CSS décrits dans le chemin d'import HTML5 paged-media s'appliquent sans changement aux documents qui contiennent des tableaux. Si vos données ne naissent pas en HTML, la route de construction directe décrite dans la construction de tableaux directement dans un PDF évite entièrement la couche d'analyse et vous donne le même comportement de grille via une API. Et parce que la hauteur de ligne dépend en fin de compte de l'endroit où les lignes se coupent, la discussion de mesure dans la justification de texte et la coupure de lignes est la pièce complémentaire pour quiconque règle une sortie tabulaire dense
La leçon réutilisable ici ne concerne pas du tout les tableaux. Quand un nouveau sous-système a besoin d'une capacité qu'un ancien sous-système possède déjà, demandez lequel des deux possède ce qui est le plus difficile à réimplémenter. L'arithmétique de grille tient en quelques dizaines de lignes et se déplace facilement. Le rendu de texte riche avec liens en ligne, exposants et styles par segment, non, donc la grille a déménagé et le texte est resté. HotPDF livre les deux chemins au sein du composant PDF Delphi HotPDF, si bien que le choix entre entrée HTML et construction directe est une décision de projet plutôt que de bibliothèque