Το PDFium Component εκθέτει τη συγχώνευση PDF μέσω μίας μόνο μεθόδου: της ImportPages. Το μοτίβο είναι πάντα το ίδιο: δημιουργήστε ένα κενό έγγραφο προορισμού, ανοίξτε κάθε αρχείο προέλευσης, καλέστε την ImportPages για να αντιγράψετε τις σελίδες, κλείστε την προέλευση και επαναλάβετε. Όταν τελειώσει ο βρόχος, η SaveAs γράφει το αποτέλεσμα στο δίσκο. Δεν υπάρχει ειδική λειτουργία συγχώνευσης, καμία ρύθμιση για εναλλαγή. Η πολυπλοκότητα βρίσκεται στις οριακές περιπτώσεις (edge cases) και υπάρχουν μερικές που "δαγκώνουν" χωρίς προειδοποίηση
Ο κεντρικός βρόχος
Δύο παρουσίες (instances) TPdf είναι το μόνο που χρειάζεστε. Η μία κρατά το έγγραφο προορισμού, το οποίο δημιουργήθηκε κενό με τη CreateDocument. Η άλλη ανοίγει κάθε αρχείο προέλευσης με τη σειρά. Παρακάτω είναι μια διαδικασία που δέχεται μια λίστα διαδρομών αρχείων και γράφει τη συγχωνευμένη έξοδο σε μία μοναδική διαδρομή:
procedure MergeFiles(const FileList: TStrings; const OutputPath: string);
var
PdfDest, PdfSrc: TPdf;
InsertAt, I: Integer;
begin
PdfDest := TPdf.Create(nil);
PdfSrc := TPdf.Create(nil);
try
PdfDest.CreateDocument;
InsertAt := 1; // ImportPages uses 1-based destination position
for I := 0 to FileList.Count - 1 do
begin
PdfSrc.FileName := FileList[I];
PdfSrc.Active := True;
if not PdfSrc.Active then
raise Exception.CreateFmt('Cannot open: %s', [FileList[I]]);
PdfDest.ImportPages(
PdfSrc,
'1-' + IntToStr(PdfSrc.PageCount), // full document range
InsertAt);
Inc(InsertAt, PdfSrc.PageCount);
PdfSrc.Active := False;
end;
PdfDest.SaveAs(OutputPath);
finally
PdfSrc.Free;
PdfDest.Free;
end;
end;
Δύο πράγματα σε αυτόν τον κώδικα είναι εύκολο να παραβλεφθούν με την πρώτη ανάγνωση. Το πρώτο είναι πώς το PDFium αναφέρει τις αποτυχίες φόρτωσης. Το Active := True δεν εγείρει ποτέ εξαίρεση: αν το αρχείο λείπει, είναι κατεστραμμένο ή προστατεύεται με κωδικό πρόσβασης, το PDFium πιάνει το σφάλμα εσωτερικά και αφήνει το Active ως False. Χωρίς τον ρητό έλεγχο στη γραμμή 10, ένα κακό αρχείο θα έβγαινε σιωπηλά από τη συγχώνευση χωρίς καμία ένδειξη στην έξοδο. Το τελικό PDF θα είχε λιγότερες σελίδες από το αναμενόμενο και δεν θα γνωρίζατε ποιο αρχείο ήταν ο ένοχος
Το δεύτερο είναι ο μετρητής InsertAt. Το τρίτο όρισμα στην ImportPages είναι η θέση στον προορισμό (με βάση το 1) όπου προσγειώνεται η πρώτη εισαγόμενη σελίδα. Ξεκινώντας από το 1 βάζει το πρώτο έγγραφο προέλευσης στην αρχή ενός κατά τα άλλα κενού αρχείου. Μετά από κάθε πηγή, ο μετρητής προχωρά κατά PdfSrc.PageCount, οπότε η επόμενη παρτίδα σελίδων προσαρτάται μετά την τελευταία. Αν ξεχάσετε να τον αυξήσετε, κάθε επόμενη πηγή αντικαθιστά σελίδες στη θέση 1, δίνοντάς σας το τελευταίο έγγραφο στη λίστα και τίποτα άλλο
Επιλεκτικά εύρη σελίδων
Δεν χρειάζεται να πάρετε κάθε σελίδα από μια πηγή. Η συμβολοσειρά εύρους που περνιέται ως δεύτερο όρισμα ακολουθεί μια απλή μορφή με κόμματα και παύλες: "1-3" παίρνει τις σελίδες 1 έως 3, "2,4,6" επιλέγει τρεις συγκεκριμένες σελίδες και "1-" σημαίνει τη σελίδα 1 έως το τέλος του εγγράφου. Τα εύρη μπορούν να συνδυαστούν σε μία μόνο συμβολοσειρά, επομένως το "1-3,5,7-" παραλείπει τις σελίδες 4 και 6. Μια λεπτομέρεια έχει σημασία εδώ: οι αριθμοί αναφέρονται πάντα σε σελίδες στο έγγραφο προέλευσης, ξεκινώντας από το 1, ανεξάρτητα από το πού καταλήγουν αυτές οι σελίδες στον προορισμό. Αν θέλετε τις σελίδες 40 έως 50 από έναν κατάλογο 200 σελίδων, η συμβολοσειρά εύρους είναι "40-50", όχι μια θέση σε σχέση με αυτό που υπάρχει ήδη στον προορισμό
// Extract cover plus a three-page executive summary from a long report
PdfSrc.FileName := 'annual-report.pdf';
PdfSrc.Active := True;
if PdfSrc.Active then
begin
// Page 1 is the cover; pages 3-5 are the summary
PdfDest.ImportPages(PdfSrc, '1,3-5', InsertAt);
Inc(InsertAt, 4); // 1 cover + 3 summary pages = 4 pages added
PdfSrc.Active := False;
end;
Κατά τον υπολογισμό της αύξησης στο InsertAt, μετρήστε τις σελίδες που πραγματικά εισαγάγατε, όχι την καταμέτρηση σελίδων της πηγής. Αν περάσετε '1,3-5' εισαγάγατε 4 σελίδες, επομένως προχωρήστε κατά 4. Η προώθηση κατά PdfSrc.PageCount θα άφηνε ένα κενό στις θέσεις προορισμού και θα τοποθετούσε το επόμενο έγγραφο προέλευσης πιο μέσα στο αρχείο από ό,τι είχατε σκοπό
Τι διατηρεί η ImportPages και τι όχι
Οι σελίδες που αντιγράφονται από την ImportPages μεταφέρουν άθικτο το ορατό τους περιεχόμενο. Κείμενο, διανυσματικά γραφικά, εικόνες ράστερ (raster), ενσωματωμένες γραμματοσειρές και μορφές XObjects μεταφέρονται όλα ως μέρος των ροών περιεχομένου της σελίδας. Οι σχολιασμοί επιπέδου σελίδας, συμπεριλαμβανομένων σχολίων, επισημάνσεων και πινελιών, μεταφέρονται επίσης, επειδή αποθηκεύονται μέσα στο λεξικό της σελίδας και όχι σε επίπεδο εγγράφου
Τα μεταδεδομένα επιπέδου εγγράφου είναι διαφορετική ιστορία. Οι συμβολοσειρές τίτλου, συγγραφέα, θέματος και λέξεων-κλειδιών στο λεξικό Info της πηγής μένουν πίσω. Το έγγραφο προορισμού ξεκινά με κενά μεταδεδομένα μετά τη CreateDocument, οπότε αν η συγχωνευμένη έξοδος χρειάζεται αυτά τα πεδία συμπληρωμένα, πρέπει να τα αναθέσετε απευθείας στο PdfDest πριν καλέσετε τη SaveAs. Οι ιδιότητες Title, Author, Subject, Keywords και Creator στο TPdf δέχονται απλές συμβολοσειρές και γράφουν στο λεξικό Info κατά την αποθήκευση
Τα διαδραστικά πεδία φόρμας είναι πιο περίπλοκα. Οι ορισμοί πεδίων AcroForm βρίσκονται σε ένα λεξικό επιπέδου εγγράφου και όχι μέσα σε μεμονωμένες ροές σελίδων. Όταν η ImportPages αντιγράφει μια σελίδα που περιέχει πεδία φόρμας, η οπτική εμφάνιση αυτών των πεδίων μεταφέρεται επειδή αποδίδεται στη ροή περιεχομένου της σελίδας, αλλά τα στοιχεία ελέγχου (widgets) πεδίου που τα καθιστούν διαδραστικά αποτελούν μέρος της δομής AcroForm και δεν ακολουθούν. Σε μια τυπική συγχώνευση, ένα πεδίο κειμένου από ένα έγγραφο προέλευσης θα εμφανίζει την τιμή που είχε κατά τη στιγμή της εισαγωγής, αλλά δεν θα είναι επεξεργάσιμο στο συγχωνευμένο αρχείο. Εάν χρειάζεστε τα πεδία να παραμείνουν συμπληρώσιμα, επιπεδώστε τα (flatten) σε κάθε έγγραφο προέλευσης πριν από την εισαγωγή: αυτό ενσωματώνει τις τρέχουσες τιμές στη ροή περιεχομένου και αφαιρεί τη διαδραστική επικάλυψη, δίνοντάς σας ένα καθαρό οπτικό αποτέλεσμα χωρίς σπασμένα στοιχεία ελέγχου (widgets) στην έξοδο
Κρυπτογραφημένα αρχεία προέλευσης
Τα έγγραφα προέλευσης που προστατεύονται με κωδικό πρόσβασης ανοίγουν με τον ίδιο τρόπο όπως τα μη κρυπτογραφημένα, με μια επιπλέον ιδιότητα να πρέπει να οριστεί πρώτη. Αναθέστε τον κωδικό πρόσβασης στο PdfSrc.Password πριν αναστρέψετε το Active := True, και το PDFium θα τον χρησιμοποιήσει κατά το άνοιγμα:
PdfSrc.Password := 'user-password';
PdfSrc.FileName := 'protected.pdf';
PdfSrc.Active := True;
if not PdfSrc.Active then
raise Exception.Create('Wrong password or file cannot be opened');
PdfDest.ImportPages(PdfSrc, '1-' + IntToStr(PdfSrc.PageCount), InsertAt);
Inc(InsertAt, PdfSrc.PageCount);
PdfSrc.Active := False;
Ένας λάθος κωδικός πρόσβασης προκαλεί το ίδιο σιωπηλό αποτέλεσμα Active = False όπως ένα αρχείο που λείπει, επομένως ο ρητός έλεγχος είναι εξίσου απαραίτητος εδώ. Η κρυπτογράφηση δεν μεταφέρεται στον προορισμό: οι σελίδες που εισάγονται από μια προστατευμένη πηγή προσγειώνονται στον προορισμό ως απροστάτευτο περιεχόμενο. Αν η συγχωνευμένη έξοδος χρειάζεται επίσης κρυπτογράφηση, ρυθμίστε τη στο PdfDest πριν καλέσετε τη SaveAs
Αποθήκευση του αποτελέσματος
Η SaveAs στο TPdf δέχεται είτε μια διαδρομή αρχείου είτε ένα TStream. Για τις περισσότερες συγχωνεύσεις, η υπερφόρτωση με αρχείο (file overload) είναι αυτό που θέλετε:
PdfDest.SaveAs('merged-output.pdf');
Το προαιρετικό δεύτερο όρισμα είναι ένα TSaveOption που ελέγχει τη λειτουργία αποθήκευσης. Η προεπιλογή, saNone, γράφει μια αυξητική ενημέρωση εάν το έγγραφο φορτώθηκε από ένα αρχείο ή μια πλήρη επανεγγραφή αν δημιουργήθηκε πρόσφατα. Δεδομένου ότι ένας προορισμός που έχει κατασκευαστεί με τη CreateDocument είναι πάντα φρέσκος, η έξοδος θα είναι ένα συμπαγές αρχείο μίας μόνο αναθεώρησης. Το τρίτο όρισμα, TPdfVersion, σας επιτρέπει να καρφιτσώσετε την κεφαλίδα έκδοσης PDF όταν έχετε καταναλωτές κατάντη (downstream consumers) που απαιτούν συγκεκριμένη έκδοση· το να το αφήσετε στο pvUnknown επιτρέπει στο PDFium να επιλέξει με βάση το περιεχόμενο
Οι μέθοδοι ImportPages και SaveAs που εμφανίζονται εδώ αποτελούν μέρος του PDFium Component για Delphi και C++Builder