Η συγχώνευση (merge) και η διαίρεση (split) είναι οι δύο λειτουργίες σελίδας στις οποίες καταφεύγουν όλοι πρώτα, και καλύπτουν πολύ έδαφος. Δεν καλύπτουν όμως τα πάντα. Υπάρχει μια ξεχωριστή οικογένεια εργασιών που αναδιατάσσει σελίδες αντί να μετακινεί ολόκληρα αρχεία: τοποθέτηση τεσσάρων διαφανειών σε ένα φύλλο για ένα φυλλάδιο, μεταφορά μιας σελίδας από το πίσω μέρος ενός εγγράφου στο μπροστινό μέρος, ή εξαγωγή των σελίδων 3, 7 και 12 σε ένα σύντομο απόσπασμα χωρίς να επηρεαστούν οι υπόλοιπες. Το PDFium εκθέτει τρεις μεθόδους ακριβώς για αυτό, και καθεμία συμπεριφέρεται διαφορετικά από τη συγχώνευση και τη διαίρεση που ήδη γνωρίζετε. Αυτό το άρθρο περιγράφει τι κάνουν, πού βρίσκονται τα σημεία εξόδου και μια λεπτομέρεια ιδιοκτησίας που έχει προκαλέσει κατάρρευση στην πράξη.
Οι τρεις μέθοδοι είναι η ImportNPagesToOne για N-up imposition, η MovePages για αναδιάταξη επί τόπου (in-place) και η ImportPagesByIndex για εξαγωγή υποσυνόλου. Η συγχώνευση στοιβάζει τα έγγραφα από άκρη σε άκρη και αφήνει τον αριθμό των σελίδων ίσο με το άθροισμα των εισόδων. Η διαίρεση γράφει διάφορα αρχεία εξόδου από μία είσοδο. Οι τρεις λειτουργίες εδώ βρίσκονται κάπου ανάμεσα: μία από αυτές αλλάζει το πόσες σελίδες προέλευσης μοιράζονται ένα φύλλο, μία αλλάζει τη σειρά μέσα σε ένα μόνο έγγραφο, και μία αντιγράφει μια επιλεγμένη ομάδα σελίδων σε ένα άλλο έγγραφο. Γνωρίζοντας ποια είναι ποια, γλιτώνετε από την ανάγκη εκτέλεσης μιας διαδικασίας συγχώνευσης και διαγραφής, όταν μια απλή κλήση θα ήταν αρκετή.
Τι κάνει στην πραγματικότητα το N-up imposition
Το Imposition είναι ο όρος της προεκτύπωσης (prepress) για τη διευθέτηση πολλών σελίδων προέλευσης σε ένα μεγαλύτερο φύλλο, έτσι ώστε το εκτυπωμένο και διπλωμένο αποτέλεσμα να διαβάζεται με τη σωστή σειρά. Η καθημερινή εκδοχή είναι το φυλλάδιο 2 σελίδων (2-up), η μορφή βιβλιαρίου 4 σελίδων (4-up) ή η σελίδα επαφής (contact sheet) που χωράει μια δωδεκάδα μικρογραφίες σε μία σελίδα. Το PDFium χειρίζεται τη γεωμετρία μέσω μίας κλήσης:
function ImportNPagesToOne(
OutputWidth, OutputHeight: Single;
NumX, NumY : Cardinal): TPdf;
Οι παράμετροι NumX και NumY περιγράφουν το πλέγμα. Μια τιμή 2, 1 τοποθετεί δύο σελίδες προέλευσης δίπλα-δίπλα. Η τιμή 2, 2 πακετάρει τέσσερις σε μια διάταξη τεταρτημορίων. Η τιμή 4, 3 δημιουργεί μια σελίδα επαφής δώδεκα θέσεων. Το PDFium διαβάζει τις σελίδες προέλευσης με τη σειρά, κλιμακώνει την καθεμία προς τα κάτω για να χωρέσει στο κελί της και γεμίζει το πλέγμα από αριστερά προς τα δεξιά, από πάνω προς τα κάτω, ξεκινώντας ένα νέο φύλλο εξόδου όποτε το τρέχον πλέγμα γεμίζει. Οι σελίδες προέλευσης δεν τροποποιούνται. Αυτό που παίρνετε πίσω είναι ένα νέο έγγραφο του οποίου οι σελίδες είναι σύνθετες (composites).
Το μέγεθος εξόδου είναι σε στιγμές (points), όχι σε εικονοστοιχεία (pixels)
Τα OutputWidth και OutputHeight είναι μονάδες χρήστη PDF, και μια μονάδα χρήστη PDF είναι μία στιγμή (point), η οποία ισούται με το ένα εβδομηκοστό δεύτερο της ίντσας. Η μονάδα δηλώνει το φυσικό μέγεθος του φύλλου εξόδου και δεν έχει καμία σχέση με τα εικονοστοιχεία της οθόνης ή το DPI απόδοσης. Αυτό είναι το πιο συνηθισμένο σημείο όπου γίνεται λάθος στο imposition, επειδή ένας προγραμματιστής συνηθισμένος σε bitmap αναζητά έναν αριθμό εικονοστοιχείων και καταλήγει με ένα φύλλο στο μέγεθος ενός γραμματοσήμου ή μιας διαφημιστικής πινακίδας.
Οι αριθμοί που αξίζει να απομνημονεύσετε είναι τα δύο μεγέθη σελίδας που θα χρησιμοποιήσετε περισσότερο. Το US Letter είναι 612 επί 792 στιγμές, επειδή 8,5 ίντσες επί 72 ισούται με 612 και 11 ίντσες επί 72 ισούται με 792. Το A4 είναι περίπου 595 επί 842 στιγμές, από τις διαστάσεις του 210 επί 297 χιλιοστών. Η ίδια η κεφαλίδα της σύνδεσης αναφέρει τον κανόνα ξεκάθαρα, ότι μία μονάδα είναι το ένα εβδομηκοστό δεύτερο της ίντσας, και η μονάδα παρέχει μια σταθερά PointsPerInch ίση με 72 εάν προτιμάτε να υπολογίζετε το μέγεθος από ίντσες στον κώδικα αντί να γράφετε την κυριολεκτική τιμή.
const
LetterW = 612.0; // 8.5 in * 72
LetterH = 792.0; // 11 in * 72
var
Source, Composite: TPdf;
begin
Source := TPdf.Create(nil);
Composite := nil;
try
Source.FileName := 'slides.pdf';
Source.Active := True;
// Four source pages per Letter sheet, 2 by 2 grid.
Composite := Source.ImportNPagesToOne(LetterW, LetterH, 2, 2);
if Composite = nil then
raise Exception.Create('PDFium rejected the imposition arguments');
Composite.SaveAs('slides-4up.pdf');
finally
Composite.Free; // see the next section: this is mandatory
Source.Free;
end;
end;
Η επιστρεφόμενη λαβή (handle) είναι δική σας για να την ελευθερώσετε
Διαβάστε ξανά την υπογραφή της μεθόδου. Η ImportNPagesToOne επιστρέφει ένα TPdf, όχι ένα Boolean. Αυτή η τιμή επιστροφής είναι μια ολοκαίνουργια λαβή εγγράφου, η οποία εκχωρείται ξεχωριστά από την πηγή, και ο καλών την κατέχει. Το αρχικό TPdf στο οποίο καλέσατε τη μέθοδο παραμένει ανέπαφο και εξακολουθεί να κατέχει τη δική του λαβή. Το composite είναι ένα δεύτερο, ανεξάρτητο αντικείμενο. Εάν αφήσετε το επιστρεφόμενο TPdf να βγει εκτός εμβέλειας χωρίς να το ελευθερώσετε, διαρρέετε ένα ολόκληρο έγγραφο PDFium.
Το πιο επικίνδυνο λάθος λειτουργεί αντίστροφα. Εσωτερικά, η μέθοδος ζητά από το PDFium ένα νέο FPDF_DOCUMENT μέσω της FPDF_ImportNPagesToOne, και στη συνέχεια τυλίγει αυτή την πρωτογενή λαβή μέσα στο επιστρεφόμενο TPdf, ώστε η διάρκεια ζωής του wrapper να κυβερνά αυτήν της λαβής. Από εκείνο το σημείο και μετά υπάρχει ακριβώς ένας ιδιοκτήτης της λαβής, και ακριβώς ένα σημείο όπου πρέπει να κλείσει: όταν καλείτε τη Free στο επιστρεφόμενο αντικείμενο. Μια απρόσεκτη διαδρομή σφάλματος που ελευθερώνει τον wrapper και ταυτόχρονα καλεί την FPDF_CloseDocument στην πρωτογενή λαβή που κατέγραψε κλείνει το ίδιο έγγραφο PDFium δύο φορές. Αυτό είναι μια διπλή απελευθέρωση (double-free), και είναι το συγκεκριμένο σφάλμα που επηρέασε έναν καλών εδώ κάποτε. Ο κανόνας που το αποτρέπει είναι σύντομος. Κλείστε το έγγραφο σε μία μόνο διαδρομή, ελευθερώνοντας το TPdf που σας παρέδωσε η μέθοδος, και μην προσπαθείτε ποτέ να προσπεράσετε τον wrapper για να κλείσετε τη λαβή που έχει ήδη υιοθετήσει.
Δύο πορίσματα προκύπτουν από αυτό. Πρώτον, η μέθοδος επιστρέφει nil όταν το PDFium απορρίπτει τα ορίσματα, όπως ένα μηδενικό σε οποιονδήποτε άξονα του πλέγματος ή μια αποτυχία κατανομής, οπότε ένας έλεγχος nil είναι απαραίτητος προτού αγγίξετε το αποτέλεσμα. Δεύτερον, αρχικοποιήστε τη μεταβλητή εξόδου σε nil πριν από το try και ελευθερώστε την στο finally, όπως κάνει το παραπάνω δείγμα, έτσι ώστε μια αποτυχία στη μέση της διαδρομής να μην σας αφήσει να ελευθερώσετε μια απροσδιόριστη αναφορά ή να παραλείψετε εντελώς την απελευθέρωση.
Αναδιάταξη σελίδων χωρίς επανεγγραφή τους
Το imposition δημιουργεί ένα νέο έγγραφο. Η αναδιάταξη αλλάζει ένα έγγραφο επί τόπου (in-place). Η μέθοδος MovePages ανυψώνει ένα σύνολο σελίδων από τις τρέχουσες θέσεις τους και τις τοποθετεί σε έναν προορισμό, μετατοπίζοντας όλα τα άλλα γύρω από το μετακινημένο μπλοκ, έτσι ώστε ο αριθμός των σελίδων να παραμένει ο ίδιος:
function MovePages(
const PageIndices: array of Integer;
DestPageIndex : Integer): Boolean;
Οι δείκτες είναι μηδενικής βάσης (zero-based). Η παράμετρος PageIndices απαριθμεί τις σελίδες προς μετακίνηση, με τη σειρά που πρέπει να καταλήξουν, και η DestPageIndex είναι ο δείκτης στον οποίο προσγειώνεται η πρώτη μετακινημένη σελίδα μετά την ολοκλήρωση της μετακίνησης. Επειδή το PDFium μετατοπίζει τις σελίδες αντί να τις αντιγράφει και να συμπιέζει ξανά το περιεχόμενό τους, η λειτουργία είναι φθηνή και χωρίς απώλειες: τα αντικείμενα σελίδας διατηρούν τις ροές τους, τους πόρους τους και την πιστότητά τους. Αυτή είναι η κλήση πίσω από ένα πάνελ σελίδων μεταφοράς και απόθεσης (drag-to-reorder), όπου ένας χρήστης σέρνει μια μικρογραφία σε μια νέα θέση και εσείς εφαρμόζετε τη νέα σειρά με μία κίνηση. Επιστρέφει False όταν ένας δείκτης είναι εκτός ορίων, οπότε επικυρώστε το αποτέλεσμα αντί να υποθέσετε ότι η αναδιάταξη πέτυχε.
var
Doc: TPdf;
begin
Doc := TPdf.Create(nil);
try
Doc.FileName := 'report.pdf';
Doc.Active := True;
// Move the last page (index 4 in a 5-page file) to the very front.
if not Doc.MovePages([4], 0) then
raise Exception.Create('MovePages rejected the index');
Doc.SaveAs('report-reordered.pdf');
finally
Doc.Free;
end;
end;
Εξαγωγή υποσυνόλου με βάση τον δείκτη
Η τρίτη λειτουργία αντιγράφει ένα συγκεκριμένο σύνολο σελίδων από ένα έγγραφο σε ένα άλλο. Η ImportPagesByIndex λαμβάνει το έγγραφο προέλευσης και έναν πίνακα δεικτών μηδενικής βάσης, και εισάγει αυτές τις σελίδες στον στόχο σε μια επιλεγμένη θέση:
function ImportPagesByIndex(
Source : TPdf;
const PageIndices: array of Integer;
InsertAt : Integer= 0): Boolean;
Την καλείτε στο έγγραφο προορισμού και περνάτε την πηγή ως το πρώτο όρισμα. Η παράμετρος PageIndices ονομάζει τις σελίδες προέλευσης που θα τραβήξετε, με τη σειρά που τις θέλετε. Η InsertAt είναι η θέση μηδενικής βάσης στον στόχο όπου πηγαίνει η πρώτη εισαγόμενη σελίδα, οπότε η τιμή 0 τις τοποθετεί πριν από την υπάρχουσα πρώτη σελίδα και ο τρέχων αριθμός σελίδων του στόχου αυξάνεται. Ένας άδειος πίνακας εισάγει κάθε σελίδα, γεγονός που καθιστά την κλήση πλήρη αντιγραφή όταν τη χρειάζεστε. Επιστρέφει False εάν κάποιος δείκτης είναι εκτός ορίων στην πηγή.
var
Source, Excerpt: TPdf;
begin
Source := TPdf.Create(nil);
Excerpt := TPdf.Create(nil);
try
Source.FileName := 'manual.pdf';
Source.Active := True;
Excerpt.CreateDocument; // start an empty target
// Pull pages 3, 7 and 12 (zero-based 2, 6, 11) into the excerpt.
if not Excerpt.ImportPagesByIndex(Source, [2, 6, 11], 0) then
raise Exception.Create('A requested page index is out of range');
Excerpt.SaveAs('manual-excerpt.pdf');
finally
Excerpt.Free;
Source.Free;
end;
end;
Καθαρή σύνθεση των λειτουργιών
Το σχήμα από άκρη σε άκρη είναι το ίδιο και για τις τρεις λειτουργίες: ανοίξτε την πηγή ορίζοντας το FileName και αλλάζοντας το Active σε True, εκτελέστε τη λειτουργία, αποθηκεύστε με τη SaveAs και ελευθερώστε ό,τι κατέχετε. Η μόνη περίπτωση που χρειάζεται προσοχή είναι ποιες κλήσεις εκχωρούν ένα νέο έγγραφο. Η MovePages τροποποιεί το έγγραφο που ήδη κρατάτε, οπότε υπάρχει ένα αντικείμενο για απελευθέρωση. Η ImportPagesByIndex γράφει σε έναν στόχο που δημιουργήσατε εσείς, οπότε ελευθερώνετε την πηγή και τον στόχο που ανοίξατε. Η ImportNPagesToOne είναι η εξαίρεση, επειδή το νέο έγγραφο είναι η τιμή επιστροφής της μεθόδου και όχι κάτι που κατασκευάσατε, και το να ξεχάσετε ότι πρόκειται για μια ξεχωριστή λαβή που ανήκει στον καλούντα είναι ο τρόπος με τον οποίο συμβαίνουν τόσο η διαρροή όσο και η διπλή απελευθέρωση. Αρχικοποιήστε το αποτέλεσμα σε nil, ελέγξτε το μετά την κλήση και ελευθερώστε το σε μία μόνο διαδρομή.
Εάν η εργασία που έχετε στην πραγματικότητα είναι ο συνδυασμός ολόκληρων αρχείων αντί για την αναδιάταξη σελίδων, δείτε τη συγχώνευση πολλαπλών αρχείων PDF σε ένα έγγραφο. Εάν είναι το αντίθετο, η διάσπαση ενός εγγράφου σε πολλαπλά αρχεία, δείτε τη διάσπαση εγγράφων PDF σε πολλαπλά αρχεία. Οι μέθοδοι imposition και αναδιάταξης που περιγράφονται εδώ παρέχονται ως μέρος του PDFium Component για Delphi και C++Builder, μαζί με τα API φόρτωσης, απόδοσης και επεξεργασίας που καλύπτονται σε άλλα σημεία αυτού του ιστολογίου.