Το HotPDF αποδίδει HTML πίνακες μέσα από το HTML5 paged-media profile του, με πραγματικό πλέγμα κατάληψης για rowspan και colspan, μετρημένα ύψη γραμμών αντί για εκτιμήσεις από πλήθος χαρακτήρων, και γραμμές κεφαλίδας που επαναλαμβάνονται σε κάθε σελίδα συνέχειας. Δύο καταστάσεις το κάνουν να αρνείται να επαναλάβει κεφαλίδα, και να τις ξέρετε από πριν κοστίζει λιγότερο από το να ψάχνετε διπλό κελί μετά
Η κατηγορία εγγράφων που επιβάλλει αυτό είναι αυτή που τελικά παραδίδει κάθε ομάδα αναφορών: ένα τιμολόγιο ή μια έκθεση συμμόρφωσης όπου η πηγή αλήθειας είναι HTML, ο πίνακας απλώνεται σε τέσσερις σελίδες, και η κεφαλίδα πρέπει να είναι ευανάγνωστη σε κάθε μία. Οτιδήποτε λιγότερο από πραγματική διάταξη πίνακα παράγει τις δύο αστοχίες που ο αναγνώστης καταλαβαίνει αμέσως, μια κεφαλίδα που εμφανίζεται μία φορά στη σελίδα ένα και γραμμές με ύψη μαντεψμένα από πλήθος χαρακτήρων
Γιατί η δυνατότητα πινάκων μετακόμισε στον HTML renderer;
Γιατί η εναλλακτική χάνει rich text, και το rich text είναι ο λόγος που το περιεχόμενο είναι HTML εξαρχής. Το προφανές σχέδιο μοιάζει με επαναχρησιμοποίηση: το HotPDF έχει ήδη αντικείμενο πίνακα στο layout DOM με σωστό πλέγμα, οπότε γέφυρα τον HTML parser σε αυτό και πάρε τα spans δωρεάν. Το πρόβλημα είναι με τι ζωγραφίζει εκείνο το αντικείμενο πίνακα. Τα κελιά του κουβαλούν κείμενο και ένα style, και το μονοπάτι σχεδίασής του εκπέμπει σκέτη έξοδο κειμένου, οπότε οτιδήποτε περιείχε πραγματικά το HTML πέρα από μια γραμματοσειρά και ένα χρώμα, links, superscripts, αλλαγές μεγέθους inline, χρώμα ανά run, έχει χαθεί μέχρι να φτάσει στη σελίδα
Η κατεύθυνση που επιβιώνει από την επαφή με πραγματικά έγγραφα είναι η αντίστροφη. Μετακινήστε τις δυνατότητες της μηχανής πινάκων, το πλέγμα κατάληψης, την πραγματική μέτρηση, την επανάληψη κεφαλίδων και τη στάθμιση στηλών, στον HTML renderer, και αφήστε την απόδοση rich-text εκεί που δουλεύει ήδη. Είναι μεγαλύτερη αλλαγή από τη γέφυρα, και είναι η αλλαγή που κρατά έναν υπερσύνδεσμο μέσα σε κελί πίνακα υπερσύνδεσμο
Rowspan χωρίς union-find
Τα κελιά span δημιουργούν ατομικές ομάδες γραμμών, αλλά η κλειστότητα πάνω σε αυτές τις ομάδες δεν θέλει γενική δομή disjoint-set, γιατί η κατάληψη είναι πάντα συνεχόμενο διάστημα. Ένα κελί με rowspan="3" που ξεκινά στη γραμμή K καταλαμβάνει τις γραμμές K έως K+2 και τίποτα άλλο, οπότε η πληροφορία ομάδας ανάγεται σε δείκτη τέλους ανά γραμμή
Ο αλγόριθμος είναι δύο γραμμές πρόθεσης. Όταν τοποθετείτε κελί span που ξεκινά στο K και τελειώνει στο E, καταγράψτε GroupEnd[K] := Max(GroupEnd[K], E). Μετά διαπεράστε τις γραμμές μία φορά ανάποδα και εφαρμόστε G[R] := G[G[R]], που διαδίδει κάθε τέλος γραμμής προς τα πίσω μέσα από επικαλυπτόμενα spans και δίνει τη μεταβατική κλειστότητα σε ένα πέρασμα. Αυτό που παίρνετε είναι, για κάθε γραμμή, η τελευταία γραμμή που πρέπει να μείνει στην ίδια σελίδα με αυτήν, που είναι ακριβώς όσο χρειάζεται το βήμα σελιδοποίησης για να αποφασίσει πού μπορεί να πέσει σπάσιμο
Η κατανομή ύψους είναι το άλλο μισό. Όταν ένα κελί span θέλει περισσότερο κάθετο χώρο απ όσο δίνουν τώρα οι γραμμές που καλύπτει, το πλεόνασμα πάει στην τελευταία γραμμή του span, και δεν μοιράζεται ομοιόμορφα. Επεξεργαστείτε τα κελιά span αφού εκκαθαριστούν τα συνηθισμένα ύψη γραμμών, και μετά συμπληρώστε την τελευταία γραμμή κάθε span. Η ομοιόμορφη διανομή του πλεονάσματος ακούγεται δίκαιη και παράγει ορατά λάθος έξοδο: γραμμές που περιέχουν μόνο κοντά κελιά μίας γραμμής φουσκώνουν επειδή κάποιο άσχετο κελί τρεις γραμμές πάνω τυχαίνει να είναι ψηλό
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 παίρνει προαιρετικό author style sheet ως δεύτερο όρισμα, που είναι το σημείο όπου ανήκουν οι κανόνες εκτύπωσης. Κρατήστε έξω το style sheet της οθόνης. Το profile είναι εκδοσιοποιημένο, και η HTML5ProfileMilestones αναφέρει ποιες ομάδες δυνατοτήτων υλοποιεί το τρέχον build, himParserCascade, himPagedLayout, himTablesForms και himBoundedResources, ώστε μια εφαρμογή να υποβαθμίζει σκόπιμα αντί να ανακαλύπτει κενό στην παραγωγή
Η μέτρηση πρέπει να συμφωνεί με τη σχεδίαση, ακριβώς
Το ύψος γραμμής είναι σωστό μόνο όταν ο κώδικας που μετρά τις τυλιγμένες γραμμές τις τυλίγει με τον ίδιο κανόνα με τον κώδικα που τις σχεδιάζει. Ακούγεται προφανές και είναι η πιο συχνή πηγή πινάκων με περιθώρια που δεν ευθυγραμμίζονται με τα περιεχόμενά τους. Το HotPDF μετρά με έναν άπληστο μετρητή γραμμών, και εκείνος ο μετρητής πρέπει να ταιριάζει τη σημασιολογία αναδίπλωσης του μονοπατιού εξόδου rich-text σε τρία συγκεκριμένα σημεία: σπάει μόνο σε κενά, δεν χωρίζει ποτέ λέξη, και μια λέξη φαρδύτερη από τη στήλη παίρνει γραμμή δική της
Η δεύτερη απαίτηση είναι η γραμματοσειρά. Η μέτρηση πρέπει να τρέχει με τη δική της γραμματοσειρά του κελιού, ορισμένη μέσω SetFont με το πραγματικό όνομα, σετ στυλ και μέγεθος προτού κληθεί η συνάρτηση πλάτους, και όχι με όποια γραμματοσειρά τυχαινε να είναι ενεργή. Το bold κείμενο είναι τακτικά πάνω από δέκα τοις εκατό φαρδύτερο από το regular στο ίδιο μέγεθος, που φτάνει για να γίνει ένα κελί τριών γραμμών κελί τεσσάρων. Ένας πίνακας όπου τα κελιά κεφαλίδας είναι bold και τα κελιά σώματος όχι, μετρημένος με μία γραμματοσειρά, θα είναι λάθος ακριβώς στις γραμμές που κοιτάζει πρώτα ο αναγνώστης
Αυτό αν το κάνετε σωστά αλλάζει τι μπορείτε να ισχυριστείτε σε test. Η παρατηρήσιμη επίδραση της ακριβούς μέτρησης είναι το διάστιχο, όχι τα πλήθη glyphs: μια γραμμή μίας σειράς είναι περίπου 20 points ψηλή ενώ μια εκτίμηση από πλήθος χαρακτήρων του ίδιου περιεχομένου προβλέπει δύο γραμμές και περίπου 35. Κάντε assert στην κατακόρυφη απόσταση μεταξύ γραμμών. Και θυμηθείτε ότι στον user space του PDF το Y μεγαλώνει προς τα πάνω, οπότε κεφαλίδα που κάθεται πάνω από γραμμή σώματος σημαίνει ότι η τιμή Y της κεφαλίδας είναι η μεγαλύτερη, που είναι το αντίθετο από όσο γράφει το ένστικτο των συντεταγμένων οθόνης
Πότε αρνείται το HotPDF να επαναλάβει κεφαλίδα;
Σε δύο περιπτώσεις, που και οι δύο θα παρήγαν ορατά λάθος έξοδο αν προχωρούσε. Η πρώτη είναι μπλοκ κεφαλίδας που περιέχει κελί span που απλώνεται πέρα από την κεφαλίδα σε γραμμές σώματος. Η επανάληψη της κεφαλίδας θα σχεδίαζε το περιεχόμενο εκείνου του κελιού δεύτερη φορά σε θέση που δεν ανήκει πια, οπότε η κεφαλίδα σχεδιάζεται μία φορά και ο πίνακας συνεχίζει χωρίς αυτήν. Η δεύτερη είναι κεφαλίδα ψηλότερη από το 90 τοις εκατό του χρήσιμου ύψους σελίδας, όπου η επανάληψη θα άφηνε σχεδόν κανέναν χώρο για δεδομένα και ο πίνακας δεν θα έκανε καμία πρόοδο
Και οι δύο αρνήσεις είναι σκόπιμες και ήσυχες εκ σχεδιασμού, γιατί η εναλλακτική είναι χειρότερη. Αν η κεφαλίδα σας δεν επαναλαμβάνεται και το περιμένατε, ελέγξτε το markup για rowspan που διασχίζει το όριο του thead προτού υποψιαστείτε τη μηχανή. Αυτό το ένα μοτίβο markup ευθύνεται για το μεγαλύτερο μέρος της έκπληξης
// Τα βάρη στηλών βγαίνουν από το markup, οπότε το print style sheet είναι
// το σημείο για να τα ελέγξετε. Τα πλάτη μεταχειρίζονται ως βάρη, όχι ως pixels
const
PrintStyleSheet =
'table { width: 100%; }' +
'thead th { font-weight: bold; background: #eee; }' +
'td.amount { text-align: right; }';
// Γραμμή κεφαλίδας που κουβαλά rowspan που διασχίζει στο σώμα καταστέλλει
// την επανάληψη κεφαλίδας. Κρατήστε τα spans μέσα σε ένα τμήμα:
// <thead><tr><th rowspan="2">Item</th>...</tr></thead> οκ
// <tr><th rowspan="3">Item</th>... απλώνεται σε tbody, χωρίς επανάληψη
Τα πλάτη στηλών συμπεριφέρονται ως βάρη και όχι ως απόλυτες μετρήσεις, που είναι η συμπεριφορά που κρατά έναν πίνακα χρήσιμο όταν το περιεχόμενο δεν ταιριάζει με την εκτίμηση του συγγραφέα. Μια στήλη δηλωμένη στο 30 τοις εκατό παίρνει περίπου 30 τοις εκατό του διαθέσιμου πλάτους, αλλά η κατανομή σέβεται το ελάχιστο πλάτος που κάθε στήλη χρειάζεται όντως, οπότε μια στενή στήλη με ένα μεγάλο αδιαίρετο token δεν ξεχειλίζει σιωπηλά το box του πίνακα
Πού χωράει αυτό στο pipeline εγγράφων
Η δουλειά πινάκων κάθεται μέσα στο ευρύτερο paged-media profile, και οι κανόνες σελιδοποίησης, τα budgets πόρων και ο χειρισμός CSS που περιγράφονται στο μονοπάτι εισαγωγής HTML5 paged-media ισχύουν αμετάβλητα για έγγραφα που περιέχουν πίνακες. Αν τα δεδομένα σας δεν ξεκινούν ως HTML, η διαδρομή άμεσης κατασκευής στο χτίσιμο πινάκων κατευθείαν σε PDF αποφεύγει εντελώς το στρώμα parsing και σας δίνει την ίδια συμπεριφορά πλέγματος μέσω API. Και επειδή το ύψος γραμμής τελικά εξαρτάται από το πού σπάνε οι γραμμές, η συζήτηση μέτρησης στο text justification και line breaking είναι το συνοδευτικό κομμάτι για όποιον ρυθμίζει πυκνή έξοδο σε μορφή πίνακα
Το επαναχρησιμοποιήσιμο δίδαγμα εδώ δεν αφορά καθόλου πίνακες. Όταν ένα νέο subsystem θέλει μια δυνατότητα που ένα παλιό subsystem έχει ήδη, ρωτήστε ποιο από τα δύο κατέχει το πράγμα που είναι το δυσκολότερο να ξαναϋλοποιηθεί. Η αριθμητική πλέγματος είναι μερικές δεκάδες γραμμές και μετακινείται εύκολα. Η απόδοση rich-text με inline links, superscripts και styling ανά run δεν είναι, οπότε το πλέγμα μετακόμισε και το κείμενο έμεινε. Το HotPDF παραδίδει και τις δύο διαδρομές ως μέρος του HotPDF Delphi PDF component, οπότε η επιλογή ανάμεσα σε είσοδο HTML και άμεση κατασκευή είναι απόφαση project και όχι βιβλιοθήκης