Μια ομάδα επεξεργασίας αξιώσεων (claims-processing) είχε αρχεία χαρτιού τριάντα ετών που περνούσαν από έναν σαρωτή τροφοδοσίας φύλλων (sheet-fed scanner). Ο σαρωτής έβγαζε ένα JPEG ανά σελίδα σε έναν φάκελο, με όνομα 0001.jpg, 0002.jpg, και ούτω καθεξής. Αυτό που χρειαζόταν πραγματικά το αρχείο ήταν ένα PDF ανά αρχείο υπόθεσης (case file), με τις σελίδες στη σειρά, ώστε ένας αξιολογητής (reviewer) να μπορεί να ανοίξει ένα μεμονωμένο έγγραφο (single document) αντί να κάνει κλικ σε εκατό μικρογραφίες εικόνων (image thumbnails). Αυτό το τελευταίο βήμα, η μετατροπή (turning) ενός αριθμημένου σωρού (pile) σαρώσεων (scans) σε ένα ενιαίο (single) διατεταγμένο (ordered) PDF, είναι η δουλειά εδώ
Το PDFium Component το χειρίζεται άμεσα (directly). Πέρα από την απόδοση (rendering) και την εξαγωγή κειμένου, το στοιχείο μπορεί να κατασκευάσει (build) ένα PDF από το μηδέν (from scratch): δημιουργήστε ένα κενό έγγραφο, προσθέστε μια κενή σελίδα σε όποιο μέγεθος θέλετε, ρίξτε (drop) μια εικόνα σε αυτήν τη σελίδα σε συντεταγμένες (coordinates) χώρου χρήστη (user-space) και στη συνέχεια αποθηκεύστε (save). Ολόκληρη η γραμμή εντολών (pipeline) ζει στο στοιχείο TPdf, επομένως ένας μετατροπέας παρτίδας (batch converter) είναι ένας βρόχος (loop) πάνω σε ονόματα αρχείων συν μερικές (handful) κλήσεις
Η μορφή της μετατροπής (The shape of the conversion)
Τρία πράγματα πρέπει να συμβούν για κάθε σάρωση (scan). Αποφασίζετε το μέγεθος της σελίδας, τοποθετείτε (place) την εικόνα μέσα στη σελίδα αφήνοντας ένα περιθώριο (margin), και προχωράτε (advance) στην επόμενη σελίδα. Το PDFium Component σας δίνει μια μέθοδο για το καθένα: το AddPage δημιουργεί μια κενή σελίδα σε ένα δεδομένο μέγεθος, το AddImage (ή AddPicture αν κρατάτε ήδη ένα TPicture) σχεδιάζει το bitmap στην τρέχουσα (current) σελίδα, και το PageNumber λέει στο στοιχείο (component) σε ποια σελίδα στοχεύουν (target) οι επόμενες κλήσεις σχεδίασης (subsequent draw calls)
Η μόνη λεπτομέρεια που μπερδεύει (trips up) τους ανθρώπους είναι το σύστημα συντεταγμένων (coordinate system). Ο χώρος χρήστη (user space) του PDF τοποθετεί την αρχή (origin) στην κάτω αριστερή γωνία (lower-left corner) της σελίδας, με το Y να αυξάνεται προς τα πάνω, το αντίθετο από τις συντεταγμένες οθόνης που χρησιμοποιούν οι προγραμματιστές του Delphi από αντανακλαστικό. Το X, Y που περνάτε (pass) στο AddImage είναι η κάτω αριστερή γωνία του ορθογωνίου (rectangle) της εικόνας, και το Width, Height (Πλάτος, Ύψος) είναι το μέγεθος τοποθέτησης σε στιγμές (points), όχι το μέγεθος pixel του αρχείου προέλευσης (source file). Αν το κάνετε αυτό ανάποδα, οι σαρώσεις σας θα προσγειωθούν (land) εκτός της σελίδας (off the page) ή ανάποδα σε σχέση με το πού τις περιμένατε
Δημιουργία του εγγράφου και μίας σελίδας ανά σάρωση
Ξεκινήστε με ένα κενό έγγραφο (empty document). Το CreateDocument εκχωρεί (allocates) ένα φρέσκο PDF και αφήνει το στοιχείο (component) ενεργό (active), οπότε δεν υπάρχει ξεχωριστό βήμα ανοίγματος (open step). Από εκεί, διασχίζετε (walk) τη λίστα των σαρωμένων αρχείων, και για κάθε ένα προσθέτετε (add) μια σελίδα, την κάνετε τρέχουσα (current) και τοποθετείτε την εικόνα. Οι διαστάσεις σελίδας (Page dimensions) εδώ είναι A4 σε στιγμές (595 × 842 κατακόρυφος προσανατολισμός), το τυπικό (standard) μέγεθος φύλλου (sheet size) για αρχειοθετημένη αλληλογραφία
procedure TArchiveForm.ScansToPdf(const Files: TStrings; const OutputPath: string);
const
PageW = 595.0; // A4 width in points
PageH = 842.0; // A4 height in points
Margin = 36.0; // half-inch border around each scan
var
I: Integer;
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument; // new, empty, already active
for I := 0 to Files.Count - 1 do
begin
Pdf.AddPage(I + 1, PageW, PageH); // 1-based page index
Pdf.PageNumber := I + 1; // make the new page current
PlaceScan(Pdf, Files[I], PageW, PageH, Margin);
end;
Pdf.SaveAs(OutputPath);
finally
Pdf.Free;
end;
end;
Κάθε επανάληψη (iteration) δημιουργεί μια σελίδα και αμέσως (immediately) της ορίζει το PageNumber. Αυτή η δεύτερη γραμμή έχει σημασία: το AddPage εισάγει (inserts) τη σελίδα, αλλά οι μέθοδοι σχεδίασης ενεργούν (act) σε όποια σελίδα είναι τρέχουσα, επομένως η ρύθμιση του PageNumber είναι αυτό που στοχεύει (aims) το AddImage στη σελίδα που μόλις φτιάξατε. Παραλείψτε το και οι εικόνες σας θα στοιβαχτούν (stack) σε όποια σελίδα έτυχε να είναι φορτωμένη πριν
Μία υπόθεση (assumption) κρύβεται (hides) σε αυτόν τον βρόχο (loop): η σειρά (order) των αρχείων (Files). Ένας σαρωτής (scanner) ονομάζει σελίδες 0001.jpg έως 0100.jpg, αλλά μια απαρίθμηση καταλόγου (directory enumeration) δεν τις επιστρέφει πάντα ταξινομημένες (sorted), και τη στιγμή (moment) που συναντάτε το page9.jpg δίπλα στο page10.jpg, μια απλή (plain) ταξινόμηση συμβολοσειράς (string sort) βάζει (puts) τη σελίδα 10 πριν από τη σελίδα 9. Ταξινομήστε τη λίστα ρητά (explicitly) πριν από τον βρόχο (loop), και προτιμήστε ονόματα με μηδενικά στην αρχή (zero-padded) κατά τη στιγμή της σάρωσης (scan time), ώστε η λεξικογραφική (lexical) σειρά να ταιριάζει με τη σειρά των σελίδων. Η ακολουθία (sequence) σελίδων είναι το μόνο πράγμα (one thing) που ένας αξιολογητής (reviewer) παρατηρεί (notices) αμέσως (immediately), και είναι το φθηνότερο λάθος προς αποτροπή (to prevent)
Τοποθέτηση μιας σάρωσης (scan) και διατήρηση του λόγου διαστάσεων (aspect ratio) της
Μια σάρωση (scan) είναι σπάνια το ίδιο σχήμα με τη σελίδα. Αν την τεντώσετε (stretch) για να γεμίσει (fill) το φύλλο (sheet), παραμορφώνετε (distort) το κείμενο· αν την τοποθετήσετε (place) στο πλήρες (full) μέγεθος pixel της, υπερχειλίζει (overflows). Η λύση (fix) είναι να κλιμακώσετε (scale) με τον μικρότερο από τους δύο λόγους (ratios), προσαρμογή-πλάτους (width-fit) ή προσαρμογή-ύψους (height-fit), και να κεντράρετε (center) ό,τι περισσεύει (left over). Επειδή η αρχή (origin) βρίσκεται (sits) κάτω αριστερά (lower-left), κεντράρισμα (centering) σημαίνει διαχωρισμό (splitting) του υπόλοιπου (leftover) χώρου ομοιόμορφα (evenly) και προσθήκη (adding) του τόσο στο X όσο και στο Y
procedure TArchiveForm.PlaceScan(Pdf: TPdf; const FileName: string;
PageW, PageH, Margin: Double);
var
Pic: TPicture;
AvailW, AvailH, Scale, DrawW, DrawH, X, Y: Double;
begin
Pic := TPicture.Create;
try
Pic.LoadFromFile(FileName); // BMP, JPG, PNG, etc. via the VCL graphics units
AvailW := PageW - 2 * Margin;
AvailH := PageH - 2 * Margin;
// Fit inside the margins without distorting the scan.
Scale := Min(AvailW / Pic.Width, AvailH / Pic.Height);
DrawW := Pic.Width * Scale;
DrawH := Pic.Height * Scale;
// Center: leftover space split evenly. Y measured from the page bottom.
X := (PageW - DrawW) / 2;
Y := (PageH - DrawH) / 2;
Pdf.AddImage(FileName, X, Y, DrawW, DrawH);
finally
Pic.Free;
end;
end;
Αυτό φορτώνει (loads) το αρχείο μία φορά (once) για να διαβάσει τις διαστάσεις (dimensions) pixel του, υπολογίζει (computes) μια ενιαία (single) ομοιόμορφη (uniform) κλίμακα, και περνά (passes) το ορθογώνιο (rectangle) τοποθέτησης στο AddImage. Το AddImage δέχεται απευθείας μια διαδρομή (path) αρχείου και τη δρομολογεί (routes) μέσω της ίδιας γραμμής εντολών εικόνας (image pipeline) με το AddPicture, επομένως οποιαδήποτε μορφή αναγνωρίζουν οι μονάδες (units) γραφικών VCL λειτουργεί (works) χωρίς ειδικές περιπτώσεις (special-casing). Εάν έχετε ήδη αποκωδικοποιήσει την εικόνα σε ένα TPicture από ένα παράθυρο προεπισκόπησης (preview pane), καλέστε AddPicture(Pic, X, Y, DrawW, DrawH) με το ίδιο ορθογώνιο και παραλείψτε τη δεύτερη ανάγνωση αρχείου (file read)
Παράλειψη της αποκωδικοποίησης (decode) για σαρώσεις JPEG
Οι σαρωτές (Scanners) σχεδόν πάντα εκπέμπουν (emit) JPEG. Η φόρτωση ενός JPEG σε ένα TPicture το αποκωδικοποιεί (decodes) σε ένα bitmap (χάρτη pixel), και στη συνέχεια (then) το PDFium το επανακωδικοποιεί (re-encodes) κατά την αποθήκευση, δύο με απώλειες "ταξίδια" (lossy round trips) που δεν χρειάζεστε. Το AddJpegImage ενσωματώνει (embeds) τα αρχικά (original) συμπιεσμένα (compressed) byte (bytes) κατευθείαν (straight) στη σελίδα από μια ροή (stream), το οποίο είναι ταχύτερο και οπτικά (visually) καθαρότερο (cleaner) για μια παρτίδα (batch) μεγάλου όγκου (high-volume)
var
Stream: TFileStream;
begin
// ... after AddPage + PageNumber for the current page ...
Stream := TFileStream.Create(FileName, fmOpenRead);
try
// Embeds the JPEG bytes as-is; no decode/re-encode cycle.
Pdf.AddJpegImage(Stream, X, Y, DrawW, DrawH);
finally
Stream.Free;
end;
end;
Εξακολουθείτε (still) να υπολογίζετε τα X, Y, DrawW (ΠλάτοςΣχεδίασης), και DrawH (ΎψοςΣχεδίασης) με τον ίδιο τρόπο (way), αφού (since) χρειάζεστε τις διαστάσεις pixel για την κλίμακα (scale). Διαβάστε (Read) τα από το αρχείο (file) ή από μια γρήγορη ανάλυση επικεφαλίδας (quick header parse), και στη συνέχεια παραδώστε (hand) την ακατέργαστη (raw) ροή (stream) στο AddJpegImage. Για σαρώσεις (scans) PNG ή TIFF η διαδρομή (path) AddImage είναι η σωστή· κρατήστε (reserve) τη συντόμευση (shortcut) JPEG για τη μορφή στην οποία πραγματικά ισχύει (applies to)
Προσθήκη ετικετών (Labeling) σε κάθε σελίδα
Οι αρχειοθετημένες σαρώσεις (Archived scans) ελέγχονται (audit) πιο εύκολα όταν κάθε σελίδα (page) φέρει (carries) το όνομα αρχείου (filename) προέλευσής της. Το AddText σχεδιάζει (draws) μια συμβολοσειρά (string) σε μια συντεταγμένη χώρου χρήστη (user-space coordinate), οπότε μια λεζάντα (caption) κάθεται (sits) ακριβώς (just) κάτω από την εικόνα. Θυμηθείτε (Remember) τον ανεστραμμένο άξονα Y: για να βάλετε (put) μια ετικέτα (label) κάτω από (below) τη σάρωση, αφαιρείτε (subtract) από το κάτω (bottom) άκρο της εικόνας αντί να προσθέτετε σε αυτό
// Caption below the scan: Y decreases toward the page bottom.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
X, Y - 14, clGray);
Ένα τελευταίο (last) σημείο σχετικά με την αποθήκευση (saving). Το SaveAs είναι μια συνάρτηση (function) που επιστρέφει ένα Boolean, οπότε στον κώδικα παραγωγής (production code) ελέγξτε το αποτέλεσμά της (result) αντί να υποθέσετε (assuming) ότι η εγγραφή (write) πέτυχε· ένας γεμάτος δίσκος ή μια κλειδωμένη διαδρομή εξόδου (locked output path) αποτυγχάνει σιωπηλά (fails quietly) διαφορετικά. Μόλις ολοκληρωθεί ο βρόχος και γραφτεί το αρχείο, έχετε ακριβώς αυτό που χρειαζόταν το αρχείο: ένα διατεταγμένο (ordered) PDF ανά αρχείο υπόθεσης (case file), σελίδες κλιμακωμένες (scaled) για προσαρμογή (fit), έτοιμο (ready) για ανάγνωση (read) σε οποιοδήποτε πρόγραμμα προβολής (viewer)
Τα ίδια δομικά στοιχεία (building blocks) καλύπτουν (cover) σχετικές (related) εργασίες (jobs). Αλλάξτε (Swap) τον κανόνα μεγέθους ανά σελίδα (per-page sizing rule) και παίρνετε (get) ένα βιβλίο φωτογραφιών με μία εικόνα ανά φύλλο (sheet)· κρατήστε τον βρόχο αλλά διαβάστε από μια πηγή πολλαπλών σελίδων (multi-page source) TIFF και έχετε έναν μετατροπέα αρχείου-φαξ (fax-archive converter). Εάν θέλετε (want) την ευρύτερη (wider) εικόνα της δημιουργίας PDF προγραμματιστικά (programmatically), δείτε τη δημιουργία εγγράφων PDF από το μηδέν με το PDFium Component· για να αποδώσετε (render) το αποτέλεσμα πίσω στην οθόνη αργότερα, δείτε τη μετατροπή σελίδων PDF σε εικόνες JPEG με το PDFium Component
Το PDFium Component από το loslab.com ομαδοποιεί (bundles) τα API δημιουργίας εγγράφων (document-creation), απόδοσης (rendering) και κειμένου (text APIs) που χρησιμοποιούνται σε ολόκληρη αυτή τη σειρά