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

Εξαγωγή και εισαγωγή XFDF στο Delphi με το PDFium Component

Το PDFium Component για Delphi και Lazarus ανταλλάσσει δεδομένα φορμών και σχόλια (annotations) μέσω του XFDF, της μορφής ανταλλαγής XML που ορίζεται από το ISO 19444-1. Η μέθοδος TPdf.ExportXFDF σειριοποιεί κάθε τιμή πεδίου φόρμας και κάθε υποστηριζόμενο σχόλιο στο φορτωμένο έγγραφο σε ένα αρχείο XFDF ή TStream· η TPdf.ImportXFDF διαβάζει πίσω ένα έγγραφο XFDF, αναδημιουργώντας τα σχόλια στις σελίδες τους και εφαρμόζοντας τις τιμές των πεδίων. Αυτός ο ένας κύκλος round-trip καλύπτει δύο ροές εργασίας που χρειάζεται τελικά κάθε εφαρμογή εγγράφων: ένας αναθεωρητής επισημαίνει ένα PDF στο Acrobat και σας στέλνει τα σχόλια για συγχώνευση, ή ένας κανόνας συμμόρφωσης ορίζει ότι τα δεδομένα της φόρμας πρέπει να ζουν σε ένα ξεχωριστό αρχείο, κατάλληλο για diff και έλεγχο, αντί να είναι ενσωματωμένα στο ίδιο το PDF

Υποστηρίζει το PDFium εισαγωγή και εξαγωγή XFDF;

Η ίδια η βιβλιοθήκη PDFium δεν την υποστηρίζει. Το εγγενές C API δεν έχει καθόλου συναρτήσεις XFDF ή FDF — τίποτα στις δημόσιες κεφαλίδες του δεν διαβάζει ή γράφει καμία από τις δύο μορφές, επομένως κανένα επίπεδο wrapping δεν μπορεί να σας οδηγήσει εκεί. Ως εκ τούτου, το PDFium Component υλοποιεί ολόκληρη τη μηχανή XFDF σε Pascal, σε μια ειδική μονάδα (unit) που δημιουργεί και αναλύει το XML απευθείας πάνω στο υπάρχον μοντέλο δεδομένων σχολίων και πεδίων φόρμας του TPdf. Ο συγγραφέας εκπέμπει το XML με το χέρι και ο αναγνώστης είναι ένας χειροκίνητα γραμμένος αναλυτής (parser) μηχανής καταστάσεων, οπότε η δυνατότητα δεν προσθέτει καμία εξάρτηση από το Delphi XMLDoc ή τις μονάδες FPC DOM και συμπεριφέρεται πανομοιότυπα σε Delphi και Lazarus

Αυτή η σχεδιαστική επιλογή έχει σημασία όταν αξιολογείτε εναλλακτικές λύσεις. Εάν καλείτε ακατέργαστες συναρτήσεις FPDF_* από την Pascal, το XFDF είναι ένας τοίχος: η δυνατότητα απλά δεν υπάρχει στο DLL. Το στοιχείο διασχίζει αυτόν τον τοίχο αντιμετωπίζοντας το XFDF ως ένα καθαρό πρόβλημα σειριοποίησης — συλλέγει τις τιμές των πεδίων και τις εγγραφές σχολίων που ο wrapper γνωρίζει ήδη πώς να διαβάζει, τις γράφει ως τυπικό XML και αντιστρέφει τη διαδικασία κατά την εισαγωγή

Τι περιέχει ένα αρχείο XFDF;

Το ISO 19444-1 ορίζει το XFDF ως την αναπαράσταση XML του FDF, και το ωφέλιμο φορτίο του χωρίζεται σε δύο μπλοκ κορυφαίου επιπέδου. Το στοιχείο <fields> μεταφέρει ιεραρχικά ονόματα πεδίων φόρμας και τις τιμές τους — οτιδήποτε πληκτρολόγησε, επέλεξε ή τσέκαρε ένας χρήστης σε μια AcroForm. Το στοιχείο <annots> μεταφέρει τα σχόλια: η μέθοδος TPdf.ExportXFDF εκπέμπει 18 υποτύπους — κείμενο (text), επισήμανση (highlight), υπογράμμιση (underline), διαγραφή (strikeout), κυματιστό (squiggly), γραμμή (line), κύκλο (circle), τετράγωνο (square), caret, πολύγωνο (polygon), πολυγραμμή (polyline), σφραγίδα (stamp), μελάνι (ink), ελεύθερο κείμενο (freetext), συνημμένο αρχείο (fileattachment), ήχο (sound), σύνδεσμο (link) και απόκρυψη (redact). Τα widget σχόλια απουσιάζουν σκόπιμα από το <annots> επειδή τα δεδομένα τους ταξιδεύουν στο <fields>, και οι εσωτερικοί υποτύποι προγράμματος προβολής όπως το Popup δεν αποτελούν μέρος του λεξιλογίου του XFDF

Για να γίνει ο κύκλος round-trip πιστός, η εγγραφή TPdfAnnotation επεκτάθηκε με τα μεταδεδομένα που αναμένει το XFDF: Name (το μοναδικό αναγνωριστικό NM), Subject, ModificationDate, CreationDate, Icon, Opacity, τελικά σημεία γραμμής, κορυφές πολυγώνου και πολυγραμμής, και διαδρομές χειρονομιών μελανιού. Κάθε νέο πεδίο συνδυάζεται με μια σημαία φρουρό τύπου boolean Has*, έτσι ώστε ο κώδικας που δημιουργούσε σχόλια έναντι παλαιότερων εκδόσεων να συνεχίζει να μεταγλωττίζεται και να παράγει τα ίδια λεξικά — ένα μη ορισμένο πεδίο δεν γράφεται ποτέ. Εάν δημιουργείτε ήδη σήμανση προγραμματιστικά, η ίδια εγγραφή που χρησιμοποιείτε για σχόλια σήμανσης κειμένου με quad points μεταφέρει τώρα όλα όσα χρειάζεται το XFDF

Πώς εξάγετε δεδομένα φορμών και σχόλια σε XFDF;

Μια κλήση κάνει όλη τη δουλειά για το έγγραφο. Φορτώστε το PDF με ενεργοποιημένη τη συμπλήρωση φόρμας, καλέστε την ExportXFDF, και το στοιχείο διατρέχει κάθε σελίδα, συλλέγει τις τιμές των πεδίων και τα σχόλια, και γράφει το XML. Η τιμή επιστροφής είναι ο αριθμός των byte UTF-8 που γράφτηκαν, γεγονός που αποτελεί έναν εύκολο έλεγχο ορθότητας (sanity check) στα αρχεία καταγραφής

var
  Pdf: TPdf;
  Bytes: Integer;
begin
  Pdf := TPdf.Create(Self);
  Pdf.FormFill := True;            // needed so field values are live
  Pdf.FileName := 'expense-report.pdf';
  Pdf.Active := True;
  Bytes := Pdf.ExportXFDF('expense-report.xfdf');
  ShowMessage(Format('%d bytes of XFDF written', [Bytes]));
end;

Και οι δύο κατευθύνσεις έχουν μια υπερφόρτωση TStream, οπότε τίποτα δεν επιβάλλει ένα προσωρινό αρχείο στο δίσκο. Εαγωγή σε ένα TMemoryStream είναι το φυσικό σχήμα όταν το XFDF κατευθύνεται σε μια απάντηση HTTP, σε ένα blob βάσης δεδομένων ή σε μια υπογεγραμμένη καταχώρηση αρχείου

var
  Buffer: TMemoryStream;
begin
  Buffer := TMemoryStream.Create;
  try
    Pdf.ExportXFDF(Buffer);        // same XML, no file involved
    Buffer.Position := 0;
    // hand the stream to a web response, blob column, or zip entry
  finally
    Buffer.Free;
  end;
end;

Πώς το ImportXFDF συγχωνεύει τα σχόλια πίσω στο έγγραφο;

Η πλευρά της εισαγωγής είναι εκεί όπου κλείνει η ροή εργασίας του αναθεωρητή. Ένας συνάδελφος σχολιάζει το συμβόλαιο στο Acrobat, εξάγει τα σχόλια ως XFDF και σας στέλνει μερικά kilobyte XML αντί για ένα δεύτερο αντίγραφο του PDF. Η μέθοδος TPdf.ImportXFDF αναλύει αυτό το αρχείο, δημιουργεί κάθε σχόλιο στη σελίδα που ονομάζει το χαρακτηριστικό της page, και γράφει τις τιμές των πεδίων στα αντίστοιχα widget σχόλια. Η συνάρτηση επιστρέφει το συνδυασμένο άθροισμα των πεδίων συν των σχολίων που εφαρμόστηκαν, έτσι ώστε το UI να μπορεί να επιβεβαιώσει ακριβώς πόσα έφτασαν

var
  Applied: Integer;
begin
  Applied := Pdf.ImportXFDF('review-comments.xfdf');
  StatusBar.SimpleText :=
    Format('%d fields and annotations merged', [Applied]);
end;

Ο αναλυτής (parser) είναι ένας χειροκίνητα γραμμένος σαρωτής ετικετών (tag scanner) κατασκευασμένος για ανοχή και όχι για αυστηρότητα: άγνωστα στοιχεία παρακάμπτονται, οι οδηγίες επεξεργασίας, οι δηλώσεις DOCTYPE και τα σχόλια αγνοούνται, τα μη αναγνωρίσιμα ονόματα σημαιών απορρίπτονται και οι τιμές των χαρακτηριστικών αποκωδικοποιούνται (unescape) μέσω του πλήρους συνόλου οντοτήτων XML και αναφορών αριθμητικών χαρακτήρων, συμπεριλαμβανομένων των ζευγών υποκατάστατων UTF-16. Το XFDF που παράγεται από το Acrobat, από ένα πρόγραμμα προβολής ιστού ή από άλλο εργαλείο συγχωνεύεται χωρίς δυσκολία. Μόλις τα σχόλια βρεθούν στο έγγραφο, ο κύκλος αναθεώρησης συνεχίζεται με τον μηχανισμό απαντήσεων και κατάστασης που περιγράφεται στο άρθρο για τη δημιουργία ροής εργασίας αναθεώρησης σχολίων με το PDFium Component, και οι εισαγόμενες τιμές πεδίων συμμετέχουν στην κανονική λογική tab-order που καλύπτεται στο πλοήγηση πεδίων φόρμας

Γιατί οι διαχωριστές δεκαδικών έχουν σημασία για το XFDF;

Κάθε σχόλιο στο XFDF τοποθετείται από συμβολοσειρές συντεταγμένων — rect="70.5,540,200,560" και παρόμοια — και το ISO 19444-1 απαιτεί την τελεία ως διαχωριστικό δεκαδικών. Η προεπιλεγμένη μορφοποίηση αριθμών κινητής υποδιαστολής στο Delphi ακολουθεί τις τοπικές ρυθμίσεις των Windows (locale), οπότε σε ένα γερμανικό ή γαλλικό σύστημα ένα απλό FloatToStr μετατρέπει το 70.5 σε 70,5, γεγονός που καταστρέφει μια λίστα συντεταγμένων διαχωρισμένη με κόμματα σε σκουπίδια που άλλοι επεξεργαστές απορρίπτουν ή διαβάζουν λάθος. Το PDFium Component κανονικοποιεί κάθε αριθμό που γράφει: ο διαχωριστής δεκαδικών των τοπικών ρυθμίσεων ξαναγράφεται ως τελεία και τα τελικά μηδενικά περικόπτονται, οπότε το εξαγόμενο αρχείο είναι πανομοιότυπο σε byte είτε παρήχθη σε μηχάνημα en-US είτε de-DE. Εάν έχετε αποσφαλματώσει ποτέ ένα εργαλείο PDF που λειτουργούσε στο γραφείο και αποτύγχανε σε έναν Ευρωπαίο πελάτη, έχετε συναντήσει ακριβώς αυτήν την κατηγορία σφαλμάτων — αξίζει να γνωρίζετε ότι το στοιχείο το χειρίζεται για εσάς

Ποια είναι τα όρια του κύκλου round-trip;

Δύο όρια αξίζει να αναφερθούν ξεκάθαρα. Πρώτον, το XFDF μεταφέρει δεδομένα σχολίων, όχι την εμφανισιακή τους απόδοση (appearance streams) — οι ροές εμφάνισης δεν αποτελούν μέρος της μορφής, επομένως το πρόγραμμα προβολής λήψης αναδημιουργεί την εμφάνιση κάθε σχολίου από τις ιδιότητές του. Μια επισήμανση ή ένα τετράγωνο θα αποδοθεί σωστά παντού· μια σφραγίδα με προσαρμοσμένη εμφάνιση θα επιστρέψει σε αυτό που σχεδιάζει το πρόγραμμα προβολής στόχου για αυτό το όνομα σφραγίδας. Δεύτερον, το υποκείμενο API του PDFium διαθέτει setters για συμβολοσειρές αλλά κανέναν για αριθμούς σχολίων ή γεωμετρία διαδρομής, επομένως η Opacity, τα τελικά σημεία γραμμής, οι κορυφές και οι χειρονομίες μελανιού είναι μόνο για εξαγωγή: το στοιχείο τις διατηρεί πιστά κατά την εγγραφή XFDF, αλλά δεν μπορεί να τις γράψει πίσω σε ένα PDF κατά την εισαγωγή. Οι τιμές πεδίων, τα περιεχόμενα, τα χρώματα, τα rects, τα quad points, οι ημερομηνίες και τα μεταδεδομένα ταυτότητας εκτελούν τον πλήρη κύκλο round-trip και προς τις δύο κατευθύνσεις

Για ένα πρακτικό ξεκίνημα, το δείγμα XfdfLab παρέχεται στα σετ δειγμάτων Delphi, C++Builder και Lazarus και συνδέει και τις δύο κλήσεις σε κουμπιά πάνω από οποιοδήποτε PDF ανοίγετε. Η υποστήριξη XFDF περιλαμβάνεται στην τρέχουσα έκδοση του PDFium Component, παράλληλα με τα APIs σχολίων και φορμών πάνω στα οποία βασίζεται