Műszaki cikk

PDF Type 2/3/4 függvények Delphiben: exponenciális, PostScript

A HotPDF, a Delphihez és C++Builderhez készült natív VCL PDF-komponens, kiértékeli a három olyan PDF-függvénytípust, amelyek képletekből épülnek fel, nem pedig mintarácsokból: a Type 2 exponenciális interpolációt, a Type 3 összefűzést (stitching), és a Type 4 PostScript kalkulátor-függvényeket, amelyek megfelelnek az ISO 32000-1 §7.10.3, §7.10.4, és §7.10.5 szakaszainak. A Type 2 két kimeneti vektor között kever egy görbe mentén, a Type 3 több alfüggvényt láncol össze egyetlen bemeneti tartományon, a Type 4 pedig egy korlátozott PostScript-programot futtat, amely elágazhat, összehasonlíthat, és szinte bármit kiszámíthat, amire egy tartalomfolyamnak szüksége van a bemeneteiből. Ha a háromból bármelyiket kicsit elrontjuk, a hiba soha nem jelenti be magát hibaként — egy holt lapos sávval rendelkező színátmenetként jelenik meg, egy direktszínként, amely tiszta feketén renderel, vagy egy kalkulátor-függvényként, amely pontosan eggyel eltér azokon a bemeneteken, amelyeket egy tesztkészlet véletlenül nem próbált ki

Ez a három egy negyedik, a Type 0 mellett áll, amely egy mintázott rácsot tárol képlet helyett, és külön tárgyalja a Type 0 szín-keresőtáblákról szóló kísérőcikk. A két család ugyanazt a problémát oldja meg, egy bemenet kimenetre képezését, de a Type 0 egyszer kiszámított és a fájlba sütött adat, míg a Type 2, 3, és 4 kód, amelyet az olvasó minden hívásnál kiértékel. Mind a négy egyetlen diszpécselő ponton osztozik a HotPDF renderelőjében, a függvényszótár /FunctionType bejegyzése alapján kulcsolva, így egy árnyalásnak, egy tónusátalakításnak vagy egy féltónus spotfüggvénynek soha nem kell tudnia, melyiket kapta a négy közül, mielőtt színt kérhetne

Hogyan működik egy PDF Type 2 exponenciális függvény?

Egy PDF Type 2 függvény egyetlen képletet számít ki — y = C0 + x^N × (C1 − C0), komponensenként alkalmazva —, ahol x a függvény egyetlen bemenete, normalizálva a saját /Domain-je szerint, mielőtt a képlet lefut (ISO 32000-1 §7.10.3). A /C0 és /C1 a kimeneti vektorok a tartomány két végén, egy szám kimeneti komponensenként, és az /N a kitevő, amely alakítja a köztük lévő görbét: N = 1 adja a legtöbb színátmenet-megálló és duotón-átalakítás mögötti egyenes lineáris rámpát, N 1 fölött a görbét C0 felé húzza, és N 0 és 1 között C1 felé tolja. A RegisterExponentialFunction öt argumentumból építi fel ezt a szótárat, és egy függvényobjektumot ad vissza, amely kész arra, hogy beépüljön egy árnyalásba, egy féltónus spotfüggvénybe, vagy bárhová máshova, ahol a specifikáció elfogad egy /Function kulcsot

A C0 és C1 közötti komponensszám-kapcsolat kétszer számít: egyszer, amikor egy Type 2 függvényt írunk, és egyszer, amikor a HotPDF-nek olyat kell renderelnie, amelyet nem ő hozott létre. A szerzői oldalon a RegisterExponentialFunction ellenőrzi a C0-t és C1-et egymás ellen, és kivételt dob, ha nem egyeznek, így egy hívás, amely eléri a BeginDoc-ot, már egy önmagával konzisztens függvényobjektumot ad. A renderelési oldalon azonban a kiértékelőnek meg kell bíznia bármilyen /C0 és /C1 tömbben, amelyet egy forrásfájl ténylegesen deklarál — mondjuk egy nyomdai fájl, amelyet előnézetre nyitottak meg, vagy egy aláírt dokumentum, amelyet visszamutatnak egy felhasználónak —, és a 2.376.0 előtti verziók ezeket a tömböket egy négy komponensre méretezett pufferbe olvasták be, a CMYK-eset feltételezve. Egy DeviceGray vagy DeviceRGB exponenciális tónus, egy- vagy háromelemű /C0 és /C1-gyel, csendben elbukott ezen az olvasáson, és mindkét tömböt nullán hagyta, így a tónus lapos feketét festett a szándékolt szín helyett. A 2.376.0-s verzió a olvasót a függvény tényleges deklarált kimeneti számára méretezte át egy fix puffer helyett — pontosan az a fajta hiba, amelyet csak egy nem-CMYK teszteset lepleztet le, mivel a meglévő tesztkészlet mindvégig CMYK-ban futott, ahol a négy-négybe mindig beleillett

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 összefűzés: alfüggvények láncolása egy Bounds tömbön keresztül

Egy PDF Type 3 függvény k alfüggvényt fűz össze egyetlen darabonkénti leképezéssé egy egyetlen bemenet /Domain-je fölött, és a két tömb, amely ezt lehetővé teszi, a /Bounds és az /Encode (ISO 32000-1 §7.10.4). A /Bounds k − 1 belső osztópontot tartalmaz, amelyek k egymást követő intervallumra vágják a /Domain-t; a kiértékelő az első olyan intervallumot választja, amelynek felső határa meghaladja a bemenetet, vagy az utolsó intervallumot, amint a bemenet eléri a végső határt, és átadja azt annak az intervallumnak az alfüggvényéhez. Az /Encode ezután újraképezi a bemenetet az intervallumon belüli pozíciójából bármilyen bemeneti tartományra, amit a kiválasztott alfüggvény maga vár — jellemzően [0, 1], ha az alfüggvény egy további exponenciális szegmens — mielőtt a kiértékelés folytatódna, egy hívással mélyebben, annak az alfüggvénynek a saját /Domain-jébe és /Range-ébe

A HotPDF összefűző kiértékelője korábban csak pontosan két alfüggvényt kezelt, és a /Bounds olvasója egy teljes nyolcelemű tömböt igényelt, így az az egyetlen osztópont, amelyre egy kétszegmenses színátmenetnek ténylegesen szüksége van — egyetlen szám a /Bounds-ban — mindig elbukott a feldolgozáson, és a függvény semmit nem adott vissza. Az /Encode egyáltalán nem került alkalmazásra. A 2.376.0-s verzió átírta a kiválasztást a specifikáció által leírt általános k-alfüggvényes keresésre, és elkezdte a /Bounds-ot a valós deklarált hosszúsága ellen olvasni, így egy három-, négy- vagy ötmegállós színátmenet, amelyet ennyi exponenciális szegmensből fűztek össze, most ugyanúgy oldódik fel, ahogyan egy kétszegmenses mindig is állította magáról. Az alábbi példa egy kétszegmenses fekete-piros-fehér rámpát épít, azt az alakzatot, amit egy axiális vagy sugárirányú árnyalás vesz elő, valahányszor egyetlen exponenciális görbe nem tud minden színmegállót elvinni, amit egy dizájn kér

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 tud egy Type 4 PostScript kalkulátor-függvény, amit a Type 2 és 3 nem?

Egy PDF Type 4 függvény egy valódi, bár szándékosan korlátozott, programot futtat: egy PostScript kalkulátort, amely felteszi bemeneteit egy operandusveremre, végrehajt aritmetikai, összehasonlító, verem-manipulációs és logikai operátorokat, plusz if/ifelse feltételeket, és a veremen hagyja kimeneteit, amikor befejezi (ISO 32000-1 §7.10.5, 42. táblázat). Nincs ciklus-konstrukció és nincs elnevezett-változó tárolás, csak a verem, ami könnyűvé teszi egy szabványkövető program végiggondolását — de ezen a korlátozott operátorkészleten belül a Type 4 olyasmit tud kifejezni, amit a Type 2 és Type 3 nem, mint egy valódi többfestékes keverési képlet egy DeviceN szétválasztáshoz, vagy egy féltónus spotfüggvény feltételes küszöbbel. A HotPDF kiértékelője, a HPDFEvalPostScriptCalculator, egyszer tokenizálja a programot — számok, operátorok, és { } eljárásblokkok —, majd bejár egy 100 bejegyzésből álló operandusvermet, azt a mélységet, amit az ISO 32000-1 §7.10.5 kér, egy 50 000 kiértékelt operátoros kemény plafon mögött, védekező hátvédként a patologikus vagy kézzel írt programok ellen

A roll operátor: az irányt könnyű visszafelé érteni

A roll az az operátor, amelyet a legvalószínűbb, hogy elsőre visszafelé valósítunk meg, mert az argumentumsorrendje és a forgatási iránya egyaránt ellentétes azzal, ahogyan az angol leírja őket. Az n j roll lehúz egy n számlálót és egy j forgatási mennyiséget, majd ciklikusan eltolja a felső n verembejegyzést j pozícióval, becsomagolva az egyik végén leeső elemeket vissza a másikra; a kanonikus példa, egyenesen a specifikációból, az a b c 3 1 roll, amely c a b-t eredményez — a felső elem a csoport aljára kerül, nem fordítva, és minden más elem eggyel feljebb tolódik, hogy helyet csináljon. A HotPDF kiértékelője az i verembejegyzés új pozícióját (i + j) mod n-ként számítja, ami pontosan illeszkedik erre a példára, de ez egy kétsoros ciklus, amit ugyanolyan könnyű megfordított forgatással megírni, és egy tükrözött roll még mindig hihetőnek tűnő színt eredményez — csak nem azt, amit a fájl szerzője kért

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;

A round nem a Delphi Round-ja: felfelé-kerekítés kontra bankári kerekítés

A PostScript round operátora egy .5-ös döntetlent minden alkalommal a nagyobb egész felé old fel, a Delphi beépített Round függvénye pedig nem: felezik-a-párosra kerekít, a bankári-kerekítési konvenció, amely váltogatja, melyik irányba esik egy .5-ös döntetlen, hogy az ismételt kerekítés ne halmozzon fel torzítást. A kettő szinte mindenhol egyetért, és pontosan azon a határon nem, ami itt számít — a Delphi Round(0.5)-je 0-t ad vissza, és a Round(2.5) 2-t, míg a PDF-specifikáció round-ja 1-et és 3-at akar ugyanezekre a bemenetekre —, így az eltérés elrejtőzik a hétköznapi tesztelés során, majd egy konzisztens eggyel-eltérésként reprodukálódik, bárhol, ahol egy kalkulátor-program köztes aritmetikája pontosan egy fél-egészre esik. Az ISO 32000-1 §7.10.5 42. táblázata egyértelmű abban, hogy a round egy törtrészű .5-öt a nagyobb egész felé told, így a HotPDF az operátort Floor(x + 0.5)-ként valósítja meg a Delphi Round-jának hívása helyett, és bármely kód, amely kézzel újravalósítja vagy pontellenőrzi egy Type 4 program aritmetikáját, ugyanezt a helyettesítést igényli

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;

A regisztrációkori érvényesítés korán elkapja a rossz kalkulátor-programot

Egy hibás formátumú Type 4 program olcsó elkapni szerzői időben, és drága elkapni bárhol máshol, így a RegisterPostScriptFunction nem csak eltárolja a forrásszöveget: egyszer próba-kiértékeli a programot, a deklarált /Domain középpontjában, mielőtt a függvényobjektumot valaha is beírnák a dokumentumba. A kiegyensúlyozatlan { } blokkok, egy felismerhetetlen operátor, egy verem-alulcsordulás, vagy egy kimeneti szám, amely nem egyezik a /Range-dzsel, mind elbuknak ezen a próbafutáson, és azonnal kivételt dobnak, a hívási verem a RegisterPostScriptFunction hívásra mutatva, nem pedig egy renderelési műtermékre, amelyet egy már kiszállított fájlon végzett minőségbiztosítás közben fedeznek fel. A középponti próba nem bizonyítja, hogy a program helyes a teljes /Domain-jén — egy feltételes elágazás, amely csak a bemeneti tartomány egyik szélénél viselkedik rosszul, még átcsúszhat egyetlen mintaponton —, de kizárja azoknak a programoknak a teljes osztályát, amelyek strukturálisan törtek, nem csupán egy sarokban rosszak

Hol vetik be ezeket a függvényeket a színátmenetek és direktszínek

A Type 2, 3, és 4 ritkán jelenik meg elszigetelten egy valódi PDF-ben; ott bukkannak fel, ahol a specifikáció elfogad egy /Function kulcsot, és a két leggyakoribb fogyasztó az árnyalások és a direktszín-tónusátalakítások. Egy axiális vagy sugárirányú színátmenet sh operátora (ISO 32000-1 §8.7.4.5) egyszer értékeli ki a /Function-jét minden pozícióra a színátmenet-tengely mentén, ami pontosan az a többmegállós eset, amiért a Type 3 összefűzés létezik. Egy Separation vagy DeviceN színtér tónusátalakítása a másik gyakori otthona ennek a háromnak, és itt szolgálja meg a Type 4 a maga hasznát: egyetlen spotfesték általában egy Type 2 vagy Type 0 görbére redukálódik, de több festék DeviceN-keveréke valós csapdázási (trapping) és túlnyomási (overprint) viselkedéssel gyakran igényli azt a feltételes logikát, amit csak egy PostScript kalkulátor tud kifejezni, ez az az eset, amelyet a Separation és DeviceN direktszínek renderelésről szóló cikk tárgyal. A RegisterSeparationFunc a párosító hívás a szerzői oldalon: átvesz egy festéknevet, egy alternatív színteret, és bármilyen objektumot, amit a Register*Function család visszaad, és bekapcsolja azt a tónusátalakítást egy Separation színtér-erőforrásba, amelyet az oldal többi része az scn/SCN-nel választhat ki

A Type 0 mintázott rácsai és ez a három, képlet-vezérelt típus együtt lefedi minden /Function-t, amelyet egy PDF deklarálhat, és a helyes kiválasztása leginkább azon múlik, mi van már meg: egy máshol kiszámított keresőtábla Type 0-vá válik, egy kétvégpontos keverés Type 2-vé, több keverés egy tartományon átfűzve Type 3-má, és bármi valódi feltételes logikával Type 4-gyé. A RegisterExponentialFunction, RegisterStitchingFunction, és RegisterPostScriptFunction a Delphihez és C++Builderhez készült szabványos HotPDF komponens részei, ISO 32000-1 függvény- és árnyalás-API-jának többi részével együtt