Skip to main content

docling_pdf/render/
function.rs

1//! PDF functions (ISO 32000-1, 7.10): sampled (type 0), exponential (2),
2//! stitching (3) and PostScript calculator (4) — what shadings, Separation /
3//! DeviceN tint transforms and `/Decode`-less colour ramps evaluate.
4
5use lopdf::{Dictionary, Document, Object};
6
7use super::objects::{deref, num, nums};
8
9#[derive(Debug, Clone)]
10pub enum Function {
11    Sampled {
12        domain: Vec<f64>,
13        range: Vec<f64>,
14        size: Vec<usize>,
15        bps: u32,
16        encode: Vec<f64>,
17        decode: Vec<f64>,
18        samples: Vec<u8>,
19        n_out: usize,
20    },
21    Exponential {
22        domain: Vec<f64>,
23        c0: Vec<f64>,
24        c1: Vec<f64>,
25        n: f64,
26    },
27    Stitching {
28        domain: Vec<f64>,
29        functions: Vec<Function>,
30        bounds: Vec<f64>,
31        encode: Vec<f64>,
32    },
33    PostScript {
34        domain: Vec<f64>,
35        range: Vec<f64>,
36        program: Vec<PsOp>,
37    },
38    /// Several 1-out functions side by side (a shading's `/Function` array).
39    Array(Vec<Function>),
40    /// The identity, for a `/Function` that could not be read: outputs are
41    /// the inputs, which keeps a broken shading visible rather than absent.
42    Identity,
43}
44
45/// One token of a type 4 program.
46#[derive(Debug, Clone)]
47pub enum PsOp {
48    Num(f64),
49    Op(&'static str),
50    /// `{ … }`: the index of the block in the flattened program, exclusive end.
51    Block(usize, usize),
52}
53
54impl Function {
55    /// Parse a `/Function` entry: a dictionary, a stream, or an array of them.
56    pub fn parse(doc: &Document, obj: &Object) -> Option<Function> {
57        Self::parse_depth(doc, obj, 0)
58    }
59
60    fn parse_depth(doc: &Document, obj: &Object, depth: usize) -> Option<Function> {
61        if depth > 8 {
62            return None;
63        }
64        let obj = deref(doc, obj);
65        if let Object::Array(a) = obj {
66            let fns: Vec<Function> = a
67                .iter()
68                .filter_map(|o| Self::parse_depth(doc, o, depth + 1))
69                .collect();
70            if fns.is_empty() {
71                return None;
72            }
73            return Some(Function::Array(fns));
74        }
75        let (dict, stream) = match obj {
76            Object::Dictionary(d) => (d, None),
77            Object::Stream(s) => (&s.dict, Some(s)),
78            _ => return None,
79        };
80        let get = |k: &[u8]| dict.get(k).ok().map(|o| deref(doc, o));
81        let getnums = |k: &[u8]| get(k).and_then(|o| nums(doc, o));
82        let ftype = get(b"FunctionType").and_then(|o| o.as_i64().ok())?;
83        let domain = getnums(b"Domain").unwrap_or_else(|| vec![0.0, 1.0]);
84        match ftype {
85            0 => {
86                let s = stream?;
87                let samples = s.decompressed_content().ok()?;
88                let size: Vec<usize> = getnums(b"Size")?
89                    .iter()
90                    .map(|v| (*v as i64).max(1) as usize)
91                    .collect();
92                let bps = get(b"BitsPerSample").and_then(|o| o.as_i64().ok())? as u32;
93                if !matches!(bps, 1 | 2 | 4 | 8 | 12 | 16 | 24 | 32) {
94                    return None;
95                }
96                let range = getnums(b"Range")?;
97                let n_out = range.len() / 2;
98                let m = size.len();
99                let mut encode = getnums(b"Encode").unwrap_or_default();
100                if encode.len() < 2 * m {
101                    encode = size
102                        .iter()
103                        .flat_map(|&s| [0.0, (s as f64 - 1.0).max(0.0)])
104                        .collect();
105                }
106                let mut decode = getnums(b"Decode").unwrap_or_default();
107                if decode.len() < 2 * n_out {
108                    decode = range.clone();
109                }
110                Some(Function::Sampled {
111                    domain,
112                    range,
113                    size,
114                    bps,
115                    encode,
116                    decode,
117                    samples,
118                    n_out,
119                })
120            }
121            2 => {
122                let c0 = getnums(b"C0").unwrap_or_else(|| vec![0.0]);
123                let c1 = getnums(b"C1").unwrap_or_else(|| vec![1.0]);
124                let n = get(b"N").and_then(num).unwrap_or(1.0);
125                Some(Function::Exponential { domain, c0, c1, n })
126            }
127            3 => {
128                let functions: Vec<Function> = get(b"Functions")
129                    .and_then(|o| o.as_array().ok())?
130                    .iter()
131                    .map(|o| Self::parse_depth(doc, o, depth + 1).unwrap_or(Function::Identity))
132                    .collect();
133                let bounds = getnums(b"Bounds").unwrap_or_default();
134                let encode = getnums(b"Encode").unwrap_or_default();
135                Some(Function::Stitching {
136                    domain,
137                    functions,
138                    bounds,
139                    encode,
140                })
141            }
142            4 => {
143                let s = stream?;
144                let src = s.decompressed_content().ok()?;
145                let program = parse_postscript(&src)?;
146                let range = getnums(b"Range").unwrap_or_default();
147                Some(Function::PostScript {
148                    domain,
149                    range,
150                    program,
151                })
152            }
153            _ => None,
154        }
155    }
156
157    /// Evaluate at `inputs`, clamped to the domain; the output count follows
158    /// the function (`Range`, `C0`, the array length …).
159    pub fn eval(&self, inputs: &[f64]) -> Vec<f64> {
160        match self {
161            Function::Identity => inputs.to_vec(),
162            Function::Array(fns) => fns
163                .iter()
164                .flat_map(|f| f.eval(inputs).into_iter().take(1))
165                .collect(),
166            Function::Exponential { domain, c0, c1, n } => {
167                let x = clamp_domain(inputs.first().copied().unwrap_or(0.0), domain, 0);
168                let t = if *n == 1.0 {
169                    x
170                } else {
171                    x.abs().powf(*n) * x.signum()
172                };
173                c0.iter()
174                    .zip(c1.iter().chain(std::iter::repeat(&1.0)))
175                    .map(|(a, b)| a + t * (b - a))
176                    .collect()
177            }
178            Function::Stitching {
179                domain,
180                functions,
181                bounds,
182                encode,
183            } => {
184                let x = clamp_domain(inputs.first().copied().unwrap_or(0.0), domain, 0);
185                let k = functions.len();
186                if k == 0 {
187                    return vec![0.0];
188                }
189                let mut i = 0;
190                while i < bounds.len() && i + 1 < k && x >= bounds[i] {
191                    i += 1;
192                }
193                let lo = if i == 0 { domain[0] } else { bounds[i - 1] };
194                let hi = if i >= bounds.len() {
195                    domain.get(1).copied().unwrap_or(1.0)
196                } else {
197                    bounds[i]
198                };
199                let e0 = encode.get(2 * i).copied().unwrap_or(0.0);
200                let e1 = encode.get(2 * i + 1).copied().unwrap_or(1.0);
201                let t = if hi > lo {
202                    e0 + (x - lo) / (hi - lo) * (e1 - e0)
203                } else {
204                    e0
205                };
206                functions[i].eval(&[t])
207            }
208            Function::Sampled {
209                domain,
210                range,
211                size,
212                bps,
213                encode,
214                decode,
215                samples,
216                n_out,
217            } => eval_sampled(
218                inputs, domain, range, size, *bps, encode, decode, samples, *n_out,
219            ),
220            Function::PostScript {
221                domain,
222                range,
223                program,
224            } => {
225                let mut stack: Vec<f64> = inputs
226                    .iter()
227                    .enumerate()
228                    .map(|(i, &v)| clamp_domain(v, domain, i))
229                    .collect();
230                exec_postscript(program, 0, program.len(), &mut stack, 0);
231                let n_out = range.len() / 2;
232                if n_out > 0 {
233                    let start = stack.len().saturating_sub(n_out);
234                    let mut out: Vec<f64> = stack[start..].to_vec();
235                    while out.len() < n_out {
236                        out.insert(0, 0.0);
237                    }
238                    for (i, v) in out.iter_mut().enumerate() {
239                        *v = v.clamp(
240                            range[2 * i].min(range[2 * i + 1]),
241                            range[2 * i].max(range[2 * i + 1]),
242                        );
243                    }
244                    out
245                } else {
246                    stack
247                }
248            }
249        }
250    }
251}
252
253fn clamp_domain(x: f64, domain: &[f64], i: usize) -> f64 {
254    match (domain.get(2 * i), domain.get(2 * i + 1)) {
255        (Some(&lo), Some(&hi)) if hi >= lo => x.clamp(lo, hi),
256        _ => x,
257    }
258}
259
260#[allow(clippy::too_many_arguments)]
261fn eval_sampled(
262    inputs: &[f64],
263    domain: &[f64],
264    range: &[f64],
265    size: &[usize],
266    bps: u32,
267    encode: &[f64],
268    decode: &[f64],
269    samples: &[u8],
270    n_out: usize,
271) -> Vec<f64> {
272    let m = size.len();
273    if m == 0 || n_out == 0 {
274        return vec![0.0; n_out.max(1)];
275    }
276    let max = ((1u64 << bps) - 1) as f64;
277    let sample_at = |idx: usize, j: usize| -> f64 {
278        let bit = (idx * n_out + j) as u64 * u64::from(bps);
279        let byte = (bit / 8) as usize;
280        let v: u64 = match bps {
281            8 => u64::from(*samples.get(byte).unwrap_or(&0)),
282            16 => {
283                let b = |k: usize| u64::from(*samples.get(byte + k).unwrap_or(&0));
284                (b(0) << 8) | b(1)
285            }
286            24 => {
287                let b = |k: usize| u64::from(*samples.get(byte + k).unwrap_or(&0));
288                (b(0) << 16) | (b(1) << 8) | b(2)
289            }
290            32 => {
291                let b = |k: usize| u64::from(*samples.get(byte + k).unwrap_or(&0));
292                (b(0) << 24) | (b(1) << 16) | (b(2) << 8) | b(3)
293            }
294            _ => {
295                // 1/2/4/12-bit: read bit by bit, MSB first.
296                let mut v = 0u64;
297                for k in 0..bps as u64 {
298                    let p = bit + k;
299                    let byte = *samples.get((p / 8) as usize).unwrap_or(&0);
300                    v = (v << 1) | u64::from((byte >> (7 - p % 8)) & 1);
301                }
302                v
303            }
304        };
305        v as f64 / max
306    };
307    // Multilinear interpolation over the first input dimension only for m == 1
308    // (the common case); nearest sample otherwise — mesh shadings and tint
309    // transforms are smooth enough that the difference is invisible.
310    let mut idx0 = 0usize;
311    let mut stride = 1usize;
312    let mut frac0 = 0.0;
313    let mut stride0 = 1usize;
314    for i in 0..m {
315        let x = clamp_domain(inputs.get(i).copied().unwrap_or(0.0), domain, i);
316        let (d0, d1) = (domain[2 * i], domain[2 * i + 1]);
317        let (e0, e1) = (encode[2 * i], encode[2 * i + 1]);
318        let e = if d1 > d0 {
319            e0 + (x - d0) * (e1 - e0) / (d1 - d0)
320        } else {
321            e0
322        };
323        let e = e.clamp(0.0, (size[i] as f64 - 1.0).max(0.0));
324        let fl = e.floor() as usize;
325        if i == 0 {
326            frac0 = e - fl as f64;
327            stride0 = stride;
328        }
329        idx0 += fl.min(size[i] - 1) * stride;
330        stride *= size[i];
331    }
332    let mut out = Vec::with_capacity(n_out);
333    for j in 0..n_out {
334        let s0 = sample_at(idx0, j);
335        let s = if frac0 > 0.0 && (idx0 / stride0) % size[0] + 1 < size[0] {
336            let s1 = sample_at(idx0 + stride0, j);
337            s0 + (s1 - s0) * frac0
338        } else {
339            s0
340        };
341        let (dmin, dmax) = (decode[2 * j], decode[2 * j + 1]);
342        let mut v = dmin + s * (dmax - dmin);
343        if let (Some(&r0), Some(&r1)) = (range.get(2 * j), range.get(2 * j + 1)) {
344            v = v.clamp(r0.min(r1), r0.max(r1));
345        }
346        out.push(v);
347    }
348    out
349}
350
351/// Tokenize a type 4 program into a flat list where `{ … }` blocks are
352/// recorded as `(start, end)` spans over the same list.
353fn parse_postscript(src: &[u8]) -> Option<Vec<PsOp>> {
354    let text = String::from_utf8_lossy(src);
355    let mut toks: Vec<String> = Vec::new();
356    let mut cur = String::new();
357    for ch in text.chars() {
358        match ch {
359            '{' | '}' => {
360                if !cur.is_empty() {
361                    toks.push(std::mem::take(&mut cur));
362                }
363                toks.push(ch.to_string());
364            }
365            c if c.is_whitespace() => {
366                if !cur.is_empty() {
367                    toks.push(std::mem::take(&mut cur));
368                }
369            }
370            c => cur.push(c),
371        }
372    }
373    if !cur.is_empty() {
374        toks.push(cur);
375    }
376    // Flatten: a block becomes PsOp::Block(start, end) placed where it appears,
377    // with its body appended after the whole enclosing sequence. Simpler: emit
378    // recursively into one vector where a block's body immediately follows a
379    // Block marker whose `end` skips it.
380    let mut out = Vec::new();
381    let mut pos = 0usize;
382    // The outermost `{ }` wraps the whole program.
383    if toks.first().map(String::as_str) == Some("{") {
384        pos = 1;
385    }
386    parse_ps_seq(&toks, &mut pos, &mut out, 0)?;
387    Some(out)
388}
389
390fn parse_ps_seq(toks: &[String], pos: &mut usize, out: &mut Vec<PsOp>, depth: usize) -> Option<()> {
391    if depth > 64 {
392        return None;
393    }
394    while *pos < toks.len() {
395        let t = toks[*pos].as_str();
396        *pos += 1;
397        match t {
398            "}" => return Some(()),
399            "{" => {
400                let marker = out.len();
401                out.push(PsOp::Block(0, 0));
402                parse_ps_seq(toks, pos, out, depth + 1)?;
403                let end = out.len();
404                out[marker] = PsOp::Block(marker + 1, end);
405            }
406            _ => {
407                if let Ok(v) = t.parse::<f64>() {
408                    out.push(PsOp::Num(v));
409                } else {
410                    out.push(PsOp::Op(ps_operator(t)?));
411                }
412            }
413        }
414    }
415    Some(())
416}
417
418fn ps_operator(t: &str) -> Option<&'static str> {
419    const OPS: &[&str] = &[
420        "abs", "add", "atan", "ceiling", "cos", "cvi", "cvr", "div", "exp", "floor", "idiv", "ln",
421        "log", "mod", "mul", "neg", "round", "sin", "sqrt", "sub", "truncate", "and", "bitshift",
422        "eq", "false", "ge", "gt", "le", "lt", "ne", "not", "or", "true", "xor", "if", "ifelse",
423        "copy", "dup", "exch", "index", "pop", "roll",
424    ];
425    OPS.iter().copied().find(|o| *o == t)
426}
427
428/// Execute `program[start..end]` on `stack`.
429fn exec_postscript(program: &[PsOp], start: usize, end: usize, stack: &mut Vec<f64>, depth: usize) {
430    if depth > 64 {
431        return;
432    }
433    let mut i = start;
434    // Pending procedure operands for `if` / `ifelse`.
435    let mut blocks: Vec<(usize, usize)> = Vec::new();
436    let pop = |s: &mut Vec<f64>| s.pop().unwrap_or(0.0);
437    while i < end {
438        if stack.len() > 1000 {
439            return;
440        }
441        match &program[i] {
442            PsOp::Num(v) => stack.push(*v),
443            PsOp::Block(s, e) => {
444                blocks.push((*s, *e));
445                i = *e;
446                continue;
447            }
448            PsOp::Op(op) => match *op {
449                "if" => {
450                    let cond = pop(stack) != 0.0;
451                    if let Some((s, e)) = blocks.pop() {
452                        if cond {
453                            exec_postscript(program, s, e, stack, depth + 1);
454                        }
455                    }
456                    blocks.clear();
457                }
458                "ifelse" => {
459                    let cond = pop(stack) != 0.0;
460                    let b2 = blocks.pop();
461                    let b1 = blocks.pop();
462                    if let (Some((s1, e1)), Some((s2, e2))) = (b1, b2) {
463                        if cond {
464                            exec_postscript(program, s1, e1, stack, depth + 1);
465                        } else {
466                            exec_postscript(program, s2, e2, stack, depth + 1);
467                        }
468                    }
469                    blocks.clear();
470                }
471                "abs" => {
472                    let a = pop(stack);
473                    stack.push(a.abs());
474                }
475                "add" => {
476                    let b = pop(stack);
477                    let a = pop(stack);
478                    stack.push(a + b);
479                }
480                "sub" => {
481                    let b = pop(stack);
482                    let a = pop(stack);
483                    stack.push(a - b);
484                }
485                "mul" => {
486                    let b = pop(stack);
487                    let a = pop(stack);
488                    stack.push(a * b);
489                }
490                "div" => {
491                    let b = pop(stack);
492                    let a = pop(stack);
493                    stack.push(if b != 0.0 { a / b } else { 0.0 });
494                }
495                "idiv" => {
496                    let b = pop(stack) as i64;
497                    let a = pop(stack) as i64;
498                    stack.push(if b != 0 { (a / b) as f64 } else { 0.0 });
499                }
500                "mod" => {
501                    let b = pop(stack) as i64;
502                    let a = pop(stack) as i64;
503                    stack.push(if b != 0 { (a % b) as f64 } else { 0.0 });
504                }
505                "neg" => {
506                    let a = pop(stack);
507                    stack.push(-a);
508                }
509                "atan" => {
510                    let den = pop(stack);
511                    let numr = pop(stack);
512                    let mut deg = numr.atan2(den).to_degrees();
513                    if deg < 0.0 {
514                        deg += 360.0;
515                    }
516                    stack.push(deg);
517                }
518                "ceiling" => {
519                    let a = pop(stack);
520                    stack.push(a.ceil());
521                }
522                "floor" => {
523                    let a = pop(stack);
524                    stack.push(a.floor());
525                }
526                "round" => {
527                    let a = pop(stack);
528                    stack.push(a.round());
529                }
530                "truncate" => {
531                    let a = pop(stack);
532                    stack.push(a.trunc());
533                }
534                "cos" => {
535                    let a = pop(stack);
536                    stack.push(a.to_radians().cos());
537                }
538                "sin" => {
539                    let a = pop(stack);
540                    stack.push(a.to_radians().sin());
541                }
542                "sqrt" => {
543                    let a = pop(stack);
544                    stack.push(a.max(0.0).sqrt());
545                }
546                "exp" => {
547                    let b = pop(stack);
548                    let a = pop(stack);
549                    stack.push(a.powf(b));
550                }
551                "ln" => {
552                    let a = pop(stack);
553                    stack.push(if a > 0.0 { a.ln() } else { 0.0 });
554                }
555                "log" => {
556                    let a = pop(stack);
557                    stack.push(if a > 0.0 { a.log10() } else { 0.0 });
558                }
559                "cvi" => {
560                    let a = pop(stack);
561                    stack.push(a.trunc());
562                }
563                "cvr" => {}
564                "dup" => {
565                    let a = stack.last().copied().unwrap_or(0.0);
566                    stack.push(a);
567                }
568                "pop" => {
569                    stack.pop();
570                }
571                "exch" => {
572                    let b = pop(stack);
573                    let a = pop(stack);
574                    stack.push(b);
575                    stack.push(a);
576                }
577                "copy" => {
578                    let n = pop(stack).max(0.0) as usize;
579                    let len = stack.len();
580                    if n <= len {
581                        for k in 0..n {
582                            stack.push(stack[len - n + k]);
583                        }
584                    }
585                }
586                "index" => {
587                    let n = pop(stack).max(0.0) as usize;
588                    let len = stack.len();
589                    let v = if n < len { stack[len - 1 - n] } else { 0.0 };
590                    stack.push(v);
591                }
592                "roll" => {
593                    let j = pop(stack) as i64;
594                    let n = pop(stack).max(0.0) as usize;
595                    let len = stack.len();
596                    if n > 0 && n <= len {
597                        let s = &mut stack[len - n..];
598                        let j = j.rem_euclid(n as i64) as usize;
599                        s.rotate_right(j);
600                    }
601                }
602                "eq" | "ne" | "gt" | "ge" | "lt" | "le" => {
603                    let b = pop(stack);
604                    let a = pop(stack);
605                    let r = match *op {
606                        "eq" => a == b,
607                        "ne" => a != b,
608                        "gt" => a > b,
609                        "ge" => a >= b,
610                        "lt" => a < b,
611                        _ => a <= b,
612                    };
613                    stack.push(if r { 1.0 } else { 0.0 });
614                }
615                "and" | "or" | "xor" => {
616                    let b = pop(stack) as i64;
617                    let a = pop(stack) as i64;
618                    let r = match *op {
619                        "and" => a & b,
620                        "or" => a | b,
621                        _ => a ^ b,
622                    };
623                    stack.push(r as f64);
624                }
625                "not" => {
626                    let a = pop(stack);
627                    // Boolean not on 0/1, bitwise on other integers.
628                    stack.push(if a == 0.0 {
629                        1.0
630                    } else if a == 1.0 {
631                        0.0
632                    } else {
633                        !(a as i64) as f64
634                    });
635                }
636                "bitshift" => {
637                    let s = pop(stack) as i64;
638                    let a = pop(stack) as i64;
639                    stack.push(if s >= 0 {
640                        a.checked_shl(s.min(63) as u32).unwrap_or(0) as f64
641                    } else {
642                        (a >> (-s).min(63)) as f64
643                    });
644                }
645                "true" => stack.push(1.0),
646                "false" => stack.push(0.0),
647                _ => {}
648            },
649        }
650        i += 1;
651    }
652}
653
654/// Convenience: a function's outputs for one input, as an `n`-vector padded /
655/// truncated to `n`.
656pub fn eval_n(f: &Function, t: f64, n: usize) -> Vec<f64> {
657    let mut v = f.eval(&[t]);
658    v.resize(n, 0.0);
659    v
660}
661
662/// `/Function` on a dictionary, if any.
663pub fn function_of(doc: &Document, dict: &Dictionary, key: &[u8]) -> Option<Function> {
664    dict.get(key).ok().and_then(|o| Function::parse(doc, o))
665}
666
667#[cfg(test)]
668mod tests {
669    use super::*;
670
671    fn ps(src: &str, inputs: &[f64], range: &[f64]) -> Vec<f64> {
672        let program = parse_postscript(src.as_bytes()).unwrap();
673        let f = Function::PostScript {
674            domain: vec![0.0, 1.0, 0.0, 1.0],
675            range: range.to_vec(),
676            program,
677        };
678        f.eval(inputs)
679    }
680
681    #[test]
682    fn postscript_calculator() {
683        assert_eq!(ps("{ add 2 div }", &[0.2, 0.6], &[0.0, 1.0]), vec![0.4]);
684        assert_eq!(
685            ps(
686                "{ dup 0.5 gt { pop 1 } { pop 0 } ifelse }",
687                &[0.7],
688                &[0.0, 1.0]
689            ),
690            vec![1.0]
691        );
692        assert_eq!(
693            ps(
694                "{ dup 0.5 gt { pop 1 } { pop 0 } ifelse }",
695                &[0.2],
696                &[0.0, 1.0]
697            ),
698            vec![0.0]
699        );
700        assert_eq!(ps("{ 1 exch sub }", &[0.25], &[0.0, 1.0]), vec![0.75]);
701        assert_eq!(
702            ps(
703                "{ 3 1 roll }",
704                &[0.1, 0.2, 0.3],
705                &[0.0, 1.0, 0.0, 1.0, 0.0, 1.0]
706            ),
707            vec![0.3, 0.1, 0.2]
708        );
709    }
710
711    #[test]
712    fn exponential_and_stitching() {
713        let f = Function::Exponential {
714            domain: vec![0.0, 1.0],
715            c0: vec![0.0, 0.0, 1.0],
716            c1: vec![1.0, 0.0, 0.0],
717            n: 1.0,
718        };
719        assert_eq!(f.eval(&[0.5]), vec![0.5, 0.0, 0.5]);
720        let st = Function::Stitching {
721            domain: vec![0.0, 1.0],
722            functions: vec![
723                Function::Exponential {
724                    domain: vec![0.0, 1.0],
725                    c0: vec![0.0],
726                    c1: vec![1.0],
727                    n: 1.0,
728                },
729                Function::Exponential {
730                    domain: vec![0.0, 1.0],
731                    c0: vec![1.0],
732                    c1: vec![0.0],
733                    n: 1.0,
734                },
735            ],
736            bounds: vec![0.5],
737            encode: vec![0.0, 1.0, 0.0, 1.0],
738        };
739        assert!((st.eval(&[0.25])[0] - 0.5).abs() < 1e-9);
740        assert!((st.eval(&[0.75])[0] - 0.5).abs() < 1e-9);
741        assert!((st.eval(&[0.5])[0] - 1.0).abs() < 1e-9);
742    }
743
744    #[test]
745    fn sampled_interpolates() {
746        let f = Function::Sampled {
747            domain: vec![0.0, 1.0],
748            range: vec![0.0, 1.0],
749            size: vec![3],
750            bps: 8,
751            encode: vec![0.0, 2.0],
752            decode: vec![0.0, 1.0],
753            samples: vec![0, 255, 0],
754            n_out: 1,
755        };
756        assert!((f.eval(&[0.25])[0] - 0.5).abs() < 0.01);
757        assert!((f.eval(&[0.5])[0] - 1.0).abs() < 0.01);
758    }
759}