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

Ενέργειες GoToR, GoToE και Launch σε PDF με Delphi

Το PDFlibPas δίνει στους προγραμματιστές Delphi και C++Builder τρία είδη ενεργειών για πλοήγηση που αφήνει πίσω την τρέχουσα σελίδα: η GoToR (Go To Remote) ανοίγει μια συγκεκριμένη σελίδα σε ένα άλλο αρχείο PDF, η GoToE (Go To Embedded) ανοίγει ένα αρχείο PDF ενσωματωμένο μέσα στο τρέχον έγγραφο, και η Launch τρέχει ένα εξωτερικό πρόγραμμα ή ανοίγει ένα αρχείο μέσω του κελύφους του λειτουργικού συστήματος. Και οι τρεις ζουν στο ISO 32000-1 §12.6.4, την ενότητα Action Types που ορίζει επίσης την καθημερινή ενέργεια GoTo, και καθεμία φέρει τη δική της παγίδα για τον απρόσεκτο: έναν αριθμό σελίδας που σημαίνει κάτι διαφορετικό ανάλογα με το ποια κλήση τον χτίζει, έναν στόχο που είναι όνομα παρά διαδρομή αρχείου, και ένα ζεύγος παραμέτρων συμβολοσειράς που φαίνονται πανομοιότυπες αλλά εξυπηρετούν δύο διαφορετικούς θεατές

Τίποτα από αυτά δεν είναι υποθετικό. Ένα πακέτο τεχνικής αναφοράς — ένα κύριο εγχειρίδιο, ένα PDF προδιαγραφών που ένας διανομέας ενημερώνει στο δικό του πρόγραμμα, ένα εργαλείο βαθμονόμησης εγκατεστημένο δίπλα σε αμφότερα — στηρίζεται ακριβώς σε αυτό το είδος διασυνδεδεμένης καλωδίωσης εγγράφων: μια παραπομπή που πρέπει να προσγειωθεί στη σελίδα 5 του αρχείου προδιαγραφών, ένα φύλλο δεδομένων που αξίζει να αποστέλλεται μέσα στο εγχειρίδιο αντί δίπλα του, έναν σύνδεσμο που παραδίδει απευθείας στο εργαλείο βαθμονόμησης. Αυτό το άρθρο είναι το κατοπτρικό είδωλο του ανάγνωσης ενεργειών bookmark και annotation πίσω από ένα υπάρχον PDF: εκείνο το κομμάτι καλύπτει την κατανάλωση μιας ενέργειας GoToR, Launch, ή GoToE που κάποιος άλλος παραγωγός έχει ήδη γράψει σε ένα αρχείο· αυτό εδώ καλύπτει το χτίσιμο αυτών των ίδιων τριών ειδών ενέργειας από την αρχή, συμπεριλαμβανομένων των κανόνων σε επίπεδο πεδίου που επιβάλλει το PDFlibPas πριν δεσμεύσει έστω ένα byte

Τρεις Τρόποι για μια Ενέργεια PDF να Αφήσει την Τρέχουσα Σελίδα

Το PDFlibPas διαχωρίζει την τοπική πλοήγηση από τα υπόλοιπα στο κλειδί /S της ενέργειας, και οι GoToR, GoToE, και Launch είναι οι τρεις υποτύποι των οποίων ο στόχος βρίσκεται έξω από την τρέχουσα σελίδα: η GoToR κάτω από το ISO 32000-1 §12.6.4.3, η GoToE κάτω από το §12.6.4.4, και η Launch κάτω από το §12.6.4.5, όλα μέσα στην ευρύτερη ενότητα §12.6.4 Action Types που ορίζει επίσης την καθημερινή ενέργεια GoTo. Ο προορισμός μιας απλής ενέργειας GoTo ονομάζει ένα αντικείμενο σελίδας που ήδη υπάρχει μέσα στο έγγραφο, οπότε το PDFlibPas μπορεί να το επικυρώσει αμέσως· οι GoToR και GoToE δεν μπορούν να το κάνουν με τον ίδιο τρόπο, αφού το εξωτερικό αρχείο μπορεί να μην υπάρχει καν σε αυτό το μηχάνημα και ο αριθμός σελίδων ενός ενσωματωμένου αρχείου δεν είναι κάτι που παρακολουθεί το έγγραφο-οικοδεσπότης, οπότε και οι δύο φέρουν μια ανεπίλυτη αναφορά αντί για έναν σκληρό σύνδεσμο — μια προδιαγραφή αρχείου συν έναν προορισμό για τη GoToR, ένα όνομα ενσωματωμένου αρχείου συν μια σελίδα στόχο για τη GoToE — ενώ η Launch αφήνει εντελώς την έννοια του προορισμού και απλώς ονομάζει κάτι για να το εκτελέσει ή να το ανοίξει το λειτουργικό σύστημα. Αυτός ο διαχωρισμός εμφανίζεται ως δύο οικογένειες κλήσεων στην πλευρά της εγγραφής: builders υψηλού επιπέδου, εφάπαξ, όπως οι AddLinkToFile, AddLinkToFileEx, AddLinkToEmbeddedPDF, και AddLinkToLocalFile δημιουργούν μαζί μια annotation συνδέσμου hotspot σελίδας και την ενέργειά της, καλύπτοντας τις περισσότερες πραγματικές διατάξεις — μια γραμμή κειμένου ή ένα εικονίδιο που κάνει κλικ ένας αναγνώστης — ενώ setters χαμηλότερου επιπέδου όπως οι SetActionRemoteDestinationEx, SetActionLaunchOptions, και τα αντίστοιχά τους AddActionNext* προσαρτούν ή αντικαθιστούν μια ενέργεια σε κάτι που ήδη κρατάτε μια λαβή του: ένα υπάρχον bookmark, έναν ενεργοποιητή πεδίου φόρμας, ή ένα συμβάν κύκλου ζωής σε επίπεδο εγγράφου ή σελίδας. Και οι δύο οικογένειες καταλήγουν να γράφουν τα ίδια σχήματα λεξικού· η διαφορά είναι πού στέκεστε όταν τις καλείτε, και, όπως καλύπτει η επόμενη ενότητα, τι σημαίνει ένας αριθμός σελίδας όταν το κάνετε

Πώς Χτίζετε έναν Σύνδεσμο GoToR που Ανοίγει μια Σελίδα σε Άλλο Αρχείο PDF;

Μια ενέργεια GoToR χρειάζεται δύο πράγματα — μια προδιαγραφή αρχείου και έναν προορισμό μέσα σε εκείνο το αρχείο — και το PDFlibPas εκθέτει δύο διαφορετικές κλήσεις για την παροχή του δεύτερου μέρους, καθεμία με τη δική της σύμβαση αρίθμησης σελίδων. Οι AddLinkToFile και AddLinkToFileEx, οι builders υψηλού επιπέδου για hotspot σελίδας, επικυρώνουν το όρισμα Page ή DestPage τους ως μεγαλύτερο από μηδέν, την ίδια αρίθμηση με βάση το 1 που χρησιμοποιεί το PDFlibPas παντού αλλού, συμπεριλαμβανομένης της SelectPage. Η SetActionRemoteDestinationEx, ο setter χαμηλότερου επιπέδου που χρησιμοποιείται για προσάρτηση ή αντικατάσταση μιας ενέργειας GoToR σε κάτι που ήδη έχετε λαβή, επικυρώνει αντ' αυτού το DestPage ως μεγαλύτερο ή ίσο με μηδέν και το γράφει απευθείας στον πίνακα ρητού προορισμού της ενέργειας χωρίς προσαρμογή: θέλει τον ακατέργαστο, με βάση το μηδέν δείκτη σελίδας του αρχείου-στόχου, την αρίθμηση που ορίζει το ISO 32000-1 για έναν απομακρυσμένο ρητό προορισμό. Καλέστε τον setter χαμηλού επιπέδου με τον ίδιο αριθμό που θα δίνατε στον builder υψηλού επιπέδου και ο σύνδεσμος ανοίγει μία σελίδα νωρίτερα

var
  Lib: TPDFlib;
  ActionID: Integer;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile('manual.pdf', '') = 1 then
    begin
      Lib.SelectPage(12);
      // Page is 1-based here, same as SelectPage above: this opens
      // the fifth page of specs.pdf.
      Lib.AddLinkToFile(72, 700, 200, 16, 'specs.pdf', 5, 0, 0, 0);

      // A later maintenance pass repoints the same link at a
      // reorganized file. SetActionRemoteDestinationEx edits the
      // action directly, and DestPage here is the zero-based index
      // PDF itself uses for a remote explicit destination -- "the
      // fifth page" is now 4, not 5.
      ActionID := Lib.GetAnnotActionID(1);
      Lib.SetActionRemoteDestinationEx(ActionID, 'specs-2026.pdf',
        4, Ord(dkFit), 0, 0, 0, 0, 0, 0, -1);
    end;
  finally
    Lib.Free;
  end;
end;

Τα υπόλοιπα ορίσματα της SetActionRemoteDestinationEx είναι εξίσου κυριολεκτικά. Το ValueMask είναι ένα σύνολο bits — 1 για αριστερά, 2 για πάνω, 4 για δεξιά, 8 για κάτω, 16 για zoom — και το PDFlibPas το ελέγχει έναντι του DestType πριν γράψει οτιδήποτε: ένας προορισμός dkFitR πρέπει να παρέχει ακριβώς 15 (και οι τέσσερις άκρες, χωρίς zoom), οι dkFit και dkFitB πρέπει να παρέχουν 0, και οι dkFitH/dkFitV δέχονται μόνο τη μία σχετική συντεταγμένη τους. Τα bits που αφήνετε ανεπηρέαστα μέσα σε μια κατά τα άλλα έγκυρη μάσκα δεν παραλείπονται από τον πίνακα· γράφονται ως ένα ρητό PDF null, το οποίο το ISO 32000-1 αντιμετωπίζει ως "κράτα ό,τι τιμή έχει ήδη ο θεατής" για εκείνη τη συντεταγμένη — ένας νόμιμος τρόπος να πείτε "πήγαινε σε αυτή τη σελίδα, άφησε το zoom ως έχει" παρά μια παράλειψη. Το ίδιο το zoom αποθηκεύεται ως κλάσμα της τιμής που περνάτε, οπότε μια κλήση που ζητά 150 τοις εκατό δίνει στον πίνακα μια αποθηκευμένη τιμή 1.5, και το έγκυρο εύρος εισόδου είναι 0 έως 6400

Πώς Συνδέεστε σε ένα PDF Ενσωματωμένο μέσα στο Δικό σας Έγγραφο;

Η AddLinkToEmbeddedPDF χτίζει την ενέργεια GoToE, και το όρισμα στόχου της, EmbeddedFileName, είναι όνομα παρά διαδρομή: πρέπει να ταιριάζει με τη συμβολοσειρά Title που ήδη περάστηκε στην EmbedFile όταν έγινε το συνημμένο, γιατί εκείνος ο τίτλος είναι το κυριολεκτικό κλειδί που αποθηκεύει το PDFlibPas στο δέντρο ονομάτων /EmbeddedFiles του εγγράφου, και η GoToE επιλύει αναζητώντας εκείνο το όνομα, όχι αγγίζοντας ξανά το σύστημα αρχείων. Η συνάρτηση απλώς ελέγχει ότι το EmbeddedFileName δεν είναι κενό και ότι το TargetPage είναι τουλάχιστον 1 — δώστε της ένα όνομα που ποτέ δεν ενσωματώθηκε πραγματικά και η κλήση εξακολουθεί να επιστρέφει επιτυχία, η ενέργεια εξακολουθεί να γράφεται, και ο σύνδεσμος απλώς αποτυγχάνει να επιλυθεί για κάθε αναγνώστη που κάνει κλικ πάνω του

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.NewDocument;
    Lib.NewPage;
    // The Title argument becomes the key PDFlibPas stores in the
    // document's EmbeddedFiles name tree -- that string, not
    // "datasheet.pdf", is the target GoToE resolves against.
    if Lib.EmbedFile('Datasheet', 'datasheet.pdf', 'application/pdf') = 1 then
      Lib.AddLinkToEmbeddedPDF(72, 700, 200, 16, 'Datasheet', 3, 0, 0);
    Lib.SaveToFile('manual.pdf');
  finally
    Lib.Free;
  end;
end;

Εδώ στοιβάζονται δύο κατώτατα όρια έκδοσης, όχι ένα. Η EmbedFile χρειάζεται PDF 1.4 για το δέντρο ονομάτων /EmbeddedFiles, και η AddLinkToEmbeddedPDF ξεχωριστά ανεβάζει το κατώφλι σε PDF 1.6 για τον ίδιο τον τύπο ενέργειας GoToE, οπότε το πραγματικό ελάχιστο για κάθε έγγραφο που χρησιμοποιεί αυτό το χαρακτηριστικό είναι 1.6, όχι 1.4. Παρατηρήστε επίσης ότι το TargetPage εδώ είναι με βάση το 1, η συνηθισμένη σύμβαση του PDFlibPas — μια σκόπιμη αντίθεση με το με βάση το μηδέν DestPage που μόλις κάλυψε η προηγούμενη ενότητα, και μια υπενθύμιση ότι ποιο σχήμα αρίθμησης σελίδων ισχύει εξαρτάται από το είδος ενέργειας και τη συγκεκριμένη κλήση, όχι από έναν γενικό κανόνα. Το λεξικό στόχου της ενέργειας μπορεί επίσης να φέρει μια καταχώριση /R με τιμή C για παιδί ή P για γονέα, υποστηρίζοντας μια αλυσίδα δύο βημάτων μέσα σε ένα ενσωματωμένο αρχείο ή πίσω έξω στον container του, αν και η AddLinkToEmbeddedPDF χτίζει μόνο ποτέ την κατεύθυνση παιδιού, αφού αυτή είναι εκείνη που βγάζει νόημα από ένα έγγραφο που κάνει την ενσωμάτωση παρά που ενσωματώνεται

Ενέργειες Launch: Ένα FileName, Δύο Στόχοι Συμβολοσειράς που Δεν Είναι Εναλλάξιμοι

Η SetActionLaunchOptions γράφει τον στόχο αρχείου μιας ενέργειας Launch σε δύο διαφορετικά κλειδιά από ένα μοναδικό όρισμα FileName, και τα δύο κλειδιά κρατούν δύο διαφορετικά είδη συμβολοσειράς. Το κλειδί ανώτατου επιπέδου /F λαμβάνει ένα λεξικό προδιαγραφής αρχείου, χτισμένο μέσω της ίδιας διαδρομής μετατροπής διαδρομών που χρησιμοποιεί το PDFlibPas για τη GoToR, που είναι η φορητή μορφή που ορίζει το ISO 32000-1 §7.11.3 για ένα λεξικό προδιαγραφής αρχείου. Το υπο-λεξικό /Win, όταν το PDFlibPas γράφει ένα, λαμβάνει το δικό του κλειδί /F ορισμένο στην ακατέργαστη τιμή FileName ακριβώς όπως περάστηκε, χωρίς καμία μετατροπή, γιατί το /Win /F τεκμηριώνεται στο ISO 32000-1 §12.6.4.5 ως μια απλή συμβολοσειρά διαδρομής Windows που προορίζεται μόνο για ανάγνωση από έναν θεατή Windows. Περάστε μια φορητή, ήδη μετατρεμμένη διαδρομή περιμένοντας και τα δύο κλειδιά να καταλήξουν πανομοιότυπα και το αντίγραφο /Win θα φέρει ό,τι δώσατε στη συνάρτηση, χωρίς να το αγγίξει

var
  Lib: TPDFlib;
  ActionID: Integer;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile('manual.pdf', '') = 1 then
    begin
      Lib.SelectPage(1);
      Lib.AddLinkToLocalFile(72, 660, 220, 16, 'calibrate.exe', 0);
      ActionID := Lib.GetAnnotActionID(1);
      // Operation 0 leaves this as a normal open -- pass 1 to ask a
      // Windows viewer to print instead. Parameters and
      // DefaultDirectory only ever reach /Win /P and /Win /D, never
      // the top-level /F.
      Lib.SetActionLaunchOptions(ActionID, 'calibrate.exe',
        '/silent /profile:default', 'C:\Tools\Calibration', 0, -1);
    end;
  finally
    Lib.Free;
  end;
end;

Αντιμετωπίστε τη Launch ως την ενέργεια με τη μεγαλύτερη τριβή από τις τρεις, γιατί ολόκληρος ο σκοπός της είναι να τρέξει ένα πρόγραμμα ή να ανοίξει ένα αρχείο έξω από το sandbox του PDF, και κάθε δημοφιλής θεατής τη μεταχειρίζεται ανάλογα. Η Enhanced Security του Adobe Acrobat μπλοκάρει ή προτρέπει σε ενέργειες Launch από προεπιλογή εκτός αν ο στόχος βρίσκεται σε μια ρητά έμπιστη τοποθεσία, και οι περισσότερες εταιρικές εγκαταστάσεις Acrobat αφήνουν ενεργοποιημένη εκείνη την προστασία. Μια ενέργεια Launch σε ένα έγγραφο που παραδίδεται στο κοινό δεν είναι επομένως αξιόπιστος ενεργοποιητής: σχεδιάστε το να είναι μπλοκαρισμένο, να προτρέπει, ή να αγνοείται σιωπηλά από όποιον θεατή ανοίγει το αρχείο, και κρατήστε το για κλειστά περιβάλλοντα όπου επίσης ελέγχετε τις ρυθμίσεις εμπιστοσύνης του θεατή — ένα εσωτερικό kiosk, μια ελεγχόμενη εταιρική ανάπτυξη, ένα έγγραφο που ποτέ δεν φεύγει από ένα μηχάνημα που διαχειρίζεστε

Η Πύλη PDF/A: Γιατί οι Κλήσεις GoToR και Launch Μπορούν να Επιστρέψουν Μηδέν

Οι SetActionRemoteDestinationEx και SetActionLaunchOptions αρνούνται εντελώς όταν το έγγραφο-στόχος βρίσκεται σε οποιαδήποτε λειτουργία συμμόρφωσης PDF/A: και οι δύο ελέγχουν τη λειτουργία PDF/A του εγγράφου ως πρώτη τους συνθήκη και εξέρχονται με αποτέλεσμα 0 πριν αγγίξουν την ενέργεια, χωρίς να εγερθεί εξαίρεση. Αυτό είναι σκόπιμο. Οι περιορισμοί του PDF/A στις διαδραστικές ενέργειες αποκλείουν συγκεκριμένα τη Launch, αφού το να δοθεί σε ένα αρχειακό αρχείο η ικανότητα να τρέχει ένα αυθαίρετο πρόγραμμα είναι ακριβώς το είδος συμπεριφοράς εξαρτημένης από περιβάλλον που οι μορφές μακροπρόθεσμης αρχειοθέτησης υπάρχουν για να αποτρέψουν, και το PDFlibPas εφαρμόζει την ίδια συντηρητική πύλη στον απομακρυσμένο setter go-to στην ίδια διαδρομή κώδικα. Η πρακτική συνέπεια είναι εύκολο να παραβλεφθεί κατά την ανάπτυξη: η πανομοιότυπη κλήση που δουλεύει σε ένα συνηθισμένο PDF θα μεταγλωττιστεί, θα τρέξει, και σιωπηλά δεν θα κάνει τίποτα σε ένα έγγραφο φορτωμένο με ορισμένο επίπεδο συμμόρφωσης PDF/A, οπότε ελέγξτε την τιμή επιστροφής αντί να υποθέσετε επιτυχία — ένα 0 εδώ δεν είναι σφάλμα κακοδιατυπωμένης εισόδου, είναι η βιβλιοθήκη να αρνείται ένα αίτημα που συγκρούεται με τον δικό του ισχυρισμό συμμόρφωσης του εγγράφου

Πού Ταιριάζουν οι GoToR, GoToE, και Launch σε μια Μεγαλύτερη Ροή Εργασίας PDFlibPas

Τα τρία είδη ενέργειας σε αυτό το άρθρο δεν φτάνουν όλα στα ίδια σημεία. Το συνοδευτικό άρθρο για ενεργοποιητές ενεργειών κύκλου ζωής εγγράφου και σελίδας καλύπτει τις SetDocumentAction και SetPageAction, οι οποίες μπορούν να προσαρτήσουν μια ενέργεια GoToR ή Launch σε έναν ενεργοποιητή όπως το WillClose μέσω των κοινών σταθερών PDF_ACTION_BUILDER_REMOTE_DESTINATION και PDF_ACTION_BUILDER_LAUNCH — ο ίδιος builder που καλύπτει επίσης έναν απλό ενεργοποιητή URI ή JavaScript. Η GoToE δεν έχει τέτοια σταθερά και καμία διαδρομή προς εκείνον τον γενικό builder καθόλου· η AddLinkToEmbeddedPDF είναι ο μόνος τρόπος με τον οποίο το PDFlibPas κατασκευάζει μία, κάτι που την κάνει αυστηρά ενέργεια hotspot σελίδας, ποτέ ενεργοποιητή επιπέδου εγγράφου ή σελίδας. Εκεί όπου οι GoToR και Launch φτάνουν πράγματι στον γενικό builder, το αντάλλαγμα είναι ο έλεγχος: χτίζει μια GoToR που δείχνει μόνο σε έναν ονομασμένο απομακρυσμένο προορισμό και μια ενέργεια Launch με μόνο ένα όνομα αρχείου και παραμέτρους, ενώ η ρητή διευθυνσιοδότηση σελίδας-και-τύπου-προσαρμογής και οι επιλογές εκκίνησης ειδικές για Windows που καλύπτονται σε αυτό το άρθρο προσεγγίζονται μόνο μέσω των SetActionRemoteDestinationEx και SetActionLaunchOptions απευθείας

Μια ιδιότητα ασφάλειας αξίζει να γνωρίζετε πριν χτίσετε ένα εργαλείο συντήρησης γύρω από αυτούς τους setters. Οι SetActionRemoteDestinationEx και SetActionLaunchOptions χτίζουν πρώτα ολόκληρη την ενέργεια αντικατάστασης σε ένα προσωρινό λεξικό, και μόνο διαγράφουν και αντιγράφουν τα κλειδιά /F, /D ή /Win, και /NewWindow στη ζωντανή ενέργεια μόλις εκείνο το προσωρινό αντίγραφο επικυρωθεί — οπότε μια κλήση που αποτυγχάνει στην επικύρωση, είτε από ένα εκτός εύρους ValueMask είτε από ένα κενό FileName, αφήνει την αρχική ενέργεια, και οποιαδήποτε αλυσίδα /Next ήδη κρέμεται από αυτή, εντελώς ανέγγιχτη αντί για μισο-αντικατεστημένη. Αυτό έχει σημασία γιατί οι ενέργειες GoToR και Launch μπορούν και οι δύο να κάθονται μέσα σε μια αλυσίδα /Next χτισμένη με τις AddActionNextRemoteDestinationEx, AddActionNextLaunchEx, ή τη γενικότερη AddActionNextEx, επιτρέποντας σε έναν μοναδικό ενεργοποιητή να πυροδοτεί μια καταχώριση καταγραφής JavaScript και μετά ένα απομακρυσμένο άλμα σε ακολουθία. Η κατασκευή GoToR, GoToE, και Launch όπως περιγράφεται εδώ είναι μέρος του PDFlibPas, της εγγενούς βιβλιοθήκης PDF για Delphi και C++Builder