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

Σχετιζόμενα αρχεία επιπέδου σελίδας σε PDF 2.0 με PDFlibPas

Το PDFlibPas συνδέει ένα ενσωματωμένο αρχείο με μία συγκεκριμένη σελίδα και όχι με το έγγραφο συνολικά, γράφοντας έναν πίνακα /AF στο λεξικό της σελίδας, ενώ το ίδιο το payload παραμένει καταχωρημένο στο EmbeddedFiles name tree του εγγράφου. Αυτός ο διαχωρισμός είναι όσο περιγράφει το ISO 32000-2 §14.13, και είναι όσο χρειάζεται για να απαντήσει ένας reader την ερώτηση που δεν μπορεί ένα attachment επιπέδου εγγράφου: σε ποια σελίδα ανήκουν αυτά τα δεδομένα

Οι περιπτώσεις χρήσης είναι πιο ειδικές από τα γενικά attachments. Μια έκθεση αποτύπωσης όπου κάθε σελίδα κουβαλά την ακατέργαστη σειρά μετρήσεων πίσω από το διάγραμμά της. Μια σαρωμένη παρτίδα όπου κάθε σελίδα κρατά το OCR αποτέλεσμα που παρήγαγε το text layer της. Ένα σετ σχεδίων όπου κάθε φύλλο κουβαλά το CAD extract από το οποίο αποδόθηκε. Σε κάθε περίπτωση μια λίστα attachments επιπέδου εγγράφου θα ήταν ένας σωρός αρχείων με ονόματα που κωδικοποιούν αριθμούς σελίδων, κάτι που είναι σύμβαση και όχι δομή

Ένα payload, δύο σημεία από όπου αναφέρεται

Το σημαντικό δομικό σημείο είναι ότι η συσχέτιση επιπέδου σελίδας δεν δημιουργεί δεύτερο αντίγραφο τίποτα. Το αρχείο ενσωματώνεται μία φορά και καταχωρείται στο EmbeddedFiles name tree ακριβώς όπως ένα attachment επιπέδου εγγράφου, με τα ίδια μηχανήματα file specification. Αλλάζει το πού γράφεται η αναφορά και το relationship key της: στο λεξικό της σελίδας αντί για τον κατάλογο του εγγράφου

Δύο συνέπειες προκύπτουν. Πρώτον, ένας reader που γνωρίζει μόνο attachments επιπέδου εγγράφου εξακολουθεί να βρίσκει το payload, γιατί είναι στο name tree όπου τέτοιος reader ψάχνει. Δεύτερον, το σβήσιμο της συσχέτισης σελίδας αφαιρεί τον δεσμό, όχι το αρχείο. Η ClearPageAssociatedFiles αποσπά τη σελίδα από τα σχετιζόμενα αρχεία της και αφήνει τα payloads προσβάσιμα μέσω του name tree, που είναι η συντηρητική συμπεριφορά: μια πράξη που λέει σβήσε τη συσχέτιση δεν πρέπει να καταστρέφει σιωπηλά δεδομένα που μπορεί να αναφέρει άλλο τμήμα του εγγράφου

Δομή ενός σχετιζόμενου αρχείου επιπέδου σελίδας σε έγγραφο PDF 2.0 γραμμένο από το PDFlibPas: το payload ενσωματώνεται μία φορά και καταχωρείται στο EmbeddedFiles name tree κάτω από τον κατάλογο του εγγράφου, ενώ το λεξικό της σελίδας κουβαλά πίνακα /AF που αναφέρει το ίδιο file specification με κλειδί AFRelationship, ώστε η ClearPageAssociatedFiles να αποσπά τον δεσμό χωρίς να καταστρέψει δεδομένα
Η συσχέτιση επιπέδου σελίδας προσθέτει δεύτερη αναφορά, όχι δεύτερο αντίγραφο: readers που ξέρουν μόνο attachments επιπέδου εγγράφου εξακολουθούν να βρίσκουν το payload στο name tree, και το σβήσιμο του δεσμού σελίδας αφήνει το ενσωματωμένο stream προσβάσιμο

Η συνάρτηση έχει μία σκόπιμα στενή συνθήκη επιτυχίας που αξίζει να ξέρετε. Αναφέρει επιτυχία μόνο όταν η σελίδα όντως κουβαλούσε κλειδί /AF. Μια σελίδα που δεν είχε ποτέ συσχετίσεις επιστρέφει αποτυχία αντί για χαρούμενη επιβεβαίωση, ώστε ένας caller να μην μπερδέψει ένα no-op με ολοκληρωμένο καθάρισμα

var
  Lib: TPDFlib;
  Idx, I: Integer;
begin
  Lib := TPDFlib.Create(nil);
  try
    Lib.LoadFromFile('survey-report.pdf');

    // Σύνδεση της σειράς μετρήσεων που παρήγαγε το διάγραμμα της σελίδας 3
    Idx := Lib.AddPageAssociatedFileFromFile(3,
      'series-03.csv',            // αρχείο στον δίσκο
      'measurements.csv',         // όνομα εμφάνισης μέσα στο PDF
      'text/csv',                 // τύπος MIME
      'Raw measurement series for figure 3',
      'Data');                    // AFRelationship, ISO 32000-2 14.13

    if Idx < 0 then
      raise Exception.Create('page association refused');

    for I := 0 to Lib.GetPageAssociatedFileCount(3) - 1 do
      Writeln('page 3 associated file, embedded index ',
        Lib.GetPageAssociatedFileEmbeddedIndex(3, I));

    Lib.SaveToFile('survey-report-with-data.pdf');
  finally
    Lib.Free;
  end;
end;

Η συμβολοσειρά σχέσης δεν είναι ελεύθερο κείμενο στην πράξη. Το ISO 32000-2 ορίζει ένα λεξιλόγιο, Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema και Unspecified, και οι καταναλωτές στηρίζονται σε αυτό. Data για τους αριθμούς πίσω από ένα διάγραμμα, Source για το έγγραφο από το οποίο παράχθηκε μια σελίδα, Alternative για μια ισοδύναμη αναπαράσταση. Διαλέξτε από το λεξιλόγιο ακόμα κι όταν τίποτα στο pipeline σας δεν το διαβάζει ακόμα, γιατί το επόμενο εργαλείο στην αλυσίδα ίσως το κάνει

Γιατί η ίδια αναζήτηση θέλει FollowRef και στις δύο κατευθύνσεις;

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

Η ανάγνωση ενός σχετιζόμενου αρχείου δείχνει την πρώτη κατεύθυνση. Για να πάρετε τον αριθμό αντικειμένου του ενσωματωμένου stream πίσω από τα κλειδιά /EF και /F του file specification, η αναζήτηση πρέπει να μην ακολουθήσει, γιατί η παρακολούθηση επιλύει την αναφορά στο αντικείμενο stream και ο αριθμός αντικειμένου χάνεται. Ο κανόνας γενικεύει: οποιοδήποτε μονοπάτι κώδικα θέλει ταυτότητα αντικειμένου και όχι περιεχόμενο αντικειμένου πρέπει να πάρει την ακατέργαστη αναφορά

Το optional content δείχνει την αντίθετη κατεύθυνση, και κόστισε περισσότερο για να βρεθεί. Το λεξικό ιδιοτήτων του optional content γράφεται στον κατάλογο ως έμμεσο αντικείμενο, οπότε κώδικας που το ξαναδιαβάζει χωρίς παρακολούθηση παίρνει αναφορά και όχι λεξικό. Ο έλεγχος τύπου πάνω σε αυτή την τιμή τότε αποτυγχάνει, και ο φυσικός κλάδος εφεδρείας, αν δεν υπάρχει configuration, φτιάξε ένα, τρέχει και ξεγράφει το configuration που ήταν ήδη εκεί. Τίποτα δεν σηκώνει exception. Τα layers που περιγράφονται στα optional content groups και layers χάνουν απλώς την προεπιλεγμένη κατάσταση ορατότητάς τους

Το δίδαγμα γενικεύει πέρα από τις δύο περιπτώσεις. Όταν μια αναζήτηση μπορεί να επιστρέψει είτε αναφορά είτε αντικείμενο, ένας γυμνός έλεγχος τύπου δεν είναι χειρισμός σφαλμάτων: είναι ένας κλάδος που κάποτε θα πάρει για λάθος λόγο. Αποφασίστε ρητά τι θέλει κάθε σημείο κλήσης, και προτιμήστε το δημόσιο API που απαντά ευθέως την ερώτηση, όπως μια ιδιότητα πλήθους optional content, αντί να ψάχνετε σε protected accessor για το λεξικό του καταλόγου

Χάρτης αποφάσεων για την παρακολούθηση αναφορών σε αναζητήσεις PDF όπως υλοποιείται στο PDFlibPas: η ανάγνωση /EF και /F κάτω από file specification δεν πρέπει να ακολουθήσει την αναφορά γιατί ο αριθμός αντικειμένου του ενσωματωμένου stream είναι η απάντηση, ενώ το έμμεσο λεξικό /OCProperties στον κατάλογο πρέπει να ακολουθηθεί αλλιώς ένας αποτυχημένος έλεγχος τύπου ξεγράφει σιωπηλά το υπάρχον configuration του optional content
Η ίδια αναζήτηση απαντά δύο διαφορετικές ερωτήσεις: η ταυτότητα θέλει την ακατέργαστη αναφορά, το περιεχόμενο θέλει το επιλυμένο αντικείμενο, και ένας γυμνός έλεγχος τύπου στη θέση αυτής της απόφασης κάποτε τρέχει τον λάθος κλάδο χωρίς exception
// Attachments επιπέδου εγγράφου και συσχετίσεις επιπέδου σελίδας συνυπάρχουν.
// Ένα ενσωματωμένο αρχείο μπορεί να σημειωθεί σχετιζόμενο και σε επίπεδο εγγράφου
if Lib.IsEmbeddedFileAssociated(0) = 0 then
  Lib.SetEmbeddedFileAssociated(0, 1, 'Supplement');

Writeln('document associated files: ', Lib.GetAssociatedFileCount);
Writeln('page 3 associated files  : ',
        Lib.GetPageAssociatedFileCount(3));

// Το σβήσιμο αποσπά τον δεσμό σελίδας· το payload μένει στο name tree
if Lib.ClearPageAssociatedFiles(3) > 0 then
  Writeln('page 3 associations removed, payloads still reachable');

Τι κάνουν τα conformance modes στα attachments

Τα archival profiles περιορίζουν τι μπορεί να ενσωματωθεί, και ο περιορισμός επιβάλλεται στο σημείο εισόδου και όχι την ώρα της αποθήκευσης. Το PDF/A-1 απαγορεύει ενσωματωμένα αρχεία εντελώς, το PDF/A-2 επιτρέπει μόνο ενσωματωμένα έγγραφα PDF/A, και το PDF/A-3 είναι το profile που άνοιξε την ενσωμάτωση σε αυθαίρετους τύπους αρχείων, που είναι ακριβώς ο λόγος που οι hybrid μορφές τιμολογίων χτίζονται πάνω του

Το PDFlibPas αρνείται το attachment όταν το ενεργό conformance mode δεν το επιτρέπει, στην κλήση, και όχι εκατοντάδες πράξεις μετά κατά την έξοδο. Αυτή είναι μια συνειδητή επιλογή για το πού είναι το σφάλμα φθηνότερο να αντιμετωπιστεί: μια άρνηση στο σημείο κλήσης κατονομάζει το αρχείο που προσθέτατε, ενώ μια άρνηση την ώρα της αποθήκευσης κατονομάζει ένα έγγραφο και σας αφήνει να βρείτε ποιο από σαράντα attachments το προκάλεσε

Γι αυτό ακριβώς τα σχετιζόμενα αρχεία εμφανίζονται τόσο συχνά στην ηλεκτρονική τιμολόγηση. Ένα hybrid τιμολόγιο είναι ένα PDF που διαβάζει άνθρωπος με επισυναπτόμενο machine-readable XML payload σημασμένο με τη σωστή σχέση, και τόσο το profile του container όσο και το κλειδί σχέσης είναι μέρος της προδιαγραφής και όχι συμβάσεις. Αυτή η κατασκευή καλύπτεται στο χτίσιμο hybrid τιμολογίων Factur-X και ZUGFeRD, με την πλευρά των metadata στο schema επέκτασης XMP του PDF/A-3

Πότε η συσχέτιση πρέπει να είναι ανά σελίδα και όχι ανά έγγραφο;

Όταν ένας καταναλωτής χρειάζεται να ξέρει σε ποια σελίδα ανήκουν τα δεδομένα, και μόνο τότε. Τα attachments επιπέδου εγγράφου είναι απλούστερα, πιο πλατιά υποστηριζόμενα από viewers, και επαρκούν όποτε το payload περιγράφει όλο το έγγραφο, ένα XML τιμολογίου, ένα signature manifest, ένα αρχείο πηγαίου κώδικα. Πηγαίνετε σε συσχέτιση επιπέδου σελίδας όταν το payload είναι πραγματικά εστιασμένο σε σελίδα και η ταυτότητα της σελίδας είναι μέρος του νοήματός του

Η υποστήριξη είναι ο πρακτικός περιορισμός. Τα σχετιζόμενα αρχεία επιπέδου σελίδας είναι κατασκεύασμα του PDF 2.0, και η υποστήριξη από viewers είναι λεπτότερη από των attachments επιπέδου εγγράφου. Επειδή το payload κάθεται στο name tree και με τους δύο τρόπους, ένας viewer που αγνοεί το /AF στις σελίδες εξακολουθεί να δείχνει το αρχείο στη λίστα attachments του, οπότε η υποβάθμιση είναι κομψή. Αλλά αν ο δεσμός σελίδας είναι ουσιώδης για τον καταναλωτή σας και όχι απλώς χρήσιμα metadata, επαληθεύστε τον reader που πραγματικά στοχεύετε αντί να υποθέσετε

Τα σχετιζόμενα αρχεία επιπέδου σελίδας, τα attachments επιπέδου εγγράφου και οι πύλες των archival profiles που κυβερνούν και τα δύο έρχονται με το PDFlibPas Delphi PDF library. Αν επισκευάζετε και παλαιότερα αρχεία στην πορεία, η δουλειά metadata και conformance στη μετατροπή σε PDF/A με επιδιόρθωση metadata είναι που αποφασίζει ποια από αυτές τις διαδρομές attachment είναι διαθέσιμη για εσάς εξαρχής