1mod exponential;
24mod postscript;
25mod sampled;
26mod stitching;
27
28pub(crate) use sampled::BitReader;
29
30pub(crate) use exponential::Exponential;
31pub use postscript::{PostScript, parse_program};
32pub(crate) use sampled::Sampled;
33pub(crate) use stitching::Stitching;
34
35use crate::error::Error;
36use crate::names;
37use pdfrum_common::{DiagKind, Diagnostics, Limits, Severity};
38use pdfrum_object::{Dict, ObjRef, Object, Resolve};
39use std::collections::{HashMap, HashSet};
40use std::sync::Arc;
41
42pub const MAX_DEPTH: u32 = 32;
44
45pub const MAX_OUTPUTS: usize = 1024;
47
48#[derive(Debug, Clone, PartialEq)]
50#[non_exhaustive]
51pub enum Function {
52 Sampled(Sampled),
54 Exponential(Exponential),
56 Stitching(Stitching),
58 PostScript(PostScript),
60}
61
62impl Function {
63 #[must_use]
65 pub fn input_count(&self) -> usize {
66 self.domain().len() / 2
67 }
68
69 #[must_use]
71 pub fn output_count(&self) -> usize {
72 match self {
73 Self::Sampled(f) => f.outputs,
74 Self::Exponential(f) => f.outputs,
75 Self::Stitching(f) => f.outputs,
76 Self::PostScript(f) => f.outputs,
77 }
78 }
79
80 #[must_use]
82 pub fn domain(&self) -> &[f32] {
83 match self {
84 Self::Sampled(f) => &f.domain,
85 Self::Exponential(f) => &f.domain,
86 Self::Stitching(f) => &f.domain,
87 Self::PostScript(f) => &f.domain,
88 }
89 }
90
91 #[must_use]
96 pub fn range(&self) -> &[f32] {
97 match self {
98 Self::Sampled(f) => &f.range,
99 Self::Exponential(f) => &f.range,
100 Self::Stitching(f) => &f.range,
101 Self::PostScript(f) => &f.range,
102 }
103 }
104
105 pub fn eval(&self, input: &[f32], out: &mut [f32]) -> Result<(), Error> {
135 let inputs = self.input_count();
136 let outputs = self.output_count();
137 if input.len() != inputs || out.len() < outputs {
138 return Err(Error::FunctionArity {
139 expected: inputs,
140 got: input.len(),
141 outputs,
142 got_outputs: out.len(),
143 });
144 }
145 let mut clamped = Vec::with_capacity(inputs);
147 let domain = self.domain();
148 for (i, v) in input.iter().enumerate() {
149 let lo = domain.get(i * 2).copied().unwrap_or(0.0);
150 let hi = domain.get(i * 2 + 1).copied().unwrap_or(0.0);
151 if lo > hi {
152 return Err(Error::FunctionInterval);
153 }
154 clamped.push(v.clamp(lo, hi));
155 }
156
157 if !self.eval_raw(&clamped, out) {
158 return Err(Error::FunctionInterval);
159 }
160
161 let range = self.range();
163 if range.is_empty() {
164 return Ok(());
165 }
166 for i in 0..outputs {
167 let lo = range.get(i * 2).copied().unwrap_or(0.0);
168 let hi = range.get(i * 2 + 1).copied().unwrap_or(0.0);
169 if lo > hi {
170 return Err(Error::FunctionInterval);
171 }
172 if let Some(slot) = out.get_mut(i) {
173 *slot = slot.clamp(lo, hi);
174 }
175 }
176 Ok(())
177 }
178
179 #[must_use]
185 pub fn eval_into(&self, input: &[f32], out: &mut [f32]) -> usize {
186 match self.eval(input, out) {
187 Ok(()) => self.output_count(),
188 Err(_) => 0,
189 }
190 }
191
192 fn eval_raw(&self, input: &[f32], out: &mut [f32]) -> bool {
195 match self {
196 Self::Sampled(f) => f.eval(input, out),
197 Self::Exponential(f) => f.eval(input, out),
198 Self::Stitching(f) => f.eval(input, out),
199 Self::PostScript(f) => f.eval(input, out),
200 }
201 }
202}
203
204#[must_use]
207pub fn interpolate(x: f32, xmin: f32, xmax: f32, ymin: f32, ymax: f32) -> f32 {
208 let divisor = xmax - xmin;
209 if divisor == 0.0 {
210 return ymin;
211 }
212 ymin + (x - xmin) * (ymax - ymin) / divisor
213}
214
215#[derive(Debug, Default)]
218pub struct FunctionCache {
219 entries: HashMap<ObjRef, Option<Arc<Function>>>,
220 in_flight: HashSet<ObjRef>,
222}
223
224impl FunctionCache {
225 #[must_use]
227 pub fn new() -> Self {
228 Self::default()
229 }
230
231 #[must_use]
233 pub fn len(&self) -> usize {
234 self.entries.len()
235 }
236
237 #[must_use]
239 pub fn is_empty(&self) -> bool {
240 self.entries.is_empty()
241 }
242
243 #[must_use]
245 pub fn load<R: Resolve>(
246 &mut self,
247 obj: &Object,
248 r: &R,
249 limits: &Limits,
250 diags: &mut Diagnostics,
251 ) -> Option<Arc<Function>> {
252 let loaded = self.load_at(obj, r, limits, diags, 0);
253 if loaded.is_none() {
254 diags.record(Severity::Suspicious, DiagKind::FunctionUnsupported, None);
255 }
256 loaded
257 }
258
259 fn load_at<R: Resolve>(
260 &mut self,
261 obj: &Object,
262 r: &R,
263 limits: &Limits,
264 diags: &mut Diagnostics,
265 depth: u32,
266 ) -> Option<Arc<Function>> {
267 if depth > MAX_DEPTH {
268 return None;
269 }
270 let reference = obj.as_ref_id();
271 if let Some(id) = reference {
272 if let Some(hit) = self.entries.get(&id) {
273 return hit.clone();
274 }
275 if !self.in_flight.insert(id) {
278 return None;
279 }
280 }
281 let resolved = obj.resolve(r).ok();
282 let built = resolved
283 .as_deref()
284 .and_then(|direct| self.build(direct, r, limits, diags, depth))
285 .map(Arc::new);
286 if let Some(id) = reference {
287 self.in_flight.remove(&id);
288 self.entries.insert(id, built.clone());
289 }
290 built
291 }
292
293 fn build<R: Resolve>(
294 &mut self,
295 obj: &Object,
296 r: &R,
297 limits: &Limits,
298 diags: &mut Diagnostics,
299 depth: u32,
300 ) -> Option<Function> {
301 let dict = match obj {
302 Object::Dict(d) => d,
303 Object::Stream(s) => &s.dict,
304 _ => return None,
305 };
306 let kind = dict.int(names::FUNCTION_TYPE, r)?;
307 let common = Common::load(dict, r)?;
308 match kind {
309 0 => obj
310 .as_stream()
311 .and_then(|s| Sampled::load(s, &common, r, limits, diags))
312 .map(Function::Sampled),
313 2 => Exponential::load(dict, &common, r).map(Function::Exponential),
314 3 => Stitching::load(dict, &common, r, self, limits, diags, depth)
315 .map(Function::Stitching),
316 4 => obj
317 .as_stream()
318 .and_then(|s| PostScript::load(s, &common, r, limits, diags))
319 .map(Function::PostScript),
320 _ => None,
322 }
323 }
324}
325
326pub(crate) struct Common {
328 pub(crate) domain: Box<[f32]>,
329 pub(crate) range: Box<[f32]>,
330}
331
332impl Common {
333 fn load(dict: &Dict, r: &impl Resolve) -> Option<Self> {
336 let domain_array = dict.array(names::DOMAIN, r)?;
337 let inputs = domain_array.len() / 2;
338 if inputs == 0 {
339 return None;
340 }
341 let domain = (0..inputs * 2)
342 .map(|i| domain_array.number_at_or_zero(i))
343 .collect();
344 let range = match dict.array(names::RANGE, r) {
345 Some(a) => {
346 let outputs = a.len() / 2;
347 if outputs > MAX_OUTPUTS {
348 return None;
349 }
350 (0..outputs * 2).map(|i| a.number_at_or_zero(i)).collect()
351 }
352 None => Box::default(),
353 };
354 Some(Self { domain, range })
355 }
356
357 pub(crate) fn inputs(&self) -> usize {
358 self.domain.len() / 2
359 }
360
361 pub(crate) fn outputs(&self) -> usize {
362 self.range.len() / 2
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 #![allow(
372 clippy::unreadable_literal,
373 clippy::float_cmp,
374 clippy::indexing_slicing,
375 clippy::cast_precision_loss,
376 clippy::cast_possible_truncation,
377 reason = "test fixtures quote oracle vectors verbatim and compare exactly"
378 )]
379
380 use super::{Function, FunctionCache, interpolate};
381 use pdfrum_common::{Diagnostics, Limits};
382 use pdfrum_object::{Array, Dict, Name, NoResolve, Object};
383
384 pub(super) fn nums(values: &[f32]) -> Object {
385 Object::Array(Array::of(values.iter().copied().map(Object::Real)))
386 }
387
388 fn load(dict: Dict) -> Option<Function> {
389 let mut cache = FunctionCache::new();
390 let mut diags = Diagnostics::default();
391 cache
392 .load(
393 &Object::Dict(dict),
394 &NoResolve,
395 &Limits::default(),
396 &mut diags,
397 )
398 .map(|f| (*f).clone())
399 }
400
401 #[test]
402 fn interpolation_survives_a_degenerate_interval() {
403 assert!((interpolate(5.0, 0.0, 10.0, 0.0, 1.0) - 0.5).abs() < 1e-6);
404 assert!((interpolate(5.0, 3.0, 3.0, 7.0, 9.0) - 7.0).abs() < 1e-6);
406 }
407
408 #[test]
409 fn only_types_zero_two_three_and_four_load() {
410 for kind in [-2i64, 1, 5, 100] {
411 let dict = Dict::from_pairs([
412 (Name::from("FunctionType"), Object::Int(kind)),
413 (Name::from("Domain"), nums(&[0.0, 1.0])),
414 (Name::from("N"), Object::Int(1)),
415 ]);
416 assert!(load(dict).is_none(), "type {kind} should not load");
417 }
418 }
419
420 #[test]
421 fn domain_is_required_and_must_describe_an_input() {
422 let dict = Dict::from_pairs([
424 (Name::from("FunctionType"), Object::Int(2)),
425 (Name::from("N"), Object::Int(1)),
426 ]);
427 assert!(load(dict).is_none());
428
429 let dict = Dict::from_pairs([
431 (Name::from("FunctionType"), Object::Int(2)),
432 (Name::from("Domain"), nums(&[])),
433 (Name::from("N"), Object::Int(1)),
434 ]);
435 assert!(load(dict).is_none());
436
437 let dict = Dict::from_pairs([
439 (Name::from("FunctionType"), Object::Int(2)),
440 (Name::from("Domain"), nums(&[0.0])),
441 (Name::from("N"), Object::Int(1)),
442 ]);
443 assert!(load(dict).is_none());
444 }
445
446 #[test]
447 fn arity_mismatches_are_errors_not_panics() {
448 let dict = Dict::from_pairs([
449 (Name::from("FunctionType"), Object::Int(2)),
450 (Name::from("Domain"), nums(&[0.0, 1.0])),
451 (Name::from("N"), Object::Int(1)),
452 ]);
453 let f = load(dict).expect("a type 2 function");
454 let mut out = [0.0f32; 4];
455 assert!(f.eval(&[0.5, 0.5], &mut out).is_err());
456 assert_eq!(f.eval_into(&[0.5, 0.5], &mut out), 0);
457 assert!(f.eval(&[0.5], &mut []).is_err());
458 }
459}