Το PDFium Component σάς δίνει μία μέθοδο για τον διαχωρισμό PDF: την ImportPages. Όλα τα υπόλοιπα, είτε απομονώνετε μία μόνο σελίδα, είτε κόβετε σε αυθαίρετα όρια, είτε ακολουθείτε τη δομή σελιδοδεικτών του ίδιου του εγγράφου, είναι απλώς διαφορετικοί τρόποι να αποφασίσετε ποιοι αριθμοί σελίδων μπαίνουν σε κάθε αρχείο εξόδου. Ο μηχανισμός μένει ο ίδιος. Το να το καταλάβετε νωρίς γλιτώνει πολλές λάθος στροφές
Πώς λειτουργεί ο βρόχος διαχωρισμού
Το μοτίβο είναι το ίδιο ανεξάρτητα από το πώς χωρίζετε το έγγραφο προέλευσης. Δημιουργήστε μια φρέσκια παρουσία TPdf, καλέστε πάνω της τη CreateDocument για να αρχικοποιήσετε ένα κενό PDF στη μνήμη, εισαγάγετε τις σελίδες που θέλετε με την ImportPages, αποθηκεύστε το αποτέλεσμα και έπειτα επαναφέρετε το Active σε False πριν από την επόμενη επανάληψη. Αυτό το τελευταίο βήμα είναι εκείνο που ξεχνούν οι περισσότεροι: η CreateDocument δεν κλείνει σιωπηρά το έγγραφο που βρίσκεται ακόμη στη μνήμη, οπότε πρέπει να αποθηκεύσετε την έξοδό σας και να επαναφέρετε ρητά το Active := False πριν την καλέσετε ξανά· η επαναφορά πρώτα κρατά την κατάσταση καθαρή και σαφώς ορισμένη. Η εξωτερική παρουσία TPdf επαναχρησιμοποιείται σε όλες τις επαναλήψεις, κάτι που κρατά χαμηλά την πίεση δέσμευσης μνήμης σε μεγάλες εργασίες
Να πώς μοιάζει ο διαχωρισμός σελίδα προς σελίδα, γυμνωμένος ως τα βασικά του:
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 = πρώτη θέση
if not PdfOut.ImportPages(Source, IntToStr(I), 1) then
raise Exception.CreateFmt('Failed to import page %d', [I]);
OutFile := OutputDir + '\page_' + Format('%.4d', [I]) + '.pdf';
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False; // επαναφορά πριν από την επόμενη CreateDocument
end;
finally
PdfOut.Free;
end;
end;
Η παράμετρος Range της ImportPages έχει την ίδια μορφή συμβολοσειράς που χρησιμοποιεί εσωτερικά το PDFium: μια λίστα αριθμών σελίδων χωρισμένων με κόμματα ή εύρη οριοθετημένα με παύλα, όλα με βάση το 1. Το '3' εισάγει τη σελίδα 3. Το '1-5' εισάγει τις σελίδες 1 έως 5 με τη σειρά. Το '2,5,8' εισάγει εκείνες τις τρεις σελίδες. Η τρίτη παράμετρος είναι η θέση εισαγωγής με βάση το 1 μέσα στο έγγραφο προορισμού· περνώντας 1 τοποθετείτε πάντα τις εισαγόμενες σελίδες στην αρχή ενός κατά τα άλλα κενού αρχείου, που είναι ακριβώς αυτό που θέλετε εδώ
Διαχωρισμός ανά εύρη σελίδων
Όταν ο καλών δίνει μια λίστα όπως 1-12,13-24,25-36, την αναλύετε σε ζεύγη αρχής και τέλους και τρέχετε τον ίδιο βρόχο, χτίζοντας τη συμβολοσειρά εύρους από κάθε ζεύγος:
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;
if not PdfOut.ImportPages(Source, RangeList[I], 1) then
raise Exception.Create('Invalid page range: ' + RangeList[I]);
OutFile := Format('%s\section_%d.pdf', [OutputDir, I + 1]);
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
Η επικύρωση πριν φτάσετε στην ImportPages έχει σημασία εδώ. Η ImportPages επιστρέφει False όταν ένας αριθμός σελίδας μέσα στη συμβολοσειρά εύρους ξεπερνά το Source.PageCount, αλλά δεν εγείρει εξαίρεση και δεν παράγει μερικό αρχείο εξόδου που να το εντοπίζετε από το όνομα και μόνο. Ελέγξτε την τιμή επιστροφής της SaveAs και καταγράψτε ξεχωριστά τις αποτυχίες· ένα εύρος που παράγει κενό αρχείο εξόδου δεν φαίνεται προφανώς λάθος μέχρι να το ανοίξει κάποιος
Διαχωρισμός στα όρια των σελιδοδεικτών
Η τρίτη προσέγγιση χρησιμοποιεί την ίδια τη δομή του εγγράφου αντί για μια λίστα που δίνεται από έξω. Κάθε σελιδοδείκτης ανώτατου επιπέδου κουβαλά έναν αριθμό σελίδας-στόχου· η ενότητα που ορίζει εκτείνεται από εκείνη τη σελίδα ως μία πριν από τη σελίδα του επόμενου σελιδοδείκτη, ή ως το τέλος του εγγράφου για την τελευταία καταχώριση
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;
if not PdfOut.ImportPages(Source, RangeStr, 1) then
begin
PdfOut.Active := False;
Continue; // παράλειψη μιας κακοσχηματισμένης ενότητας αντί για εγγραφή κενού αρχείου
end;
SafeTitle := StringReplace(Bm[I].Title, '/', '_', [rfReplaceAll]);
SafeTitle := StringReplace(SafeTitle, ':', '_', [rfReplaceAll]);
OutFile := Format('%s\%02d_%s.pdf', [OutputDir, I + 1, SafeTitle]);
if not PdfOut.SaveAs(OutFile) then
raise Exception.Create('Failed to save ' + OutFile);
PdfOut.Active := False;
end;
finally
PdfOut.Free;
end;
end;
Ένα έγγραφο χωρίς σελιδοδείκτες δεν είναι κατάσταση σφάλματος που αξίζει να παρουσιαστεί ως τέτοια στον χρήστη· σημαίνει απλώς ότι αυτή η λειτουργία διαχωρισμού δεν έχει από πού να ξεκινήσει. Ο έλεγχος Length(Bm) = 0 το χειρίζεται σιωπηλά. Αυτό που αξίζει να αναδειχθεί είναι όταν ο αριθμός σελίδας ενός σελιδοδείκτη βρίσκεται εκτός του εύρους του εγγράφου, κάτι που συμβαίνει σε κακοσχηματισμένα αρχεία όπου η διάρθρωση δεν ενημερώθηκε ποτέ μετά τη διαγραφή σελίδων. Ο έλεγχος ορίων στα StartPage και EndPage παραλείπει αυτές τις καταχωρίσεις αντί να περάσει σκουπίδια ως εύρος στην ImportPages
Ονοματοδοσία αρχείων εξόδου και η επαναφορά του Active
Η ασφάλεια των ονομάτων αρχείων που προκύπτουν από σελιδοδείκτες χρειάζεται ρητή προσοχή. Οι τίτλοι σελιδοδεικτών μπορεί να περιέχουν χαρακτήρες έγκυρους μέσα σε συμβολοσειρά PDF αλλά όχι σε διαδρομή αρχείου. Κατ' ελάχιστον, αντικαταστήστε την κάθετο, την ανάστροφη κάθετο και την άνω και κάτω τελεία πριν χτίσετε τη διαδρομή εξόδου. Στα Windows απαγορεύονται επίσης τα *, ?, ", <, > και |· ένας απλός βρόχος πάνω σε ένα σταθερό σύνολο τα καλύπτει χωρίς να φέρετε μέσα regex
Η γραμμή Active := False στο τέλος κάθε επανάληψης αξίζει έμφαση, επειδή είναι η μόνη μη προφανής απαίτηση του μοτίβου. Η CreateDocument δεν κλείνει σιωπηρά ό,τι είναι ανοιχτό. Αν το Active είναι ακόμη True όταν τρέξει ξανά η CreateDocument, το έγγραφο που βρίσκεται ακόμη στη μνήμη δεν έκλεισε ούτε αποθηκεύτηκε ποτέ σωστά, και δεν μπορείτε να βασιστείτε σε σαφώς ορισμένη συμπεριφορά σε αυτή την κατάσταση, οπότε αποθηκεύστε και επαναφέρετε ρητά πριν ξεκινήσετε το επόμενο έγγραφο. Σκεφτείτε το ως το ταίρι του try/finally: το μπλοκ finally απελευθερώνει το εξωτερικό αντικείμενο, ενώ το Active := False επαναφέρει την κατάσταση του εσωτερικού εγγράφου ανάμεσα στις επαναλήψεις του βρόχου
Η χρήση μνήμης σε μια μεγάλη εργασία διαχωρισμού μένει επίπεδη με αυτή την προσέγγιση, επειδή ποτέ δεν κρατάτε πάνω από ένα έγγραφο εξόδου στη μνήμη ταυτόχρονα. Το έγγραφο προέλευσης παραμένει ανοιχτό και μόνο για ανάγνωση σε όλη τη διάρκεια· η ImportPages αντιγράφει δεδομένα σελίδων στο νέο έγγραφο χωρίς να τροποποιεί την προέλευση. Αν η προέλευση είναι κρυπτογραφημένη, ανοίξτε την με τον κωδικό της πριν από τον βρόχο και οι αντιγραμμένες σελίδες σε κάθε αρχείο εξόδου θα είναι μη κρυπτογραφημένες, που συνήθως είναι η σωστή συμπεριφορά για διαχωρισμένη έξοδο που διανέμεται σε διαφορετικούς παραλήπτες
Ένα ακόμη σχετικά με τη SaveAs: επιστρέφει Boolean. Ένας κατάλογος εξόδου που δεν υπάρχει, μια διαδρομή με χαρακτήρες που απορρίπτει το λειτουργικό σύστημα ή ένας γεμάτος δίσκος θα κάνουν όλα τη SaveAs να επιστρέψει False χωρίς να εγείρει εξαίρεση. Σε μια μαζική εργασία που διαχωρίζει ένα έγγραφο 200 σελίδων σε 200 μονοσέλιδα αρχεία, μια σιωπηλή αποτυχία στη σελίδα 147 προσπερνιέται εύκολα. Ελέγχετε την τιμή επιστροφής σε κάθε κλήση και μετρήστε τις επιτυχίες έναντι του αναμενόμενου συνόλου όταν τελειώσει ο βρόχος
Οι μέθοδοι ImportPages και CreateDocument που παρουσιάζονται εδώ αποτελούν μέρος του PDFium Component για Delphi και C++Builder