HotPDF, componenta PDF VCL nativă pentru Delphi și C++Builder, evaluează cele trei tipuri de funcții PDF construite din formule, nu din grile de eșantioane: interpolarea exponențială Type 2, coaserea (stitching) Type 3 și funcțiile calculator PostScript Type 4, corespunzătoare ISO 32000-1 §7.10.3, §7.10.4 și §7.10.5. Type 2 amestecă între doi vectori de ieșire de-a lungul unei curbe, Type 3 înlănțuie mai multe subfuncții pe un singur domeniu de intrare, iar Type 4 rulează un program PostScript restrâns care poate ramifica, compara și calcula aproape orice are nevoie un flux de conținut din intrările sale. Greșiți puțin oricare dintre cele trei, iar eșecul nu se anunță niciodată ca un bug — apare ca un gradient cu o bandă complet plată, o culoare spot care se randează negru pur, sau o funcție calculator care este eronată cu exact o unitate la intrările pe care o suită de teste s-a întâmplat să nu le încerce
Aceste trei stau lângă un al patrulea, Type 0, care stochează o grilă eșantionată în loc de o formulă și este acoperit separat în articolul complementar despre tabelele de căutare de culoare (LUT) Type 0. Cele două familii rezolvă aceeași problemă, maparea unei intrări la o ieșire, dar Type 0 este date calculate o singură dată și încorporate în fișier, în timp ce Type 2, 3 și 4 sunt cod pe care cititorul îl evaluează la fiecare apel. Toate patru împart un singur punct de dispecerizare în renderer-ul HotPDF, cheiat pe intrarea /FunctionType a dicționarului de funcție, astfel încât o umbrire, o transformare de nuanță sau o funcție spot de tramare (halftone) nu trebuie niciodată să știe pe care din cele patru l-a primit înainte de a putea cere o culoare
Cum funcționează o funcție PDF Type 2 exponențială?
O funcție PDF Type 2 calculează o singură formulă — y = C0 + x^N × (C1 − C0), aplicată componentă cu componentă — unde x este intrarea unică a funcției, normalizată față de /Domain-ul său înainte ca formula să ruleze (ISO 32000-1 §7.10.3). /C0 și /C1 sunt vectorii de ieșire la cele două capete ale acelui interval, câte un număr pentru fiecare componentă de ieșire, iar /N este exponentul care modelează curba dintre ele: N = 1 dă rampa liniară dreaptă din spatele majorității opririlor de gradient și conversiilor duotone, N peste 1 trage curba spre C0, iar N între 0 și 1 o împinge spre C1. RegisterExponentialFunction construiește acel dicționar din cinci argumente și returnează un obiect funcție gata de conectat la o umbrire, o funcție spot de tramare, sau oriunde altundeva specificația acceptă o cheie /Function
Relația de număr de componente între C0 și C1 contează de două ori: o dată când generați o funcție Type 2, și din nou ori de câte ori HotPDF trebuie să randeze una pe care nu a creat-o el. Pe partea de generare, RegisterExponentialFunction verifică C0 și C1 unul față de celălalt și ridică o excepție dacă sunt în dezacord, astfel încât un apel care ajunge la BeginDoc are deja un obiect funcție auto-consistent. Pe partea de randare, totuși, evaluatorul trebuie să aibă încredere în orice tablouri /C0 și /C1 declară efectiv un fișier sursă — un fișier de tipografie deschis pentru previzualizare, de exemplu, sau un document semnat afișat înapoi unui utilizator — iar versiunile dinainte de 2.376.0 citeau acele tablouri într-un buffer dimensionat pentru patru componente, cazul CMYK. O nuanță exponențială DeviceGray sau DeviceRGB, cu un /C0 și /C1 de unul sau trei elemente, eșua acea citire în tăcere și lăsa ambele tablouri la zero, așa că nuanța se picta negru plat în loc de culoarea sa dorită. Versiunea 2.376.0 a redimensionat cititorul la numărul de ieșire declarat efectiv al funcției, în loc de un buffer fix — exact genul de bug pe care doar un caz de test non-CMYK îl expune, întrucât suita existentă rula CMYK peste tot, unde patru-în-patru se potrivea întotdeauna
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;
Coaserea (stitching) Type 3: înlănțuirea subfuncțiilor printr-un tablou Bounds
O funcție PDF Type 3 coase k subfuncții într-o mapare pe bucăți peste /Domain-ul unei singure intrări, iar cele două tablouri care fac asta să funcționeze sunt /Bounds și /Encode (ISO 32000-1 §7.10.4). /Bounds conține k − 1 puncte de divizare interioare care taie /Domain în k intervale consecutive; evaluatorul alege primul interval a cărui limită superioară depășește intrarea, sau ultimul interval odată ce intrarea atinge limita finală, și predă acelei subfuncții din intervalul respectiv. /Encode apoi remapează intrarea din poziția sa în interiorul acelui interval pe orice interval de intrare așteaptă subfuncția aleasă ea însăși — de obicei [0, 1] dacă subfuncția este încă un segment exponențial — înainte ca evaluarea să continue, cu un apel mai adânc, în propriul /Domain și /Range al acelei subfuncții
Evaluatorul de coasere al HotPDF obișnuia să gestioneze doar exact două subfuncții, iar cititorul său de /Bounds necesita un tablou complet de opt elemente, așa că singurul punct de divizare de care are nevoie efectiv un gradient cu două segmente — un singur număr în /Bounds — eșua întotdeauna la analiză, iar funcția nu returna nimic. /Encode nu era deloc aplicat. Versiunea 2.376.0 a rescris selecția ca o căutare generală pentru k subfuncții, așa cum descrie specificația, și a început să citească /Bounds față de lungimea sa reală declarată, astfel încât un gradient cu trei, patru sau cinci opriri, cusut din atâtea segmente exponențiale, se rezolvă acum la fel cum un gradient cu două segmente a pretins mereu că se rezolvă. Exemplul de mai jos construiește o rampă cu două segmente de la negru la roșu la alb, forma la care o umbrire axială sau radială recurge ori de câte ori o singură curbă exponențială nu poate transporta fiecare oprire de culoare cerută de un design
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;
Ce poate face o funcție calculator PostScript Type 4 pe care Type 2 și 3 nu o pot face?
O funcție PDF Type 4 rulează un program autentic, deși deliberat restrâns: un calculator PostScript care își împinge intrările pe o stivă de operanzi, execută operatori aritmetici, de comparație, de manipulare a stivei și booleeni plus condiționale if/ifelse, și lasă ieșirile sale pe stivă când termină (ISO 32000-1 §7.10.5, Tabelul 42). Nu există nicio construcție de buclă și nicio stocare de variabile numite, doar stiva, ceea ce menține un program conform ușor de urmărit — dar în cadrul acelui set restrâns de operatori, Type 4 poate exprima lucruri pe care Type 2 și Type 3 nu le pot exprima, precum o formulă reală de amestec multi-cerneală pentru o separare DeviceN sau o funcție spot de tramare cu un prag condiționat. Evaluatorul HotPDF, HPDFEvalPostScriptCalculator, tokenizează programul o singură dată — numere, operatori și blocuri de procedură { } — apoi parcurge o stivă de operanzi cu 100 de intrări, adâncimea cerută de ISO 32000-1 §7.10.5, în spatele unui plafon dur de 50.000 de operatori evaluați, ca o măsură defensivă de siguranță împotriva programelor patologice sau scrise manual
Operatorul roll: direcția este ușor de greșit
roll este operatorul cel mai probabil să iasă invers la o primă încercare, pentru că atât ordinea argumentelor sale, cât și direcția sa de rotație rulează opus modului în care le descrie limbajul obișnuit. n j roll extrage un contor n și o cantitate de rotație j, apoi deplasează ciclic cele mai de sus n intrări ale stivei cu j poziții, înfășurând elementele care cad de la un capăt înapoi pe celălalt; exemplul canonic, direct din specificație, este a b c 3 1 roll producând c a b — elementul de sus se mută la baza grupului, nu invers, iar fiecare alt element se deplasează în sus cu unu pentru a face loc. Evaluatorul HotPDF calculează noua poziție a intrării de stivă i ca (i + j) mod n, ceea ce corespunde exact acelui exemplu, dar este o buclă de două linii la fel de ușor de scris cu rotația inversată, iar un roll în oglindă tot produce o culoare cu aspect plauzibil — doar că nu este culoarea pe care a cerut-o autorul fișierului
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 nu este Round-ul din Delphi: rotunjire spre în sus la jumătate versus rotunjire bancară
Operatorul round din PostScript rezolvă o egalitate de .5 întotdeauna spre întregul mai mare, iar funcția încorporată Round din Delphi nu face asta: ea rotunjește la jumătate-spre-par, convenția de rotunjire bancară care alternează în ce direcție cade o egalitate de .5, astfel încât rotunjirea repetată să nu acumuleze o abatere sistematică. Cele două sunt de acord aproape peste tot și dezacord exact pe granița care contează aici — Round(0.5) din Delphi returnează 0 și Round(2.5) returnează 2, în timp ce round-ul din specificația PDF vrea 1 și 3 pentru aceleași intrări — așa că discrepanța se ascunde în testarea ocazională și apoi se reproduce ca o eroare consistentă de o unitate oriunde matematica intermediară a unui program calculator aterizează exact pe un semi-întreg. ISO 32000-1 §7.10.5 Tabelul 42 este explicit că round împinge o fracție de .5 spre întregul mai mare, așa că HotPDF implementează operatorul ca Floor(x + 0.5) în loc să apeleze Round-ul din Delphi, iar orice cod care reimplementează sau verifică manual matematica unui program Type 4 are nevoie de aceeași substituție
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;
Validarea la momentul înregistrării prinde din timp un program calculator defect
Un program Type 4 malformat este ieftin de prins la momentul generării și costisitor de prins oriunde altundeva, așa că RegisterPostScriptFunction nu doar stochează textul sursă: evaluează experimental programul o singură dată, la mijlocul /Domain-ului declarat, înainte ca obiectul funcție să fie vreodată scris în document. Blocuri { } nebalansate, un operator nerecunoscut, un underflow de stivă, sau un număr de ieșiri care nu se potrivește cu /Range eșuează toate acea rulare experimentală și ridică imediat o excepție, cu stiva de apeluri arătând spre apelul RegisterPostScriptFunction, în loc de un artefact de randare descoperit în timpul QA pe un fișier deja livrat. Rularea experimentală la mijloc nu demonstrează că programul este corect pe întregul său /Domain — o ramură condiționată care se comportă greșit doar aproape de o margine a intervalului de intrare tot poate scăpa pe lângă un singur punct de eșantion — dar închide întreaga clasă de programe care sunt structural rupte, nu doar greșite într-un colț
Unde pun gradientele și culorile spot aceste funcții la treabă
Type 2, 3 și 4 apar rareori izolate într-un PDF real; ele apar oriunde specificația acceptă o cheie /Function, iar cei mai comuni doi consumatori sunt umbririle și transformările de nuanță pentru culori spot. Operatorul sh al unui gradient axial sau radial (ISO 32000-1 §8.7.4.5) evaluează /Function-ul său o singură dată per poziție de-a lungul axei gradientului, ceea ce este exact cazul cu opriri multiple pentru care există coaserea Type 3. Transformarea de nuanță a unui spațiu de culoare Separation sau DeviceN este cealaltă casă frecventă pentru aceste trei tipuri, și acolo Type 4 își câștigă locul: o singură cerneală spot de obicei se reduce la o curbă Type 2 sau Type 0, dar un amestec DeviceN de mai multe cerneluri cu comportament real de trapping și suprapunere de multe ori are nevoie de logica condiționată pe care doar un calculator PostScript o poate exprima, cazul acoperit în articolul despre randarea culorilor spot Separation și DeviceN. RegisterSeparationFunc este apelul pereche pe partea de generare: preia un nume de colorant, un spațiu de culoare alternativ, și orice obiect returnat de familia Register*Function, și conectează acea transformare de nuanță într-o resursă de spațiu de culoare Separation, pe care restul paginii o poate selecta cu scn/SCN
Împreună, grilele eșantionate ale Type 0 și aceste trei tipuri conduse de formule acoperă fiecare /Function pe care o poate declara un PDF, iar alegerea celei potrivite este mai ales o întrebare despre ce aveți deja: un tabel de căutare calculat în altă parte devine Type 0, un amestec cu două capete devine Type 2, mai multe amestecuri înlănțuite peste un domeniu devin Type 3, iar orice are logică condiționată reală devine Type 4. RegisterExponentialFunction, RegisterStitchingFunction și RegisterPostScriptFunction fac parte din componenta HotPDF standard pentru Delphi și C++Builder, alături de restul API-ului său de funcții și umbriri conform ISO 32000-1