Το PDFium Component σας δίνει μία μέθοδο για τον διαχωρισμό PDF: το ImportPages. Οτιδήποτε άλλο, είτε απομονώνετε μία μόνο σελίδα, είτε κόβετε σε αυθαίρετα όρια, είτε ακολουθείτε τη δική του δομή σελιδοδεικτών του εγγράφου, είναι απλώς διαφορετικοί τρόποι για να αποφασίσετε ποιοι αριθμοί σελίδων πηγαίνουν σε κάθε αρχείο εξόδου. Η μηχανική παραμένει ίδια. Η έγκαιρη κατανόηση αυτού του γεγονότος εξοικονομεί πολλές λανθασμένες στροφές
Πώς λειτουργεί ο βρόχος διαχωρισμού (split loop)
Το μοτίβο είναι το ίδιο ανεξάρτητα από το πώς χωρίζετε το έγγραφο προέλευσης. Δημιουργήστε ένα νέο στιγμιότυπο TPdf, καλέστε σε αυτό το CreateDocument για να αρχικοποιήσετε ένα κενό PDF στη μνήμη, εισαγάγετε τις σελίδες που θέλετε με το ImportPages, αποθηκεύστε το αποτέλεσμα, και στη συνέχεια επαναφέρετε το Active σε False πριν από την επόμενη επανάληψη. Αυτό το τελευταίο βήμα είναι αυτό που παραλείπουν πολλοί: το CreateDocument ξεκινά πάντα ένα νέο έγγραφο, αλλά εάν το Active εξακολουθεί να είναι True όταν εκτελεστεί ξανά, απορρίπτει σιωπηρά το έγγραφο που βρίσκεται ακόμα στη μνήμη, επομένως η επαναφορά πρώτα διατηρεί την κατάσταση καθαρή και καλά καθορισμένη. Το εξωτερικό στιγμιότυπο TPdf επαναχρησιμοποιείται σε όλες τις επαναλήψεις, γεγονός που διατηρεί την πίεση κατανομής (allocation pressure) χαμηλή σε μεγάλες εργασίες
Δείτε πώς φαίνεται ο διαχωρισμός σελίδα προς σελίδα περιορισμένος στα απολύτως απαραίτητα:
procedure SplitIntoPages(Source: TPdf; const OutputDir: string);
var
I: Integer;
PdfOut: TPdf;
OutFile: string;
begin
PdfOut := TPdf.Create(nil);
try
for I := 1 to Source.PageCount do
begin
PdfOut.CreateDocument;
// Το Range είναι μια συμβολοσειρά αριθμού σελίδας με βάση το 1; σημείο εισαγωγής 1 = πρώτη θέση
PdfOut.ImportPages(Source, IntToStr(I), 1);
OutFile := OutputDir + '\page_' + Format('%.4d', [I]) + '.pdf';
PdfOut.SaveAs(OutFile);
PdfOut.Active := False; // επαναφορά πριν από το επόμενο CreateDocument
end;
finally
PdfOut.Free;
end;
end;
Η παράμετρος Range στο ImportPages είναι η ίδια μορφή συμβολοσειράς που χρησιμοποιεί το PDFium εσωτερικά: μια λίστα αριθμών σελίδων χωρισμένων με κόμμα, ή εύρη που οριοθετούνται με παύλα, όλα με βάση το 1 (1-based). Το '3' εισάγει τη σελίδα 3. Το '1-5' εισάγει τις σελίδες 1 έως 5 με τη σειρά. Το '2,5,8' εισάγει αυτές τις τρεις σελίδες. Η τρίτη παράμετρος είναι η 1-based θέση εισαγωγής στο έγγραφο προορισμού. περνώντας 1 τοποθετεί πάντα τις εισαγόμενες σελίδες στην αρχή ενός κατά τα άλλα κενού αρχείου, που είναι αυτό που θέλετε εδώ
Διαχωρισμός ανά εύρος σελίδων
Όταν ο καλών παρέχει μια λίστα όπως 1-12,13-24,25-36, την αναλύετε σε ζεύγη έναρξης/λήξης και εκτελείτε τον ίδιο βρόχο, κατασκευάζοντας τη συμβολοσειρά εύρους (range string) από κάθε ζεύγος:
procedure SplitByRanges(Source: TPdf; const RangeList: array of string;
const OutputDir: string);
var
I: Integer;
PdfOut: TPdf;
OutFile: string;
begin
PdfOut := TPdf.Create(nil);
try
for I := 0 to High(RangeList) do
begin
PdfOut.CreateDocument;
PdfOut.ImportPages(Source, RangeList[I], 1);
OutFile := Format('%s\section_%d.pdf', [OutputDir, I + 1]);
PdfOut.SaveAs(OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
Η επικύρωση πριν φτάσετε στο ImportPages έχει σημασία εδώ. Το ImportPages επιστρέφει False όταν ένας αριθμός σελίδας στη συμβολοσειρά εύρους υπερβαίνει το Source.PageCount, αλλά δεν δημιουργεί εξαίρεση και δεν παράγει ένα μερικό αρχείο εξόδου που μπορείτε να ανιχνεύσετε μόνο από το όνομα. Ελέγξτε την τιμή επιστροφής της SaveAs και καταγράψτε τις αποτυχίες ξεχωριστά. ένα εύρος που παράγει ένα κενό αρχείο εξόδου δεν είναι προφανώς λάθος μέχρι κάποιος να το ανοίξει
Διαχωρισμός στα όρια σελιδοδεικτών
Η τρίτη προσέγγιση χρησιμοποιεί τη δική του δομή του εγγράφου και όχι μια εξωτερικά παρεχόμενη λίστα. Κάθε σελιδοδείκτης (bookmark) ανώτατου επιπέδου φέρει έναν αριθμό σελίδας στόχου. η ενότητα που ορίζει εκτείνεται από αυτήν τη σελίδα μέχρι τη σελίδα πριν από την επόμενη του σελιδοδείκτη, ή μέχρι το τέλος του εγγράφου για την τελευταία καταχώρηση
procedure SplitByBookmarks(Source: TPdf; const OutputDir: string);
var
Bm: TBookmarks;
I, StartPage, EndPage: Integer;
PdfOut: TPdf;
RangeStr, OutFile, SafeTitle: string;
begin
Bm := Source.Bookmarks;
if Length(Bm) = 0 then
Exit;
PdfOut := TPdf.Create(nil);
try
for I := 0 to High(Bm) do
begin
StartPage := Bm[I].PageNumber;
if I < High(Bm) then
EndPage := Bm[I + 1].PageNumber - 1
else
EndPage := Source.PageCount;
if (StartPage < 1) or (EndPage < StartPage) then
Continue;
RangeStr := Format('%d-%d', [StartPage, EndPage]);
PdfOut.CreateDocument;
PdfOut.ImportPages(Source, RangeStr, 1);
SafeTitle := StringReplace(Bm[I].Title, '/', '_', [rfReplaceAll]);
SafeTitle := StringReplace(SafeTitle, ':', '_', [rfReplaceAll]);
OutFile := Format('%s\%02d_%s.pdf', [OutputDir, I + 1, SafeTitle]);
PdfOut.SaveAs(OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
Ένα έγγραφο που δεν έχει σελιδοδείκτες δεν είναι μια κατάσταση σφάλματος που αξίζει να εμφανιστεί στον χρήστη ως τέτοια. απλώς σημαίνει ότι αυτή η λειτουργία διαχωρισμού δεν έχει τίποτα από το οποίο να δουλέψει. Ο φρουρός Length(Bm) = 0 το χειρίζεται αθόρυβα. Αυτό που αξίζει να εμφανιστεί είναι όταν ο αριθμός σελίδας ενός σελιδοδείκτη βρίσκεται εκτός του εύρους του εγγράφου, κάτι που συμβαίνει σε κακοσχηματισμένα (malformed) αρχεία όπου το περίγραμμα (outline) δεν ενημερώθηκε ποτέ μετά τη διαγραφή σελίδων. Ο έλεγχος ορίων στα StartPage και EndPage παραλείπει αυτές τις εγγραφές αντί να περάσει ένα εύρος σκουπιδιών (garbage range) στο ImportPages
Ονομασία αρχείου εξόδου και επαναφορά του Active
Η ασφάλεια του ονόματος αρχείου για ονόματα που προέρχονται από σελιδοδείκτες χρειάζεται ρητή προσοχή. Οι τίτλοι των σελιδοδεικτών μπορεί να περιέχουν χαρακτήρες που ισχύουν σε μια συμβολοσειρά PDF αλλά όχι σε μια διαδρομή συστήματος αρχείων (filesystem). Τουλάχιστον, αντικαταστήστε την κάθετο, την ανάποδη κάθετο και την άνω και κάτω τελεία προτού δημιουργήσετε τη διαδρομή εξόδου. Στα Windows, απαγορεύονται επίσης τα *, ?, ", <, > και |. Ένας απλός βρόχος πάνω από ένα σταθερό σύνολο τα καλύπτει χωρίς να τραβήξει μια κανονική έκφραση (regex)
Η γραμμή Active := False στο τέλος κάθε επανάληψης αξίζει έμφαση επειδή είναι η μόνη μη προφανής απαίτηση στο μοτίβο. Η CreateDocument δεν κλείνει σιωπηρά ό,τι είναι ανοιχτό. Εάν η Active εξακολουθεί να είναι True όταν η CreateDocument εκτελείται ξανά, το PDFium απορρίπτει το τρέχον έγγραφο και ξεκινά ένα νέο χωρίς σφάλμα, αλλά η συμπεριφορά ορίζεται από την υλοποίηση (implementation-defined) σε ακραίες περιπτώσεις και η πρόθεση είναι σαφέστερη όταν κάνετε επαναφορά ρητά. Σκεφτείτε το ως το ζεύγος της try/finally: το μπλοκ finally απελευθερώνει το εξωτερικό αντικείμενο. Το Active := False επαναφέρει την εσωτερική κατάσταση του εγγράφου μεταξύ των επαναλήψεων βρόχου
Η χρήση μνήμης σε μια μεγάλη εργασία διαχωρισμού παραμένει επίπεδη με αυτήν την προσέγγιση επειδή δεν κρατάτε ποτέ περισσότερα από ένα έγγραφα εξόδου στη μνήμη ταυτόχρονα. Το έγγραφο προέλευσης παραμένει ανοιχτό και μόνο για ανάγνωση σε όλη τη διάρκεια. Το ImportPages αντιγράφει δεδομένα σελίδας στο νέο έγγραφο χωρίς να τροποποιεί την πηγή. Εάν η πηγή είναι κρυπτογραφημένη, ανοίξτε την με τον κωδικό πρόσβασής της πριν από τον βρόχο και οι αντιγραμμένες σελίδες σε κάθε αρχείο εξόδου θα είναι μη κρυπτογραφημένες, η οποία είναι συνήθως η σωστή συμπεριφορά για διαχωρισμένη έξοδο που διανέμεται σε διαφορετικούς παραλήπτες
Ένα ακόμη πράγμα για τη SaveAs: επιστρέφει ένα Boolean. Ένας κατάλογος εξόδου που δεν υπάρχει, μια διαδρομή με χαρακτήρες που το λειτουργικό σύστημα απορρίπτει ή μια κατάσταση γεμάτου δίσκου, θα κάνουν τη SaveAs να επιστρέψει False χωρίς να δημιουργηθεί εξαίρεση. Σε μια μαζική εργασία που χωρίζει ένα έγγραφο 200 σελίδων σε 200 αρχεία μιας σελίδας, μια σιωπηρή αποτυχία στη σελίδα 147 είναι εύκολο να παραβλεφθεί. Ελέγξτε την τιμή επιστροφής σε κάθε κλήση και μετρήστε τις επιτυχίες έναντι του αναμενόμενου συνόλου όταν τελειώσει ο βρόχος
Οι μέθοδοι ImportPages και CreateDocument που εμφανίζονται εδώ αποτελούν μέρος του PDFium Component για Delphi και C++Builder