Tehnični članak

Funkcije PDF Type 2/3/4 v Delphiju: eksponentna, PostScript

HotPDF, izvorna komponenta VCL PDF za Delphi in C++Builder, vrednoti tri vrste funkcij PDF, zgrajene iz formul in ne iz vzorčnih mrež: eksponentno interpolacijo Type 2, sestavljanje Type 3 in kalkulatorske funkcije PostScript Type 4, ki ustrezajo standardu ISO 32000-1 §7.10.3, §7.10.4 in §7.10.5. Type 2 interpolira med dvema izhodnima vektorjema vzdolž krivulje, Type 3 poveže več podfunkcij v eni vhodni domeni, Type 4 pa zažene omejen program PostScript, ki lahko razveja, primerja in izračuna skoraj vse, kar tok vsebine potrebuje iz svojih vhodov. Če katero koli od teh treh izvedete nekoliko napačno, napaka nikoli ne bo sama sporočila, da gre za napako, ampak se pokaže kot gradient z mrtvim ravnim pasom, spotna barva, ki se izriše kot čista črna, ali kalkulatorska funkcija, ki je za natanko ena napačna pri vhodih, ki jih testni paket po naključju ni preveril

Te tri stojijo ob četrti, Type 0, ki namesto formule shranjuje vzorčeno mrežo in je obravnavana ločeno v spremljevalnem članku o barvnih iskalnih tabelah Type 0. Obe družini rešujeta isti problem, preslikavo vhoda v izhod, vendar je Type 0 podatek, izračunan enkrat in vdelan v datoteko, medtem ko so Type 2, 3 in 4 koda, ki jo bralnik vrednoti ob vsakem klicu. Vse štiri imajo v upodabljalniku HotPDF eno skupno odpremno točko, določeno z vnosom /FunctionType v slovarju funkcije, zato senčenje, tonska transformacija ali funkcija spotne barve pred zahtevo po barvi nikoli ne potrebuje vedeti, katero od štirih je prejela

Kako deluje eksponentna funkcija PDF Type 2

Funkcija PDF Type 2 izračuna eno formulo, y = C0 + x^N × (C1 − C0), uporabljeno komponento za komponento, pri čemer je x enotni vhod funkcije, normaliziran glede na /Domain, preden se formula izvede (ISO 32000-1 §7.10.3). /C0 in /C1 sta izhodna vektorja na obeh koncih tega razpona, po ena številka za vsako izhodno komponento, /N pa je eksponent, ki oblikuje krivuljo med njima: N = 1 da raven linearni prehod za večino gradientnih postankov in duotonskih pretvorb, N nad 1 povleče krivuljo proti C0, N med 0 in 1 pa jo potisne proti C1. RegisterExponentialFunction zgradi ta slovar iz petih argumentov in vrne funkcijski objekt, pripravljen za priključitev na senčenje, funkcijo spotne pike poltona ali kamor koli drugam, kjer specifikacija sprejema ključ /Function

Razmerje med številom komponent C0 in C1 je pomembno dvakrat: ko ustvarjate funkcijo Type 2 in vsakič, ko mora HotPDF izrisati funkcijo, ki je ni ustvaril. Na strani ustvarjanja RegisterExponentialFunction preveri C0 in C1 med seboj ter sproži izjemo, če se ne ujemata, zato ima klic, ki doseže BeginDoc, že notranje usklajen funkcijski objekt. Na strani upodabljanja pa mora vrednotenje zaupati poljubnim poljem /C0 in /C1, ki jih izvorna datoteka dejansko navede, na primer datoteki iz tiskarne, odprti za predogled, ali podpisanemu dokumentu, prikazanemu uporabniku, različice pred 2.376.0 pa so ta polja prebrale v medpomnilnik, velik za štiri komponente, torej za primer CMYK. Eksponentni odtenek DeviceGray ali DeviceRGB z eno- ali trielemen­tnim poljem /C0 in /C1 je to branje izvedel tiho neuspešno in obe polji pustil pri nič, zato je odtenek pobarval ravno črno namesto predvidene barve. Različica 2.376.0 je bralnik povečala na dejansko navedeno izhodno število funkcije namesto na nespremenljiv medpomnilnik, kar je natanko vrsta napake, ki jo razkrije le ne-CMYK testni primer, saj je obstoječi paket ves čas uporabljal CMYK, kjer se štiri v štiri vedno prilega

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;

Sestavljanje Type 3: povezovanje podfunkcij prek polja Bounds

Funkcija PDF Type 3 poveže k podfunkcij v eno kosovno preslikavo prek /Domain enega vhoda, pri čemer sta polji, ki to omogočata, /Bounds in /Encode (ISO 32000-1 §7.10.4). /Bounds vsebuje k − 1 notranjih razdelilnih točk, ki /Domain razrežejo na k zaporednih intervalov; vrednotenje izbere prvi interval, katerega zgornja meja je večja od vhoda, ali zadnji interval, ko vhod doseže končno mejo, nato pa izvajanje preda podfunkciji tega intervala. /Encode nato preslika vhod iz njegovega položaja znotraj tega intervala v poljubni vhodni razpon, ki ga pričakuje izbrana podfunkcija, običajno [0, 1], če je podfunkcija še en eksponentni odsek, nato pa se vrednotenje poglobi še za en klic v lastna /Domain in /Range te podfunkcije

HotPDF-jevo vrednotenje sestavljanja je nekoč obravnavalo samo natanko dve podfunkciji, bralnik /Bounds pa je zahteval celotno osemelementno polje, zato ene razdelilne točke, ki jo dejansko potrebuje gradient z dvema odsekoma, ni bilo mogoče razčleniti in funkcija ni vrnila ničesar. /Encode se sploh ni uporabil. Različica 2.376.0 je izbiro prepisala kot splošno iskanje k podfunkcij, opisano v specifikaciji, in začela brati /Bounds glede na njegovo dejansko prijavljeno dolžino, zato se gradient s tremi, štirimi ali petimi postanki, sestavljen iz enakega števila eksponentnih odsekov, zdaj razreši enako kot gradient z dvema odsekoma, za katerega se je to vedno pričakovalo. Spodnji primer zgradi dvodelni prehod črna prek rdeče do bele, obliko, po kateri poseže osno ali radialno senčenje, kadar ena eksponentna krivulja ne more nositi vseh barvnih postankov, ki jih zahteva zasnova

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;

Kaj lahko kalkulatorska funkcija PostScript Type 4 naredi, česar Type 2 in 3 ne moreta

Funkcija PDF Type 4 zažene pravi, čeprav namenoma omejen program: kalkulator PostScript, ki svoje vhode potisne na operandni sklad, izvede aritmetične, primerjalne, skladovne in logične operatorje ter pogoje if/ifelse, ob koncu pa pusti izhode na skladu (ISO 32000-1 §7.10.5, tabela 42). Zanke ni in ni shranjevanja poimenovanih spremenljivk, samo sklad, zaradi česar je skladen program enostaven za razumevanje, vendar lahko Type 4 znotraj tega omejenega nabora operatorjev izrazi stvari, ki jih Type 2 in Type 3 ne moreta, na primer pravo formulo mešanja več črnil za ločitev DeviceN ali funkcijo spotne pike poltona s pogojnim pragom. HotPDF-jevo vrednotenje, HPDFEvalPostScriptCalculator, program tokenizira enkrat, torej števila, operatorje in proceduralne bloke { }, nato pa ga izvaja na operandnem skladu s 100 vnosi, kot zahteva ISO 32000-1 §7.10.5, za zaščito pred patološkimi ali ročno napisanimi programi pa uporablja trdo omejitev 50.000 izvedenih operatorjev

Operator roll: smer je preprosto obrniti

roll je operator, pri katerem se začetnik najverjetneje zmoti, saj sta vrstni red argumentov in smer vrtenja nasprotna temu, kako ju opisuje angleščina. n j roll odstrani število n in količino vrtenja j, nato pa ciklično premakne zgornjih n vnosov sklada za j mest ter elemente, ki padejo čez en konec, ovije nazaj na drugega; kanonični primer neposredno iz specifikacije je a b c 3 1 roll, ki ustvari c a b, pri čemer se zgornji element premakne na dno skupine in ne v nasprotno smer, vsak drug element pa se za eno mesto pomakne navzgor, da naredi prostor. HotPDF-jevo vrednotenje izračuna nov položaj vnosa sklada i kot (i + j) mod n, kar se natanko ujema s tem primerom, vendar gre za zanko v dveh vrsticah, ki jo je prav tako lahko zapisati z obrnjeno rotacijo, zrcalni roll pa še vedno ustvari barvo, ki je videti verjetna, le da ni barva, ki jo je zahteval avtor datoteke

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 ni Delphi Round: zaokroževanje half-up proti bančnemu zaokroževanju

Operator PostScript round vsakič razreši izenačenje .5 proti večjemu celemu številu, vgrajena Delphi funkcija Round pa ne: zaokrožuje na sodo, po konvenciji bančnega zaokroževanja, ki izmenjuje smer izenačenja .5, da se pri ponavljajočem zaokroževanju ne kopiči pristranskost. Ujemata se skoraj povsod in razlikujeta natanko na meji, ki je tukaj pomembna, saj Delphi Round(0.5) vrne 0 in Round(2.5) vrne 2, medtem ko specifikacija PDF round za ista vhoda zahteva 1 in 3, zato se neusklajenost pri površnem testiranju skrije, nato pa se ponovi kot dosledna napaka za ena povsod, kjer vmesna matematika kalkulatorskega programa pristane natanko na polovici celega števila. ISO 32000-1 §7.10.5 tabela 42 izrecno določa, da round potisne ulomek .5 proti večjemu celemu številu, zato HotPDF operator izvede kot Floor(x + 0.5) namesto klica Delphi Round, enako zamenjavo pa potrebuje vsaka koda, ki ročno ponovno izvaja ali preverja aritmetiko programa 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;

Preverjanje ob registraciji zgodaj ujame slab kalkulatorski program

Okvarjen program Type 4 je poceni ujeti ob ustvarjanju in drag za odkrivanje kjer koli drugje, zato RegisterPostScriptFunction ne shrani samo izvornega besedila, ampak program enkrat poskusno ovrednoti na sredini navedene /Domain, preden se funkcijski objekt sploh zapiše v dokument. Neuravnoteženi bloki { }, neprepoznan operator, spodtok sklada ali izhodno število, ki se ne ujema z /Range, vsi povzročijo neuspeh poskusnega izvajanja in takoj sprožijo izjemo, pri čemer klicni sklad kaže na klic RegisterPostScriptFunction in ne na artefakt upodabljanja, odkrit med zagotavljanjem kakovosti datoteke, ki je že izšla. Poskus na sredini ne dokazuje, da je program pravilen v celotni /Domain, saj lahko pogojna veja, ki se napačno obnaša samo blizu roba vhodnega razpona, uide eni sami vzorčni točki, vendar zapre celoten razred strukturno okvarjenih programov in ne le programov, ki so napačni v enem kotu

Kje gradienti in spotne barve uporabljajo te funkcije

Type 2, 3 in 4 se v resničnem PDF redko pojavijo ločeno, ampak tam, kjer specifikacija sprejema ključ /Function, najpogostejša uporabnika pa sta senčenja in tonske transformacije spotnih barv. Operator sh za osni ali radialni gradient (ISO 32000-1 §8.7.4.5) ovrednoti svoj /Function enkrat za vsak položaj vzdolž gradientne osi, kar je natanko primer z več postanki, za katerega obstaja sestavljanje Type 3. Tonska transformacija barvnega prostora Separation ali DeviceN je drugi pogosti dom teh treh vrst in tam Type 4 pokaže svojo vrednost: eno spotno črnilo se običajno zmanjša na krivuljo Type 2 ali Type 0, mešanica DeviceN več črnil z dejanskim lovljenjem in vedenjem pretiska pa pogosto potrebuje pogojno logiko, ki jo lahko izrazi samo kalkulator PostScript, kot je obravnavano v članku o upodabljanju spotnih barv Separation in DeviceN. RegisterSeparationFunc je ustrezni klic na strani ustvarjanja: sprejme ime barvila, nadomestni barvni prostor in kateri koli objekt, ki ga vrne družina funkcij Register*Function, nato pa to tonsko transformacijo poveže z virom barvnega prostora Separation, ki ga lahko preostanek strani izbere z scn/SCN

Skupaj vzorčene mreže Type 0 in te tri vrste, ki jih poganjajo formule, pokrivajo vsako /Function, ki jo lahko deklarira PDF, izbira prave pa je večinoma odvisna od tega, kaj že imate: iskalna tabela, izračunana drugje, postane Type 0, mešanica dveh končnih točk postane Type 2, več mešanic, povezanih prek domene, postane Type 3, vse z resnično pogojno logiko pa Type 4. RegisterExponentialFunction, RegisterStitchingFunction in RegisterPostScriptFunction so del standardne komponente HotPDF za Delphi in C++Builder, skupaj s preostalim API-jem funkcij in senčenja ISO 32000-1