Η συρραφή σελίδων συνθέτει το περιεχόμενο υπαρχουσών σελίδων σε έναν κοινό καμβά, κάτι που αποτελεί διαφορετική λειτουργία από τη συγχώνευση αρχείων. Η losLab PDF Library υλοποιεί τη συρραφή σελίδων Form XObject και συνθέσιμα πρότυπα υδατογραφημάτων για το Delphi: τα StitchPageOverlay, StitchPagesSideBySide και StitchPagesVertically τοποθετούν τις ληφθείσες σελίδες σε οποιαδήποτε κλίμακα με πλήρη πιστότητα διανύσματος (vector fidelity), και το TPDFlibCompositeWatermark στοιβάζει υδατογραφήματα κειμένου και εικόνας σε ένα εύρος σελίδων με μία κλήση. Εάν αυτό που πραγματικά χρειάζεστε είναι να προσαρτήσετε ολόκληρες σελίδες από διάφορα έγγραφα σε ένα αρχείο, αυτή είναι η οικογένεια συγχώνευσης (merge), η οποία καλύπτεται στο άρθρο για τη γρήγορη συγχώνευση PDF σε επίπεδο byte· αυτό το άρθρο αφορά τον συνδυασμό περιεχομένου σελίδας μέσα σε έναν κοινό χώρο συντεταγμένων
Τα αιτήματα που οδηγούν εδώ είναι συνηθισμένα και επίμονα. Ένα σχολείο θέλει δύο σελίδες εξετάσεων τοποθετημένες σε ένα οριζόντιο φύλλο (landscape sheet) για να μειώσει τη χρήση χαρτιού στο μισό. Ένας υπεύθυνος αναθεώρησης τοπικοποίησης θέλει την αγγλική σελίδα και τη μετάφρασή της δίπλα-δίπλα σε μία σελίδα, με κοινή κύλιση. Μια νομική ομάδα θέλει κάθε προσχέδιο να σφραγίζεται με τρία επίπεδα ταυτόχρονα: ένα λογότυπο στη γωνία, ένα διαγώνιο CONFIDENTIAL στη μέση και μια μικρή γραμμή υποσέλιδου, μόνο στις σελίδες 1 έως 3. Και τα τρία αποτελούν το ίδιο υποκείμενο πρόβλημα, δηλαδή την τοποθέτηση ήδη στοιχειοθετημένου περιεχομένου σελίδας κάπου αλλού, σε κάποιο άλλο μέγεθος, χωρίς να υποβαθμίζεται σε bitmap
Γιατί αποτυγχάνει η συνένωση των ροών περιεχομένου PDF;
Η απλή συρραφή, δηλαδή η αντιγραφή της ροής /Contents της σελίδας προέλευσης στη σελίδα προορισμού, αποτυγχάνει για τρεις δομικούς λόγους. Πρώτον, οι συντεταγμένες συγκρούονται: και οι δύο ροές περιεχομένου υποθέτουν τη δική τους προέλευση (origin) στο κάτω αριστερό μέρος του δικού τους MediaBox, επομένως το επικολλημένο περιεχόμενο προσγειώνεται πάνω από το υπάρχον περιεχόμενο σε λάθος σημείο. Δεύτερον, τα ονόματα των πόρων (resources) συγκρούονται: κάθε σελίδα επιλύει τελεστές όπως /F1 Tf και /Im0 Do έναντι του δικού της λεξικού /Resources, και δύο σελίδες συνήθως δεσμεύουν το ίδιο όνομα σε διαφορετικές γραμματοσειρές ή εικόνες, οπότε μία από αυτές αποδίδεται σιωπηλά με λάθος πόρο. Τρίτον, μια ακατέργαστη αντιγραφή ροής δεν σας δίνει καθόλου κλιμάκωση (scaling)· το καλύτερο που μπορείτε να κάνετε είναι η μετατόπιση (translation)
Τα Form XObjects λύνουν και τα τρία προβλήματα ταυτόχρονα, και γι' αυτό η losLab PDF Library βασίζει τη συρραφή της σε αυτά. Το πρότυπο ISO 32000-1 §8.10 ορίζει ένα form XObject ως μια αυτόνομη ροή περιεχομένου με το δικό της λεξικό /Resources και το δικό της χώρο συντεταγμένων, τοποθετημένη σε μια σελίδα καλώντας το όνομά της κάτω από οποιονδήποτε τρέχοντα ενεργό πίνακα μετασχηματισμού (transformation matrix). Το CapturePage τυλίγει μια ολόκληρη υπάρχουσα σελίδα, περιεχόμενο συν πόρους, σε ένα XObject /Subtype /Form, και οι συναρτήσεις συρραφής στη συνέχεια το τοποθετούν με έναν πίνακα cm: κλίμακα (scale), μετατόπιση (translate), περιστροφή (rotate), κατά βούληση. Επειδή η πηγή παραμένει διανυσματική, το κείμενο παραμένει κείμενο και τα μονοπάτια (paths) παραμένουν μονοπάτια σε οποιοδήποτε επίπεδο ζουμ, η ίδια ιδιότητα που καθιστά τα διανυσματικά γραφικά και τα μοτίβα σκίασης ανεξάρτητα από την ανάλυση. Τίποτα δεν ραστεροποιείται, και το απομονωμένο λεξικό πόρων σημαίνει ότι η ληφθείσα σελίδα δεν μπορεί να συγκρουστεί με τη σελίδα προορισμού για το όνομα /F1
Πώς συνδυάζετε δύο σελίδες PDF σε μία σελίδα;
Το StitchPagesSideBySide(Page1, Page2, Gap, TargetHeight) είναι η άμεση απάντηση: λαμβάνει και τις δύο σελίδες προέλευσης, δημιουργεί μια νέα ευρύτερη σελίδα και τοποθετεί τις δύο λήψεις αριστερά και δεξιά με ένα διάκενο (gutter) μεταξύ τους, καθεμία κλιμακούμενη ώστε να ταιριάζει στο TargetHeight. Η συνάρτηση επιστρέφει τον αριθμό της νέας σελίδας (με βάση το 1) ή 0 σε περίπτωση αποτυχίας, και οι δύο σελίδες προέλευσης λαμβάνονται και κρύβονται ως μέρος της λειτουργίας. Η περίπτωση των εξετάσεων με δύο σελίδες ανά φύλλο είναι μια δουλειά τριών γραμμών
var
Lib: TPDFlib;
NewPage: Integer;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('exam.pdf', '');
// Pages 1 and 2 on one new wider page with an 18pt gutter,
// both scaled to a 595pt output height (A4 landscape)
NewPage := Lib.StitchPagesSideBySide(1, 2, 18, 595);
if NewPage > 0 then
Lib.SaveToFile('exam-2up.pdf');
finally
Lib.Free;
end;
end;
Για κατακόρυφη σύνθεση, το StitchPagesVertically(PageRanges, Gap, TargetWidth) στοιβάζει ένα ολόκληρο εύρος σελίδων σε μία ψηλή σελίδα στυλ αφίσας. Το PageRanges δέχεται την τυπική σύνταξη εύρους όπως '1-3,5'. Και κάθε σελίδα προέλευσης κλιμακώνεται έτσι ώστε το πλάτος της να ταιριάζει με το TargetWidth, οπότε η συνεισφορά ύψους κάθε σελίδας είναι το δικό της ύψος επί τον δικό της συντελεστή κλίμακας, και το ύψος της σελίδας εξόδου είναι το άθροισμα αυτών συν ένα Gap μεταξύ κάθε ζεύγους. Αυτό είναι το σχήμα που θέλετε για έγγραφα αναθεώρησης συνεχούς κύλισης ή μεγάλες αποδείξεις που συναρμολογούνται από πηγές μεγέθους letter
Πώς επικαλύπτετε μια σελίδα πάνω σε μια άλλη;
Το StitchPageOverlay(SourcePage, TargetPage, Left, Top, Width, Height, Opacity) σχεδιάζει μια υπάρχουσα σελίδα πάνω σε μια άλλη υπάρχουσα σελίδα, αντί να δημιουργήσει μια νέα. Η σελίδα προέλευσης λαμβάνεται ως form XObject και σχεδιάζεται στον προορισμό στο ορθογώνιο που δίνετε, με Opacity από 0 έως 1 (το 1 είναι αδιαφανές). Το πλάτος και το ύψος είναι ελεύθερες παράμετροι, επομένως η τοποθέτηση τεντώνεται εάν η αναλογία σας διαφέρει από την αναλογία διαστάσεων της σελίδας προέλευσης· περάστε ένα αναλογικό ζεύγος όταν θέλετε μια μη παραμορφωμένη μικρογραφία της πηγής. Μια τυπική χρήση είναι ένα έγγραφο του οποίου το μπλοκ έγκρισης διατηρείται ως δική του σελίδα και σφραγίζεται στο εξώφυλλο
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('report.pdf', '');
// Page 5 holds the approval block; place it on page 1
// in a 260 x 180 box at 45% opacity. Page 5 is consumed:
// it is captured and hidden, so pages 6..n become 5..n-1
if Lib.StitchPageOverlay(5, 1, 300, 500, 260, 180, 0.45) = 1 then
Lib.SaveToFile('report-approved.pdf');
finally
Lib.Free;
end;
end;
Τι συμβαίνει με την αρίθμηση των σελίδων μετά από μια λήψη;
Το CapturePage καταναλώνει τη σελίδα προέλευσης: το περιεχόμενό της μεταφέρεται στο XObject και η άδεια πλέον σελίδα κρύβεται, έτσι ώστε κάθε σελίδα που έχει αρίθμηση μεγαλύτερη από την πηγή να μετατοπίζεται αμέσως προς τα κάτω κατά μία. Αυτή είναι η πιο σημαντική σημασιολογία σε ολόκληρο το API συρραφής. Η losLab PDF Library χειρίζεται την αριθμητική μέσα σε κάθε κλήση συρραφής, το StitchPageOverlay προσαρμόζει τον δείκτη προορισμού όταν ο προορισμός βρίσκεται πάνω από την πηγή, και το StitchPagesSideBySide λαμβάνει πρώτα τη σελίδα με τη μεγαλύτερη αρίθμηση, έτσι ώστε ο δείκτης της δεύτερης λήψης να είναι ακόμα έγκυρος, αλλά ο δικός σας περιβάλλων κώδικας πρέπει να λαμβάνει υπόψη τη μετατόπιση μεταξύ των κλήσεων. Δύο κανόνες διατηρούν τον κώδικα πολλαπλών συρραφών σωστό. Καταγράψτε τυχόν μεγέθη σελίδων ή αριθμούς σελίδων που θα χρειαστείτε πριν από την πρώτη λήψη, επειδή αλλάζουν μετά από αυτήν. Και όταν λαμβάνετε μόνοι σας πολλές σελίδες σε έναν βρόχο (loop), επαναλάβετε σε φθίνουσα σειρά σελίδων, έτσι ώστε η απόκρυψη μιας μεταγενέστερης σελίδας να μην διαταράσσει ποτέ τον δείκτη μιας προηγούμενης που δεν έχετε λάβει ακόμα
Πώς εφαρμόζετε ένα υδατογράφημα πολλαπλών επιπέδων σε ένα εύρος σελίδων;
Το επίπεδο προτύπων υδατογραφημάτων στο PDFlibWatermark.pas μετατρέπει το "ορισμός μία φορά, εφαρμογή σε πολλές σελίδες" σε αντικείμενα. Το TPDFlibWatermarkBase μεταφέρει όσα μοιράζεται κάθε υδατογράφημα: μια Position εννέα σημείων (από wpTopLeft έως wpBottomRight, συν το wpCustom που χρησιμοποιεί ακατέργαστες συντεταγμένες X/Y), μια γωνία Angle σε μοίρες, μια διαφάνεια Opacity από 0 έως 1 και μια συμβολοσειρά PageRange όπου το κενό σημαίνει όλες τις σελίδες. Το TPDFlibTextWatermark προσθέτει κείμενο, ένα ID γραμματοσειράς (με προεπιλογή το Helvetica), μέγεθος και ένα χρώμα RGB· το TPDFlibImageWatermark προσθέτει ένα ID εικόνας από μια εικόνα που προστέθηκε προηγουμένως, συν ένα πλάτος και ύψος τοποθέτησης. Το TPDFlibCompositeWatermark κρατά μια λίστα με υπο-υδατογραφήματα και τα εφαρμόζει αναδρομικά, και το TPDFlib.ApplyWatermark είναι το σημείο εισόδου μίας κλήσης που σέβεται το εύρος σελίδων του ίδιου του προτύπου και επιστρέφει τον αριθμό των σελίδων που σφραγίστηκαν. Η νομική σφραγίδα τριών επιπέδων μοιάζει με αυτό
var
Lib: TPDFlib;
Logo: TPDFlibImageWatermark;
Diagonal, Footer: TPDFlibTextWatermark;
Stack: TPDFlibCompositeWatermark;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('draft.pdf', '');
Stack := TPDFlibCompositeWatermark.Create;
try
Logo := TPDFlibImageWatermark.Create;
Logo.ImageID := Lib.AddImageFromFile('logo.png', 0);
Logo.Position := wpTopRight;
Logo.Width := 90;
Logo.Height := 32;
Stack.Add(Logo);
Diagonal := TPDFlibTextWatermark.Create;
Diagonal.Text := 'CONFIDENTIAL';
Diagonal.Position := wpCenter;
Diagonal.Angle := 45;
Diagonal.FontSize := 60;
Diagonal.Opacity := 0.15;
Stack.Add(Diagonal);
Footer := TPDFlibTextWatermark.Create;
Footer.Text := 'Internal review copy';
Footer.Position := wpBottomCenter;
Footer.FontSize := 9;
Footer.Opacity := 0.6;
Stack.Add(Footer);
Stack.PageRange := '1-3'; // children inherit this range
Lib.ApplyWatermark(Stack);
Lib.SaveToFile('draft-marked.pdf');
finally
Stack.Free; // composite frees its children
end;
finally
Lib.Free;
end;
end;
Δύο λεπτομέρειες σύνθεσης αξίζουν να διευκρινιστούν. Η κληρονομικότητα εύρους είναι μονόδρομη και μη καταστροφική: ένα παιδί με κενό PageRange κληρονομεί προσωρινά το εύρος του σύνθετου υδατογραφήματος κατά την Apply, ενώ ένα παιδί που ορίζει το δικό του εύρος το διατηρεί, ώστε να μπορείτε να καρφιτσώσετε το υποσέλιδο σε κάθε σελίδα, ενώ η διαγώνια σφραγίδα καλύπτει μόνο το τμήμα του σχεδίου. Και η ιδιοκτησία ακολουθεί το σύνθετο υδατογράφημα: το TPDFlibCompositeWatermark.Destroy απελευθερώνει τα παιδιά του, οπότε απελευθερώνετε το σύνθετο υδατογράφημα μία φορά και τίποτα άλλο, όπως στο παραπάνω παράδειγμα
Όρια που αξίζει να γνωρίζετε
Ισχύουν δύο ειλικρινή όρια, και τα δύο προκύπτουν από τον τρόπο λειτουργίας του μηχανισμού. Πρώτον, μια λήψη τυλίγει μόνο τη ροή περιεχομένου και τους πόρους της σελίδας. Τα σχόλια (annotations), οι σύνδεσμοι, τα πεδία φόρμας και τα σχόλια χρηστών ζουν ως ξεχωριστά αντικείμενα στο λεξικό της σελίδας, όχι στη ροή περιεχομένου, επομένως δεν μεταφέρονται στο XObject· μια συρραμμένη διάταξη μεταφέρει το ορατό περιεχόμενο της σελίδας αλλά όχι το διαδραστικό επίπεδο των πηγών της. Δεύτερον, η τοποθέτηση κειμένου εννέα σημείων είναι μια προσέγγιση: χωρίς μια πλήρη μηχανή διάταξης, η βιβλιοθήκη δεν μπορεί να γνωρίζει το ακριβές αποδοθέν πλάτος μιας συμβολοσειράς, επομένως υπολογίζει το πλαίσιο κειμένου στο 40% του πλάτους της σελίδας κατά την επίλυση θέσεων όπως η wpCenter. Αυτό είναι αρκετά ακριβές για το κεντράρισμα μιας σύντομης ετικέτας, αλλά για τοποθέτηση μεγάλων συμβολοσειρών με ακρίβεια pixel, μεταβείτε στο wpCustom και υπολογίστε τις συντεταγμένες μόνοι σας
Η κλίμακα δεν αποτελεί πρακτικό περιορισμό εδώ, καθώς η λήψη και η συρραφή χειρίζονται αναφορές αντικειμένων αντί να κωδικοποιούν εκ νέου το περιεχόμενο της σελίδας· εάν οι πηγές σας είναι εκατοντάδες megabytes, η ίδια πειθαρχία που περιγράφεται στον οδηγό για τη συγχώνευση και διαίρεση μεγάλων αρχείων με απευθείας πρόσβαση ισχύει πριν κάνετε συρραφή. Οι συναρτήσεις συρραφής και οι κλάσεις προτύπων υδατογραφημάτων που εμφανίζονται εδώ αποστέλλονται στην τρέχουσα έκδοση της losLab PDF Library για Delphi, C# και VB.NET, μαζί με τα χαμηλότερου επιπέδου πρωτεύοντα στοιχεία (primitives) CapturePage, DrawCapturedPage και DrawCapturedPageMatrix όταν χρειάζεστε μια διάταξη που δεν καλύπτουν οι τρεις προκατασκευασμένες κλήσεις συρραφής