Τεχνικό Άρθρο

Σχεδίαση σε Καμβά (Canvas Drawing) HotPDF στο Delphi: Διανυσματικές Διαδρομές (Vector Paths) και Χρώμα

Το HotPDF σχεδιάζει διανυσματικά (vector) γραφικά δημιουργώντας (building) μια διαδρομή (path) στην τρέχουσα σελίδα και στη συνέχεια ζητώντας να βαφτεί (painted). Δεν υπάρχει ενδιάμεσο βήμα bitmap. Μια γραμμή που σχεδιάζετε (draw) με MoveTo και LineTo καταλήγει ως τελεστές (operators) διαδρομής (path) PDF στη ροή περιεχομένου (content stream), έτσι παραμένει (stays) ένα πραγματικό (true) διάνυσμα (vector): ευκρινές (crisp) σε 50% ζουμ, ευκρινές σε 1600%, και ένα κλάσμα του μεγέθους που θα κόστιζε μια ραστεροποιημένη (rasterized) έκδοση. Για διαγράμματα, κανόνες (rules) πινάκων, άξονες (axes) γραφημάτων και διακοσμήσεις φόρμας (form decorations), αυτό είναι ακριβώς αυτό (exactly what) που θέλετε, και το API πίσω από αυτό είναι αρκετά μικρό (small enough) για να το μάθετε (learn) με μια (one) καθισιά (sitting)

Ολόκληρη η επιφάνεια σχεδίασης ζει στο THotPDF.CurrentPage. Ανάμεσα (Between) στο BeginDoc και στο EndDoc ορίζετε το χρώμα (color) και το πλάτος της γραμμής (line width) σε αυτό (that) το αντικείμενο σελίδας (page object), τοποθετείτε (lay down) γεωμετρία, και καλείτε (call) έναν τελεστή (operator) βαψίματος (painting) για να το (it) δεσμεύσετε (commit). Τα τέσσερα (four) αρχέγονα (primitives) που θα χρησιμοποιήσετε (use) περισσότερο είναι τα MoveTo και LineTo για αυθαίρετες (arbitrary) διαδρομές (paths), Rectangle για κουτιά (boxes), Circle για δίσκους (disks), και οι δύο τελεστές βαψίματος (painting operators) Stroke και Fill

Το σύστημα συντεταγμένων είναι κάτω-αριστερά

Αυτό (This) είναι το ένα (one) πράγμα (thing) που μπερδεύει (trips up) όσους έρχονται (arriving) από (from) το VCL. Ο TCanvas με τον οποίο ζωγραφίζετε στοιχεία ελέγχου (controls) τοποθετεί (puts) την αρχή (origin) στην (at the) επάνω-αριστερή (top-left) γωνία με το Υ να αυξάνεται (growing) προς τα κάτω (downward). Το PDF κάνει το αντίθετο (opposite). Το HotPDF μετρά (measures) από (from) την (the) κάτω-αριστερή (bottom-left) γωνία (corner) της σελίδας σε σημεία (points) (1/72 ίντσας), με το Υ να αυξάνεται καθώς μετακινείστε (move) προς τα πάνω (up). Ένα σημείο (point) στο Y := 720 κάθεται κοντά στην κορυφή μιας σελίδας US Letter, η οποία (which) έχει ύψος 792 σημεία, και το Y := 50 κάθεται κοντά στο κάτω (bottom) μέρος. Αν το (your) πρώτο (first) σας (your) σχέδιο (drawing) βγει καθρεφτισμένο κάθετα (mirrored vertically), αυτός (this) είναι (is) ο λόγος (why): ο κώδικας (code) που (that) μεταφέρθηκε (ported) από τα γραφικά (graphics) της οθόνης (screen) υποθέτει (assumes) τη λάθος (wrong) κατεύθυνση (direction) και βγαίνει έξω (runs off) από (from) το (the) κάτω (bottom) άκρο (edge)

Η (The) ίδια (same) σύμβαση (convention) διέπει (governs) το TextOut, έτσι (so) το κείμενο (text) και (and) τα σχήματα (shapes) μοιράζονται (share) ένα (one) νοητικό (mental) μοντέλο (model) μόλις το (it) εσωτερικεύσετε (internalize). Σχεδιάστε (Plan) μια (a) διάταξη (layout) αποφασίζοντας (deciding) πού (where) κάθεται (sits) το κάτω μέρος κάθε στοιχείου, όχι το επάνω, και τα υπόλοιπα (rest) ακολουθούν (follows)

Διαδρομές: MoveTo, LineTo, Stroke

Μια (A) διαδρομή (path) με περίγραμμα (stroked) είναι (is) ένα στυλό (pen) που (that) σηκώθηκε (lifted), τοποθετήθηκε (placed), και (and) σύρθηκε (dragged). Το (The) MoveTo σηκώνει (lifts) το (the) στυλό (pen) και (and) ορίζει (sets) το (the) σημείο έναρξης (start point) χωρίς να (without) σημαδεύει (marking) τίποτα (anything). Κάθε LineTo επεκτείνει την τρέχουσα διαδρομή σε ένα νέο σημείο. Τίποτα δεν εμφανίζεται στη σελίδα μέχρι να καλέσετε το Stroke, το οποίο σχεδιάζει τη συσσωρευμένη διαδρομή χρησιμοποιώντας το τρέχον χρώμα περιγράμματος (stroke color) και το πλάτος γραμμής, και στη συνέχεια καθαρίζει τη διαδρομή (clears the path) έτσι ώστε το επόμενο MoveTo να ξεκινήσει από την αρχή (fresh)

var
  Page: TPDFPage;
begin
  Page := Pdf.CurrentPage;
  
  // Set line width to 2.0 points, color to black
  Page.SetLineWidth(2.0);
  Page.SetRGBStrokeColor(0, 0, 0);

  // Draw a continuous zigzag path
  Page.MoveTo(100, 100);
  Page.LineTo(150, 200);
  Page.LineTo(200, 100);
  Page.LineTo(250, 200);
  Page.Stroke;             // This actually draws the path

  // Draw a separate horizontal rule, thinner and gray
  Page.SetLineWidth(0.5);
  Page.SetRGBStrokeColor(128, 128, 128);
  Page.MoveTo(100, 80);
  Page.LineTo(250, 80);
  Page.Stroke;
end;

Δύο (Two) λεπτομέρειες (details) εξοικονομούν πραγματικό (real) χρόνο αποσφαλμάτωσης (debugging time). Το πλάτος της γραμμής είναι κατάσταση (state), όχι όρισμα: το SetLineWidth το ορίζει μία φορά και κάθε (every) επακόλουθο (subsequent) Stroke χρησιμοποιεί αυτήν την τιμή μέχρι να το αλλάξετε ξανά (again), το οποίο είναι (is) ο λόγος (why) που η polyline παραπάνω είναι πιο παχιά από τον κανόνα (rule). Και η (And the) διαδρομή επαναφέρεται (resets) μετά από κάθε Stroke, επομένως (so) ένα ξεχασμένο Stroke σημαίνει (means) ότι (that) η γεωμετρία (geometry) που με τόση προσοχή σχεδιάσατε δεν αποδίδεται (renders) ποτέ (never). Εάν (If a) σχήμα (shape) λείπει από (is missing from) την (the) έξοδο (output), η (the) κλήση (call) βαψίματος (painting) είναι το (the) πρώτο μέρος (first place) για να (to) κοιτάξετε (look)

Οι συντεταγμένες είναι σημεία (points), και τα σημεία είναι κλασματικά (fractional). Το MoveTo και το LineTo δέχονται τιμές Single, έτσι (so) μια εξαιρετικά (hairline) λεπτή γραμμή (hairline) στα 0.5 σημεία (points) ή μια (a) θέση (position) στα (at) 72.25 είναι νόμιμη και έχει νόημα (meaningful), δεν (not) στρογγυλοποιείται (rounded away) στην πλησιέστερη ακέραιη μονάδα (nearest whole unit). Αυτή η (That) ακρίβεια (precision) έχει σημασία (matters) σε (in) δύο (two) αντίθετες (opposite) κατευθύνσεις (directions). Ένα πλάτος (width) γραμμής (line) κάτω από (below) περίπου (about) 0.5 μπορεί να αποδοθεί ως μια λεπτότερη-δυνατή γραμμή (thinnest-possible line) εξαρτώμενη από τη συσκευή, που (that) εξαφανίζεται (disappears) στην (on) οθόνη (screen) και (and) επανεμφανίζεται (reappears) όταν (when) εκτυπώνεται (printed), επομένως ένας ορατός (visible) κανόνας (rule) θέλει ένα πλάτος (width) που (you) ορίζετε επίτηδες (on purpose) αντί (rather than) για (for) την προεπιλογή (default). Από την άλλη πλευρά, ο (the) εξαναγκασμός (snapping) των (of) κανόνων (rules) πινάκων (table) και γραμμών πλέγματος (gridlines) σε (to) ακέραιες (whole-point) συντεταγμένες εμποδίζει (keeps) ένα (a) πυκνό πλέγμα (dense grid) να (from) φαίνεται (looking) ελαφρώς (slightly) ανομοιόμορφο (uneven) όπου παρακείμενες (adjacent) γραμμές στρογγυλοποιούνται (round) διαφορετικά (differently). Αποφασίστε το κενό του πλέγματος (grid spacing) σε σημεία εκ των προτέρων (up front) και η υπόλοιπη (rest) διάταξη (layout) το (it) κληρονομεί (inherits)

Γεμισμένα (Filled) σχήματα (shapes) και (and) χρώμα (color)

Κλειστά (Closed) αρχέγονα (primitives) μπορούν (can) να (to) γεμιστούν (filled) αντί (instead) να περιγραφούν (outlined). Το Rectangle παίρνει μια (a) θέση (position) και (and) ένα (a) μέγεθος (size), το Circle παίρνει (takes) ένα (a) κέντρο (center) και (and) μια (a) ακτίνα (radius), και (and) οποιοδήποτε από τα δύο (either one) δεσμεύεται (is committed) με το Fill, το οποίο βάφει (paints) το εσωτερικό στο τρέχον (current) χρώμα γεμίσματος (fill color), ή με το Stroke για περίγραμμα (outline) μόνο (only). Το (The) χρώμα γεμίσματος και (and) το (the) χρώμα περιγράμματος (stroke color) είναι (are) ξεχωριστά κομμάτια (pieces) κατάστασης (state), που (that) ορίζονται με τα SetRGBFillColor και SetRGBStrokeColor, και τα δύο εκ των οποίων (both of which) παίρνουν (take) ένα (a) μόνο (single) TColor. Αυτό (That) σημαίνει (means) ότι (that) μπορείτε (you can) να (to) επαναχρησιμοποιήσετε (reuse) τις σταθερές χρώματος του Delphi (Delphi's color constants) και (and) τον βοηθό (helper) RGB απευθείας (directly)

uses Graphics;  // for clRed, clBlue

var
  Page: TPDFPage;
begin
  Page := Pdf.CurrentPage;
  
  // Blue filled rectangle
  Page.SetRGBFillColor(clBlue);
  Page.Rectangle(100, 300, 50, 50); // X, Y, Width, Height
  Page.Fill;
  
  // Red stroked circle
  Page.SetRGBStrokeColor(clRed);
  Page.SetLineWidth(1.0);
  Page.Circle(200, 325, 25);        // CenterX, CenterY, Radius
  Page.Stroke;
end;

Παρακολουθήστε (Watch) το (the) σχήμα (shape) ορίσματος (argument) στο (on) Rectangle. Είναι θέση-συν-μέγεθος, X, Y, Width, Height, όχι (not) δύο (two) απέναντι (opposite) γωνίες (corners). Το TCanvas.Rectangle που γνωρίζουν (know) οι προγραμματιστές (developers) του (the) Delphi παίρνει (takes) (Left, Top, Right, Bottom), επομένως η μυϊκή μνήμη (muscle memory) θα δώσει στο HotPDF μια (a) δεύτερη γωνία (second corner) εκεί (where) που περιμένει (it expects) πλάτος και (and) ύψος (height), και (and) το κουτί (box) βγαίνει στο λάθος (wrong) μέγεθος (size). Το ζεύγος (pair) (X, Y) είναι η κάτω-αριστερή (lower-left) γωνία, συνεπές (consistent) με την αρχή (origin) της σελίδας. Για έναν (a) κύκλο (circle), το (X, Y) είναι το κέντρο και (and) το (the) τρίτο (third) όρισμα (argument) είναι (is) η ακτίνα σε σημεία (points)

Μια επιλογή χρώματος που το αρχικό δείγμα έκανε λάθος

Μια παλαιότερη (An older) έκδοση (version) αυτού (of this) του παραδείγματος σπόριζε (seeded) τα χρώματα με Random($FFFFFF) σε (on) κάθε (every) σχήμα (shape). Φαίνεται ζωηρό (lively), και (and) είναι (is) το λάθος (wrong) ένστικτο (instinct) για (for) δημιουργημένα (generated) έγγραφα (documents). Ένα PDF που φτιάχνετε από κώδικα (code) είναι (is) συνήθως (usually) κάτι (something) που (you) θέλετε επίσης να δοκιμάσετε (test), και (and) τα (the) τυχαία χρώματα γεμίσματος κάνουν (make) την έξοδο (output) αδύνατη να συγκριθεί (to compare) από εκτέλεση σε εκτέλεση (run to run): μια (a) διαφοροποίηση byte-προς-byte (byte-for-byte diff) έναντι (against) ενός (a) γνωστού-καλού (known-good) αρχείου (file) αποτυγχάνει κάθε φορά, χωρίς πραγματικό (real) λόγο (reason). Επιλέξτε ρητά χρώματα (explicit colors). Όταν θέλετε ποικιλία σε μια (a) σειρά σχημάτων (series of shapes), οδηγήστε την (drive it) από (from) τα δεδομένα σας (your data) ή (or) έναν σταθερό (fixed) πίνακα παλέτας, ώστε (so) η ίδια είσοδος (input) να παράγει πάντα (always produces) το ίδιο (same) αρχείο (file). Ο ντετερμινισμός (Determinism) αξίζει περισσότερο από (more than) την πρωτοτυπία (novelty) όταν το αντικείμενο μετακινείται (moves) μέσω μιας γραμμής (pipeline) έκδοσης (release)

Σύνθεση των primitives σε πλαίσιο επισήμανσης

Ένα πρακτικό callout box συνδυάζει MoveTo, LineTo, FillAndStroke και κείμενο στην ίδια σελίδα. Ρυθμίστε πρώτα τη γραφική κατάσταση και επαναφέρετέ την μετά τη σχεδίαση, ώστε οι επόμενες παράγραφοι να μη μεταφέρουν κατά λάθος το χρώμα ή το dash pattern

Καμπύλες, διακεκομμένες γραμμές και διαφάνεια

Για καμπύλες Bezier χρησιμοποιήστε τις CurveToC, CurveToV και CurveToY, ενώ το SetDash ελέγχει το μοτίβο διακεκομμένης γραμμής και το NoDash το επαναφέρει. Η διαφάνεια απαιτεί καταχώριση κατάστασης γραφικών μέσω RegisterExtGState και πρέπει να ελέγχεται στο τελικό PDF, επειδή ορισμένοι rasterizers και εκτυπωτές την αποδίδουν διαφορετικά

Εκεί που αξίζει η διανυσματική σχεδίαση (vector drawing), και εκεί που δεν αξίζει

Προτιμήστε αυτές τις κλήσεις διαδρομής (path) και σχήματος (shape calls) όταν (when) η γεωμετρία (geometry) παράγεται (generated): γραμμές πλέγματος (gridlines) και ράβδους (bars) γραφήματος (chart), τις γραμμές (ruled lines) ενός πίνακα (table) τιμολογίου (invoice), κουτιά (boxes) επεξηγήσεων σε (on) ένα (a) διάγραμμα (diagram), ένα σήμα (mark) λογότυπου που (that) εκφράζεται ως μια χούφτα (handful) διαδρομών (paths). Όλα αυτά (All of it) κλιμακώνονται (scales) χωρίς να θολώνουν και (and) δεν προσθέτουν (adds almost nothing) σχεδόν τίποτα στο μέγεθος του αρχείου (file size), επειδή ένα (a) ορθογώνιο είναι (is) λίγοι (few) αριθμοί (numbers) αντί (rather than) για (for) χιλιάδες pixel. Η άλλη (flip) όψη (side) είναι επίσης ειλικρινής. Αν (If) αυτό (what) που (you) έχετε πραγματικά (actually have) είναι (is) μια фотографиία ή ένα screenshot, σχεδιάστε (draw) την ως εικόνα με (with) AddImage και (and) ShowImage αντί αυτού (instead)· η ιχνηλάτηση (tracing) ενός (a) bitmap με διανυσματικές (vector) κλήσεις δεν σας προσφέρει (buys you) τίποτα (nothing). Οι περίπλοκες καμπύλες (Complex curves) είναι επίσης εκτός πεδίου εφαρμογής εδώ. Τα (The) αρχέγονα (primitives) παραπάνω είναι ευθύγραμμα τμήματα (straight segments), ορθογώνια, και κύκλοι, τα οποία φέρουν τη (the) μεγάλη πλειονότητα (large majority) της (of) πραγματικής εργασίας αναφοράς (real reporting work)· οτιδήποτε χρειάζεται (needing) ελεύθερης μορφής (freeform) καμπύλες Bezier είναι ένα (a) ξεχωριστό (separate) μέρος (part) του API

Η (The) υπόλοιπη (remaining) συνήθεια (habit) που αξίζει να (worth) διατηρήσετε (keeping) είναι (is) η (the) επαλήθευση (verification). Η δημιουργημένη γεωμετρία (Generated geometry) μπορεί (can) να (to) περάσει στο μηχάνημά σας και να (and) αποτύχει (fail) στου (on a) πελάτη (customer's), συνήθως λόγω της υποκατάστασης (substitution) γραμματοσειράς στο κείμενο που αναμιγνύετε ή μιας (a) υπόθεσης (assumption) μεγέθους σελίδας που δεν (does not) ισχύει (hold). Ανοίξτε το έτοιμο (finished) αρχείο σε μερικά (few) επίπεδα (levels) ζουμ για να (to) επιβεβαιώσετε (confirm) ότι οι άκρες παραμένουν (stay) καθαρές, και (and) ελέγξτε ότι (that) κάθε σχήμα (shape) προσγειώνεται μέσα στο (inside the) πλαίσιο (box) περιθωρίου (margin) που (you) σκοπεύατε (intended). Με (With) ένα (a) ντετερμινιστικό (deterministic) συνδυασμό χρωμάτων (color scheme), αυτός ο έλεγχος (check) μπορεί (can) να (to) αυτοματοποιηθεί (automated) σε (against) ένα PDF αναφοράς (reference) αντί να ελέγχεται οπτικά (eyeballed)

Οι κλήσεις (The ... calls) MoveTo, LineTo, Stroke, Fill, και χρώματος που εμφανίζονται (shown) εδώ αποτελούν (are) μέρος (part) του (of the) Στοιχείου Ελέγχου HotPDF για το Delphi και (and) το C++Builder