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) }