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

Δηλωτική διάταξη PDF στη Delphi με σημασμένη έξοδο

Το HotPDF μπορεί να χτίσει ένα σελιδοποιημένο έγγραφο από ένα δηλωτικό δέντρο αντί από συντεταγμένες. Συναρμολογείτε ένα THPDFDOMDocument από ενότητες, στοίβες, κείμενο, λίστες και πίνακες, το παραδίδετε στον THPDFDOMRenderer, και ο αποδότης μετρά, σελιδοποιεί, σχεδιάζει τα στοιχεία εξοπλισμού σελίδας, και, όταν ζητηθεί, εκπέμπει το δέντρο δομής PDF/UA που καθιστά το αποτέλεσμα προσβάσιμο. Ο κώδικας διάταξης δεν υπολογίζει ποτέ συντεταγμένη y

Όποιος έχει συντηρήσει μια μηχανή αναφορών βασισμένη σε συντεταγμένες ξέρει γιατί αυτό έχει σημασία. Η πρώτη έκδοση λειτουργεί. Έπειτα μια διεύθυνση πελάτη μεγαλώνει σε τρεις γραμμές, ένας πίνακας αποκτά γραμμές, μια τοπικοποιημένη επικεφαλίδα αναδιπλώνεται, και κάθε επόμενη θέση y είναι λανθασμένη. Οι διορθώσεις συσσωρεύονται ως χειροκίνητοι έλεγχοι αλλαγής σελίδας διάσπαρτοι μέσα στη λογική επιχείρησης, και η απαίτηση σημασμένου PDF που φτάνει δύο χρόνια αργότερα δεν μπορεί να προσαρτηθεί σε κώδικα που δεν έχει ιδέα τι είναι μια παράγραφος

Τι κατέχει το δέντρο, και γιατί η κυριότητα είναι αυστηρή

Το DOM επιβάλλει μοναδική κυριότητα σε κάθε επίπεδο: το έγγραφο κατέχει τις ενότητές του, μια ενότητα κατέχει το σώμα, την κεφαλίδα και το υποσέλιδό της, και οι στοίβες, τα δοχεία και οι πίνακες κατέχουν τα παιδιά τους. Η επαναχρησιμοποίηση γίνεται μέσω Clone ή μέσω καταχωρημένου εργοστασίου, ποτέ με προσάρτηση του ίδιου αντικειμένου σε δύο γονείς. Αυτός ο κανόνας δεν είναι τυπικότητα. Ένα στοιχείο που εμφανίζεται δύο φορές στο δέντρο θα μετριόταν δύο φορές με διαφορετικούς περιορισμούς και θα απελευθερωνόταν δύο φορές κατά την αποδόμηση

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

uses
  HPDFDoc, HPDFLayoutDOM;

var
  Doc: THPDFDOMDocument;
  Section: THPDFDOMSection;
  Table: THPDFDOMTable;
  Row: THPDFDOMTableRow;
  I: Integer;
begin
  Doc := THPDFDOMDocument.Create;
  Doc.GenerateStructure := True;        // εκπομπή του δέντρου δομής PDF/UA
  Doc.Language := 'en-US';

  Section := Doc.AddSection;
  Section.PageWidth := 595;           // A4 σε points
  Section.PageHeight := 842;
  Section.MarginLeft := 56;
  Section.MarginTop := 56;
  Section.MarginRight := 56;
  Section.MarginBottom := 56;
  Section.Style.FontName := 'Helvetica';
  Section.Style.FontSize := 10;

  Section.Body.AddHeading('Annual maintenance report', 1);
  Section.Body.AddText('Every asset inspected during the reporting ' +
    'period is listed below, grouped by site.');
  Section.Body.AddSpacer(12);

  Table := THPDFDOMTable.Create('assets');
  Table.AddColumn(3);                 // βάρη, όχι απόλυτα πλάτη
  Table.AddColumn(1);
  Table.AddColumn(1);
  Table.RepeatHeaders := True;
  Row := Table.AddRow(18, True);      // γραμμή κεφαλίδας
  Row[0].Text := 'Asset';
  Row[1].Text := 'Last service';
  Row[2].Text := 'Status';
  for I := 0 to High(Assets) do
  begin
    Row := Table.AddRow(16);
    Row[0].Text := Assets[I].Name;
    Row[1].Text := Assets[I].ServiceDate;
    Row[2].Text := Assets[I].Status;
  end;
  Section.Body.Add(Table);
end;

Πώς αποφεύγει η σελιδοποίηση το τετραγωνικό κόστος;

Ο απλοϊκός τρόπος σελιδοποίησης ενός δέντρου είναι να κλωνοποιήσετε ό,τι δεν χώρεσε και να το μεταφέρετε στην επόμενη σελίδα. Σε έναν πίνακα με δέκα χιλιάδες γραμμές, αυτό κλωνοποιεί τις υπόλοιπες γραμμές μία φορά ανά σελίδα και μετατρέπει ένα γραμμικό έγγραφο σε τετραγωνικό

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

Η μέτρηση παραμένει ειλικρινής ως προς τις παρενέργειες. Η THPDFLayoutElement.Measure απαιτείται να είναι απαλλαγμένη από παρενέργειες σχεδίασης, και η πραγματική τοποθέτηση εκτελείται πάντα μέσω της THotPDF.PlaceLayoutElement, της ίδιας κεντρικής ρουτίνας που ξαναμετρά το τοποθετημένο θραύσμα, στήνει την κυριότητα υπερχείλισης και καταγράφει διαγνωστικά. Ο αποδότης DOM αποφασίζει μόνο την πολιτική νέας σελίδας, τον εξοπλισμό σελίδας, το διάστημα και τη διάρκεια ζωής των συνεχειών

Οι κανόνες κεφαλίδας πίνακα που αποτρέπουν ένα ατέρμονο έγγραφο

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

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

Υπάρχει επίσης ένα αμυντικό ανώτατο όριο στο βάθος συνέχειας, επειδή ένα προσαρμοσμένο στοιχείο είναι ελεύθερο να υλοποιήσει το Split με τρόπο που πάντα επιστρέφει ισοδύναμη ουρά. Ο αποδότης ελέγχει το όριο μετά την αποσύνδεση της ουράς και πριν την έναρξη της επόμενης σελίδας, και η τρέχουσα επανάληψη απελευθερώνει την ουρά στο δικό της μπλοκ finally, ώστε ένα κακώς συμπεριφερόμενο στοιχείο τρίτου μέρους να αποτυγχάνει με διαγνώσιμο σφάλμα αντί να γεμίζει έναν δίσκο

Ένα λογικό στοιχείο, πολλά θραύσματα σελίδας

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

Το HotPDF επιλύει αυτό διατηρώντας ένα μοναδικό στοιχείο δομής και προσαρτώντας μια αναφορά σημασμένου περιεχομένου στον πίνακα /K του για κάθε θραύσμα, με το ζεύγος /Pg και /MCID να προσδιορίζει τη σελίδα και το αναγνωριστικό. Η θέση ParentTree για εκείνο το MCID δείχνει πίσω στο ίδιο στοιχείο. Αυτό είναι ακριβώς ό,τι αναμένει το ISO 14289, και είναι ο λόγος που οι κλώνοι συνέχειας διαφέρουν από τους συνηθισμένους κλώνους: ένας συνηθισμένος Clone σημαίνει νέο λογικό περιεχόμενο και λαμβάνει νέα σημασιολογική ταυτότητα, ενώ ο εσωτερικός κλώνος συνέχειας κληρονομεί την ταυτότητα του στοιχείου που συνεχίζει

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

Κανόνες δομής που ο αποδότης επιβάλλει εκ των προτέρων

Με ενεργοποιημένο το GenerateStructure, αρκετοί κανόνες PDF/UA ελέγχονται καθώς το δέντρο αποδίδεται, αντί μετά την ύπαρξη του αρχείου. Οι επικεφαλίδες ξεκινούν από το επίπεδο 1 και δεν επιτρέπεται να παραλείπουν επίπεδα. Το LI μπορεί να εμφανιστεί μόνο μέσα σε L, και τα Lbl και LBody μόνο μέσα σε LI. Το TR ανήκει σε πίνακα, και τα TH και TD σε γραμμή. Ένα σχήμα χωρίς εναλλακτικό κείμενο απορρίπτεται σε λειτουργία PDF/UA

Η πρόωρη απόρριψη είναι η σκόπιμη επιλογή εδώ. Ένας επικυρωτής που αναφέρει ελλείπον εναλλακτικό κείμενο αφού το έγγραφο έχει γραφτεί σας λέει ότι μια παρτίδα δέκα χιλιάδων καταστάσεων χρειάζεται αναδημιουργία· ένας αποδότης που απορρίπτει το στοιχείο σας λέει ποιο στοιχείο, ενώ τα δεδομένα που το παρήγαγαν βρίσκονται ακόμη στο πεδίο εφαρμογής. Η επαλήθευση συμμόρφωσης εξακολουθεί να ανήκει στον αγωγό ως ξεχωριστό βήμα, και οι μηχανισμοί αυτού καλύπτονται στην επικύρωση PDF/A, PDF/X και PDF/UA

var
  Pdf: THotPDF;
  Renderer: THPDFDOMRenderer;
  Stats: THPDFDOMRenderStatistics;
begin
  Pdf := THotPDF.Create(nil);
  Renderer := THPDFDOMRenderer.Create;
  try
    Pdf.FileName := 'maintenance-report.pdf';
    Pdf.BeginDoc;
    Stats := Renderer.Render(Doc, Pdf);
    Pdf.EndDoc;

    Writeln(Format('%d page(s), %d placement(s), %d split(s)',
      [Stats.PageCount, Stats.PlacementCount, Stats.SplitCount]));
    Writeln(Format('structure elements=%d marked content=%d artifacts=%d',
      [Stats.StructureElementCount, Stats.MarkedContentCount,
       Stats.ArtifactCount]));
    Writeln(Format('deepest continuation chain: %d',
      [Stats.MaximumContinuationDepth]));
  finally
    Renderer.Free;
    Doc.Free;
    Pdf.Free;
  end;
end;

Η καταγραφή στατιστικών είναι πιο χρήσιμη απ' ό,τι φαίνεται αρχικά. Το SplitCount που αυξάνεται απότομα μετά από μια αλλαγή προτύπου συνήθως σημαίνει ότι ένα στοιχείο άρχισε να μετριέται ψηλότερο από το δοχείο του. Το MaximumContinuationDepth που ανεβαίνει σταδιακά είναι η έγκαιρη προειδοποίηση για ένα στοιχείο του οποίου το Split κάνει πολύ μικρή πρόοδο ανά σελίδα. Και η σύγκριση του ArtifactCount με τον αριθμό σελίδων συνέχειας επιβεβαιώνει ότι οι επαναλαμβανόμενες κεφαλίδες πράγματι σημάνθηκαν ως τεχνουργήματα

Πού ταιριάζει το DOM δίπλα στο άμεσο API

Το DOM δεν αντικαθιστά την άμεση σχεδίαση· κάθεται πάνω στα ίδια αντικείμενα σελίδας. Οτιδήποτε τοποθετεί ο αποδότης μπορεί να διαπλέκεται με άμεσες κλήσεις στο THotPDF, κάτι που έχει σημασία όταν μια αναφορά χρειάζεται ένα χειροκίνητα τοποθετημένο στοιχείο όπως μια εικόνα υπογραφής σε ακριβή θέση. Το κλείσιμο σελίδων παραμένει υπό τον έλεγχο των AddPage και EndDoc, οπότε η άμεση λειτουργία εκκένωσης δεν κρατά ολοκληρωμένες σελίδες στη μνήμη και η μνήμη κατοίκησης παραμένει καθοριζόμενη από τις τρέχουσες συνέχειες, τους πόρους γραμματοσειράς και το συνηθισμένο γράφημα αντικειμένων εγγράφου

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

Η δηλωτική διάταξη, η αυτόματη σήμανση και το API άμεσης σχεδίασης διανέμονται στο ίδιο εξάρτημα για Delphi και C++Builder· η πλήρης λίστα χαρακτηριστικών βρίσκεται στη σελίδα του εξαρτήματος HotPDF PDF για Delphi