HotPDF, savasis VCL PDF komponentas, skirtas Delphi ir C++Builder, vertina tris PDF funkcijų tipus, sukurtus iš formulių, o ne pavyzdinių tinklelių: 2 tipo eksponentinę interpoliaciją, 3 tipo jungimą ir 4 tipo PostScript skaičiuotuvo funkcijas, atitinkančias ISO 32000-1 §7.10.3, §7.10.4 ir §7.10.5. 2 tipas maišo du išvesties vektorius pagal kreivę, 3 tipas sujungia kelias dalines funkcijas viename įvesties domene, o 4 tipas vykdo ribotą PostScript programą, kuri gali šakotis, lyginti ir iš įvesčių apskaičiuoti beveik viską, ko reikia turinio srautui. Jei kurią nors iš šių trijų funkcijų įgyvendinsite net šiek tiek neteisingai, klaida niekada nepasiskelbs kaip klaida — ji pasirodys kaip gradientas su visiškai lygiu ruožu, gryna juoda dėmė arba skaičiuotuvo funkcija, kuri tiksliai vienetu skiriasi tose įvestyse, kurių testų rinkinys nebandė
Šie trys tipai yra greta ketvirtojo, 0 tipo, kuris vietoj formulės saugo atrinktą tinklelį ir aprašomas atskirai gretimame straipsnyje apie 0 tipo spalvų paieškos lenteles. Abi šeimos sprendžia tą pačią užduotį, susieja įvestį su išvestimi, tačiau 0 tipas yra kitur iš anksto apskaičiuoti ir faile įrašyti duomenys, o 2, 3 ir 4 tipai yra kodas, kurį skaitytuvas įvertina kiekvieno iškvietimo metu. Visi keturi tipai naudoja vieną HotPDF atvaizduotuvo persiuntimo tašką, parenkamą pagal funkcijų žodyno /FunctionType įrašą, todėl šešėliavimas, atspalvio transformacija ar pustonio dėmės funkcija neprivalo žinoti, kurį iš keturių gavo, prieš prašydama spalvos
Kaip veikia PDF 2 tipo eksponentinė funkcija?
PDF 2 tipo funkcija apskaičiuoja vieną formulę — y = C0 + x^N × (C1 − C0), taikomą kiekvienam komponentui atskirai, kur x yra vienintelė funkcijos įvestis, normalizuojama pagal jos /Domain prieš vykdant formulę (ISO 32000-1 §7.10.3). /C0 ir /C1 yra išvesties vektoriai dviejuose šio intervalo galuose, po vieną skaičių kiekvienam išvesties komponentui, o /N yra eksponentė, formuojanti kreivę tarp jų: N = 1 sukuria tiesią linijinę rampą, esančią už daugelio gradiento sustojimų ir dvitonių konversijų, N virš 1 kreivę traukia link C0, o N tarp 0 ir 1 stumia ją link C1. RegisterExponentialFunction sukuria šį žodyną iš penkių argumentų ir grąžina funkcijos objektą, parengtą prijungti prie šešėliavimo, pustonio dėmės funkcijos ar bet kurios kitos vietos, kur specifikacija priima /Function raktą
C0 ir C1 komponentų skaičiaus ryšys svarbus du kartus: kai kuriate 2 tipo funkciją ir kiekvieną kartą, kai HotPDF turi atvaizduoti funkciją, kurios nesukūrė. Kūrimo pusėje RegisterExponentialFunction sutikrina C0 ir C1 tarpusavyje ir iškelia klaidą, jei jie nesutampa, todėl BeginDoc pasiekiantis iškvietimas jau turi nuoseklų funkcijos objektą. Tačiau atvaizdavimo pusėje vertintojas turi pasitikėti tuo, kokius /C0 ir /C1 masyvus deklaruoja tikrasis šaltinio failas, pavyzdžiui, spaustuvės failas, atvertas peržiūrai, arba pasirašytas dokumentas, rodomas naudotojui. Iki 2.376.0 versijos skaitytuvas šiuos masyvus įkeldavo į keturiems komponentams skirtą buferį, skirtą CMYK atvejui. DeviceGray arba DeviceRGB eksponentinis atspalvis su vieno arba trijų elementų /C0 ir /C1 masyvais tyliai neįveikdavo šio nuskaitymo ir abu masyvai likdavo lygūs nuliui, todėl atspalvis buvo nudažomas lygia juoda spalva, o ne numatyta spalva. 2.376.0 versijoje skaitytuvas pakeitė dydį pagal tikrąjį deklaruotą funkcijos išvesties komponentų skaičių, o ne pagal fiksuotą buferį — būtent tokią klaidą atskleidžia tik ne CMYK testas, nes esamas rinkinys visur naudojo CMYK, kuriam keturi į keturis visada tiko
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;
3 tipo jungimas: dalinių funkcijų grandinė per Bounds masyvą
PDF 3 tipo funkcija sujungia k dalinių funkcijų į vieną dalinį atvaizdavimą per vienos įvesties /Domain, o du tai atliekantys masyvai yra /Bounds ir /Encode (ISO 32000-1 §7.10.4). /Bounds turi k − 1 vidinius skaidymo taškus, kurie padalija /Domain į k nuoseklių intervalų; vertintojas parenka pirmą intervalą, kurio viršutinė riba viršija įvestį, arba paskutinį intervalą, kai įvestis pasiekia galutinę ribą, ir perduoda ją to intervalo dalinei funkcijai. Tada /Encode perkelia įvestį iš jos vietos tame intervale į tokį įvesties diapazoną, kokio tikisi pasirinkta dalinė funkcija, paprastai [0, 1], jei dalinė funkcija yra dar vienas eksponentinis segmentas, ir vertinimas tęsiasi vienu lygiu giliau, tos dalinės funkcijos /Domain ir /Range ribose
HotPDF jungimo vertintojas anksčiau apdorodavo tiksliai dvi dalines funkcijas, o jo /Bounds skaitytuvas reikalaudavo pilno aštuonių elementų masyvo, todėl vienas skaidymo taškas, kurio iš tikrųjų reikia dviejų segmentų gradientui, visada nepavykdavo perskaityti ir funkcija nieko negrąžindavo. /Encode apskritai nebuvo taikomas. 2.376.0 versija atranką perrašė kaip bendrą k dalinių funkcijų paiešką, aprašytą specifikacijoje, ir pradėjo skaityti /Bounds pagal tikrąjį deklaruotą ilgį, todėl trijų, keturių ar penkių sustojimų gradientas, sujungtas iš tiek pat eksponentinių segmentų, dabar išsprendžiamas taip pat, kaip visada turėjo būti išsprendžiamas dviejų segmentų gradientas. Toliau pateiktas pavyzdys sukuria dviejų segmentų juodą–raudoną–baltą rampą, tokią, kokios ašinis arba radialinis šešėliavimas imasi, kai viena eksponentinė kreivė negali perteikti visų dizainui reikalingų spalvų sustojimų
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;
Ką gali 4 tipo PostScript skaičiuotuvo funkcija, ko negali 2 ir 3 tipai?
PDF 4 tipo funkcija vykdo tikrą, nors sąmoningai apribotą programą: PostScript skaičiuotuvą, kuris nustumia įvestis ant operandų dėklo, vykdo aritmetinius, palyginimo, dėklo valdymo ir loginius operatorius bei if/ifelse sąlyginius veiksmus, o baigęs palieka išvestis dėkle (ISO 32000-1 §7.10.5, 42 lentelė). Ciklo konstrukcijos ir pavadintų kintamųjų saugyklos nėra, yra tik dėklas, todėl atitinkančią programą lengva suprasti, tačiau su šiuo ribotu operatorių rinkiniu 4 tipas gali išreikšti tai, ko negali 2 ir 3 tipai, pavyzdžiui, tikrą kelių dažų maišymo formulę DeviceN separacijai arba pustonio dėmės funkciją su sąlyginiu slenksčiu. HotPDF vertintojas HPDFEvalPostScriptCalculator programą suskaido į žetonus vieną kartą, skaičius, operatorius ir { } procedūrų blokus, tada pereina per 100 įrašų operandų dėklą, kurio reikalauja ISO 32000-1 §7.10.5, ir taiko griežtą 50 000 įvertintų operatorių ribą kaip apsaugą nuo patologinių ar ranka parašytų programų
Operatorių roll lengva pasukti priešinga kryptimi
roll yra operatorius, kurį pirmuoju bandymu lengviausia įgyvendinti atvirkščiai, nes jo argumentų tvarka ir sukimo kryptis abi prieštarauja tam, kaip jas apibūdintume įprasta kalba. n j roll nuima skaičių n ir sukimo dydį j, tada cikliškai perkelia viršutinius n dėklo įrašų per j pozicijų, apvyniodamas viename gale nukritusius elementus į kitą; kanoninis pavyzdys, paimtas tiesiai iš specifikacijos, yra a b c 3 1 roll, gaunantis c a b — viršutinis elementas perkeliamas į grupės apačią, o ne kita kryptimi, ir visi kiti elementai paslenkami vienetu aukštyn, kad atsirastų vietos. HotPDF vertintojas apskaičiuoja naują dėklo įrašo i padėtį kaip (i + j) mod n, o tai tiksliai atitinka pavyzdį, tačiau tai yra dviejų eilučių ciklas, kurį taip pat lengva parašyti apvertus sukimą, o veidrodinis roll vis tiek sukuria įtikinamai atrodantį atspalvį — jis tiesiog nėra toks, kokio paprašė failo autorius
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 nėra Delphi Round: apvalinimas į viršų ir bankininko apvalinimas
PostScript operatorius round kiekvieną kartą išsprendžia .5 lygybę link didesnio sveikojo skaičiaus, o integruota Delphi funkcija Round to nedaro: ji apvalina iki lyginio skaičiaus, taikydama bankininko apvalinimo susitarimą, kuris keičia .5 lygybės kryptį, kad kartotinis apvalinimas nesukauptų poslinkio. Beveik visur abu būdai sutampa, o tiksliai svarbioje riboje jie skiriasi: Delphi Round(0.5) grąžina 0, o Round(2.5) grąžina 2, tuo tarpu PDF specifikacijos round toms pačioms įvestims nori atitinkamai 1 ir 3, todėl neatitikimas slepiasi atliekant paviršutiniškus testus ir vėliau pasikartoja kaip nuosekli vieneto paklaida visur, kur skaičiuotuvo programos tarpinė matematika tiksliai pasiekia pusinį sveikąjį skaičių. ISO 32000-1 §7.10.5 42 lentelėje aiškiai nurodoma, kad round stumia trupmeninį .5 link didesnio sveikojo skaičiaus, todėl HotPDF operatorių įgyvendina kaip Floor(x + 0.5), o ne kviesdama Delphi Round, ir bet kuriam kodui, kuris iš naujo įgyvendina arba rankiniu būdu tikrina 4 tipo programos aritmetiką, reikia tokio pat pakeitimo
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;
Registracijos metu atliekamas tikrinimas anksti aptinka blogą skaičiuotuvo programą
Netinkamai suformuotą 4 tipo programą pigu aptikti kūrimo metu ir brangu aptikti bet kur kitur, todėl RegisterPostScriptFunction ne tik išsaugo pradinį tekstą: prieš funkcijos objektą įrašant į dokumentą, ji vieną kartą bandomai įvertina programą deklaruoto /Domain viduryje. Nesubalansuoti { } blokai, neatpažintas operatorius, dėklo perpildymas arba išvesties skaičius, nesutampantis su /Range, nutraukia bandomąjį vykdymą ir iškart iškelia išimtį, o iškvietimų dėklas rodo į RegisterPostScriptFunction iškvietimą, o ne į atvaizdavimo artefaktą, aptiktą atliekant jau išleisto failo kokybės patikrą. Vidurio taško bandymas neįrodo, kad programa teisinga visame /Domain, nes sąlyginė šaka, kuri netinkamai veikia tik ties viena įvesties diapazono kraštine, vis tiek gali praslysti pro vieną mėginio tašką, tačiau jis pašalina visą struktūriškai sugadintų, o ne tik viename kampe neteisingų programų klasę
Kur gradientai ir dėmių spalvos naudoja šias funkcijas
Realiame PDF 2, 3 ir 4 tipai retai pasirodo atskirai; jie naudojami visur, kur specifikacija priima /Function raktą, o du dažniausi vartotojai yra šešėliavimai ir dėmių spalvų atspalvio transformacijos. Ašinio arba radialinio gradiento sh operatorius (ISO 32000-1 §8.7.4.5) įvertina savo /Function kiekvienoje gradiento ašies padėtyje, būtent tam ir skirtas kelių sustojimų atvejis, kurį sprendžia 3 tipo jungimas. Kitas dažnas šių trijų tipų pritaikymas yra Separation arba DeviceN spalvų erdvės atspalvio transformacija, ir būtent čia 4 tipas atsiskleidžia: vienas dėmės rašalas paprastai sutalpinamas į 2 arba 0 tipo kreivę, tačiau DeviceN kelių rašalų deriniui su tikru persidengimo ir spaudos viršaus elgesiu dažnai reikia sąlyginės logikos, kurią gali išreikšti tik PostScript skaičiuotuvas, kaip aprašoma straipsnyje apie Separation ir DeviceN dėmių spalvų atvaizdavimą. RegisterSeparationFunc yra susiejantis iškvietimas kūrimo pusėje: jis paima dažiklio pavadinimą, alternatyvią spalvų erdvę ir bet kokį objektą, kurį grąžina Register*Function šeima, o tada prijungia tą atspalvio transformaciją prie Separation spalvų erdvės ištekliaus, kurį likęs puslapis gali pasirinkti naudodamas scn/SCN
Kartu 0 tipo atrinkti tinkleliai ir šie trys formulėmis pagrįsti tipai apima kiekvieną PDF galimą deklaruoti /Function, o tinkamo tipo pasirinkimas daugiausia priklauso nuo to, ką jau turite: kitur apskaičiuota paieškos lentelė tampa 0 tipu, dviejų galinių taškų mišinys tampa 2 tipu, keli per domeną sujungti mišiniai tampa 3 tipu, o viskas, kam reikia tikros sąlyginės logikos, tampa 4 tipu. RegisterExponentialFunction, RegisterStitchingFunction ir RegisterPostScriptFunction yra standartinio HotPDF komponento, skirto Delphi ir C++Builder, dalis kartu su likusia ISO 32000-1 funkcijų ir šešėliavimo API