Τεχνικό Άρθρο

Flexbox, CSS Grid και υποσημειώσεις σε PDF από τη Delphi

Η PDF Library for Delphi αποδίδει HTML σε σελίδα PDF με πραγματική δισδιάστατη διάταξη: τα display: flex και display: grid μετρώνται και τοποθετούνται αντί να υποβαθμίζονται σε στοιβαγμένα μπλοκ, και οι υποσημειώσεις δεσμεύονται στο κάτω μέρος του πλαισίου που φέρει την αναφορά τους, με αρίθμηση που παραμένει συνεχής σε στήλες και σελίδες. Τα σημεία εισόδου είναι τα γνωστά, η DrawHTMLTextBox για ένα μοναδικό πλαίσιο και η DrawHTMLStory για ροή πολλαπλών στηλών

Αυτό έχει σημασία επειδή το HTML είναι ο τρόπος με τον οποίο φτάνει τώρα η περισσότερη περιεχόμενο αναφορών. Τα πρότυπα συγγράφονται από άτομα που γράφουν CSS, οι πίνακες ελέγχου σχεδιάζονται ως κάρτες, και ένας αποδότης που καταρρέει σιωπηλά μια γραμμή flex σε τέσσερα στοιβαγμένα μπλοκ παράγει ένα έγγραφο που καθόλου δεν μοιάζει με τον σχεδιασμό. Μέχρι να υπάρξει αυτή η δυνατότητα, ο μόνος δισδιάστατος περιέκτης που μετρούσε η μηχανή ήταν ο πίνακας, οπότε κάθε διάταξη κάρτας έπρεπε να ξανασυγγραφεί ως πίνακας με το χέρι

Τι άλλαξε στο μοντέλο διάταξης;

Ο προηγούμενος κύριος βρόχος διατηρούσε ένα μοναδικό πλαίσιο γραμμής και προχωρούσε προς τα κάτω στη σελίδα. Εκείνο το μοντέλο χειρίζεται τέλεια ενσωματωμένο περιεχόμενο και στοιβαγμένα μπλοκ και δεν μπορεί να εκφράσει έναν περιέκτη του οποίου τα παιδιά διαστασιολογούνται σε σχέση μεταξύ τους. Οι πίνακες ήταν η μοναδική εξαίρεση, με το δικό τους πέρασμα μέτρησης δύο φάσεων

Το flex και το grid προσθέτουν καθένα ένα φραγμένο πέρασμα μέτρησης πάνω στα παιδιά ενός περιέκτη, και η σημαντική λέξη είναι φραγμένο. Ένας περιέκτης flex μετρά έως 256 άμεσα παιδιά σε σταθερό πίνακα. Ένα grid χρησιμοποιεί έναν πίνακα κατάληψης το πολύ 64 επί 64 κελιά για ντετερμινιστική αυτόματη τοποθέτηση. Εκείνα τα ανώτατα όρια υπάρχουν ώστε ένα εχθρικό ή δημιουργημένο φύλλο στυλ να μην μπορεί να οδηγήσει αφραγμό αναδρομή ή τετραγωνική μνήμη τοποθέτησης, κάτι που είναι πραγματική ανησυχία όταν το HTML προέρχεται από ένα πρότυπο που επεξεργάζεται ένας πελάτης

Πώς τα στοιχεία flex λαμβάνουν τα μεγέθη τους

Στην κατεύθυνση γραμμής, ο περιέκτης αθροίζει τη βάση κάθε στοιχείου μαζί με τα βάρη ανάπτυξης και συρρίκνωσής του, έπειτα διανέμει τον εναπομείναντα χώρο, θετικό ή αρνητικό, σύμφωνα με εκείνα τα βάρη. Με το flex-wrap, κάθε γραμμή επιλύεται ανεξάρτητα, οπότε μια γραμμή που σπάει σε δύο γραμμές αναθέτει ελεύθερο χώρο ανά γραμμή αντί σε ολόκληρο τον περιέκτη. Στην κατεύθυνση στήλης η ίδια διανομή κύριου άξονα εκτελείται έναντι είτε ρητού ύψους είτε ύψους περιεχομένου

Τα justify-content, align-items, gap και οι αντίστροφες κατευθύνσεις λειτουργούν πάνω σε γεωμετρία που έχει ήδη μετρηθεί. Μετακινούν πλαίσια· ποτέ δεν ενεργοποιούν επαναμέτρηση περιεχομένου στοιχείου. Εκείνος ο διαχωρισμός είναι αυτό που εμποδίζει έναν σύνθετο πίνακα ελέγχου από το να μετρά τα παιδιά του αρκετές φορές

uses
  PDFlibrary;

var
  Lib: TPDFlib;
  Html, Remainder: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.NewDocument;
    Lib.SetPageSize('A4');
    Lib.NewPage;

    Html :=
      '<div style="display:flex; gap:12px;">' +
      '  <div style="flex:2 1 0; background:#f4f6f8; padding:8px;">' +
      '    <b>Revenue</b><br/>EUR 4,182,300</div>' +
      '  <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
      '    <b>Margin</b><br/>18.4%</div>' +
      '  <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
      '    <b>Backlog</b><br/>92 days</div>' +
      '</div>';

    Remainder := Lib.DrawHTMLTextBox(40, 40, 515, 120, Html);
    if Remainder <> '' then
      Log('content did not fit - carry the remainder to the next box');

    Lib.SaveToFile('dashboard.pdf');
  finally
    Lib.Free;
  end;
end;

Η τιμή επιστροφής είναι η συμβολοσειρά συνέχειας, που είναι ο τρόπος με τον οποίο κάθε σημείο εισόδου σχεδίασης HTML αναφέρει τι δεν χώρεσε. Περάστε την στο επόμενο πλαίσιο ή στην επόμενη σελίδα και η ροή συνεχίζεται από εκεί που σταμάτησε

Τοποθέτηση grid, και τι μπορεί να είναι ένα track

Τα track grid δέχονται σταθερά μήκη, ποσοστά, τη μονάδα fr, απλές εκφράσεις repeat() και minmax(). Η αυτόματη τοποθέτηση γεμίζει τον πίνακα κατάληψης ντετερμινιστικά, οπότε το ίδιο HTML πάντα παράγει την ίδια διάταξη. Ρητές συντεταγμένες επιτρέπεται να επικαλύπτονται, κάτι που είναι σκόπιμο: ένας σχεδιασμός που στρώνει ένα σήμα πάνω από μια κάρτα εκφράζει πρόθεση, όχι σφάλμα. Όταν δίνεται ρητά μόνο ένας άξονας, η τοποθέτηση αναζητά μόνο τον άλλο άξονα

Στοιχεία που εκτείνονται σε αρκετές γραμμές συνεισφέρουν το μετρημένο ύψος τους πίσω στις γραμμές που καλύπτουν, κατά μέσο όρο σε αυτές, κάτι που εμποδίζει ένα ψηλό εκτεινόμενο στοιχείο από το να στριμώξει μία μοναδική γραμμή αφήνοντας τους γείτονές του κοντούς:

Html :=
  '<div style="display:grid; grid-template-columns:repeat(3, 1fr); ' +
  '            gap:10px;">' +
  '  <div style="grid-row:span 2; background:#eef;">Site plan</div>' +
  '  <div>Inspector</div>' +
  '  <div>Date</div>' +
  '  <div style="grid-column:2 / span 2;">Findings summary</div>' +
  '</div>';

Remainder := Lib.DrawHTMLTextBox(40, 180, 515, 260, Html);

Τα παιδιά flex και grid αποδίδονται μέσω του ίδιου αποδότη HTML όπως όλα τα υπόλοιπα, που είναι η ιδιότητα που κάνει το χαρακτηριστικό χρησιμοποιήσιμο αντί για έναν ξεχωριστό κόσμο. Γραμματοσειρές, ο καταρράκτης CSS, σύνδεσμοι, εικόνες, πίνακες και περαιτέρω εμφωλευμένοι περιέκτες flex ή grid όλα συμπεριφέρονται μέσα σε ένα στοιχείο flex ακριβώς όπως στο ανώτατο επίπεδο, και το εξωτερικό σχέδιο διάταξης καταγράφει τις τελικές εντολές κειμένου και ορθογωνίου ώστε η επαναλαμβανόμενη σχεδίαση να επαναχρησιμοποιεί την υπάρχουσα προσωρινή μνήμη μέτρησης

Γιατί οι υποσημειώσεις είναι πρόβλημα σελιδοποίησης;

Μια υποσημείωση δεν είναι περιεχόμενο που ρέει μετά την παράγραφο που περιέχει την αναφορά της· είναι περιεχόμενο που πρέπει να εμφανιστεί στο κάτω μέρος του ίδιου πλαισίου με την αναφορά της. Αυτό αντιστρέφει τη συνηθισμένη σειρά μέτρησης, επειδή ο διαθέσιμος χώρος για το κύριο κείμενο εξαρτάται τώρα από περιεχόμενο που δεν έχει διαταχθεί ακόμη

Ο αποδότης επομένως μετρά τη σημείωση όταν συναντά την αναφορά, και αφαιρεί την περιοχή σημείωσης από τον προϋπολογισμό ύψους σώματος του τρέχοντος φραγμένου πλαισίου. Αν η αναφορά, το κείμενο σώματος μέχρι τώρα και η σημείωση δεν χωρούν όλα μαζί, ο δείκτης υποσημείωσης και οτιδήποτε ακολουθεί μετακινούνται στη συμβολοσειρά συνέχειας μαζί. Εκείνος ο κανόνας είναι αυτό που αποτρέπει τις δύο κλασικές αποτυχίες: μια σημείωση να επικαλύπτεται με το κείμενο σώματος, και μια σημείωση αφημένη σε μια σελίδα της οποίας η αναφορά βρίσκεται στην προηγούμενη

Σε ένα φραγμένο πλαίσιο η περιοχή σημείωσης καρφιτσώνεται στο κάτω μέρος με έναν κανόνα διαχωρισμού από πάνω της. Σε αφραγμό μέτρηση, όπου δεν υπάρχει ύψος πλαισίου για καρφίτσωμα, η περιοχή σημείωσης ακολουθεί αμέσως μετά το σώμα. Η αρίθμηση μεταφέρεται σε ένα πεδίο επέκτασης στη στοίβα συνέχειας, οπότε η DrawHTMLTextBox και η DrawHTMLStory κρατούν την ακολουθία να τρέχει σε στήλες και σελίδες, και μια συμβολοσειρά συνέχειας που παράχθηκε πριν υπάρξει εκείνο το πεδίο εξακολουθεί να συνεχίζει σωστά

// Οι υποσημειώσεις μέσα σε μια ιστορία πολλαπλών στηλών κρατούν μία
// τρέχουσα ακολουθία
Html := LoadTemplate('chapter.html');    // χρησιμοποιεί δείκτες float:footnote
Remainder := Lib.DrawHTMLStory(40, 40, 515, 700,
  2,        // στήλες
  16,       // διάκενο σε points
  20,       // μέγιστες σελίδες για αυτή την ιστορία
  Html);
if Remainder <> '' then
  Log('story exceeded its page budget');

Πρακτική καθοδήγηση για συγγραφείς προτύπων

Σχεδιάστε μέσα στα τεκμηριωμένα ανώτατα όρια. Ένας περιέκτης flex με περισσότερα από 256 άμεσα παιδιά είναι σχεδόν πάντα ένας πίνακας δεδομένων φορώντας κοστούμι flex, και η διαδρομή πίνακα τον μετρά καλύτερα ούτως ή άλλως. Ένα grid μεγαλύτερο από 64 επί 64 είναι ένα υπολογιστικό φύλλο, και ισχύει η ίδια συμβουλή. Για κείμενο σώματος πολλαπλών στηλών, η συμπεριφορά στήλης και συλλαβισμού που περιγράφεται στον συλλαβισμό και ισορροπημένες στήλες κειμένου διέπει το πώς φαίνεται η ροή μέσα σε κάθε στήλη

Μετρήστε πριν σχεδιάσετε όταν μια διάταξη πρέπει να χωρέσει. Η GetHTMLTextHeight αναφέρει το ύψος που θα χρειαζόταν ένα δεδομένο πλάτος, που είναι ο φθηνός τρόπος να αποφασίσετε ανάμεσα σε μια διάταξη και μια άλλη πριν δεσμευτείτε σε μελάνι. Και αντιμετωπίστε μια μη κενή συμβολοσειρά συνέχειας ως κανονική αντί για εξαιρετική: είναι ο μηχανισμός με τον οποίο μακρύ περιεχόμενο σελιδοποιείται, όχι σήμα σφάλματος

Όπου το HTML προέρχεται από μια μηχανή αναφορών αντί από χειρόγραφα πρότυπα, η διαδρομή βασισμένη σε σύνολο δεδομένων στη μηχανή αναφορών συνόλου δεδομένων συνδυάζεται καλά με αυτό, παράγοντας τη σήμανση που το flex και το grid έπειτα διατάσσουν. Και όταν το ίδιο περιεχόμενο πρέπει επίσης να φύγει ξανά από το PDF, η σημασιολογική διαδρομή εξαγωγής στην εξαγωγή PDF σε Markdown και DOCX κλείνει τον κύκλο

Η διάταξη HTML, η δημιουργία αναφορών και η σημασιολογική εξαγωγή αποτελούν μέρος μιας βιβλιοθήκης για Delphi, C++Builder και Free Pascal· η πλήρης λίστα χαρακτηριστικών βρίσκεται στη σελίδα της PDF Library for Delphi