Το HotPDF, το γηγενές εξάρτημα PDF VCL για Delphi και C++Builder, αξιολογεί τους τρεις τύπους συναρτήσεων PDF που χτίζονται από τύπους αντί από πλέγματα δειγμάτων: την εκθετική παρεμβολή Type 2, τη ραφή (stitching) Type 3, και τις συναρτήσεις υπολογιστή PostScript Type 4, που αντιστοιχούν στα ISO 32000-1 §7.10.3, §7.10.4, και §7.10.5. Το Type 2 αναμειγνύει μεταξύ δύο διανυσμάτων εξόδου κατά μήκος μιας καμπύλης, το Type 3 αλυσιδώνει πολλές υποσυναρτήσεις σε ένα πεδίο εισόδου, και το Type 4 εκτελεί ένα περιορισμένο πρόγραμμα PostScript που μπορεί να διακλαδίζεται, να συγκρίνει, και να υπολογίζει σχεδόν οτιδήποτε χρειάζεται μια ροή περιεχομένου από τις εισόδους της. Κάντε ελαφρώς λάθος οποιονδήποτε από τους τρεις και η αποτυχία ποτέ δεν ανακοινώνεται ως σφάλμα — εμφανίζεται ως μια διαβάθμιση με μια νεκρή επίπεδη ζώνη, ένα χρώμα spot που αποδίδεται καθαρό μαύρο, ή μια συνάρτηση υπολογιστή που είναι εκτός κατά ακριβώς ένα στις εισόδους που μια σουίτα δοκιμών έτυχε να μην δοκιμάσει
Αυτοί οι τρεις βρίσκονται δίπλα σε έναν τέταρτο, το Type 0, που αποθηκεύει ένα πλέγμα δείγματος αντί για τύπο και καλύπτεται ξεχωριστά στο συνοδευτικό άρθρο για τους πίνακες αναζήτησης χρώματος Type 0. Οι δύο οικογένειες λύνουν το ίδιο πρόβλημα, την αντιστοίχιση μιας εισόδου σε μια έξοδο, αλλά το Type 0 είναι δεδομένα υπολογισμένα μία φορά και ενσωματωμένα στο αρχείο, ενώ τα Type 2, 3, και 4 είναι κώδικας που ο αναγνώστης αξιολογεί σε κάθε κλήση. Και τα τέσσερα μοιράζονται ένα σημείο αποστολής στον αποδότη του HotPDF, με κλειδί την καταχώριση /FunctionType του λεξικού συνάρτησης, οπότε μια σκίαση, ένας μετασχηματισμός απόχρωσης, ή μια συνάρτηση spot ημιτονισμού ποτέ δεν χρειάζεται να γνωρίζει ποιο από τα τέσσερα έλαβε πριν μπορέσει να ζητήσει χρώμα
Πώς λειτουργεί μια εκθετική συνάρτηση PDF Type 2;
Μια συνάρτηση PDF Type 2 υπολογίζει έναν τύπο — y = C0 + x^N × (C1 − C0), εφαρμοζόμενο συστατικό προς συστατικό — όπου x είναι η μοναδική είσοδος της συνάρτησης, κανονικοποιημένη έναντι του /Domain της πριν εκτελεστεί ο τύπος (ISO 32000-1 §7.10.3). Τα /C0 και /C1 είναι τα διανύσματα εξόδου στα δύο άκρα εκείνου του εύρους, ένας αριθμός ανά συστατικό εξόδου, και το /N είναι ο εκθέτης που διαμορφώνει την καμπύλη μεταξύ τους: N = 1 δίνει την ευθεία γραμμική ράμπα πίσω από τα περισσότερα σημεία διαβάθμισης και τις μετατροπές duotone, N πάνω από 1 τραβά την καμπύλη προς το C0, και N μεταξύ 0 και 1 την σπρώχνει προς το C1. Το RegisterExponentialFunction χτίζει αυτό το λεξικό από πέντε ορίσματα και παραδίδει πίσω ένα αντικείμενο συνάρτησης έτοιμο να συνδεθεί σε μια σκίαση, μια συνάρτηση spot ημιτονισμού, ή οπουδήποτε αλλού δέχεται η προδιαγραφή ένα κλειδί /Function
Η σχέση αριθμού συστατικών μεταξύ C0 και C1 έχει σημασία δύο φορές: μία φορά όταν συντάσσετε μια συνάρτηση Type 2, και ξανά όποτε το HotPDF πρέπει να αποδώσει μία που δεν δημιούργησε το ίδιο. Στην πλευρά της σύνταξης, το RegisterExponentialFunction ελέγχει τα C0 και C1 μεταξύ τους και εγείρει εξαίρεση αν διαφωνούν, οπότε μια κλήση που φτάνει στο BeginDoc είναι ήδη ένα αυτοσυνεπές αντικείμενο συνάρτησης. Στην πλευρά της απόδοσης όμως, ο αξιολογητής πρέπει να εμπιστευτεί όποιους πίνακες /C0 και /C1 δηλώνει πράγματι ένα αρχείο προέλευσης — ένα αρχείο τυπογραφείου που ανοίχτηκε για προεπισκόπηση, ας πούμε, ή ένα υπογεγραμμένο έγγραφο που εμφανίζεται πίσω σε έναν χρήστη — και εκδόσεις πριν την 2.376.0 διάβαζαν αυτούς τους πίνακες σε ένα buffer με μέγεθος για τέσσερα συστατικά, την περίπτωση CMYK. Μια απόχρωση εκθετική DeviceGray ή DeviceRGB, με ένα ή τρία στοιχεία /C0 και /C1, απέτυχε σιωπηλά σε αυτή την ανάγνωση και άφησε και τους δύο πίνακες στο μηδέν, οπότε η απόχρωση ζωγράφισε επίπεδο μαύρο αντί για το προτιθέμενο χρώμα της. Η έκδοση 2.376.0 άλλαξε το μέγεθος του αναγνώστη στον πραγματικό δηλωμένο αριθμό εξόδου της συνάρτησης αντί για ένα σταθερό buffer — ακριβώς το είδος σφάλματος που μόνο μια περίπτωση δοκιμής μη-CMYK αποκαλύπτει, αφού η υπάρχουσα σουίτα έτρεχε παντού CMYK, όπου το τέσσερα-σε-τέσσερα ταίριαζε πάντα
var
EaseIn: THPDFDictionaryObject;
begin
// Type 2: one input, N > 1 biases the ramp toward C0 (an ease-in curve)
EaseIn := Pdf.RegisterExponentialFunction(
[0,1], // Domain: single input, clamped to [0,1]
[0, 0, 0], // C0: output at x = 0
[0.8, 0, 0], // C1: output at x = 1
3, // N: exponent, 1 = linear, > 1 eases toward C0
[]); // Range omitted: defaults to a [0,1] clamp per output
end;
Ραφή (stitching) Type 3: αλυσίδωση υποσυναρτήσεων κατά μήκος ενός πίνακα Bounds
Μια συνάρτηση PDF Type 3 ράβει k υποσυναρτήσεις σε μία τμηματική αντιστοίχιση πάνω στο /Domain μιας μοναδικής εισόδου, και οι δύο πίνακες που το κάνουν αυτό να λειτουργεί είναι οι /Bounds και /Encode (ISO 32000-1 §7.10.4). Το /Bounds κρατά k − 1 εσωτερικά σημεία διαχωρισμού που τεμαχίζουν το /Domain σε k διαδοχικά διαστήματα· ο αξιολογητής επιλέγει το πρώτο διάστημα του οποίου το άνω όριο υπερβαίνει την είσοδο, ή το τελευταίο διάστημα μόλις η είσοδος φτάσει το τελικό όριο, και παραδίδει στην υποσυνάρτηση εκείνου του διαστήματος. Το /Encode έπειτα επαναχαρτογραφεί την είσοδο από τη θέση της μέσα σε εκείνο το διάστημα σε όποιο εύρος εισόδου αναμένει η ίδια η επιλεγμένη υποσυνάρτηση — συνήθως [0, 1] αν η υποσυνάρτηση είναι ένα ακόμη εκθετικό τμήμα — προτού συνεχίσει η αξιολόγηση, μία κλήση βαθύτερα, μέσα στο δικό της /Domain και /Range εκείνης της υποσυνάρτησης
Ο αξιολογητής ραφής του HotPDF παλαιότερα χειριζόταν μόνο ακριβώς δύο υποσυναρτήσεις, και ο αναγνώστης του /Bounds απαιτούσε έναν πλήρη πίνακα οκτώ στοιχείων, οπότε το μοναδικό σημείο διαχωρισμού που χρειάζεται πράγματι μια διαβάθμιση δύο τμημάτων — έναν αριθμό στο /Bounds — πάντα απέτυχε στην ανάλυση και η συνάρτηση δεν επέστρεφε τίποτα. Το /Encode δεν εφαρμοζόταν καθόλου. Η έκδοση 2.376.0 ξαναέγραψε την επιλογή ως τη γενική αναζήτηση k-υποσυναρτήσεων που περιγράφει η προδιαγραφή και άρχισε να διαβάζει το /Bounds έναντι του πραγματικού δηλωμένου μήκους του, οπότε μια διαβάθμιση τριών, τεσσάρων, ή πέντε σημείων ραμμένη από τόσα εκθετικά τμήματα τώρα επιλύεται με τον ίδιο τρόπο που πάντα ισχυριζόταν μια δύο τμημάτων. Το παράδειγμα παρακάτω χτίζει μια ράμπα δύο τμημάτων μαύρο-μέσω-κόκκινου-σε-λευκό, το σχήμα που καταφεύγει μια αξονική ή ακτινική σκίαση όποτε μία εκθετική καμπύλη δεν μπορεί να μεταφέρει κάθε σημείο χρώματος που απαιτεί ένα σχέδιο
var
ToRed, ToWhite, Ramp: THPDFDictionaryObject;
begin
// Two linear segments: black->red over [0, 0.5], red->white over [0.5, 1]
ToRed := Pdf.RegisterExponentialFunction([0,1], [0, 0, 0], [0.8, 0, 0], 1, []);
ToWhite := Pdf.RegisterExponentialFunction([0,1], [0.8, 0, 0], [1, 1, 1], 1, []);
Ramp := Pdf.RegisterStitchingFunction(
[0,1], // Domain: the stitched function's own input range
[ToRed, ToWhite], // Functions: k = 2 sub-functions
[0.5], // Bounds: k - 1 = 1 split point
[0,1, 0,1], // Encode: 2 numbers per sub-function
[]); // Range omitted: inherited from each sub-function
end;
Τι μπορεί να κάνει μια συνάρτηση υπολογιστή PostScript Type 4 που δεν μπορούν τα Type 2 και 3;
Μια συνάρτηση PDF Type 4 εκτελεί ένα γνήσιο, αν και σκόπιμα περιορισμένο, πρόγραμμα: έναν υπολογιστή PostScript που σπρώχνει τις εισόδους του σε μια στοίβα τελεστέων, εκτελεί τελεστές αριθμητικής, σύγκρισης, χειρισμού στοίβας, και boolean συν υπό συνθήκη if/ifelse, και αφήνει τις εξόδους του στη στοίβα όταν τελειώνει (ISO 32000-1 §7.10.5, Πίνακας 42). Δεν υπάρχει κατασκευή βρόχου και καμία αποθήκευση ονομασμένης μεταβλητής, μόνο η στοίβα, κάτι που κρατά ένα συμμορφούμενο πρόγραμμα εύκολο να το σκεφτείτε — αλλά μέσα σε αυτό το περιορισμένο σύνολο τελεστών, το Type 4 μπορεί να εκφράσει πράγματα που τα Type 2 και Type 3 δεν μπορούν, όπως έναν πραγματικό τύπο ανάμειξης πολλαπλών μελανιών για έναν διαχωρισμό DeviceN ή μια συνάρτηση spot ημιτονισμού με ένα κατώφλι υπό συνθήκη. Ο αξιολογητής του HotPDF, το HPDFEvalPostScriptCalculator, διασπά σε tokens το πρόγραμμα μία φορά — αριθμούς, τελεστές, και μπλοκ διαδικασίας { } — έπειτα διατρέχει μια στοίβα τελεστέων 100 καταχωρίσεων, το βάθος που ζητά το ISO 32000-1 §7.10.5, πίσω από ένα σκληρό ανώτατο όριο 50.000 αξιολογημένων τελεστών ως αμυντικό ανάχωμα έναντι παθολογικών ή χειρόγραφων προγραμμάτων
Ο τελεστής roll: η κατεύθυνση είναι εύκολο να μπερδευτεί
Το roll είναι ο τελεστής που είναι πιο πιθανό να βγει ανάποδα σε μια πρώτη προσπάθεια, επειδή τόσο η σειρά των ορισμάτων του όσο και η κατεύθυνση περιστροφής του τρέχουν αντίθετα από τον τρόπο που τα περιγράφουν τα ελληνικά ή τα αγγλικά. Το n j roll βγάζει από τη στοίβα έναν μετρητή n και ένα ποσό περιστροφής j, έπειτα μετατοπίζει κυκλικά τις κορυφαίες n καταχωρίσεις στοίβας κατά j θέσεις, τυλίγοντας στοιχεία που πέφτουν από το ένα άκρο πίσω στο άλλο· το κλασικό παράδειγμα, απευθείας από την προδιαγραφή, είναι το a b c 3 1 roll που παράγει c a b — το κορυφαίο στοιχείο μετακινείται στο κάτω μέρος της ομάδας, όχι το αντίστροφο, και κάθε άλλο στοιχείο μετατοπίζεται προς τα πάνω κατά ένα για να κάνει χώρο. Ο αξιολογητής του HotPDF υπολογίζει τη νέα θέση της καταχώρισης στοίβας i ως (i + j) mod n, που ταιριάζει ακριβώς με εκείνο το παράδειγμα, αλλά είναι ένας βρόχος δύο γραμμών που είναι εξίσου εύκολο να γραφτεί με την περιστροφή ανάποδα, και ένα κατοπτρικό roll εξακολουθεί να παράγει ένα εύλογα φαινόμενο χρώμα — απλώς δεν είναι το χρώμα που ζήτησε ο συγγραφέας του αρχείου
const
Prog = '{ 3 1 roll }'; // (a b c) -> (c a b): the third input moves to the front
var
Reorder: THPDFStreamObject;
begin
// Type 4: 3 inputs, 3 outputs, no extra clamping beyond Domain/Range
Reorder := Pdf.RegisterPostScriptFunction(
[0,1, 0,1, 0,1], // Domain: 2 numbers per input
[0,1, 0,1, 0,1], // Range: 2 numbers per output (required for Type 4)
Prog);
end;
Το round δεν είναι το Round του Delphi: στρογγυλοποίηση προς τα πάνω έναντι τραπεζικής στρογγυλοποίησης
Ο τελεστής round του PostScript επιλύει πάντα μια ισοπαλία .5 προς τον μεγαλύτερο ακέραιο, και η ενσωματωμένη συνάρτηση Round του Delphi δεν το κάνει: στρογγυλοποιεί μισό-προς-ζυγό, τη σύμβαση τραπεζικής στρογγυλοποίησης που εναλλάσσει προς ποια κατεύθυνση πέφτει μια ισοπαλία .5 ώστε η επαναλαμβανόμενη στρογγυλοποίηση να μη συσσωρεύει μεροληψία. Οι δύο συμφωνούν σχεδόν παντού και διαφωνούν ακριβώς στο όριο που έχει σημασία εδώ — το Round(0.5) του Delphi επιστρέφει 0 και το Round(2.5) επιστρέφει 2, ενώ το round της προδιαγραφής PDF θέλει 1 και 3 για τις ίδιες αυτές εισόδους — οπότε η ασυμφωνία κρύβεται μέσα από πρόχειρες δοκιμές και έπειτα αναπαράγεται ως ένα σταθερό σφάλμα κατά ένα όπου κι αν προσγειώνεται η ενδιάμεση αριθμητική ενός προγράμματος υπολογιστή ακριβώς σε έναν μισό ακέραιο. Το ISO 32000-1 §7.10.5 Πίνακας 42 είναι σαφές ότι το round σπρώχνει ένα κλασματικό .5 προς τον μεγαλύτερο ακέραιο, οπότε το HotPDF υλοποιεί τον τελεστή ως Floor(x + 0.5) αντί να καλεί το Round του Delphi, και οποιοσδήποτε κώδικας επαναϋλοποιεί ή επαληθεύει δειγματοληπτικά με το χέρι την αριθμητική ενός προγράμματος Type 4 χρειάζεται την ίδια αντικατάσταση
function PostScriptRound(const X: Double): Double;
begin
// ISO 32000-1 7.10.5 Table 42: round pushes .5 toward the greater
// integer. Delphi's Round() is banker's rounding and disagrees here:
// Round(0.5) = 0, Round(2.5) = 2 - both one short of the spec value.
Result := Floor(X + 0.5);
end;
Η επικύρωση κατά την καταχώριση πιάνει ένα κακό πρόγραμμα υπολογιστή νωρίς
Ένα κακοσχηματισμένο πρόγραμμα Type 4 είναι φθηνό να το πιάσετε κατά τη σύνταξη και ακριβό να το πιάσετε οπουδήποτε αλλού, οπότε το RegisterPostScriptFunction δεν αποθηκεύει απλώς το κείμενο πηγής: αξιολογεί δοκιμαστικά το πρόγραμμα μία φορά, στο μέσο του δηλωμένου /Domain, προτού το αντικείμενο συνάρτησης γραφτεί ποτέ στο έγγραφο. Ανισοζυγισμένα μπλοκ { }, ένας μη αναγνωρισμένος τελεστής, μια υποχείλιση στοίβας, ή ένας αριθμός εξόδου που δεν ταιριάζει με το /Range όλα αποτυγχάνουν σε εκείνη τη δοκιμαστική εκτέλεση και εγείρουν εξαίρεση αμέσως, με τη στοίβα κλήσεων να δείχνει στην κλήση RegisterPostScriptFunction αντί σε ένα τεχνούργημα απόδοσης που ανακαλύφθηκε κατά τον έλεγχο ποιότητας σε ένα αρχείο που είχε ήδη κυκλοφορήσει. Η δοκιμή στο μέσο δεν αποδεικνύει ότι το πρόγραμμα είναι σωστό σε ολόκληρο το /Domain του — ένας κλάδος υπό συνθήκη που μόνο συμπεριφέρεται λάθος κοντά σε μία άκρη του εύρους εισόδου μπορεί ακόμη να ξεφύγει από ένα μοναδικό σημείο δείγματος — αλλά κλείνει ολόκληρη την κατηγορία προγραμμάτων που είναι δομικά χαλασμένα αντί απλώς λάθος σε μία γωνία
Πού βάζουν σε δουλειά αυτές τις συναρτήσεις οι διαβαθμίσεις και τα χρώματα spot
Τα Type 2, 3, και 4 σπάνια εμφανίζονται μεμονωμένα σε ένα πραγματικό PDF· εμφανίζονται όπου δέχεται η προδιαγραφή ένα κλειδί /Function, και οι δύο πιο συνηθισμένοι καταναλωτές είναι οι σκιάσεις και οι μετασχηματισμοί απόχρωσης χρώματος spot. Ο τελεστής sh μιας αξονικής ή ακτινικής διαβάθμισης (ISO 32000-1 §8.7.4.5) αξιολογεί το /Function της μία φορά ανά θέση κατά μήκος του άξονα διαβάθμισης, που είναι ακριβώς η περίπτωση πολλαπλών σημείων για την οποία υπάρχει η ραφή Type 3. Ο μετασχηματισμός απόχρωσης ενός χώρου χρώματος Separation ή DeviceN είναι το άλλο συχνό σπίτι για αυτούς τους τρεις τύπους, και είναι εκεί που το Type 4 κερδίζει τη θέση του: ένα μεμονωμένο μελάνι spot συνήθως ανάγεται σε μια καμπύλη Type 2 ή Type 0, αλλά μια ανάμειξη DeviceN πολλών μελανιών με πραγματική συμπεριφορά trapping και overprint συχνά χρειάζεται τη λογική υπό συνθήκη που μόνο ένας υπολογιστής PostScript μπορεί να εκφράσει, η περίπτωση που καλύπτεται στο άρθρο για την απόδοση χρωμάτων spot Separation και DeviceN. Το RegisterSeparationFunc είναι η αντίστοιχη κλήση στην πλευρά της σύνταξης: δέχεται ένα όνομα χρωστικής, έναν εναλλακτικό χώρο χρώματος, και οποιοδήποτε αντικείμενο επιστρέφει η οικογένεια Register*Function, και καλωδιώνει εκείνο τον μετασχηματισμό απόχρωσης σε έναν πόρο χώρου χρώματος Separation που το υπόλοιπο της σελίδας μπορεί να επιλέξει με scn/SCN
Μαζί, τα δειγματισμένα πλέγματα του Type 0 και αυτοί οι τρεις τύποι βασισμένοι σε τύπους καλύπτουν κάθε /Function που μπορεί να δηλώσει ένα PDF, και η επιλογή του σωστού είναι κυρίως ζήτημα του τι ήδη έχετε: ένας πίνακας αναζήτησης υπολογισμένος αλλού γίνεται Type 0, μια ανάμειξη δύο άκρων γίνεται Type 2, πολλές αναμείξεις αλυσιδωμένες σε ένα πεδίο γίνονται Type 3, και οτιδήποτε με πραγματική λογική υπό συνθήκη γίνεται Type 4. Τα RegisterExponentialFunction, RegisterStitchingFunction, και RegisterPostScriptFunction είναι μέρος του τυπικού εξαρτήματος HotPDF για Delphi και C++Builder, μαζί με το υπόλοιπο API συναρτήσεων και σκιάσεων του κατά ISO 32000-1