HotPDF, izvorna VCL PDF komponenta za Delphi i C++Builder, procjenjuje tri PDF tipa funkcija izgrađena od formula, a ne od mreža uzoraka: eksponencijalnu interpolaciju tipa 2, spajanje (stitching) tipa 3 i PostScript kalkulator funkcije tipa 4, koje odgovaraju ISO 32000-1 §7.10.3, §7.10.4 i §7.10.5. Tip 2 prelijeva se između dva izlazna vektora duž krivulje, tip 3 povezuje nekoliko podfunkcija preko jedne ulazne domene, a tip 4 pokreće ograničeni PostScript program koji se može grananjem, usporedbom i izračunom baviti gotovo svime što sadržajni tok treba od svojih ulaza. Pogriješite li ijedan od ova tri malo, kvar se nikad ne najavljuje kao bug — pokazuje se kao gradijent s potpuno ravnom trakom, spot boja koja se iscrtava kao čisto crna ili kalkulator funkcija koja je za točno jedan pogrešna na ulazima koje paket testova slučajno nije isprobao
Ova tri stoje uz četvrti tip, tip 0, koji pohranjuje uzorkovanu mrežu umjesto formule i zasebno je opisan u pratećem članku o tablicama pretraživanja boja tipa 0. Obje obitelji rješavaju isti problem, preslikavanje ulaza u izlaz, no tip 0 su podaci izračunati jednom i ugrađeni u datoteku, dok su tipovi 2, 3 i 4 kod koji čitač procjenjuje pri svakom pozivu. Sva četiri tipa dijele jednu točku raspodjele u HotPDF-ovom rendereru, ovisnu o unosu /FunctionType u rječniku funkcije, tako da sjenčanje, transformacija tinte ili spot funkcija poluraster nikad ne moraju znati koji su od ta četiri primili prije nego što mogu zatražiti boju
Kako radi PDF eksponencijalna funkcija tipa 2?
PDF funkcija tipa 2 izračunava jednu formulu — y = C0 + x^N × (C1 − C0), primijenjenu komponentu po komponentu — gdje je x jedini ulaz funkcije, normaliziran prema svojoj /Domain prije nego što se formula izvrši (ISO 32000-1 §7.10.3). /C0 i /C1 izlazni su vektori na dva kraja tog raspona, jedan broj po izlaznoj komponenti, a /N je eksponent koji oblikuje krivulju između njih: N = 1 daje ravnu linearnu rampu iza većine prijelaznih točaka gradijenta i duotone konverzija, N veći od 1 povlači krivulju prema C0, a N između 0 i 1 gura je prema C1. RegisterExponentialFunction gradi taj rječnik iz pet argumenata i vraća objekt funkcije spreman za ugradnju u sjenčanje, spot funkciju poluraster ili bilo gdje drugdje gdje specifikacija prihvaća ključ /Function
Odnos broja komponenata između C0 i C1 važan je dvaput: jednom kad sami pišete funkciju tipa 2, i ponovno kad god HotPDF mora iscrtati onu koju nije sam stvorio. Na strani pisanja, RegisterExponentialFunction uspoređuje C0 i C1 međusobno i izbacuje iznimku ako se ne slažu, pa poziv koji stigne do BeginDoc već ima samodosljedan objekt funkcije. No na strani iscrtavanja, evaluator mora vjerovati bilo kojim poljima /C0 i /C1 koje izvorna datoteka doista deklarira — recimo, tiskarskoj datoteci otvorenoj za pregled ili potpisanom dokumentu prikazanom korisniku natrag — a verzije prije 2.376.0 čitale su ta polja u spremnik veličine za četiri komponente, CMYK slučaj. DeviceGray ili DeviceRGB eksponencijalna tinta, s jednoelementnim ili tročlanim /C0 i /C1, tiho je propadala pri tom čitanju i ostavljala oba polja na nuli, pa se tinta iscrtavala kao ravna crna umjesto namijenjene boje. Verzija 2.376.0 promijenila je veličinu čitača prema stvarno deklariranom broju izlaza funkcije umjesto fiksnog spremnika — točno ona vrsta buga koju otkriva samo test slučaj koji nije CMYK, jer je postojeći paket testova posvuda koristio CMYK, gdje se četiri-u-četiri uvijek uklapalo
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;
Spajanje tipa 3: povezivanje podfunkcija preko polja Bounds
PDF funkcija tipa 3 spaja k podfunkcija u jedno preslikavanje po dijelovima preko /Domain jedinog ulaza, a dva polja koja to omogućuju jesu /Bounds i /Encode (ISO 32000-1 §7.10.4). /Bounds sadrži k − 1 unutarnjih točaka razdvajanja koje dijele /Domain na k uzastopnih intervala; evaluator bira prvi interval čija gornja granica premašuje ulaz, ili posljednji interval čim ulaz dosegne konačnu granicu, i predaje kontrolu podfunkciji tog intervala. /Encode zatim preslikava ulaz iz njegove pozicije unutar tog intervala na bilo koji ulazni raspon koji sama odabrana podfunkcija očekuje — obično [0, 1] ako je podfunkcija još jedan eksponencijalni segment — prije nego što se procjena nastavi, jedan poziv dublje, u vlastite /Domain i /Range te podfunkcije
HotPDF-ov evaluator spajanja nekad je obrađivao samo točno dvije podfunkcije, a njegov čitač za /Bounds zahtijevao je puno polje od osam elemenata, pa se jedina točka razdvajanja koja je dvosegmentnom gradijentu doista potrebna — jedan broj u /Bounds — uvijek neuspješno raščlanjivala i funkcija nije vraćala ništa. /Encode se uopće nije primjenjivao. Verzija 2.376.0 preradila je odabir u opću k-podfunkcijsku pretragu koju opisuje specifikacija i počela čitati /Bounds prema njegovoj stvarno deklariranoj duljini, pa se gradijent s tri, četiri ili pet prijelaznih točaka, spojen od odgovarajućeg broja eksponencijalnih segmenata, sad razrješava jednako kao što je dvosegmentni oduvijek tvrdio da radi. Primjer dolje gradi dvosegmentnu rampu crna-preko-crvene-do-bijele, oblik po koji aksijalno ili radijalno sjenčanje poseže kad god jedna eksponencijalna krivulja ne može ponijeti sve prijelazne boje koje dizajn zahtijeva
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;
Što PostScript kalkulator funkcija tipa 4 može učiniti što tipovi 2 i 3 ne mogu?
PDF funkcija tipa 4 pokreće pravi, iako namjerno ograničen, program: PostScript kalkulator koji gura svoje ulaze na stog operanada, izvršava aritmetičke, usporedbene, stog-manipulacijske i booleove operatore te uvjete if/ifelse, i na kraju ostavlja svoje izlaze na stogu (ISO 32000-1 §7.10.5, tablica 42). Nema konstrukta petlje niti pohrane imenovanih varijabli, samo stog, što usklađen program čini lakim za praćenje — no unutar tog ograničenog skupa operatora, tip 4 može izraziti ono što tipovi 2 i 3 ne mogu, poput prave formule miješanja više tinti za DeviceN separaciju ili spot funkcije poluraster s uvjetnim pragom. HotPDF-ov evaluator, HPDFEvalPostScriptCalculator, jednom tokenizira program — brojeve, operatore i blokove postupaka { } — a zatim prolazi kroz stog operanada od 100 unosa, dubinu koju zahtijeva ISO 32000-1 §7.10.5, iza tvrde granice od 50.000 procijenjenih operatora kao obrambene mjere protiv patoloških ili ručno napisanih programa
Operator roll: smjer se lako pobrka
roll je operator kod kojeg je najlakše kod prvog pokušaja pogriješiti smjer, jer i redoslijed njegovih argumenata i smjer rotacije idu suprotno od onoga kako bi ih se opisalo riječima. n j roll skida sa stoga broj n i iznos rotacije j, zatim ciklički pomiče gornjih n unosa stoga za j pozicija, omatajući stavke koje ispadnu s jednog kraja natrag na drugi; kanonski primjer, izravno iz specifikacije, jest a b c 3 1 roll koji proizvodi c a b — gornja stavka se pomiče na dno grupe, ne obrnuto, a svaka druga stavka pomiče se za jedno mjesto naviše kako bi napravila prostora. HotPDF-ov evaluator izračunava novu poziciju stavke stoga i kao (i + j) mod n, što se točno podudara s tim primjerom, no riječ je o dvorednoj petlji koju je jednako lako napisati s obrnutom rotacijom, a zrcaljeni roll i dalje proizvodi boju koja izgleda vjerodostojno — samo nije ona boja koju je autor datoteke tražio
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 nije Delphijev Round: zaokruživanje na više naspram bankarskog zaokruživanja
PostScriptov operator round svaki put razrješava neriješenu situaciju na .5 prema većem cijelom broju, a Delphijeva ugrađena funkcija Round to ne čini: ona zaokružuje na parni broj (half-to-even), konvenciju bankarskog zaokruživanja koja izmjenjuje smjer u kojem pada neriješena situacija na .5 kako se ponovljenim zaokruživanjem ne bi nakupljala pristranost. Ta dva pristupa slažu se gotovo posvuda, a razlikuju se točno na granici koja je ovdje bitna — Delphijev Round(0.5) vraća 0, a Round(2.5) vraća 2, dok PDF specifikacijin round za te iste ulaze traži 1 i 3 — pa se ta neusklađenost krije kroz uzgredno testiranje i zatim se ponavlja kao dosljedna pogreška za jedan gdje god međurezultat aritmetike kalkulator programa padne točno na polucijeli broj. Tablica 42 u ISO 32000-1 §7.10.5 izričito navodi da round gura razlomljeni .5 prema većem cijelom broju, pa HotPDF implementira operator kao Floor(x + 0.5) umjesto poziva Delphijeve funkcije Round, a svaki kod koji ručno ponovno implementira ili provjerava aritmetiku programa tipa 4 treba istu zamjenu
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;
Validacija u trenutku registracije rano otkriva neispravan kalkulator program
Neispravan program tipa 4 jeftino je uhvatiti u trenutku pisanja, a skupo bilo gdje drugdje, pa RegisterPostScriptFunction ne samo da pohranjuje izvorni tekst: probno procjenjuje program jednom, na sredini deklariranog /Domain, prije nego što je objekt funkcije uopće zapisan u dokument. Neuravnoteženi blokovi { }, neprepoznat operator, podljev stoga (stack underflow) ili broj izlaza koji se ne podudara s /Range — sve to propada u tom probnom pokretanju i odmah izbacuje iznimku, sa stogom poziva koji pokazuje na poziv RegisterPostScriptFunction umjesto na artefakt iscrtavanja otkriven tijekom testiranja kvalitete na datoteci koja je već isporučena. Probno pokretanje na sredini ne dokazuje da je program ispravan preko cijelog svog /Domain — uvjetna grana koja se loše ponaša samo blizu jednog ruba ulaznog raspona i dalje može proći nezapaženo kroz jednu jedinu probnu točku — no zatvara cijelu klasu programa koji su strukturno neispravni, a ne samo pogrešni u jednom kutu
Gdje gradijenti i spot boje stavljaju ove funkcije na posao
Tipovi 2, 3 i 4 rijetko se pojavljuju izolirano u stvarnom PDF-u; pojavljuju se gdje god specifikacija prihvaća ključ /Function, a dva najčešća potrošača jesu sjenčanja i transformacije tinte spot boje. Operator sh aksijalnog ili radijalnog gradijenta (ISO 32000-1 §8.7.4.5) procjenjuje svoju /Function jednom po poziciji duž osi gradijenta, što je točno slučaj s više prijelaznih točaka za koji postoji spajanje tipa 3. Transformacija tinte prostora boja Separation ili DeviceN drugi je čest dom za ova tri tipa, i upravo tu tip 4 opravdava svoje mjesto: jedna spot tinta obično se svodi na krivulju tipa 2 ili tipa 0, no DeviceN mješavina nekoliko tinti sa stvarnim ponašanjem zamke (trapping) i preklapanja (overprint) često treba uvjetnu logiku koju može izraziti samo PostScript kalkulator, slučaj opisan u članku o iscrtavanju Separation i DeviceN spot boja. RegisterSeparationFunc je odgovarajući poziv na strani pisanja: prima naziv tvari za bojanje (colorant), alternativni prostor boja i bilo koji objekt koji vraća obitelj Register*Function, te povezuje tu transformaciju tinte s resursom prostora boja Separation koji ostatak stranice može odabrati pomoću scn/SCN
Zajedno, uzorkovane mreže tipa 0 i ova tri tipa vođena formulama pokrivaju svaku /Function koju PDF može deklarirati, a odabir pravog uglavnom je pitanje onoga što već imate: tablica pretraživanja izračunata drugdje postaje tip 0, prijelaz između dvije krajnje točke postaje tip 2, nekoliko prijelaza povezanih preko domene postaje tip 3, a sve sa stvarnom uvjetnom logikom postaje tip 4. RegisterExponentialFunction, RegisterStitchingFunction i RegisterPostScriptFunction dio su standardne HotPDF komponente za Delphi i C++Builder, uz ostatak njezinog ISO 32000-1 API-ja za funkcije i sjenčanja