Files
2026-09-25 23:32:29 +02:00

1169 lines
36 KiB
Odin

package pdf
import "core:math"
// PDF functions (ISO 32000-2, 7.10). Four types share one struct because
// callers only ever evaluate them, and a union would put a switch at every
// call site for no gain.
Function_Kind :: enum u8 {
Sampled = 0, // type 0
Exponential = 2, // type 2
Stitching = 3, // type 3
PostScript = 4, // type 4
}
// The most inputs and outputs the library will accept. The format sets no
// limit, but a shading with 33 outputs is a malformed file, not a design.
MAX_FUNCTION_INPUTS :: 32
MAX_FUNCTION_OUTPUTS :: 32
Function :: struct {
kind: Function_Kind,
n_in: int,
n_out: int,
// Always 2*n_in long. Inputs are clamped to it before evaluation.
domain: []f64,
// 2*n_out long, or empty for a type 2 or 3 function that omits it.
// Outputs are clamped to it when present.
range: []f64,
// Type 0: a sampled table.
size: []int, // n_in entries
bits_per_sample: int,
encode: []f64, // 2*n_in
decode: []f64, // 2*n_out
samples: []u8, // raw, packed at bits_per_sample
order: int, // 1 linear, 3 cubic (cubic falls back to linear)
// Type 2: C0 + x^N * (C1 - C0).
c0: []f64,
c1: []f64,
exponent: f64,
// Type 3: k sub-functions stitched across the domain.
functions: []^Function,
bounds: []f64,
// Type 4: a compiled PostScript calculator program.
program: []Ps_Op,
}
// Ps_Op is one instruction of a type 4 function, already parsed. Branches carry
// absolute jump targets so evaluation is a flat loop rather than a tree walk,
// which keeps the interpreter's step budget easy to enforce.
Ps_Op :: struct {
kind: Ps_Op_Kind,
value: f64,
// For Jump and Jump_If_False: the index to continue at.
target: int,
}
Ps_Op_Kind :: enum u8 {
Push,
// Arithmetic
Add, Sub, Mul, Div, Idiv, Mod, Neg, Abs, Ceiling, Floor, Round, Truncate,
Sqrt, Sin, Cos, Atan, Exp, Ln, Log, Cvi, Cvr,
// Relational and boolean
Eq, Ne, Gt, Ge, Lt, Le, And, Or, Xor, Not, Bitshift, True, False,
// Stack
Pop, Exch, Dup, Copy, Index, Roll,
// Control
Jump, Jump_If_False,
}
// ---------------------------------------------------------------------------
// Limits
//
// Everything a file controls -- how many sub-functions stitch together, how
// long a calculator program is, how many samples a table claims -- is bounded
// here, so that a hostile or merely broken function costs a fixed amount of
// time and memory instead of an unbounded one.
// ---------------------------------------------------------------------------
// How deeply type 3 functions may nest. This is also what stops a stitching
// function that lists itself among its own /Functions: the nested load stops
// here, and because a function is only entered into the cache once it is
// completely built, the graph that results is always acyclic.
MAX_FUNCTION_DEPTH :: 8
// Entries read from any one numeric array (/Domain, /Bounds, /Encode ...).
MAX_FUNCTION_ARRAY :: 4096
// Sub-functions of one type 3 function.
MAX_SUB_FUNCTIONS :: 1024
// Samples one type 0 table may hold, across every input and output. 64 Mi
// samples is far beyond any real gradient and keeps size * n_out in range.
MAX_FUNCTION_SAMPLES :: 1 << 26
// Inputs a type 0 function is interpolated over. Multilinear interpolation
// visits 2^n_in corners per evaluation, so beyond this the nearest sample is
// used instead. Real sampled functions have one or two inputs; a shading's
// has exactly one.
MAX_INTERPOLATED_INPUTS :: 8
// Operands a type 4 program may stack. ISO 32000-2, 7.10.5 fixes this at 100.
MAX_PS_STACK :: 100
// Instructions in a compiled type 4 program.
MAX_PS_OPS :: 1 << 16
// Nesting of { } in a type 4 program.
MAX_PS_DEPTH :: 32
// Instructions one evaluation may execute. A type 4 function has no loop
// construct, so a real program always finishes in fewer steps than it has
// instructions; this only ever fires on a program built to spin.
MAX_PS_STEPS :: 10000
// ---------------------------------------------------------------------------
// Loading
// ---------------------------------------------------------------------------
// function_load builds the function `obj` describes -- a dictionary for types
// 2 and 3, a stream for types 0 and 4.
//
// The result is owned by the Document and must not be freed. It is cached,
// keyed by the object it was built from, because a shading that names the same
// tint transform from every one of its patterns must build it once.
function_load :: proc(doc: ^Document, obj: Object) -> (^Function, Error) {
return fn_load(doc, obj, 0)
}
@(private)
fn_load :: proc(doc: ^Document, obj: Object, depth: int) -> (^Function, Error) {
if doc == nil {
return nil, .Invalid_Argument
}
if depth > MAX_FUNCTION_DEPTH {
add_repair(doc, .Recursion_Limit_Hit, 0)
return nil, .Recursion_Limit
}
o := resolve(doc, obj)
key := object_identity(o)
if key != 0 {
if cached, ok := doc.fn_cache[key]; ok {
return cached, .None
}
}
d, is_dict := to_dict(o)
if !is_dict {
return nil, .Bad_Function
}
stm, _ := to_stream(o)
kind: Function_Kind
switch dict_int(doc, d, "FunctionType", -1) {
case 0: kind = .Sampled
case 2: kind = .Exponential
case 3: kind = .Stitching
case 4: kind = .PostScript
case: return nil, .Bad_Function
}
f := new(Function, doc.allocator)
f.kind = kind
f.domain = fn_num_array(doc, d, "Domain")
if len(f.domain) < 2 {
// /Domain is required, but a function that omits it is still usable:
// every type but 0 has an obvious default and a shading feeding it
// will clamp to [0 1] anyway.
f.domain = fn_make_pair(doc, 0, 1)
add_repair(doc, .Bad_Resource, 0)
}
f.n_in = min(len(f.domain) / 2, MAX_FUNCTION_INPUTS)
f.range = fn_num_array(doc, d, "Range")
f.n_out = min(len(f.range) / 2, MAX_FUNCTION_OUTPUTS)
err: Error
switch kind {
case .Sampled: err = fn_load_sampled(doc, f, d, stm)
case .Exponential: err = fn_load_exponential(doc, f, d)
case .Stitching: err = fn_load_stitching(doc, f, d, depth)
case .PostScript: err = fn_load_postscript(doc, f, stm)
}
if err != .None {
return nil, err
}
if f.n_in < 1 || f.n_out < 1 {
return nil, .Bad_Function
}
// Entered only now that it is complete: a half-built function in the cache
// is how a self-referential /Functions array would become a cycle.
if key != 0 {
if doc.fn_cache == nil {
doc.fn_cache = make(map[uintptr]^Function, 16, doc.allocator)
}
doc.fn_cache[key] = f
}
return f, .None
}
@(private)
fn_num_array :: proc(doc: ^Document, d: ^Dict, key: string) -> []f64 {
a := dict_array(doc, d, key)
n := array_len(a)
if n <= 0 {
return nil
}
n = min(n, MAX_FUNCTION_ARRAY)
out := make([]f64, n, doc.allocator)
for i in 0 ..< n {
// A non-finite bound would propagate into every value the function
// produces and from there into the geometry.
out[i] = clamp_finite(array_num(doc, a, i, 0), 0)
}
return out
}
@(private)
fn_make_pair :: proc(doc: ^Document, lo, hi: f64) -> []f64 {
out := make([]f64, 2, doc.allocator)
out[0], out[1] = lo, hi
return out
}
@(private)
fn_make_single :: proc(doc: ^Document, v: f64) -> []f64 {
out := make([]f64, 1, doc.allocator)
out[0] = v
return out
}
// ---------------------------------------------------------------------------
// Type 0: sampled
// ---------------------------------------------------------------------------
@(private)
fn_load_sampled :: proc(doc: ^Document, f: ^Function, d: ^Dict, stm: ^Stream) -> Error {
if stm == nil {
return .Bad_Function
}
// /Range is what says how many outputs a sample has; without it the
// sample table cannot be read at all.
if f.n_out < 1 {
return .Bad_Function
}
switch dict_int(doc, d, "BitsPerSample", 0) {
case 1, 2, 4, 8, 12, 16, 24, 32:
f.bits_per_sample = int(dict_int(doc, d, "BitsPerSample", 8))
case:
return .Bad_Function
}
sizes := dict_array(doc, d, "Size")
n := min(array_len(sizes), MAX_FUNCTION_INPUTS)
if n < 1 {
return .Bad_Function
}
// /Size and /Domain must agree on how many inputs there are. Trusting the
// shorter of the two keeps every later index in range.
f.n_in = min(f.n_in, n)
f.size = make([]int, f.n_in, doc.allocator)
total := i64(1)
for i in 0 ..< f.n_in {
s := array_int(doc, sizes, i, 0)
if s < 1 {
// A zero or negative dimension would make the table empty and
// every index into it invalid.
s = 1
add_repair(doc, .Bad_Resource, 0)
}
// A single dimension larger than the whole table may be is not a
// table this library will read; nor is one whose dimensions multiply
// out past the limit.
if s > MAX_FUNCTION_SAMPLES {
return .Bad_Function
}
f.size[i] = int(s)
total *= s
if total > MAX_FUNCTION_SAMPLES {
return .Bad_Function
}
}
if total * i64(f.n_out) > MAX_FUNCTION_SAMPLES {
return .Bad_Function
}
f.order = int(dict_int(doc, d, "Order", 1))
f.encode = fn_num_array(doc, d, "Encode")
if len(f.encode) < 2 * f.n_in {
// Default /Encode is [0 Size0-1 0 Size1-1 ...] (Table 38).
enc := make([]f64, 2 * f.n_in, doc.allocator)
for i in 0 ..< f.n_in {
enc[2 * i] = 0
enc[2 * i + 1] = f64(f.size[i] - 1)
}
f.encode = enc
}
f.decode = fn_num_array(doc, d, "Decode")
if len(f.decode) < 2 * f.n_out {
f.decode = f.range
}
data, derr := stream_data(doc, stm)
if derr != .None {
return .Bad_Function
}
f.samples = data
have := i64(len(data)) * 8
need := total * i64(f.n_out) * i64(f.bits_per_sample)
if have < need {
// Short sample tables do occur, usually from a truncated stream. The
// missing samples read as zero rather than the whole function failing.
add_repair(doc, .Bad_Resource, 0, stm.ref)
}
return .None
}
// fn_sample reads sample number `ord` (already including the output index) out
// of the packed table. Out-of-range ordinals read as 0, which is what makes a
// truncated table safe to evaluate.
@(private)
fn_sample :: proc(f: ^Function, ord: i64) -> f64 {
if ord < 0 {
return 0
}
bps := f.bits_per_sample
bit := ord * i64(bps)
if bit + i64(bps) > i64(len(f.samples)) * 8 {
return 0
}
if bps == 8 {
return f64(f.samples[bit >> 3])
}
v := u64(0)
for k in 0 ..< bps {
b := bit + i64(k)
byte_val := f.samples[b >> 3]
bit_val := (byte_val >> (7 - u8(b & 7))) & 1
v = v << 1 | u64(bit_val)
}
return f64(v)
}
@(private)
fn_eval_sampled :: proc(f: ^Function, in_: []f64, out: []f64) {
if len(f.size) < f.n_in || f.n_in < 1 {
return
}
// Position within the table, split into the sample below and the fraction
// between it and the next one.
base: [MAX_FUNCTION_INPUTS]int
frac: [MAX_FUNCTION_INPUTS]f64
for i in 0 ..< f.n_in {
x := in_[i] if i < len(in_) else 0
e := fn_interp(x, f.domain[2 * i], f.domain[2 * i + 1],
f.encode[2 * i], f.encode[2 * i + 1])
e = clamp(clamp_finite(e, 0), 0, f64(f.size[i] - 1))
lo := int(math.floor(e))
if lo > f.size[i] - 1 { lo = f.size[i] - 1 }
if lo < 0 { lo = 0 }
base[i] = lo
frac[i] = e - f64(lo)
}
// Strides through the flattened table: the first input varies fastest
// (ISO 32000-2, 7.10.2).
stride: [MAX_FUNCTION_INPUTS]i64
s := i64(1)
for i in 0 ..< f.n_in {
stride[i] = s
s *= i64(f.size[i])
}
max_val := f64(u64(1) << u64(f.bits_per_sample)) - 1
if f.bits_per_sample == 32 {
max_val = 4294967295
}
// /Order 3 asks for a cubic spline. Linear interpolation is used instead:
// the difference is invisible in the gradients these tables encode, and
// the format explicitly allows a reader to substitute a lower order.
corners := 1 << uint(f.n_in) if f.n_in <= MAX_INTERPOLATED_INPUTS else 1
for j in 0 ..< f.n_out {
if j >= len(out) {
break
}
acc := 0.0
for corner in 0 ..< corners {
weight := 1.0
idx := i64(0)
for i in 0 ..< f.n_in {
bit := (corner >> uint(i)) & 1
coord := base[i]
if bit == 1 {
weight *= frac[i]
if coord + 1 <= f.size[i] - 1 {
coord += 1
}
} else {
weight *= 1 - frac[i]
}
idx += i64(coord) * stride[i]
}
if weight == 0 {
continue
}
acc += weight * fn_sample(f, idx * i64(f.n_out) + i64(j))
}
lo, hi := 0.0, 1.0
if 2 * j + 1 < len(f.decode) {
lo, hi = f.decode[2 * j], f.decode[2 * j + 1]
}
out[j] = lo + acc * (hi - lo) / max_val
}
}
// ---------------------------------------------------------------------------
// Type 2: exponential interpolation
// ---------------------------------------------------------------------------
@(private)
fn_load_exponential :: proc(doc: ^Document, f: ^Function, d: ^Dict) -> Error {
f.c0 = fn_num_array(doc, d, "C0")
f.c1 = fn_num_array(doc, d, "C1")
// C0 defaults to [0.0] and C1 to [1.0] (ISO 32000-2, Table 40).
if len(f.c0) == 0 {
f.c0 = fn_make_single(doc, 0)
}
if len(f.c1) == 0 {
f.c1 = fn_make_single(doc, 1)
}
f.exponent = clamp_finite(dict_num(doc, d, "N", 1), 1)
n := min(len(f.c0), len(f.c1))
n = min(n, MAX_FUNCTION_OUTPUTS)
if n < 1 {
return .Bad_Function
}
// /Range is optional here; when it is absent the number of outputs is the
// length of C0 and C1.
if f.n_out == 0 || f.n_out > n {
f.n_out = n
}
// A type 2 function takes exactly one input (7.10.3).
f.n_in = 1
return .None
}
@(private)
fn_eval_exponential :: proc(f: ^Function, in_: []f64, out: []f64) {
x := len(in_) > 0 ? in_[0] : 0
p := x
if f.exponent != 1 {
p = math.pow(x, f.exponent)
// A negative base under a fractional exponent is not a real number;
// the format says the domain must exclude it, so a file that does not
// gets zero rather than a NaN travelling into the raster.
if !is_finite(p) {
p = 0
}
}
for j in 0 ..< f.n_out {
if j >= len(out) {
break
}
a := f.c0[j] if j < len(f.c0) else 0
b := f.c1[j] if j < len(f.c1) else 1
out[j] = a + p * (b - a)
}
}
// ---------------------------------------------------------------------------
// Type 3: stitching
// ---------------------------------------------------------------------------
@(private)
fn_load_stitching :: proc(doc: ^Document, f: ^Function, d: ^Dict, depth: int) -> Error {
subs := dict_array(doc, d, "Functions")
k := min(array_len(subs), MAX_SUB_FUNCTIONS)
if k < 1 {
return .Bad_Function
}
f.functions = make([]^Function, k, doc.allocator)
n_out := 0
for i in 0 ..< k {
sub, serr := fn_load(doc, array_get_raw(subs, i), depth + 1)
if serr != .None {
// One broken sub-function leaves a hole that evaluates to zero
// rather than discarding the whole stitch.
add_repair(doc, .Bad_Resource, 0)
continue
}
f.functions[i] = sub
if n_out == 0 {
n_out = sub.n_out
}
}
f.bounds = fn_num_array(doc, d, "Bounds")
f.encode = fn_num_array(doc, d, "Encode")
if f.n_out == 0 {
// /Range is optional for a type 3 function; the sub-functions say how
// many outputs there are.
f.n_out = min(n_out, MAX_FUNCTION_OUTPUTS)
}
// A type 3 function takes exactly one input (7.10.4).
f.n_in = 1
return .None
}
@(private)
fn_eval_stitching :: proc(f: ^Function, in_: []f64, out: []f64) {
k := len(f.functions)
if k == 0 {
return
}
x := len(in_) > 0 ? in_[0] : 0
d0, d1 := f.domain[0], f.domain[1]
// Subdomain i is [Bounds[i-1], Bounds[i]), so an input exactly on a bound
// belongs to the later sub-function (ISO 32000-2, 7.10.4).
i := 0
for i < k - 1 {
b := f.bounds[i] if i < len(f.bounds) else d1
if x < b {
break
}
i += 1
}
lo := d0
if i > 0 && i - 1 < len(f.bounds) {
lo = f.bounds[i - 1]
}
hi := d1
if i < k - 1 && i < len(f.bounds) {
hi = f.bounds[i]
}
e0, e1 := 0.0, 1.0
if 2 * i + 1 < len(f.encode) {
e0, e1 = f.encode[2 * i], f.encode[2 * i + 1]
}
sub_in: [1]f64
sub_in[0] = fn_interp(x, lo, hi, e0, e1)
function_eval(f.functions[i], sub_in[:], out)
}
// ---------------------------------------------------------------------------
// Type 4: the PostScript calculator
// ---------------------------------------------------------------------------
@(private)
fn_load_postscript :: proc(doc: ^Document, f: ^Function, stm: ^Stream) -> Error {
if stm == nil || f.n_out < 1 {
// Without /Range there is no way to know how many of the values left
// on the stack are outputs.
return .Bad_Function
}
src, derr := stream_data(doc, stm)
if derr != .None || len(src) == 0 {
return .Bad_Function
}
// Compiled into scratch memory first: the Document's arena cannot release
// the intermediate blocks a growing array leaves behind.
ops := make([dynamic]Ps_Op, 0, 64, context.allocator)
defer delete(ops)
p := Ps_Lexer{src = src}
if err := ps_compile(&p, &ops); err != .None {
return err
}
if len(ops) == 0 {
return .Bad_Function
}
prog := make([]Ps_Op, len(ops), doc.allocator)
copy(prog, ops[:])
f.program = prog
return .None
}
@(private)
Ps_Token_Kind :: enum u8 {
End,
Number,
Lbrace,
Rbrace,
Operator,
If,
Ifelse,
Unknown,
}
@(private)
Ps_Token :: struct {
kind: Ps_Token_Kind,
op: Ps_Op_Kind,
value: f64,
}
@(private)
Ps_Lexer :: struct {
src: []byte,
pos: int,
}
@(private)
ps_is_space :: proc "contextless" (c: byte) -> bool {
return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f' || c == 0
}
@(private)
ps_next :: proc(p: ^Ps_Lexer) -> Ps_Token {
for p.pos < len(p.src) {
c := p.src[p.pos]
if ps_is_space(c) {
p.pos += 1
continue
}
// A comment runs to the end of the line, as everywhere else in PDF.
if c == '%' {
for p.pos < len(p.src) && p.src[p.pos] != '\n' && p.src[p.pos] != '\r' {
p.pos += 1
}
continue
}
break
}
if p.pos >= len(p.src) {
return Ps_Token{kind = .End}
}
c := p.src[p.pos]
if c == '{' {
p.pos += 1
return Ps_Token{kind = .Lbrace}
}
if c == '}' {
p.pos += 1
return Ps_Token{kind = .Rbrace}
}
start := p.pos
for p.pos < len(p.src) {
b := p.src[p.pos]
if ps_is_space(b) || b == '{' || b == '}' || b == '%' {
break
}
p.pos += 1
}
word := string(p.src[start:p.pos])
if len(word) == 0 {
// Cannot happen -- the delimiters above are all consumed as tokens --
// but a zero-length word would spin the parser, so it ends it.
p.pos = len(p.src)
return Ps_Token{kind = .End}
}
if c == '+' || c == '-' || c == '.' || (c >= '0' && c <= '9') {
obj, ok := parse_number(word)
if ok {
v, _ := to_number(obj)
return Ps_Token{kind = .Number, value = clamp_finite(v, 0)}
}
return Ps_Token{kind = .Unknown}
}
return ps_operator_token(word)
}
@(private)
ps_operator_token :: proc(word: string) -> Ps_Token {
k: Ps_Op_Kind
switch word {
case "add": k = .Add
case "sub": k = .Sub
case "mul": k = .Mul
case "div": k = .Div
case "idiv": k = .Idiv
case "mod": k = .Mod
case "neg": k = .Neg
case "abs": k = .Abs
case "ceiling": k = .Ceiling
case "floor": k = .Floor
case "round": k = .Round
case "truncate": k = .Truncate
case "sqrt": k = .Sqrt
case "sin": k = .Sin
case "cos": k = .Cos
case "atan": k = .Atan
case "exp": k = .Exp
case "ln": k = .Ln
case "log": k = .Log
case "cvi": k = .Cvi
case "cvr": k = .Cvr
case "eq": k = .Eq
case "ne": k = .Ne
case "gt": k = .Gt
case "ge": k = .Ge
case "lt": k = .Lt
case "le": k = .Le
case "and": k = .And
case "or": k = .Or
case "xor": k = .Xor
case "not": k = .Not
case "bitshift": k = .Bitshift
case "true": k = .True
case "false": k = .False
case "pop": k = .Pop
case "exch": k = .Exch
case "dup": k = .Dup
case "copy": k = .Copy
case "index": k = .Index
case "roll": k = .Roll
case "if": return Ps_Token{kind = .If}
case "ifelse": return Ps_Token{kind = .Ifelse}
case: return Ps_Token{kind = .Unknown}
}
return Ps_Token{kind = .Operator, op = k}
}
// ps_compile reads the whole program. The outer braces are expected but not
// required: a stream that forgot them still compiles, because the alternative
// is refusing a function every other viewer runs.
@(private)
ps_compile :: proc(p: ^Ps_Lexer, ops: ^[dynamic]Ps_Op) -> Error {
save := p.pos
t := ps_next(p)
if t.kind == .Lbrace {
return ps_compile_block(p, ops, 0)
}
p.pos = save
return ps_compile_block(p, ops, 0)
}
// ps_compile_block emits instructions until the '}' that closes the current
// block, which it consumes. Conditionals become jumps with absolute targets:
//
// {A} if -> jump_if_false end; A; end:
// {A} {B} ifelse -> jump_if_false b; A; jump end; b: B; end:
//
// so a slot for the branch is reserved before the block body is emitted and
// patched once the operator that follows the body says what it is.
@(private)
ps_compile_block :: proc(p: ^Ps_Lexer, ops: ^[dynamic]Ps_Op, depth: int) -> Error {
if depth > MAX_PS_DEPTH {
return .Bad_Function
}
for {
if len(ops) > MAX_PS_OPS {
return .Bad_Function
}
t := ps_next(p)
switch t.kind {
case .End:
// End of stream: a missing '}' is not worth rejecting a program
// that is otherwise complete.
return .None
case .Rbrace:
return .None
case .Number:
append(ops, Ps_Op{kind = .Push, value = t.value})
case .Operator:
append(ops, Ps_Op{kind = t.op})
case .Lbrace:
branch := len(ops)
append(ops, Ps_Op{kind = .Jump})
ps_compile_block(p, ops, depth + 1) or_return
save := p.pos
t2 := ps_next(p)
#partial switch t2.kind {
case .If:
ops[branch] = Ps_Op{kind = .Jump_If_False, target = len(ops)}
case .Lbrace:
skip := len(ops)
append(ops, Ps_Op{kind = .Jump})
ps_compile_block(p, ops, depth + 1) or_return
t3 := ps_next(p)
if t3.kind != .Ifelse {
return .Bad_Function
}
ops[branch] = Ps_Op{kind = .Jump_If_False, target = skip + 1}
ops[skip] = Ps_Op{kind = .Jump, target = len(ops)}
case:
// A block that no `if` or `ifelse` consumes never runs in
// PostScript either, so it is jumped over rather than
// executed.
ops[branch] = Ps_Op{kind = .Jump, target = len(ops)}
p.pos = save
}
case .If, .Ifelse, .Unknown:
// An `if` with no block before it, or a word this calculator has
// no operator for. Skipping it keeps the rest of the program
// running, which is what a partly-understood tint transform needs.
}
}
}
// Ps_Value is one operand. The type 4 language distinguishes booleans from
// numbers -- `and`, `or`, `xor` and `not` are logical on the first and bitwise
// on the second -- so the tag travels with the value.
@(private)
Ps_Value :: struct {
v: f64,
is_bool: bool,
}
@(private)
Ps_Stack :: struct {
v: [MAX_PS_STACK]Ps_Value,
sp: int,
// Set when a push found the stack full. Evaluation stops, leaving whatever
// has been computed so far rather than discarding it.
overflow: bool,
}
@(private)
ps_push :: proc(s: ^Ps_Stack, val: f64, is_bool := false) {
if s.sp >= MAX_PS_STACK {
s.overflow = true
return
}
s.v[s.sp] = Ps_Value{v = clamp_finite(val, 0), is_bool = is_bool}
s.sp += 1
}
// ps_pop returns zero on an empty stack. An underflowing program is malformed,
// but a defined result costs nothing and a crash costs the page.
@(private)
ps_pop :: proc(s: ^Ps_Stack) -> Ps_Value {
if s.sp <= 0 {
return Ps_Value{}
}
s.sp -= 1
return s.v[s.sp]
}
@(private)
ps_int :: proc(x: f64) -> i32 {
if !is_finite(x) {
return 0
}
y := math.trunc(x)
if y > 2147483647 {
return 2147483647
}
if y < -2147483648 {
return -2147483648
}
return i32(y)
}
@(private)
ps_exec :: proc(prog: []Ps_Op, s: ^Ps_Stack) {
pc := 0
for steps := 0; steps < MAX_PS_STEPS; steps += 1 {
if pc < 0 || pc >= len(prog) || s.overflow {
return
}
op := prog[pc]
pc += 1
switch op.kind {
case .Push:
ps_push(s, op.value)
case .Add:
b, a := ps_pop(s), ps_pop(s)
ps_push(s, a.v + b.v)
case .Sub:
b, a := ps_pop(s), ps_pop(s)
ps_push(s, a.v - b.v)
case .Mul:
b, a := ps_pop(s), ps_pop(s)
ps_push(s, a.v * b.v)
case .Div:
b, a := ps_pop(s), ps_pop(s)
// Division by zero is undefined in PostScript; zero is the value
// every other reader substitutes.
ps_push(s, b.v != 0 ? a.v / b.v : 0)
case .Idiv:
b, a := ps_pop(s), ps_pop(s)
ib, ia := ps_int(b.v), ps_int(a.v)
// -2147483648 idiv -1 overflows a two's-complement divide.
ps_push(s, ib != 0 && !(ia == -2147483648 && ib == -1) ? f64(ia / ib) : 0)
case .Mod:
b, a := ps_pop(s), ps_pop(s)
ib, ia := ps_int(b.v), ps_int(a.v)
ps_push(s, ib != 0 && !(ia == -2147483648 && ib == -1) ? f64(ia % ib) : 0)
case .Neg:
a := ps_pop(s)
ps_push(s, -a.v)
case .Abs:
a := ps_pop(s)
ps_push(s, abs(a.v))
case .Ceiling:
a := ps_pop(s)
ps_push(s, math.ceil(a.v))
case .Floor:
a := ps_pop(s)
ps_push(s, math.floor(a.v))
case .Round:
a := ps_pop(s)
// PostScript rounds a halfway case towards positive infinity.
ps_push(s, math.floor(a.v + 0.5))
case .Truncate:
a := ps_pop(s)
ps_push(s, math.trunc(a.v))
case .Sqrt:
a := ps_pop(s)
ps_push(s, a.v > 0 ? math.sqrt(a.v) : 0)
case .Sin:
a := ps_pop(s)
ps_push(s, math.sin(a.v * math.PI / 180))
case .Cos:
a := ps_pop(s)
ps_push(s, math.cos(a.v * math.PI / 180))
case .Atan:
den, num := ps_pop(s), ps_pop(s)
deg := math.atan2(num.v, den.v) * 180 / math.PI
// `atan` answers in degrees between 0 and 360, never negative.
if deg < 0 {
deg += 360
}
ps_push(s, deg)
case .Exp:
e, base := ps_pop(s), ps_pop(s)
r := math.pow(base.v, e.v)
ps_push(s, is_finite(r) ? r : 0)
case .Ln:
a := ps_pop(s)
ps_push(s, a.v > 0 ? math.ln(a.v) : 0)
case .Log:
a := ps_pop(s)
ps_push(s, a.v > 0 ? math.log10(a.v) : 0)
case .Cvi:
a := ps_pop(s)
ps_push(s, math.trunc(a.v))
case .Cvr:
a := ps_pop(s)
ps_push(s, a.v)
case .Eq:
b, a := ps_pop(s), ps_pop(s)
ps_push(s, a.v == b.v ? 1 : 0, true)
case .Ne:
b, a := ps_pop(s), ps_pop(s)
ps_push(s, a.v != b.v ? 1 : 0, true)
case .Gt:
b, a := ps_pop(s), ps_pop(s)
ps_push(s, a.v > b.v ? 1 : 0, true)
case .Ge:
b, a := ps_pop(s), ps_pop(s)
ps_push(s, a.v >= b.v ? 1 : 0, true)
case .Lt:
b, a := ps_pop(s), ps_pop(s)
ps_push(s, a.v < b.v ? 1 : 0, true)
case .Le:
b, a := ps_pop(s), ps_pop(s)
ps_push(s, a.v <= b.v ? 1 : 0, true)
case .And:
b, a := ps_pop(s), ps_pop(s)
if a.is_bool && b.is_bool {
ps_push(s, (a.v != 0 && b.v != 0) ? 1 : 0, true)
} else {
ps_push(s, f64(ps_int(a.v) & ps_int(b.v)))
}
case .Or:
b, a := ps_pop(s), ps_pop(s)
if a.is_bool && b.is_bool {
ps_push(s, (a.v != 0 || b.v != 0) ? 1 : 0, true)
} else {
ps_push(s, f64(ps_int(a.v) | ps_int(b.v)))
}
case .Xor:
b, a := ps_pop(s), ps_pop(s)
if a.is_bool && b.is_bool {
ps_push(s, ((a.v != 0) != (b.v != 0)) ? 1 : 0, true)
} else {
ps_push(s, f64(ps_int(a.v) ~ ps_int(b.v)))
}
case .Not:
a := ps_pop(s)
if a.is_bool {
ps_push(s, a.v == 0 ? 1 : 0, true)
} else {
ps_push(s, f64(~ps_int(a.v)))
}
case .Bitshift:
sh, a := ps_pop(s), ps_pop(s)
n := ps_int(sh.v)
bits := u32(ps_int(a.v))
// A shift of 32 or more is undefined in C and in Odin; the value
// it must produce here is zero.
switch {
case n >= 32 || n <= -32: ps_push(s, 0)
case n >= 0: ps_push(s, f64(i32(bits << u32(n))))
case: ps_push(s, f64(i32(bits >> u32(-n))))
}
case .True:
ps_push(s, 1, true)
case .False:
ps_push(s, 0, true)
case .Pop:
ps_pop(s)
case .Exch:
b, a := ps_pop(s), ps_pop(s)
if s.sp + 2 <= MAX_PS_STACK {
s.v[s.sp] = b
s.v[s.sp + 1] = a
s.sp += 2
} else {
s.overflow = true
}
case .Dup:
if s.sp > 0 {
top := s.v[s.sp - 1]
ps_push(s, top.v, top.is_bool)
} else {
ps_push(s, 0)
}
case .Copy:
n := int(ps_int(ps_pop(s).v))
if n <= 0 {
break
}
if n > s.sp || s.sp + n > MAX_PS_STACK {
s.overflow = true
break
}
src := s.sp - n
for i in 0 ..< n {
s.v[s.sp + i] = s.v[src + i]
}
s.sp += n
case .Index:
n := int(ps_int(ps_pop(s).v))
if n < 0 || n >= s.sp {
// Indexing past the bottom of the stack is malformed; zero
// keeps the program running.
ps_push(s, 0)
break
}
val := s.v[s.sp - 1 - n]
ps_push(s, val.v, val.is_bool)
case .Roll:
j := int(ps_int(ps_pop(s).v))
n := int(ps_int(ps_pop(s).v))
if n <= 0 || n > s.sp {
break
}
// Rotation by three reversals, so no scratch stack is needed.
k := j %% n
if k != 0 {
lo := s.sp - n
ps_reverse(s.v[lo:lo + n - k])
ps_reverse(s.v[lo + n - k:lo + n])
ps_reverse(s.v[lo:lo + n])
}
case .Jump:
pc = op.target
case .Jump_If_False:
cond := ps_pop(s)
if cond.v == 0 {
pc = op.target
}
}
}
}
@(private)
ps_reverse :: proc(vals: []Ps_Value) {
for i, j := 0, len(vals) - 1; i < j; i, j = i + 1, j - 1 {
vals[i], vals[j] = vals[j], vals[i]
}
}
@(private)
fn_eval_postscript :: proc(f: ^Function, in_: []f64, out: []f64) {
s: Ps_Stack
for i in 0 ..< f.n_in {
ps_push(&s, i < len(in_) ? in_[i] : 0)
}
ps_exec(f.program, &s)
// The outputs are the topmost n_out values, bottom-most first. A program
// that left fewer behind gets zeros for the rest.
for j in 0 ..< f.n_out {
if j >= len(out) {
break
}
idx := s.sp - f.n_out + j
out[j] = idx >= 0 && idx < s.sp ? s.v[idx].v : 0
}
}
// ---------------------------------------------------------------------------
// Evaluation
// ---------------------------------------------------------------------------
// function_eval evaluates `fn` at `in_` and writes n_out values into `out`.
//
// Inputs are clamped to /Domain and outputs to /Range, both of which the
// format requires (ISO 32000-2, 7.10.1) and neither of which is optional in
// practice: a shading whose tint escapes its range paints garbage. `out` may
// be shorter than n_out, in which case the extra outputs are dropped; it is
// always fully written, so a nil or broken function reads as zeros rather than
// as whatever the caller's buffer held.
function_eval :: proc(fn: ^Function, in_: []f64, out: []f64) {
for i in 0 ..< len(out) {
out[i] = 0
}
if fn == nil || fn.n_out < 1 {
return
}
clamped: [MAX_FUNCTION_INPUTS]f64
n := min(fn.n_in, MAX_FUNCTION_INPUTS)
for i in 0 ..< n {
x := i < len(in_) ? clamp_finite(in_[i], 0) : 0
if 2 * i + 1 < len(fn.domain) {
lo, hi := fn.domain[2 * i], fn.domain[2 * i + 1]
if lo > hi {
lo, hi = hi, lo
}
x = clamp(x, lo, hi)
}
clamped[i] = x
}
switch fn.kind {
case .Sampled: fn_eval_sampled(fn, clamped[:n], out)
case .Exponential: fn_eval_exponential(fn, clamped[:n], out)
case .Stitching: fn_eval_stitching(fn, clamped[:n], out)
case .PostScript: fn_eval_postscript(fn, clamped[:n], out)
}
for j in 0 ..< min(fn.n_out, len(out)) {
out[j] = clamp_finite(out[j], 0)
if 2 * j + 1 < len(fn.range) {
lo, hi := fn.range[2 * j], fn.range[2 * j + 1]
if lo > hi {
lo, hi = hi, lo
}
out[j] = clamp(out[j], lo, hi)
}
}
}
// fn_interp maps x from [xmin xmax] onto [ymin ymax]. A degenerate source
// interval -- which /Bounds and /Encode arrays out of real files do produce --
// maps to the start of the destination rather than dividing by zero.
@(private)
fn_interp :: proc(x, xmin, xmax, ymin, ymax: f64) -> f64 {
if xmax == xmin {
return ymin
}
return ymin + (x - xmin) * (ymax - ymin) / (xmax - xmin)
}