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

Αυτόματη επισήμανση δομής για προσβάσιμο PDF σε Delphi

Το PDF Library for Delphi μπορεί να επισημάνει ένα έγγραφο όσο αυτό σχεδιάζεται. Ενεργοποιήστε το SetAutoTagMode και οι κοινές κλήσεις DrawText γίνονται παράγραφοι, το κείμενο που σχεδιάζεται αμέσως μετά την RegisterHeading γίνεται τίτλος αντίστοιχου επιπέδου, οι κεφαλίδες και τα υποσέλιδα επανάληψης γίνονται artifacts που ο αναγνώστης παραλείπει, οι εικόνες γίνονται figures, και η DrawTableRows μεταφέρει τον πίνακα, τις γραμμές και τα κελιά του στο δέντρο δομής

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

Τι καλύπτουν τα bit της λειτουργίας

Η SetAutoTagMode δέχεται μια μάσκα bit και επιστρέφει την προηγουμένως ισχύουσα λειτουργία. Το AUTOTAG_TEXT (1) επισημαίνει το κείμενο ως παράγραφο, ή ως τίτλο όταν κάποιος είναι σε αναμονή. Το AUTOTAG_FURNITURE (2) μαρκάρει τις κεφαλίδες, τα υποσέλιδα και τους αριθμούς σελίδων επανάληψης ως artifacts. Το AUTOTAG_FIGURE (4) μετατρέπει μια σχεδιασμένη εικόνα σε figure, ή σε artifact όταν έχει δηλωθεί ως διακοσμητική. Το AUTOTAG_TABLE (8) μεταφέρει τους σχεδιασμένους πίνακες στο δέντρο δομής. Το AUTOTAG_DEFAULT είναι 15, δηλαδή και τα τέσσερα

Η ενεργοποίηση της λειτουργίας μαρκάρει επίσης το έγγραφο ως tagged, και αυτό το βήμα είναι λιγότερο καλλυντικό από όσο ακούγεται. Ένας αναγνώστης θεωρεί ένα έγγραφο μη tagged εκτός αν ο κατάλογος λέει διαφορετικά (ISO 32000-1 §14.7.1), οπότε ένα αρχείο που φέρει πλήρες δέντρο δομής χωρίς δήλωση /MarkInfo ανακοινώνεται από τις βοηθητικές τεχνολογίες ως μην έχοντας καμία δομή. Το δέντρο υπάρχει· τίποτα δεν το διαβάζει

Διάγραμμα των μασκών bit του SetAutoTagMode που αντιστοιχούν απλές κλήσεις σχεδίασης Delphi σε δομικό δέντρο PDF με ετικέτες, με τη σημαία Marked επισημασμένη ως υποχρεωτική
Κάθε ενεργό bit του SetAutoTagMode δρομολογεί τις συνηθισμένες κλήσεις σχεδίασης σε αντίστοιχο στοιχείο του δέντρου δομής
var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);
    Lib.SetAutoTagMode(AUTOTAG_DEFAULT);   // text + furniture + figures + tables
    Lib.AddStandardFont(4);
    Lib.SetTextSize(18);
    Lib.RegisterHeading(1, 'Annual service report');
    Lib.DrawText(72, 96, 'Annual service report');   // becomes H1
    Lib.SetTextSize(11);
    Lib.DrawText(72, 130, 'Every unit installed before 2024 was inspected.');
    Lib.SaveToFile('report.pdf');
  finally
    Lib.Free;
  end;
end;

Πώς ξέρει ένας τίτλος σε ποιο κείμενο ανήκει;

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

Ο ίδιος κανόνας «καταναλώνεται σε ένα στοιχείο» διέπει τα figures. Η RegisterFigure παρέχει την περιγραφή που θα φέρει η επόμενη εικόνα, και η RegisterDecoration δηλώνει την επόμενη εικόνα ως υπομνηματική γραμμή, περίγραμμα ή φόντο που δεν φέρει νόημα. Και οι δύο καταναλώνονται από μία εικόνα, ώστε μια μεταγενέστερη εικόνα να μην κληρονομεί ποτέ περιγραφή προορισμένη για προηγούμενη — αυτός είναι ο τρόπος με τον οποίο το εναλλακτικό κείμενο καταλήγει προσαρτημένο σε λάθος εικόνα σε χειροκίνητα tagged κώδικα

Η περιγραφή έχει μεγαλύτερη σημασία από οποιαδήποτε άλλη μεμονωμένη συμβολοσειρά σε ένα προσβάσιμο έγγραφο. Ένας αναγνώστης χωρίς όραση λαμβάνει την περιγραφή στη θέση της εικόνας, και αυτό είναι το σύνολο όσων λαμβάνει. Το «Γράφημα» δεν είναι περιγραφή· το «Τριμηνιαία έσοδα ανά περιοχή, με την ανατολική περιοχή υψηλότερη στο Γ3 τρίμηνο» είναι

Διάγραμμα του PDF Library for Delphi που δείχνει μια καταχωρημένη επικεφαλίδα PDF να περιμένει όσο παρεμβάλλεται μια εικόνα, ενώ το RegisterFigure και το RegisterDecoration δαπανώνται το καθένα σε ακριβώς μία εικόνα
Η RegisterHeading περιμένει το επόμενο κείμενο, ανεξάρτητα από ό,τι άλλο σχεδιάζεται στο μεταξύ
Lib.RegisterFigure('Exploded view of the gearbox assembly');
Lib.AddImageFromFile('gearbox.png', 0);      // becomes a tagged Figure

Lib.RegisterDecoration;                       // meaningless rule
Lib.AddImageFromFile('divider.png', 0);       // drawn inside a layout artifact

Πίνακες, κεφαλίδες και πού ζει η απόφαση επανάληψης

Με το bit του πίνακα ενεργό, η DrawTableRows μεταφέρει τον πίνακα, τις γραμμές και τα κελιά του στο δέντρο δομής, ώστε ένας αναγνώστης να μπορεί να πει σε ποια στήλη βρίσκεται μια τιμή αντί να διαβάζει ολόκληρο τον πίνακα ως μια ακολουθία άσχετου κειμένου. Η SetTableHeaderRowCount ονομάζει πόσες από τις πρώτες γραμμές είναι κεφαλίδες· αυτές οι γραμμές γράφονται ως κελιά κεφαλίδας που φέρουν εμβέλεια στήλης, που είναι ακριβώς αυτό που επιτρέπει σε έναν αναγνώστη να ανακοινώνει την επικεφαλίδα της τιμής στην οποία βρίσκεται ο χρήστης

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

Διάγραμμα του PDF Library for Delphi όπου κελιά επικεφαλίδας πίνακα φέρουν εμβέλεια στήλης στο δομικό δέντρο, ενώ το RepeatHeaderRows κρατά την επανάληψη στο σπάσιμο σελίδας απόφαση μόνο διάταξης
Οι γραμμές κεφαλίδας υπάρχουν μία φορά στο δέντρο δομής, με την εμβέλεια στηλών προσαρτημένη σε κάθε κελί TH
var
  TableID: Integer;
begin
  TableID := Lib.CreateTable(40, 3);
  Lib.SetTableHeaderRowCount(TableID, 1);       // η γραμμή 1 είναι η ζώνη κεφαλίδας
  Lib.SetTableCellContent(TableID, 1, 1, 'Part');
  Lib.SetTableCellContent(TableID, 1, 2, 'Torque');
  Lib.SetTableCellContent(TableID, 1, 3, 'Unit');
  // ... συμπληρώστε τις γραμμές δεδομένων ...
  // Σχεδιάστε τις γραμμές 1..40 σε ζώνη 600pt, επαναλαμβάνοντας μία γραμμή κεφαλίδας ανά σελίδα
  Lib.DrawTaggedTableRows(TableID, 72, 150, 600, 1, 40, 1);
end;

Συνδυασμός αυτόματης και χειροκίνητης επισήμανσης

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

Δύο κανόνες ασφαλείας κρατούν την έξοδο καθαρή. Τίποτα δεν επισημαίνεται μέσα σε artifact, επειδή το περιεχόμενο που μαρκαρίζεται ως artifact δεν πρέπει να φέρει κανένα στοιχείο δομής. Και το κενό κείμενο δεν ανοίγει κανένα στοιχείο, οπότε ένα παραπεταμένο DrawText με κενή συμβολοσειρά δεν μπορεί να παράξει στοιχείο δομής που ένας αναγνώστης θα ανακοινώσει ως κενό. Και τα δύο είναι ο τύπος ελαττώματος που τα χειροκίνητα tagged έγγραφα συσσωρεύουν αθόρυβα και που ένας validator αναφέρει μαζικά μήνες αργότερα

Τι δεν αποφασίζει ακόμα για εσάς η αυτόματη επισήμανση

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

Όταν το έγγραφο ολοκληρωθεί, επικυρώστε αντί να υποθέσετε: οι σημειώσεις για το preflight PDF/A και PDF/UA δείχνουν πώς να πάρετε ετυμηγορία πάνω στη δομή που παράξατε, και η ανάλυση της εξαγωγής αναφορών βάσει dataset καλύπτει πού αυτές οι κλήσεις χωράνε σε μια μηχανή αναφορών που παράγει τη διάταξή της από δεδομένα

Το PDF Library for Delphi είναι μια native Pascal βιβλιοθήκη PDF για Delphi, C++Builder και Lazarus χωρίς εξωτερικό PDF runtime, οπότε η προσβάσιμη έξοδος παράγεται από τον ίδιο κώδικα που σχεδιάζει το έγγραφο — δείτε τη σελίδα προϊόντος PDF Library for Delphi για το πλήρες API και τη λίστα πλατφορμών