Οι διαστάσεις της σελίδας PDF είναι σταθερές τη στιγμή της δημιουργίας μιας σελίδας, οπότε δεν μπορείτε απλώς να κλιμακώσετε το περιεχόμενο επιτόπου (in place) με τον τρόπο που θα μπορούσατε να αλλάξετε το μέγεθος μιας εικόνας. Το μοντέλο της βιβλιοθήκης που κάνει τη σμίκρυνση πρακτική είναι η λήψη και επανασχεδίαση (capture-and-redraw): ανασηκώστε το περιεχόμενο κάθε σελίδας από το έγγραφο σε μια λαβή (handle), δημιουργήστε μια νέα κενή σελίδα στο αρχικό μέγεθος μέσου (media size) και, στη συνέχεια, σχεδιάστε ξανά το περιεχόμενο που έχετε λάβει σε ένα μειωμένο πλαίσιο οριοθέτησης (bounding box). Ο περιβάλλων λευκός χώρος γίνεται το περιθώριο. Σε κλίμακα 70% σε μια σελίδα A4, για παράδειγμα, το 15% του πλάτους πέφτει σε κάθε πλευρά και το ίδιο κλάσμα πάνω και κάτω, το οποίο είναι ακριβώς αυτό που παράγει η αριθμητική περιθωρίων παρακάτω
Πώς λειτουργεί η CapturePage
Η CapturePage παίρνει έναν αριθμό σελίδας, προωθεί το περιεχόμενο αυτής της σελίδας σε ένα αντικείμενο λήψης (capture object) στη μνήμη και αφαιρεί τη σελίδα από το δέντρο σελίδων του εγγράφου. Αυτή η αφαίρεση είναι σκόπιμη και είναι ο λόγος που ο βρόχος (loop) επιλέγει πάντα τη σελίδα 1 ανεξάρτητα από τον δείκτη επανάληψης: μόλις η σελίδα 1 ληφθεί και διαγραφεί, αυτό που ήταν η σελίδα 2 γίνεται η νέα σελίδα 1, και ούτω καθεξής. Εάν αυξάνετε τον επιλογέα σελίδας μαζί με τον μετρητή βρόχου, θα παραλείπετε κάθε δεύτερη σελίδα και θα καταλήξετε με τη μισή αναμενόμενη έξοδο
Η λαβή λήψης που επιστρέφεται από την CapturePage δεν είναι αναφορά σελίδας· μοιάζει περισσότερο με ένα στιγμιότυπο περιεχομένου. Παραμένει έγκυρη μέχρι να καλέσετε την DrawCapturedPage ή να την απελευθερώσετε ρητά. Η DrawCapturedPage παίρνει αυτήν τη λαβή συν ένα ορθογώνιο προορισμού που δίνεται ως αριστερή μετατόπιση (left offset), κάτω μετατόπιση (bottom offset), πλάτος και ύψος, όλα σε σημεία (points). Η βιβλιοθήκη κλιμακώνει το περιεχόμενο που έχει ληφθεί για να ταιριάζει ακριβώς σε αυτό το ορθογώνιο, διατηρώντας την αναλογία διαστάσεων μόνο εάν το ορθογώνιό σας τυχαίνει να ταιριάζει με τις αρχικές αναλογίες. Για ομοιόμορφη κλιμάκωση θέλετε το ορθογώνιο να είναι το αρχικό μέγεθος πολλαπλασιασμένο με τον συντελεστή κλίμακας (scale factor), κεντραρισμένο στη σελίδα
Τα μαθηματικά του κεντραρίσματος
Με συντελεστή κλίμακας 70%, το υπόλοιπο 30% κάθε διάστασης μοιράζεται εξίσου μεταξύ των δύο πλευρών. Επομένως, η οριζόντια εσοχή (inset) είναι pageWidth * (1.0 - 0.70) / 2, που είναι το 15% του πλάτους, και η κατακόρυφη εσοχή ακολουθεί τον ίδιο τύπο χρησιμοποιώντας το ύψος της σελίδας. Το ορθογώνιο προορισμού για την DrawCapturedPage ξεκινά τότε στο (horizBorder, vertBorder) και εκτείνεται κατά pageWidth - 2 * horizBorder επί pageHeight - 2 * vertBorder. Αυτή η αριθμητική δεν είναι ειδική για τη βιβλιοθήκη· είναι απλώς η γεωμετρία της συμμετρικής προσαρμογής ενός μικρότερου ορθογωνίου μέσα σε ένα μεγαλύτερο
Ένα πράγμα που αξίζει να σημειωθεί: η SetOrigin(1) τοποθετεί την αρχή των συντεταγμένων επάνω αριστερά αντί για κάτω αριστερά. Οι τιμές περιθωρίου που περνάτε στην DrawCapturedPage μετρώνται από όποια αρχή (origin) κι αν ορίσετε, οπότε αν αλλάξετε λειτουργίες αρχής μεταξύ φόρτωσης και σχεδίασης, το κεντράρισμα θα είναι λανθασμένο
Παράδειγμα C#
Ο παρακάτω κώδικας επεξεργάζεται κάθε σελίδα του Pages.pdf μέσω του κύκλου λήψης και επανασχεδίασης και γράφει το αποτέλεσμα στο newpages.pdf. Το PDFL είναι το αντικείμενο περιτυλίγματος ActiveX/COM που προστέθηκε στο έργο από το PDFlibDLL64.dll
private void ScalePages_Click(object sender, EventArgs e)
{
File.Delete("newpages.pdf");
double pageWidth, pageHeight, horizBorder, vertBorder;
double scaleFactor = 0.70;
int capturedPageId, ret;
PDFL.LoadFromFile("Pages.pdf", "");
PDFL.SetOrigin(1);
int numPages = PDFL.PageCount();
for (int i = 1; i <= numPages; i++)
{
// Always select page 1: CapturePage removes the page, so page 2
// becomes page 1 on the next iteration.
PDFL.SelectPage(1);
pageWidth = PDFL.PageWidth();
pageHeight = PDFL.PageHeight();
horizBorder = pageWidth * (1.0 - scaleFactor) / 2;
vertBorder = pageHeight * (1.0 - scaleFactor) / 2;
capturedPageId = PDFL.CapturePage(1);
PDFL.NewPage();
PDFL.SetPageDimensions(pageWidth, pageHeight);
ret = PDFL.DrawCapturedPage(
capturedPageId,
horizBorder, vertBorder,
pageWidth - 2 * horizBorder,
pageHeight - 2 * vertBorder);
}
PDFL.SaveToFile("newpages.pdf");
}
Παράδειγμα Delphi
Η έκδοση Delphi χρησιμοποιεί την TPDFlib απευθείας αντί μέσω του επιπέδου COM, αλλά η ακολουθία κλήσεων είναι πανομοιότυπη. Μια πρακτική διαφορά είναι ο φρουρός (guard) του αρχείου εξόδου: FileExists συν DeleteFile αντί για File.Delete, επειδή η SaveToFile θα αποτύχει εάν ο προορισμός είναι κλειδωμένος από προηγούμενη εκτέλεση που είναι ακόμα ανοιχτή σε κάποιο πρόγραμμα προβολής
procedure TForm1.ScalePagesClick(Sender: TObject);
var
PDFLib: TPDFlib;
pageWidth, pageHeight, horizBorder, vertBorder: Double;
scaleFactor: Double;
capturedPageId, ret, numPages, i: Integer;
begin
if FileExists('newpages.pdf') then
DeleteFile('newpages.pdf');
scaleFactor := 0.70;
PDFLib := TPDFlib.Create;
try
PDFLib.LoadFromFile('Pages.pdf', '');
PDFLib.SetOrigin(1);
numPages := PDFLib.PageCount();
for i := 1 to numPages do
begin
PDFLib.SelectPage(1);
pageWidth := PDFLib.PageWidth();
pageHeight := PDFLib.PageHeight();
horizBorder := pageWidth * (1.0 - scaleFactor) / 2;
vertBorder := pageHeight * (1.0 - scaleFactor) / 2;
capturedPageId := PDFLib.CapturePage(1);
PDFLib.NewPage();
PDFLib.SetPageDimensions(pageWidth, pageHeight);
ret := PDFLib.DrawCapturedPage(
capturedPageId,
horizBorder, vertBorder,
pageWidth - 2 * horizBorder,
pageHeight - 2 * vertBorder);
end;
PDFLib.SaveToFile('newpages.pdf');
finally
PDFLib.Free;
end;
end;
Τι ελέγχει πραγματικά ο συντελεστής κλίμακας
Η τιμή 0.70 εδώ σημαίνει ότι το αποδοθέν περιεχόμενο καταλαμβάνει το 70% κάθε διάστασης της σελίδας, όχι ότι το αρχείο έχει το 70% του αρχικού του μεγέθους σε byte. Το μέγεθος αρχείου μετά από αυτήν τη λειτουργία εξαρτάται από την πολυπλοκότητα του αρχικού περιεχομένου· μια σελίδα με μεγάλες εικόνες δεν θα συρρικνωθεί αναλογικά επειδή τα δεδομένα pixel επανασχεδιάζονται στην ίδια ανάλυση σε μικρότερη περιοχή. Εάν ο στόχος είναι η συμπίεση σε επίπεδο byte, η σωστή προσέγγιση είναι η LinearizeFile ή η εκ νέου αποθήκευση (re-saving) με συμπίεση ροής, όχι η γεωμετρική κλιμάκωση
Το ποσοστό 70% δεν αποτελεί επίσης αυστηρό όριο (hard limit). Οποιαδήποτε τιμή μεταξύ 0.0 και 1.0 λειτουργεί, και τιμές πάνω από 1.0 μεγεθύνουν το περιεχόμενο πέρα από το αρχικό όριο της σελίδας, το οποίο περικόπτεται στο άκρο του πλαισίου μέσων (media box) εκτός εάν αυξήσετε επίσης τις διαστάσεις της σελίδας. Έγγραφα μικτού μεγέθους χειρίζονται φυσιολογικά επειδή τα PageWidth και PageHeight ερωτώνται ανά σελίδα πριν από τον υπολογισμό του περιθωρίου, επομένως ένα έγγραφο όπου οι μονές σελίδες είναι A4 και οι ζυγές σελίδες είναι A3 θα παράγει σωστά κεντραρισμένη έξοδο σε κάθε μέγεθος σελίδας χωρίς καμία ειδική μεταχείριση
Πού μπορούν να πάνε στραβά τα πράγματα
Δύο λειτουργίες αστοχίας (failure modes) προκύπτουν στην πράξη. Η πρώτη είναι ένα αρχείο εξόδου που έχει παραμείνει ανοιχτό σε ένα πρόγραμμα προβολής PDF από προηγούμενη εκτέλεση: Η SaveToFile θα αποτύχει ή θα γράψει μηδέν byte ανάλογα με την πλατφόρμα, και η νέα έξοδος δεν φτάνει ποτέ. Ο φρουρός διαγραφής αρχείου στο επάνω μέρος της συνάρτησης το χειρίζεται αυτό για την ανάπτυξη, αλλά σε μια γραμμή παραγωγής, η εγγραφή σε μια προσωρινή διαδρομή και η μετονομασία κατά την επιτυχία είναι ασφαλέστερη
Η δεύτερη είναι η αναντιστοιχία καταμέτρησης σελίδων. Επειδή η CapturePage αφαιρεί σελίδες από το έγγραφο καθώς τις επεξεργάζεται, η καταμέτρηση που διαβάζετε από το PageCount() πριν από τον βρόχο είναι το σωστό όριο για επανάληψη. Η κλήση της PageCount() μέσα στον βρόχο θα επέστρεφε έναν μειούμενο αριθμό σε κάθε πέρασμα και θα υπήρχε πρόωρη έξοδος, αφήνοντας τις τελευταίες σελίδες χωρίς επεξεργασία. Η μεταβλητή βρόχου στα παραδείγματα χρησιμεύει μόνο ως μετρητής εναπομενουσών επαναλήψεων· δεν χρησιμοποιείται ποτέ για την επιλογή σελίδας, επειδή η σελίδα προς επιλογή είναι πάντα το 1 για τον λόγο που εξηγήθηκε προηγουμένως
Οι κλήσεις χειρισμού σελίδων που παρουσιάζονται εδώ, συμπεριλαμβανομένων των CapturePage, DrawCapturedPage και SetPageDimensions, αποτελούν μέρος της losLab PDF Library για Delphi, C#, VB.NET και C++