Skip to main content

pdf_interpret/function/
mod.rs

1//! PDF functions.
2//!
3//! PDF has the concept of functions, representing objects that take a certain number of values
4//! as input, do some processing on them and then return some output.
5
6mod type0;
7mod type2;
8mod type3;
9mod type4;
10
11use crate::WarningSinkFn;
12use crate::function::type0::Type0;
13use crate::function::type2::Type2;
14use crate::function::type3::Type3;
15use crate::function::type4::Type4;
16use log::warn;
17use pdf_syntax::object::Dict;
18use pdf_syntax::object::dict::keys::{DOMAIN, FUNCTION_TYPE, RANGE};
19use pdf_syntax::object::{Object, dict_or_stream};
20use smallvec::SmallVec;
21use std::sync::Arc;
22
23/// The input/output type of functions.
24pub(crate) type Values = SmallVec<[f32; 4]>;
25type TupleVec = SmallVec<[(f32, f32); 4]>;
26
27#[derive(Debug)]
28enum FunctionType {
29    Type0(Type0),
30    Type2(Type2),
31    Type3(Type3),
32    Type4(Type4),
33}
34
35/// A PDF function.
36#[derive(Debug, Clone)]
37pub struct Function(Arc<FunctionType>);
38
39impl Function {
40    /// Create a new function.
41    pub fn new(obj: &Object<'_>) -> Option<Self> {
42        let no_op: WarningSinkFn = std::sync::Arc::new(|_: crate::InterpreterWarning| {});
43        Self::new_with_sink(obj, &no_op)
44    }
45
46    pub(crate) fn new_with_sink(obj: &Object<'_>, warning_sink: &WarningSinkFn) -> Option<Self> {
47        let (dict, stream) = dict_or_stream(obj)?;
48
49        let function_type = match dict.get::<u8>(FUNCTION_TYPE)? {
50            0 => FunctionType::Type0(Type0::new(&stream?, warning_sink)?),
51            2 => FunctionType::Type2(Type2::new(&dict)?),
52            3 => FunctionType::Type3(Type3::new(&dict)?),
53            4 => FunctionType::Type4(Type4::new(&stream?, warning_sink)?),
54            _ => return None,
55        };
56
57        Some(Self(Arc::new(function_type)))
58    }
59
60    /// Evaluate the function with the given input.
61    pub fn eval(&self, input: Values) -> Option<Values> {
62        match self.0.as_ref() {
63            FunctionType::Type0(t0) => t0.eval(input),
64            FunctionType::Type2(t2) => Some(t2.eval(*input.first()?)),
65            FunctionType::Type3(t3) => t3.eval(*input.first()?),
66            FunctionType::Type4(t4) => Some(t4.eval(input)?),
67        }
68    }
69}
70
71#[derive(Debug, Clone)]
72struct Clamper {
73    domain: TupleVec,
74    range: Option<TupleVec>,
75}
76
77impl Clamper {
78    fn new(dict: &Dict<'_>) -> Option<Self> {
79        let domain = dict.get::<TupleVec>(DOMAIN)?;
80        let range = dict.get::<TupleVec>(RANGE);
81
82        Some(Self { domain, range })
83    }
84
85    fn clamp_input(&self, input: &mut [f32]) {
86        if input.len() != self.domain.len() {
87            warn!("the domain of the function didn't match the input arguments");
88        }
89
90        for ((min, max), val) in self.domain.iter().zip(input.iter_mut()) {
91            *val = val.min(*max).max(*min);
92        }
93    }
94
95    fn clamp_output(&self, output: &mut [f32]) {
96        if let Some(range) = &self.range {
97            if range.len() != output.len() {
98                warn!("the range of the function didn't match the output arguments");
99            }
100
101            for ((min, max), val) in range.iter().zip(output.iter_mut()) {
102                *val = val.min(*max).max(*min);
103            }
104        }
105    }
106}
107
108/// Linearly interpolate the value `x`, assuming that it lies within the range `x_min` and `x_max`,
109/// to the range `y_min` and `y_max`.
110pub(crate) fn interpolate(x: f32, x_min: f32, x_max: f32, y_min: f32, y_max: f32) -> f32 {
111    y_min + (x - x_min) * (y_max - y_min) / (x_max - x_min)
112}