HotPDF, den native VCL-PDF-komponent til Delphi og C++Builder, evaluerer de tre PDF-funktionstyper bygget af formler frem for sample-grids: Type 2 eksponentiel interpolation, Type 3 stitching og Type 4 PostScript-beregnerfunktioner, svarende til ISO 32000-1 §7.10.3, §7.10.4 og §7.10.5. Type 2 blander mellem to output-vektorer langs en kurve, Type 3 kæder flere underfunktioner sammen over ét inputdomæne, og Type 4 kører et begrænset PostScript-program, der kan forgrene, sammenligne og beregne næsten alt, en content stream har brug for fra sine inputs. Får man en af de tre en anelse forkert, annoncerer fejlen sig aldrig som en bug — den viser sig som en gradient med et dødt fladt bånd, en spotfarve der gengives ren sort, eller en beregnerfunktion der er præcis én forkert ved de inputs, en testsuite tilfældigvis ikke prøvede
Disse tre sidder ved siden af en fjerde, Type 0, som gemmer et samplet grid i stedet for en formel og dækkes separat i følgeartiklen om Type 0-farveopslagstabeller. De to familier løser det samme problem, at mappe et input til et output, men Type 0 er data beregnet én gang og bagt ind i filen, hvor Type 2, 3 og 4 er kode, læseren evaluerer ved hvert kald. Alle fire deler ét dispatch-punkt i HotPDFs renderer, nøglet på funktionsordbogens /FunctionType-post, så en shading, en tint-transform eller en halvtone-spotfunktion aldrig behøver vide, hvilken af de fire den modtog, før den kan bede om en farve
Hvordan fungerer en PDF Type 2-eksponentiel funktion?
En PDF Type 2-funktion beregner én formel — y = C0 + x^N × (C1 − C0), anvendt komponent for komponent — hvor x er funktionens ene input, normaliseret mod dets /Domain, før formlen kører (ISO 32000-1 §7.10.3). /C0 og /C1 er output-vektorerne ved de to ender af det interval, ét tal pr. output-komponent, og /N er eksponenten der former kurven mellem dem: N = 1 giver den lige lineære rampe bag de fleste gradient-stop og duotone-konverteringer, N over 1 trækker kurven mod C0, og N mellem 0 og 1 skubber den mod C1. RegisterExponentialFunction bygger den ordbog ud fra fem argumenter og overdrager et funktionsobjekt klar til at blive tilsluttet en shading, en halvtone-spotfunktion, eller hvor som helst ellers specifikationen accepterer en /Function-nøgle
Komponent-antal-forholdet mellem C0 og C1 betyder noget to gange: én gang når man forfatter en Type 2-funktion, og igen når HotPDF skal gengive en, den ikke selv oprettede. På forfatter-siden tjekker RegisterExponentialFunction C0 og C1 mod hinanden og kaster en undtagelse, hvis de er uenige, så et kald, der når BeginDoc, allerede er et selv-konsistent funktionsobjekt. På gengivelses-siden må evaluatoren dog stole på, hvilke /C0- og /C1-arrays en kildefil rent faktisk erklærer — en trykkerifil åbnet til forhåndsvisning, for eksempel, eller et signeret dokument vist tilbage til en bruger — og versioner før 2.376.0 læste de arrays ind i en buffer dimensioneret til fire komponenter, CMYK-tilfældet. En DeviceGray- eller DeviceRGB-eksponentiel tint, med et ét- eller tre-elements /C0 og /C1, fejlede den læsning i stilhed og efterlod begge arrays på nul, så tinten males fladt sort i stedet for sin tiltænkte farve. Version 2.376.0 tilpassede læserens størrelse til funktionens faktiske erklærede output-antal i stedet for en fast buffer — præcis den slags fejl, kun et ikke-CMYK-testtilfælde afslører, da den eksisterende suite kørte CMYK gennemgående, hvor fire-ind-i-fire altid passede
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-stitching: kæder underfunktioner sammen på tværs af et Bounds-array
En PDF Type 3-funktion stitcher k underfunktioner sammen til én stykkevis mapning over ét inputs /Domain, og de to arrays, der får det til at fungere, er /Bounds og /Encode (ISO 32000-1 §7.10.4). /Bounds indeholder k − 1 indre splitpunkter, der skærer /Domain i k fortløbende intervaller; evaluatoren vælger det første interval, hvis øvre grænse overstiger inputtet, eller det sidste interval, når inputtet når den endelige grænse, og overdrager til det intervals underfunktion. /Encode remapper derefter inputtet fra dets position inde i det interval til hvilket input-interval den valgte underfunktion selv forventer — typisk [0, 1] hvis underfunktionen er endnu et eksponentielt segment — før evalueringen fortsætter, ét kald dybere, ind i den underfunktions eget /Domain og /Range
HotPDFs stitching-evaluator kunne tidligere kun håndtere nøjagtig to underfunktioner, og dens /Bounds-læser krævede et fuldt otte-elements array, så det ene splitpunkt en to-segments gradient rent faktisk har brug for — ét tal i /Bounds — altid fejlede at parse, og funktionen returnerede ingenting. /Encode blev slet ikke anvendt. Version 2.376.0 omskrev valget som den generelle k-underfunktion-søgning specifikationen beskriver og begyndte at læse /Bounds mod dens reelle erklærede længde, så en tre-, fire- eller fem-stop-gradient stitchet fra det antal eksponentielle segmenter nu løses på samme måde, som et to-segments et altid påstod. Eksemplet nedenfor bygger en to-segments sort-gennem-rød-til-hvid-rampe, formen en axial eller radial shading griber til, når én eksponentiel kurve ikke kan bære hvert farve-stop et design kræver
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;
Hvad kan en Type 4 PostScript-beregnerfunktion, som Type 2 og 3 ikke kan?
En PDF Type 4-funktion kører et ægte, om end bevidst begrænset, program: en PostScript-beregner, der skubber sine inputs på en operand-stak, udfører aritmetiske, sammenlignings-, stak-manipulations- og boolske operatorer plus if/ifelse-betingelser, og efterlader sine outputs på stakken, når den er færdig (ISO 32000-1 §7.10.5, Tabel 42). Der er ingen løkke-konstruktion og ingen navngiven variabel-lagring, kun stakken, hvilket holder et konformt program let at ræsonnere om — men inden for det begrænsede operatorsæt kan Type 4 udtrykke ting, Type 2 og Type 3 ikke kan, såsom en rigtig multi-blæk-blandingsformel til en DeviceN-separation eller en halvtone-spotfunktion med en betinget tærskel. HotPDFs evaluator, HPDFEvalPostScriptCalculator, tokeniserer programmet én gang — tal, operatorer og { }-procedureblokke — og gennemgår derefter en 100-posters operand-stak, den dybde ISO 32000-1 §7.10.5 kræver, bag et hårdt loft på 50.000 evaluerede operatorer som en defensiv sikring mod patologiske eller håndskrevne programmer
Roll-operatoren: retningen er let at få omvendt
roll er den operator, der mest sandsynligt kommer ud omvendt ved første forsøg, fordi dens argumentrækkefølge og dens rotationsretning begge løber modsat af, hvordan dansk beskriver dem. n j roll popper en optælling n og en rotationsmængde j, og skifter derefter cyklisk de øverste n stak-poster med j positioner og wrapper elementer, der falder af den ene ende, tilbage på den anden; det kanoniske eksempel, direkte fra specifikationen, er a b c 3 1 roll, der producerer c a b — det øverste element flytter til bunden af gruppen, ikke omvendt, og hvert andet element skifter op med én for at give plads. HotPDFs evaluator beregner den nye position af stak-post i som (i + j) mod n, hvilket matcher det eksempel nøjagtigt, men det er en to-linjers løkke, der er lige så let at skrive med rotationen vendt om, og en spejlvendt roll producerer stadig en plausibelt udseende farve — det er bare ikke den farve, filens forfatter bad om
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 er ikke Delphis Round: half-up versus banker's rounding
PostScripts round-operator løser en .5-uafgjort mod det større heltal hver gang, og Delphis indbyggede Round-funktion gør ikke det: den runder half-to-even, banker's-rounding-konventionen der skifter, hvilken vej en .5-uafgjort falder, så gentagen afrunding ikke akkumulerer bias. De to er enige næsten overalt og uenige præcis på den grænse, der betyder noget her — Delphis Round(0.5) returnerer 0, og Round(2.5) returnerer 2, mens PDF-specifikationens round vil have 1 og 3 for de samme inputs — så uoverensstemmelsen gemmer sig gennem afslappet testning og reproducerer derefter som en konsekvent én-forkert, hvor end en beregnerprograms mellemregning lander præcis på et halvt heltal. ISO 32000-1 §7.10.5 Tabel 42 er eksplicit om, at round skubber en brøkdel på .5 mod det større heltal, så HotPDF implementerer operatoren som Floor(x + 0.5) i stedet for at kalde Delphis Round, og enhver kode, der genimplementerer eller stikprøvetjekker et Type 4-programs aritmetik i hånden, har brug for den samme substitution
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;
Validering ved registreringstidspunktet fanger et dårligt beregnerprogram tidligt
Et misdannet Type 4-program er billigt at fange på forfattertidspunktet og dyrt at fange noget som helst andet sted, så RegisterPostScriptFunction gemmer ikke bare kildeteksten: den prøve-evaluerer programmet én gang, ved midtpunktet af det erklærede /Domain, før funktionsobjektet nogensinde skrives ind i dokumentet. Ubalancerede { }-blokke, en ukendt operator, et stak-underflow, eller et output-antal der ikke matcher /Range fejler alle den prøvekørsel og kaster en undtagelse med det samme, med kaldstakken pegende på RegisterPostScriptFunction-kaldet i stedet for på et gengivelsesartefakt opdaget under QA på en fil, der allerede er sendt ud. Midtpunkt-prøven beviser ikke, at programmet er korrekt på tværs af hele dets /Domain — en betinget forgrening, der kun opfører sig forkert nær den ene kant af inputintervallet, kan stadig slippe forbi et enkelt sample-punkt — men den lukker af for hele klassen af programmer, der er strukturelt ødelagte frem for blot forkerte i ét hjørne
Hvor gradienter og spotfarver sætter disse funktioner i arbejde
Type 2, 3 og 4 optræder sjældent isoleret i en rigtig PDF; de dukker op, hvor end specifikationen accepterer en /Function-nøgle, og de to mest almindelige forbrugere er shadings og spotfarve-tint-transformationer. En axial eller radial gradients sh-operator (ISO 32000-1 §8.7.4.5) evaluerer sin /Function én gang pr. position langs gradient-aksen, hvilket er nøjagtig det multi-stop-tilfælde, Type 3-stitching findes for. En Separation- eller DeviceN-farverums tint-transformation er det andet hyppige hjem for disse tre typer, og det er her, Type 4 gør gavn: en enkelt spot-blæk reducerer sædvanligvis til en Type 2- eller Type 0-kurve, men en DeviceN-blanding af flere blæk med reel trapping- og overprint-opførsel har ofte brug for den betingede logik, kun en PostScript-beregner kan udtrykke, tilfældet dækket i artiklen om gengivelse af Separation- og DeviceN-spotfarver. RegisterSeparationFunc er den tilhørende kald på forfatter-siden: den tager et blæknavn, et alternativt farverum, og et hvilket som helst objekt Register*Function-familien returnerer, og forbinder den tint-transformation til en Separation-farverumsressource, resten af siden kan vælge med scn/SCN
Sammen dækker Type 0's samplede grids og disse tre formeldrevne typer hver /Function, en PDF kan erklære, og at vælge den rigtige er mest et spørgsmål om, hvad man allerede har: en opslagstabel beregnet et andet sted bliver Type 0, en to-endepunkts-blanding bliver Type 2, flere blandinger kædet sammen på tværs af et domæne bliver Type 3, og alt med reel betinget logik bliver Type 4. RegisterExponentialFunction, RegisterStitchingFunction og RegisterPostScriptFunction er en del af standard-HotPDF-komponenten til Delphi og C++Builder, sammen med resten af dens ISO 32000-1 funktions- og shading-API