1use crate::object::{PdfDict, PdfObject};
12
13#[derive(Debug, Clone)]
15pub enum PdfFunction {
16 Exponential {
18 domain: Vec<(f64, f64)>,
19 range: Vec<(f64, f64)>,
20 c0: Vec<f64>,
21 c1: Vec<f64>,
22 n: f64,
23 },
24 Stitching {
26 domain: Vec<(f64, f64)>,
27 range: Vec<(f64, f64)>,
28 functions: Vec<PdfFunction>,
29 bounds: Vec<f64>,
30 encode: Vec<f64>,
31 },
32 PostScript {
34 domain: Vec<(f64, f64)>,
35 range: Vec<(f64, f64)>,
36 ops: Vec<PsOp>,
37 },
38}
39
40#[derive(Debug, Clone)]
42pub enum PsOp {
43 Num(f64),
45 Add,
47 Sub,
48 Mul,
49 Div,
50 Idiv,
51 Mod,
52 Neg,
53 Abs,
54 Ceiling,
55 Floor,
56 Round,
57 Truncate,
58 Sqrt,
59 Exp,
60 Ln,
61 Log,
62 Sin,
63 Cos,
64 Atan,
65 Eq,
67 Ne,
68 Gt,
69 Ge,
70 Lt,
71 Le,
72 And,
74 Or,
75 Not,
76 Xor,
77 Bitshift,
79 If(Vec<PsOp>),
81 IfElse(Vec<PsOp>, Vec<PsOp>),
82 Dup,
84 Exch,
85 Pop,
86 Copy,
87 Index,
88 Roll,
89 Cvi,
91 Cvr,
92 True,
93 False,
94}
95
96impl PdfFunction {
97 pub fn parse(obj: &PdfObject) -> Option<Self> {
99 let dict = match obj {
100 PdfObject::Dict(d) => d,
101 PdfObject::Stream { dict, .. } => dict,
102 _ => return None,
103 };
104
105 let func_type = dict.get_i64(b"FunctionType")?;
106 let domain = parse_domain_range(dict, b"Domain");
107 let range = parse_domain_range(dict, b"Range");
108
109 match func_type {
110 2 => Self::parse_exponential(dict, domain, range),
111 3 => Self::parse_stitching(dict, domain, range),
112 4 => Self::parse_postscript(obj, domain, range),
113 _ => None,
114 }
115 }
116
117 fn parse_exponential(
118 dict: &PdfDict,
119 domain: Vec<(f64, f64)>,
120 range: Vec<(f64, f64)>,
121 ) -> Option<Self> {
122 let c0 = dict
123 .get_array(b"C0")
124 .map(|a| a.iter().filter_map(|o| o.as_f64()).collect())
125 .unwrap_or_else(|| vec![0.0]);
126 let c1 = dict
127 .get_array(b"C1")
128 .map(|a| a.iter().filter_map(|o| o.as_f64()).collect())
129 .unwrap_or_else(|| vec![1.0]);
130 let n = dict
131 .get(b"N")
132 .and_then(|o| o.as_f64())
133 .unwrap_or(1.0);
134
135 Some(PdfFunction::Exponential {
136 domain,
137 range,
138 c0,
139 c1,
140 n,
141 })
142 }
143
144 fn parse_stitching(
145 dict: &PdfDict,
146 domain: Vec<(f64, f64)>,
147 range: Vec<(f64, f64)>,
148 ) -> Option<Self> {
149 let bounds = dict
150 .get_array(b"Bounds")
151 .map(|a| a.iter().filter_map(|o| o.as_f64()).collect())
152 .unwrap_or_default();
153 let encode = dict
154 .get_array(b"Encode")
155 .map(|a| a.iter().filter_map(|o| o.as_f64()).collect())
156 .unwrap_or_default();
157
158 let functions: Vec<PdfFunction> = dict
159 .get_array(b"Functions")
160 .map(|arr| arr.iter().filter_map(|o| PdfFunction::parse(o)).collect())
161 .unwrap_or_default();
162
163 Some(PdfFunction::Stitching {
164 domain,
165 range,
166 functions,
167 bounds,
168 encode,
169 })
170 }
171
172 fn parse_postscript(
173 obj: &PdfObject,
174 domain: Vec<(f64, f64)>,
175 range: Vec<(f64, f64)>,
176 ) -> Option<Self> {
177 let stream_data = match obj {
178 PdfObject::Stream { data, .. } => data,
179 _ => return None,
180 };
181
182 let code = std::str::from_utf8(stream_data).ok()?;
183 let ops = parse_ps_code(code)?;
184
185 Some(PdfFunction::PostScript {
186 domain,
187 range,
188 ops,
189 })
190 }
191
192 pub fn evaluate(&self, input: &[f64]) -> Vec<f64> {
195 match self {
196 PdfFunction::Exponential {
197 domain,
198 range,
199 c0,
200 c1,
201 n,
202 } => {
203 let x = clamp_input(input.first().copied().unwrap_or(0.0), domain);
204 let out_len = c0.len().max(c1.len());
205 let mut result = Vec::with_capacity(out_len);
206 for i in 0..out_len {
207 let a = c0.get(i).copied().unwrap_or(0.0);
208 let b = c1.get(i).copied().unwrap_or(1.0);
209 let val = a + x.powf(*n) * (b - a);
210 result.push(clamp_output(val, range, i));
211 }
212 result
213 }
214 PdfFunction::Stitching {
215 domain,
216 range,
217 functions,
218 bounds,
219 encode,
220 } => {
221 if functions.is_empty() {
222 return vec![0.0];
223 }
224 let x = clamp_input(input.first().copied().unwrap_or(0.0), domain);
225
226 let mut idx = 0;
228 for (i, &b) in bounds.iter().enumerate() {
229 if x < b {
230 idx = i;
231 break;
232 }
233 idx = i + 1;
234 }
235 idx = idx.min(functions.len() - 1);
236
237 let sub_domain_start = bounds.get(idx.wrapping_sub(1)).copied().unwrap_or_else(|| {
239 domain.first().map(|d| d.0).unwrap_or(0.0)
240 });
241 let sub_domain_end = bounds.get(idx).copied().unwrap_or_else(|| {
242 domain.first().map(|d| d.1).unwrap_or(1.0)
243 });
244
245 let enc_start = encode.get(idx * 2).copied().unwrap_or(0.0);
246 let enc_end = encode.get(idx * 2 + 1).copied().unwrap_or(1.0);
247
248 let denom = sub_domain_end - sub_domain_start;
249 let encoded = if denom.abs() > 1e-10 {
250 enc_start + (x - sub_domain_start) / denom * (enc_end - enc_start)
251 } else {
252 enc_start
253 };
254
255 let result = functions[idx].evaluate(&[encoded]);
256 result
257 .iter()
258 .enumerate()
259 .map(|(i, &v)| clamp_output(v, range, i))
260 .collect()
261 }
262 PdfFunction::PostScript {
263 domain,
264 range,
265 ops,
266 } => {
267 let mut stack: Vec<f64> = Vec::new();
268 for (i, &val) in input.iter().enumerate() {
270 stack.push(clamp_input(val, &domain[i..i + 1].iter().copied().collect::<Vec<_>>()));
271 }
272
273 execute_ps_ops(&mut stack, ops);
274
275 stack
277 .iter()
278 .enumerate()
279 .map(|(i, &v)| clamp_output(v, range, i))
280 .collect()
281 }
282 }
283 }
284}
285
286fn clamp_input(x: f64, domain: &[(f64, f64)]) -> f64 {
287 if let Some(&(lo, hi)) = domain.first() {
288 x.clamp(lo, hi)
289 } else {
290 x.clamp(0.0, 1.0)
291 }
292}
293
294fn clamp_output(val: f64, range: &[(f64, f64)], idx: usize) -> f64 {
295 if let Some(&(lo, hi)) = range.get(idx) {
296 val.clamp(lo, hi)
297 } else {
298 val
299 }
300}
301
302fn parse_domain_range(dict: &PdfDict, key: &[u8]) -> Vec<(f64, f64)> {
303 dict.get_array(key)
304 .map(|arr| {
305 arr.chunks(2)
306 .map(|pair| {
307 let lo = pair.first().and_then(|o| o.as_f64()).unwrap_or(0.0);
308 let hi = pair.get(1).and_then(|o| o.as_f64()).unwrap_or(1.0);
309 (lo, hi)
310 })
311 .collect()
312 })
313 .unwrap_or_default()
314}
315
316fn parse_ps_code(code: &str) -> Option<Vec<PsOp>> {
321 let code = code.trim();
323 let code = if code.starts_with('{') && code.ends_with('}') {
324 &code[1..code.len() - 1]
325 } else {
326 code
327 };
328
329 parse_ps_block(code)
330}
331
332fn parse_ps_block(code: &str) -> Option<Vec<PsOp>> {
333 let mut ops = Vec::new();
334 let tokens = tokenize_ps(code);
335 let mut i = 0;
336
337 while i < tokens.len() {
338 let token = &tokens[i];
339 i += 1;
340
341 match token.as_str() {
342 "{" => {
343 let (block, end) = extract_block(&tokens, i)?;
345 i = end;
346
347 if i < tokens.len() && tokens[i] == "if" {
349 i += 1;
350 let block_ops = parse_ps_block(&block)?;
351 ops.push(PsOp::If(block_ops));
352 } else if i + 1 < tokens.len() && tokens[i] == "{" {
353 let (block2, end2) = extract_block(&tokens, i + 1)?;
355 if end2 < tokens.len() && tokens[end2] == "ifelse" {
356 let block1_ops = parse_ps_block(&block)?;
357 let block2_ops = parse_ps_block(&block2)?;
358 ops.push(PsOp::IfElse(block1_ops, block2_ops));
359 i = end2 + 1;
360 } else {
361 let block_ops = parse_ps_block(&block)?;
363 ops.extend(block_ops);
364 }
365 } else {
366 let block_ops = parse_ps_block(&block)?;
367 ops.extend(block_ops);
368 }
369 }
370 "add" => ops.push(PsOp::Add),
371 "sub" => ops.push(PsOp::Sub),
372 "mul" => ops.push(PsOp::Mul),
373 "div" => ops.push(PsOp::Div),
374 "idiv" => ops.push(PsOp::Idiv),
375 "mod" => ops.push(PsOp::Mod),
376 "neg" => ops.push(PsOp::Neg),
377 "abs" => ops.push(PsOp::Abs),
378 "ceiling" => ops.push(PsOp::Ceiling),
379 "floor" => ops.push(PsOp::Floor),
380 "round" => ops.push(PsOp::Round),
381 "truncate" => ops.push(PsOp::Truncate),
382 "sqrt" => ops.push(PsOp::Sqrt),
383 "exp" => ops.push(PsOp::Exp),
384 "ln" => ops.push(PsOp::Ln),
385 "log" => ops.push(PsOp::Log),
386 "sin" => ops.push(PsOp::Sin),
387 "cos" => ops.push(PsOp::Cos),
388 "atan" => ops.push(PsOp::Atan),
389 "eq" => ops.push(PsOp::Eq),
390 "ne" => ops.push(PsOp::Ne),
391 "gt" => ops.push(PsOp::Gt),
392 "ge" => ops.push(PsOp::Ge),
393 "lt" => ops.push(PsOp::Lt),
394 "le" => ops.push(PsOp::Le),
395 "and" => ops.push(PsOp::And),
396 "or" => ops.push(PsOp::Or),
397 "not" => ops.push(PsOp::Not),
398 "xor" => ops.push(PsOp::Xor),
399 "bitshift" => ops.push(PsOp::Bitshift),
400 "dup" => ops.push(PsOp::Dup),
401 "exch" => ops.push(PsOp::Exch),
402 "pop" => ops.push(PsOp::Pop),
403 "copy" => ops.push(PsOp::Copy),
404 "index" => ops.push(PsOp::Index),
405 "roll" => ops.push(PsOp::Roll),
406 "cvi" => ops.push(PsOp::Cvi),
407 "cvr" => ops.push(PsOp::Cvr),
408 "true" => ops.push(PsOp::True),
409 "false" => ops.push(PsOp::False),
410 "if" | "ifelse" => {} _ => {
412 if let Ok(n) = token.parse::<f64>() {
414 ops.push(PsOp::Num(n));
415 }
416 }
418 }
419 }
420
421 Some(ops)
422}
423
424fn tokenize_ps(code: &str) -> Vec<String> {
425 let mut tokens = Vec::new();
426 let mut current = String::new();
427
428 for ch in code.chars() {
429 match ch {
430 '{' | '}' => {
431 if !current.is_empty() {
432 tokens.push(std::mem::take(&mut current));
433 }
434 tokens.push(ch.to_string());
435 }
436 ' ' | '\t' | '\n' | '\r' => {
437 if !current.is_empty() {
438 tokens.push(std::mem::take(&mut current));
439 }
440 }
441 _ => current.push(ch),
442 }
443 }
444 if !current.is_empty() {
445 tokens.push(current);
446 }
447
448 tokens
449}
450
451fn extract_block(tokens: &[String], start: usize) -> Option<(String, usize)> {
454 let mut depth = 1;
455 let mut i = start;
456 let mut parts = Vec::new();
457
458 while i < tokens.len() && depth > 0 {
459 if tokens[i] == "{" {
460 depth += 1;
461 parts.push(tokens[i].clone());
462 } else if tokens[i] == "}" {
463 depth -= 1;
464 if depth > 0 {
465 parts.push(tokens[i].clone());
466 }
467 } else {
468 parts.push(tokens[i].clone());
469 }
470 i += 1;
471 }
472
473 Some((parts.join(" "), i))
474}
475
476fn execute_ps_ops(stack: &mut Vec<f64>, ops: &[PsOp]) {
481 for op in ops {
482 match op {
483 PsOp::Num(n) => stack.push(*n),
484 PsOp::True => stack.push(1.0),
485 PsOp::False => stack.push(0.0),
486
487 PsOp::Add => {
489 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
490 stack.push(a + b);
491 }
492 }
493 PsOp::Sub => {
494 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
495 stack.push(a - b);
496 }
497 }
498 PsOp::Mul => {
499 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
500 stack.push(a * b);
501 }
502 }
503 PsOp::Div => {
504 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
505 stack.push(if b.abs() > 1e-20 { a / b } else { 0.0 });
506 }
507 }
508 PsOp::Idiv => {
509 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
510 let bi = b as i64;
511 let ai = a as i64;
512 stack.push(if bi != 0 { (ai / bi) as f64 } else { 0.0 });
513 }
514 }
515 PsOp::Mod => {
516 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
517 let bi = b as i64;
518 let ai = a as i64;
519 stack.push(if bi != 0 { (ai % bi) as f64 } else { 0.0 });
520 }
521 }
522 PsOp::Neg => {
523 if let Some(a) = stack.pop() {
524 stack.push(-a);
525 }
526 }
527 PsOp::Abs => {
528 if let Some(a) = stack.pop() {
529 stack.push(a.abs());
530 }
531 }
532 PsOp::Ceiling => {
533 if let Some(a) = stack.pop() {
534 stack.push(a.ceil());
535 }
536 }
537 PsOp::Floor => {
538 if let Some(a) = stack.pop() {
539 stack.push(a.floor());
540 }
541 }
542 PsOp::Round => {
543 if let Some(a) = stack.pop() {
544 stack.push(a.round());
545 }
546 }
547 PsOp::Truncate => {
548 if let Some(a) = stack.pop() {
549 stack.push(a.trunc());
550 }
551 }
552 PsOp::Sqrt => {
553 if let Some(a) = stack.pop() {
554 stack.push(if a >= 0.0 { a.sqrt() } else { 0.0 });
555 }
556 }
557 PsOp::Exp => {
558 if let (Some(e), Some(base)) = (stack.pop(), stack.pop()) {
559 stack.push(base.powf(e));
560 }
561 }
562 PsOp::Ln => {
563 if let Some(a) = stack.pop() {
564 stack.push(if a > 0.0 { a.ln() } else { 0.0 });
565 }
566 }
567 PsOp::Log => {
568 if let Some(a) = stack.pop() {
569 stack.push(if a > 0.0 { a.log10() } else { 0.0 });
570 }
571 }
572 PsOp::Sin => {
573 if let Some(a) = stack.pop() {
574 stack.push(a.to_radians().sin());
575 }
576 }
577 PsOp::Cos => {
578 if let Some(a) = stack.pop() {
579 stack.push(a.to_radians().cos());
580 }
581 }
582 PsOp::Atan => {
583 if let (Some(x), Some(y)) = (stack.pop(), stack.pop()) {
584 stack.push(y.atan2(x).to_degrees());
585 }
586 }
587
588 PsOp::Eq => {
590 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
591 stack.push(if (a - b).abs() < 1e-10 { 1.0 } else { 0.0 });
592 }
593 }
594 PsOp::Ne => {
595 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
596 stack.push(if (a - b).abs() >= 1e-10 { 1.0 } else { 0.0 });
597 }
598 }
599 PsOp::Gt => {
600 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
601 stack.push(if a > b { 1.0 } else { 0.0 });
602 }
603 }
604 PsOp::Ge => {
605 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
606 stack.push(if a >= b { 1.0 } else { 0.0 });
607 }
608 }
609 PsOp::Lt => {
610 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
611 stack.push(if a < b { 1.0 } else { 0.0 });
612 }
613 }
614 PsOp::Le => {
615 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
616 stack.push(if a <= b { 1.0 } else { 0.0 });
617 }
618 }
619
620 PsOp::And => {
622 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
623 stack.push(((a as i64) & (b as i64)) as f64);
624 }
625 }
626 PsOp::Or => {
627 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
628 stack.push(((a as i64) | (b as i64)) as f64);
629 }
630 }
631 PsOp::Not => {
632 if let Some(a) = stack.pop() {
633 stack.push(if a == 0.0 { 1.0 } else { 0.0 });
634 }
635 }
636 PsOp::Xor => {
637 if let (Some(b), Some(a)) = (stack.pop(), stack.pop()) {
638 stack.push(((a as i64) ^ (b as i64)) as f64);
639 }
640 }
641 PsOp::Bitshift => {
642 if let (Some(shift), Some(val)) = (stack.pop(), stack.pop()) {
643 let v = val as i64;
644 let s = shift as i32;
645 let result = if s > 0 { v << s } else { v >> (-s) };
646 stack.push(result as f64);
647 }
648 }
649
650 PsOp::If(block) => {
652 if let Some(cond) = stack.pop() {
653 if cond != 0.0 {
654 execute_ps_ops(stack, block);
655 }
656 }
657 }
658 PsOp::IfElse(true_block, false_block) => {
659 if let Some(cond) = stack.pop() {
660 if cond != 0.0 {
661 execute_ps_ops(stack, true_block);
662 } else {
663 execute_ps_ops(stack, false_block);
664 }
665 }
666 }
667
668 PsOp::Dup => {
670 if let Some(&top) = stack.last() {
671 stack.push(top);
672 }
673 }
674 PsOp::Exch => {
675 let len = stack.len();
676 if len >= 2 {
677 stack.swap(len - 1, len - 2);
678 }
679 }
680 PsOp::Pop => {
681 stack.pop();
682 }
683 PsOp::Copy => {
684 if let Some(n) = stack.pop() {
685 let n = n as usize;
686 let len = stack.len();
687 if n <= len {
688 let start = len - n;
689 let copied: Vec<f64> = stack[start..].to_vec();
690 stack.extend_from_slice(&copied);
691 }
692 }
693 }
694 PsOp::Index => {
695 if let Some(n) = stack.pop() {
696 let n = n as usize;
697 let len = stack.len();
698 if n < len {
699 let val = stack[len - 1 - n];
700 stack.push(val);
701 }
702 }
703 }
704 PsOp::Roll => {
705 if let (Some(j), Some(n)) = (stack.pop(), stack.pop()) {
706 let n = n as usize;
707 let j = j as i64;
708 let len = stack.len();
709 if n > 0 && n <= len {
710 let start = len - n;
711 let j = ((j % n as i64) + n as i64) as usize % n;
712 let split = n - j;
713 let mut temp: Vec<f64> = stack[start..].to_vec();
714 temp.rotate_left(split);
715 stack[start..].copy_from_slice(&temp);
716 }
717 }
718 }
719
720 PsOp::Cvi => {
722 if let Some(a) = stack.pop() {
723 stack.push((a as i64) as f64);
724 }
725 }
726 PsOp::Cvr => {
727 }
729 }
730 }
731}
732
733#[cfg(test)]
734mod tests {
735 use super::*;
736
737 #[test]
738 fn test_ps_simple_add() {
739 let func = PdfFunction::PostScript {
740 domain: vec![(0.0, 1.0)],
741 range: vec![(0.0, 2.0)],
742 ops: vec![PsOp::Num(1.0), PsOp::Add],
743 };
744 let result = func.evaluate(&[0.5]);
745 assert!((result[0] - 1.5).abs() < 0.001);
746 }
747
748 #[test]
749 fn test_ps_mul_sub() {
750 let func = PdfFunction::PostScript {
751 domain: vec![(0.0, 1.0)],
752 range: vec![(0.0, 1.0)],
753 ops: vec![PsOp::Num(2.0), PsOp::Mul, PsOp::Num(0.5), PsOp::Sub],
754 };
755 let result = func.evaluate(&[0.75]);
757 assert!((result[0] - 1.0).abs() < 0.001);
758 }
759
760 #[test]
761 fn test_ps_dup() {
762 let func = PdfFunction::PostScript {
763 domain: vec![(0.0, 1.0)],
764 range: vec![(0.0, 1.0), (0.0, 1.0)],
765 ops: vec![PsOp::Dup],
766 };
767 let result = func.evaluate(&[0.7]);
768 assert_eq!(result.len(), 2);
769 assert!((result[0] - 0.7).abs() < 0.001);
770 assert!((result[1] - 0.7).abs() < 0.001);
771 }
772
773 #[test]
774 fn test_ps_if() {
775 let func = PdfFunction::PostScript {
777 domain: vec![(0.0, 1.0)],
778 range: vec![(0.0, 1.0)],
779 ops: vec![
780 PsOp::Dup,
781 PsOp::Num(0.5),
782 PsOp::Gt,
783 PsOp::IfElse(vec![PsOp::Pop, PsOp::Num(1.0)], vec![PsOp::Pop, PsOp::Num(0.0)]),
784 ],
785 };
786 assert!((func.evaluate(&[0.8])[0] - 1.0).abs() < 0.001);
787 assert!((func.evaluate(&[0.3])[0] - 0.0).abs() < 0.001);
788 }
789
790 #[test]
791 fn test_ps_neg_abs() {
792 let func = PdfFunction::PostScript {
793 domain: vec![(-1.0, 1.0)],
794 range: vec![(0.0, 1.0)],
795 ops: vec![PsOp::Neg, PsOp::Abs],
796 };
797 assert!((func.evaluate(&[-0.5])[0] - 0.5).abs() < 0.001);
798 assert!((func.evaluate(&[0.3])[0] - 0.3).abs() < 0.001);
799 }
800
801 #[test]
802 fn test_exponential() {
803 let func = PdfFunction::Exponential {
804 domain: vec![(0.0, 1.0)],
805 range: vec![(0.0, 1.0)],
806 c0: vec![0.0],
807 c1: vec![1.0],
808 n: 2.0,
809 };
810 assert!((func.evaluate(&[0.5])[0] - 0.25).abs() < 0.001);
812 }
813
814 #[test]
815 fn test_parse_ps_code() {
816 let code = "{ 1 add 2 mul }";
817 let ops = parse_ps_code(code).unwrap();
818 assert_eq!(ops.len(), 4); }
820
821 #[test]
822 fn test_ps_trig() {
823 let func = PdfFunction::PostScript {
824 domain: vec![(0.0, 360.0)],
825 range: vec![(-1.0, 1.0)],
826 ops: vec![PsOp::Sin],
827 };
828 assert!((func.evaluate(&[90.0])[0] - 1.0).abs() < 0.001);
830 assert!((func.evaluate(&[0.0])[0] - 0.0).abs() < 0.001);
832 }
833
834 #[test]
835 fn test_ps_exch_roll() {
836 let func = PdfFunction::PostScript {
837 domain: vec![(0.0, 1.0), (0.0, 1.0)],
838 range: vec![(0.0, 1.0), (0.0, 1.0)],
839 ops: vec![PsOp::Exch],
840 };
841 let result = func.evaluate(&[0.2, 0.8]);
842 assert!((result[0] - 0.8).abs() < 0.001);
843 assert!((result[1] - 0.2).abs() < 0.001);
844 }
845}