Tekninen artikkeli

PDF Type 2/3/4 -funktiot Delphissä: eksponentiaalinen, PostScript

HotPDF, natiivi VCL-PDF-komponentti Delphille ja C++Builderille, arvioi kolme PDF-funktiotyyppiä, jotka on rakennettu kaavoista näytehilan sijaan: Type 2 -eksponentiaalinen interpolointi, Type 3 -yhdistely (stitching) ja Type 4 -PostScript-laskinfunktiot, vastaten ISO 32000-1 §7.10.3, §7.10.4 ja §7.10.5. Type 2 sekoittaa kahden tulostevektorin väliltä käyrää pitkin, Type 3 ketjuttaa useita alifunktioita yhden syötteen alueella, ja Type 4 ajaa rajoitetun PostScript-ohjelman, joka voi haarautua, vertailla ja laskea lähes mitä tahansa, mitä sisältövirta tarvitsee syötteistään. Toteuta mikä tahansa näistä kolmesta hieman väärin, ja virhe ei koskaan ilmoita itseään bugina — se näkyy liukuvärjäyksenä, jossa on täysin tasainen kaista, spottivärinä, joka renderöityy puhtaan mustana, tai laskinfunktiona, joka on täsmälleen yhden verran väärässä syötteillä, joita testisarja ei sattunut kokeilemaan

Nämä kolme istuvat neljännen, Type 0:n, vieressä, joka tallentaa näytehilan kaavan sijaan ja käsitellään erikseen artikkelissa rinnakkaisartikkeli Type 0 -värihakutauluista. Nämä kaksi perhettä ratkaisevat saman ongelman, syötteen kuvaamisen tulosteeksi, mutta Type 0 on dataa, joka lasketaan kerran ja leivotaan tiedostoon, kun taas Type 2, 3 ja 4 ovat koodia, jonka lukija arvioi jokaisella kutsulla. Kaikilla neljällä on yksi yhteinen jakelupiste HotPDF:n renderöijässä, avaimena funktiosanakirjan /FunctionType-kenttä, joten varjostuksen, sävynmuunnoksen tai puolisävytepistefunktion ei koskaan tarvitse tietää, minkä näistä neljästä se sai, ennen kuin se voi pyytää väriä

Miten PDF Type 2 -eksponentiaalinen funktio toimii?

PDF Type 2 -funktio laskee yhden kaavan — y = C0 + x^N × (C1 − C0), sovellettuna komponentti kerrallaan — jossa x on funktion ainoa syöte, normalisoituna sen /Domain-arvoa vasten ennen kaavan ajamista (ISO 32000-1 §7.10.3). /C0 ja /C1 ovat tulostevektorit tuon alueen kummassakin päässä, yksi luku per tulostekomponentti, ja /N on eksponentti, joka muotoilee käyrän niiden välillä: N = 1 antaa suoran lineaarisen rampin useimpien liukuvärjäyspysäkkien ja duplex-muunnosten takana, N yli 1:n vetää käyrän kohti C0:aa, ja N välillä 0–1 työntää sitä kohti C1:tä. RegisterExponentialFunction rakentaa tuon sanakirjan viidestä argumentista ja antaa takaisin funktio-olion, joka on valmis liitettäväksi varjostukseen, puolisävytepistefunktioon tai mihin tahansa muualle, missä spesifikaatio hyväksyy /Function-avaimen

Komponenttimäärän suhde C0:n ja C1:n välillä on merkityksellinen kahdesti: kerran kun laadit Type 2 -funktion, ja uudelleen aina kun HotPDF:n on renderöitävä sellainen, jota se ei itse luonut. Laatimispuolella RegisterExponentialFunction tarkistaa C0:n ja C1:n toisiaan vasten ja nostaa poikkeuksen, jos ne ovat ristiriidassa, joten kutsu, joka saapuu BeginDoc:iin, on jo itsessään johdonmukainen funktio-olio. Renderöintipuolella evaluaattorin on kuitenkin luotettava mihin tahansa /C0- ja /C1-taulukoihin, joita lähdetiedosto todella ilmoittaa — esimerkiksi esikatselua varten avattu painotalon tiedosto tai käyttäjälle takaisin näytetty allekirjoitettu asiakirja — ja versiot ennen 2.376.0:aa lukivat nuo taulukot puskuriin, joka oli mitoitettu neljälle komponentille, CMYK-tapaukselle. DeviceGray- tai DeviceRGB-eksponentiaalinen sävy, jonka /C0 ja /C1 olivat yhden tai kolmen alkion taulukoita, epäonnistui tuossa luvussa hiljaisesti ja jätti molemmat taulukot nollaan, joten sävy maalautui tasaisen mustana aiotun värin sijaan. Versio 2.376.0 muutti lukijan kokoa vastaamaan funktion todella ilmoitettua tulostemäärää kiinteän puskurin sijaan — juuri sellainen bugi, jonka paljastaa vain muu kuin CMYK-testitapaus, koska olemassa oleva testisarja ajoi CMYK:ta kauttaaltaan, jossa neljä-neljään-sopi aina

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;

Type 3 -yhdistely: alifunktioiden ketjuttaminen Bounds-taulukon yli

PDF Type 3 -funktio yhdistää k alifunktiota yhdeksi paloittain määritellyksi kuvaukseksi yhden syötteen /Domain-alueella, ja kaksi taulukkoa, jotka saavat tämän toimimaan, ovat /Bounds ja /Encode (ISO 32000-1 §7.10.4). /Bounds sisältää k − 1 sisäistä jakopistettä, jotka veistävät /Domain-alueen k peräkkäiseen väliin; evaluaattori valitsee ensimmäisen välin, jonka yläraja ylittää syötteen, tai viimeisen välin, kun syöte saavuttaa viimeisen rajan, ja siirtää käsittelyn kyseisen välin alifunktiolle. /Encode kartoittaa sitten syötteen uudelleen sen sijainnista tuon välin sisällä mille tahansa syötealueelle, jota valittu alifunktio itse odottaa — tyypillisesti [0, 1], jos alifunktio on vielä yksi eksponentiaalinen segmentti — ennen kuin arviointi jatkuu, yhden kutsun syvemmälle, tuon alifunktion omaan /Domain- ja /Range-arvoon

HotPDF:n yhdistelyevaluaattori käsitteli aiemmin vain täsmälleen kahta alifunktiota, ja sen /Bounds-lukija vaati täyden kahdeksan alkion taulukon, joten se yksi jakopiste, jonka kaksisegmenttinen liukuvärjäys todella tarvitsee — yksi luku /Bounds-taulukossa — epäonnistui jäsentymään aina, ja funktio ei palauttanut mitään. /Encode-arvoa ei sovellettu lainkaan. Versio 2.376.0 kirjoitti valinnan uudelleen yleiseksi k-alifunktiohauksi, jonka spesifikaatio kuvaa, ja alkoi lukea /Bounds-arvoa sen todellista ilmoitettua pituutta vasten, joten kolme-, neljä- tai viisipysäkkinen liukuvärjäys, joka on yhdistetty vastaavasta määrästä eksponentiaalisia segmenttejä, ratkeaa nyt samalla tavalla kuin kaksisegmenttinen aina väitti ratkeavansa. Alla oleva esimerkki rakentaa kaksisegmenttisen musta-punainen-valkoinen-rampin, muodon, jota aksiaalinen tai säteittäinen varjostus tavoittelee aina, kun yksi eksponentiaalinen käyrä ei riitä kantamaan jokaista värikohtaa, jota suunnittelu vaatii

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;

Mitä Type 4 -PostScript-laskinfunktio pystyy tekemään, mihin Type 2 ja 3 eivät pysty?

PDF Type 4 -funktio ajaa aidon, joskin tarkoituksella rajoitetun, ohjelman: PostScript-laskimen, joka työntää syötteensä operandipinoon, suorittaa aritmeettisia, vertailu-, pinonkäsittely- ja boolen-operaattoreita sekä if/ifelse-ehtolauseita, ja jättää tulosteensa pinoon lopetettuaan (ISO 32000-1 §7.10.5, taulukko 42). Silmukkarakennetta tai nimettyä muuttujavarastointia ei ole, vain pino, mikä pitää standardinmukaisen ohjelman helppona hahmottaa — mutta tuon rajoitetun operaattorijoukon sisällä Type 4 voi ilmaista asioita, joihin Type 2 ja Type 3 eivät pysty, kuten todellisen moni-mustesekoituskaavan DeviceN-erotukselle tai puolisävytepistefunktion ehdollisella kynnysarvolla. HotPDF:n evaluaattori, HPDFEvalPostScriptCalculator, jäsentää ohjelman kerran tokeneiksi — luvut, operaattorit ja { }-menettelylohkot — ja käy sitten läpi 100 merkinnän operandipinon, syvyyden, jota ISO 32000-1 §7.10.5 vaatii, kovan 50 000 arvioidun operaattorin katon takana puolustavana varmistuksena patologisia tai käsin kirjoitettuja ohjelmia vastaan

Roll-operaattori: suunta on helppo saada väärin päin

roll on operaattori, joka todennäköisimmin tulee väärinpäin ensimmäisellä yrityksellä, koska sekä sen argumenttijärjestys että sen kiertosuunta kulkevat vastakkain siihen, miten suomi tai englanti kuvailisi niitä. n j roll poimii lukumäärän n ja kiertomäärän j, ja siirtää sitten pinon n ylintä merkintää syklisesti j paikkaa, kierrättäen toisesta päästä pois putoavat alkiot takaisin toiseen päähän; kanoninen esimerkki, suoraan spesifikaatiosta, on a b c 3 1 roll, joka tuottaa c a b — ylin alkio siirtyy ryhmän pohjalle, ei toisin päin, ja jokainen muu alkio siirtyy yhdellä ylöspäin tehdäkseen tilaa. HotPDF:n evaluaattori laskee pinon merkinnän i uuden paikan kaavalla (i + j) mod n, mikä täsmää tuon esimerkin kanssa tarkasti, mutta se on kaksirivinen silmukka, joka on yhtä helppo kirjoittaa kierto käänteisenä, ja peilattu roll tuottaa silti uskottavan näköisen värin — se ei vain ole se väri, jota tiedoston tekijä pyysi

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 ei ole Delphin Round: puoli-ylös vastaan pankkiirin pyöristys

PostScriptin round-operaattori ratkaisee .5-tasapelin aina suurempaan kokonaislukuun päin, eikä Delphin sisäänrakennettu Round-funktio tee niin: se pyöristää puolesta parilliseen (half-to-even), pankkiirin pyöristyskäytännön, joka vuorottelee, kumpaan suuntaan .5-tasapeli kaatuu, jotta toistuva pyöristys ei kerää harhaa. Nämä kaksi ovat samaa mieltä lähes kaikkialla ja eri mieltä täsmälleen sillä rajalla, joka tässä on merkityksellinen — Delphin Round(0.5) palauttaa 0:n ja Round(2.5) palauttaa 2:n, kun taas PDF-spesifikaation round haluaa 1:n ja 3:n samoille syötteille — joten eroavaisuus piiloutuu satunnaisen testauksen läpi ja toistuu sitten johdonmukaisena yhden verran väärin -virheenä aina, kun laskinohjelman välilaskenta osuu tarkalleen puolikokonaisluvulle. ISO 32000-1 §7.10.5 taulukko 42 on suora siitä, että round työntää murtoluvun .5 kohti suurempaa kokonaislukua, joten HotPDF toteuttaa operaattorin muodossa Floor(x + 0.5) Delphin Round-kutsun sijaan, ja mikä tahansa koodi, joka toteuttaa uudelleen tai pistetarkistaa Type 4 -ohjelman aritmetiikkaa käsin, tarvitsee saman korvauksen

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;

Rekisteröintiaikainen validointi nappaa huonon laskinohjelman aikaisin

Virheellinen Type 4 -ohjelma on halpa napata laadinta-aikaan ja kallis napata missä tahansa muualla, joten RegisterPostScriptFunction ei vain tallenna lähdetekstiä: se koe-arvioi ohjelman kerran, ilmoitetun /Domain-alueen keskipisteessä, ennen kuin funktio-olio kirjoitetaan koskaan asiakirjaan. Epätasapainoiset { }-lohkot, tunnistamaton operaattori, pinon alivuoto tai tulostemäärä, joka ei täsmää /Range-arvon kanssa, epäonnistuvat kaikki tuossa koeajossa ja nostavat poikkeuksen välittömästi, kutsupinon osoittaessa RegisterPostScriptFunction-kutsuun sen sijaan, että se osoittaisi renderöintiartefaktiin, joka löydettiin QA:ssa jo julkaistusta tiedostosta. Keskipisteen koeajo ei todista, että ohjelma on oikein koko /Domain-alueellaan — ehtohaara, joka toimii väärin vain lähellä syötealueen yhtä reunaa, voi silti livahtaa yhden näytepisteen ohi — mutta se sulkee pois koko luokan ohjelmia, jotka ovat rakenteellisesti rikki eivätkä vain väärässä yhdessä nurkassa

Missä liukuvärjäykset ja spottivärit käyttävät näitä funktioita

Type 2, 3 ja 4 esiintyvät harvoin erillään todellisessa PDF:ssä; ne ilmestyvät kaikkialla, missä spesifikaatio hyväksyy /Function-avaimen, ja kaksi yleisintä kuluttajaa ovat varjostukset ja spottivärien sävynmuunnokset. Aksiaalisen tai säteittäisen liukuvärjäyksen sh-operaattori (ISO 32000-1 §8.7.4.5) arvioi /Function-arvonsa kerran per sijainti liukuvärjäysakselilla, mikä on juuri se moniaskelinen tapaus, jota varten Type 3 -yhdistely on olemassa. Separation- tai DeviceN-väriavaruuden sävynmuunnos on toinen yleinen koti näille kolmelle tyypille, ja siinä Type 4 ansaitsee paikkansa: yksi spottimuste redusoituu yleensä Type 2- tai Type 0-käyräksi, mutta useiden musteiden DeviceN-sekoitus todellisella loukku- (trapping) ja ylipainokäytöksellä tarvitsee usein ehdollista logiikkaa, jota vain PostScript-laskin voi ilmaista, tapaus, joka käsitellään artikkelissa Separation- ja DeviceN-spottivärien renderöinti. RegisterSeparationFunc on laadintapuolen pariutuva kutsu: se ottaa väriaineen nimen, vaihtoehtoisen väriavaruuden ja minkä tahansa olion, jonka Register*Function-perhe palauttaa, ja liittää tuon sävynmuunnoksen Separation-väriavaruusresurssiin, jonka sivun muu sisältö voi valita scn/SCN-operaattorilla

Yhdessä Type 0:n näytehilat ja nämä kolme kaavapohjaista tyyppiä kattavat jokaisen /Function-arvon, jonka PDF voi ilmoittaa, ja oikean valitseminen on lähinnä kysymys siitä, mitä sinulla jo on: muualla laskettu hakutaulukko muuttuu Type 0:ksi, kahden päätepisteen välinen sekoitus muuttuu Type 2:ksi, useita sekoituksia ketjutettuna yhden alueen yli muuttuu Type 3:ksi, ja mikä tahansa aidolla ehtologiikalla muuttuu Type 4:ksi. RegisterExponentialFunction, RegisterStitchingFunction ja RegisterPostScriptFunction ovat osa vakiomuotoista HotPDF-komponenttia Delphille ja C++Builderille, yhdessä sen muun ISO 32000-1 -funktio- ja varjostus-API:n kanssa