1use pdfplumber_core::geometry::Ctm;
8use pdfplumber_core::painting::{Color, GraphicsState};
9
10use crate::color_space::ResolvedColorSpace;
11
12#[derive(Debug, Clone)]
18pub struct InterpreterState {
19 ctm: Ctm,
21 graphics_state: GraphicsState,
23 stack: Vec<SavedState>,
25 stroking_color_space: Option<ResolvedColorSpace>,
27 non_stroking_color_space: Option<ResolvedColorSpace>,
29}
30
31impl PartialEq for InterpreterState {
32 fn eq(&self, other: &Self) -> bool {
33 self.ctm == other.ctm && self.graphics_state == other.graphics_state
34 }
35}
36
37#[derive(Debug, Clone)]
39struct SavedState {
40 ctm: Ctm,
41 graphics_state: GraphicsState,
42 stroking_color_space: Option<ResolvedColorSpace>,
43 non_stroking_color_space: Option<ResolvedColorSpace>,
44}
45
46impl Default for InterpreterState {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl InterpreterState {
53 pub fn new() -> Self {
55 Self {
56 ctm: Ctm::identity(),
57 graphics_state: GraphicsState::default(),
58 stack: Vec::new(),
59 stroking_color_space: None,
60 non_stroking_color_space: None,
61 }
62 }
63
64 pub fn ctm(&self) -> &Ctm {
66 &self.ctm
67 }
68
69 pub fn ctm_array(&self) -> [f64; 6] {
71 [
72 self.ctm.a, self.ctm.b, self.ctm.c, self.ctm.d, self.ctm.e, self.ctm.f,
73 ]
74 }
75
76 pub fn graphics_state(&self) -> &GraphicsState {
78 &self.graphics_state
79 }
80
81 pub fn graphics_state_mut(&mut self) -> &mut GraphicsState {
83 &mut self.graphics_state
84 }
85
86 pub fn stack_depth(&self) -> usize {
88 self.stack.len()
89 }
90
91 pub fn save_state(&mut self) {
95 self.stack.push(SavedState {
96 ctm: self.ctm,
97 graphics_state: self.graphics_state.clone(),
98 stroking_color_space: self.stroking_color_space.clone(),
99 non_stroking_color_space: self.non_stroking_color_space.clone(),
100 });
101 }
102
103 pub fn restore_state(&mut self) -> bool {
107 if let Some(saved) = self.stack.pop() {
108 self.ctm = saved.ctm;
109 self.graphics_state = saved.graphics_state;
110 self.stroking_color_space = saved.stroking_color_space;
111 self.non_stroking_color_space = saved.non_stroking_color_space;
112 true
113 } else {
114 false
115 }
116 }
117
118 pub fn concat_matrix(&mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
125 let new_matrix = Ctm::new(a, b, c, d, e, f);
126 self.ctm = new_matrix.concat(&self.ctm);
127 }
128
129 pub fn set_line_width(&mut self, width: f64) {
133 self.graphics_state.line_width = width;
134 }
135
136 pub fn set_dash_pattern(&mut self, dash_array: Vec<f64>, dash_phase: f64) {
140 self.graphics_state.set_dash_pattern(dash_array, dash_phase);
141 }
142
143 pub fn set_stroking_gray(&mut self, gray: f32) {
147 self.stroking_color_space = None;
148 self.graphics_state.stroke_color = Color::Gray(gray);
149 }
150
151 pub fn set_non_stroking_gray(&mut self, gray: f32) {
153 self.non_stroking_color_space = None;
154 self.graphics_state.fill_color = Color::Gray(gray);
155 }
156
157 pub fn set_stroking_rgb(&mut self, r: f32, g: f32, b: f32) {
159 self.stroking_color_space = None;
160 self.graphics_state.stroke_color = Color::Rgb(r, g, b);
161 }
162
163 pub fn set_non_stroking_rgb(&mut self, r: f32, g: f32, b: f32) {
165 self.non_stroking_color_space = None;
166 self.graphics_state.fill_color = Color::Rgb(r, g, b);
167 }
168
169 pub fn set_stroking_cmyk(&mut self, c: f32, m: f32, y: f32, k: f32) {
171 self.stroking_color_space = None;
172 self.graphics_state.stroke_color = Color::Cmyk(c, m, y, k);
173 }
174
175 pub fn set_non_stroking_cmyk(&mut self, c: f32, m: f32, y: f32, k: f32) {
177 self.non_stroking_color_space = None;
178 self.graphics_state.fill_color = Color::Cmyk(c, m, y, k);
179 }
180
181 pub fn set_stroking_color(&mut self, components: &[f32]) {
186 self.graphics_state.stroke_color = if let Some(ref cs) = self.stroking_color_space {
187 cs.resolve_color(components)
188 } else {
189 color_from_components(components)
190 };
191 }
192
193 pub fn set_non_stroking_color(&mut self, components: &[f32]) {
198 self.graphics_state.fill_color = if let Some(ref cs) = self.non_stroking_color_space {
199 cs.resolve_color(components)
200 } else {
201 color_from_components(components)
202 };
203 }
204
205 pub fn set_stroking_color_space(&mut self, cs: ResolvedColorSpace) {
207 self.stroking_color_space = Some(cs);
208 }
209
210 pub fn set_non_stroking_color_space(&mut self, cs: ResolvedColorSpace) {
212 self.non_stroking_color_space = Some(cs);
213 }
214}
215
216fn color_from_components(components: &[f32]) -> Color {
218 match components.len() {
219 1 => Color::Gray(components[0]),
220 3 => Color::Rgb(components[0], components[1], components[2]),
221 4 => Color::Cmyk(components[0], components[1], components[2], components[3]),
222 _ => Color::Other(components.to_vec()),
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use pdfplumber_core::geometry::Point;
230 use pdfplumber_core::painting::DashPattern;
231
232 #[test]
235 fn test_new_has_identity_ctm() {
236 let state = InterpreterState::new();
237 assert_eq!(*state.ctm(), Ctm::identity());
238 }
239
240 #[test]
241 fn test_new_has_default_graphics_state() {
242 let state = InterpreterState::new();
243 let gs = state.graphics_state();
244 assert_eq!(gs.line_width, 1.0);
245 assert_eq!(gs.stroke_color, Color::black());
246 assert_eq!(gs.fill_color, Color::black());
247 assert!(gs.dash_pattern.is_solid());
248 assert_eq!(gs.stroke_alpha, 1.0);
249 assert_eq!(gs.fill_alpha, 1.0);
250 }
251
252 #[test]
253 fn test_new_has_empty_stack() {
254 let state = InterpreterState::new();
255 assert_eq!(state.stack_depth(), 0);
256 }
257
258 #[test]
259 fn test_default_equals_new() {
260 assert_eq!(InterpreterState::default(), InterpreterState::new());
261 }
262
263 #[test]
264 fn test_ctm_array() {
265 let state = InterpreterState::new();
266 assert_eq!(state.ctm_array(), [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
267 }
268
269 #[test]
272 fn test_save_state_increments_depth() {
273 let mut state = InterpreterState::new();
274 state.save_state();
275 assert_eq!(state.stack_depth(), 1);
276 state.save_state();
277 assert_eq!(state.stack_depth(), 2);
278 }
279
280 #[test]
281 fn test_restore_state_decrements_depth() {
282 let mut state = InterpreterState::new();
283 state.save_state();
284 state.save_state();
285 assert_eq!(state.stack_depth(), 2);
286
287 assert!(state.restore_state());
288 assert_eq!(state.stack_depth(), 1);
289
290 assert!(state.restore_state());
291 assert_eq!(state.stack_depth(), 0);
292 }
293
294 #[test]
295 fn test_restore_on_empty_stack_returns_false() {
296 let mut state = InterpreterState::new();
297 assert!(!state.restore_state());
298 }
299
300 #[test]
301 fn test_save_restore_preserves_ctm() {
302 let mut state = InterpreterState::new();
303
304 state.save_state();
306 state.concat_matrix(2.0, 0.0, 0.0, 2.0, 10.0, 20.0);
307 assert_ne!(*state.ctm(), Ctm::identity());
308
309 state.restore_state();
311 assert_eq!(*state.ctm(), Ctm::identity());
312 }
313
314 #[test]
315 fn test_save_restore_preserves_graphics_state() {
316 let mut state = InterpreterState::new();
317
318 state.save_state();
320 state.set_line_width(5.0);
321 state.set_stroking_rgb(1.0, 0.0, 0.0);
322 state.set_non_stroking_gray(0.5);
323 state.set_dash_pattern(vec![3.0, 2.0], 1.0);
324
325 assert_eq!(state.graphics_state().line_width, 5.0);
327 assert_eq!(
328 state.graphics_state().stroke_color,
329 Color::Rgb(1.0, 0.0, 0.0)
330 );
331 assert_eq!(state.graphics_state().fill_color, Color::Gray(0.5));
332
333 state.restore_state();
335 assert_eq!(state.graphics_state().line_width, 1.0);
336 assert_eq!(state.graphics_state().stroke_color, Color::black());
337 assert_eq!(state.graphics_state().fill_color, Color::black());
338 assert!(state.graphics_state().dash_pattern.is_solid());
339 }
340
341 #[test]
342 fn test_nested_save_restore() {
343 let mut state = InterpreterState::new();
344
345 state.set_stroking_rgb(1.0, 0.0, 0.0);
347
348 state.save_state();
350
351 state.set_stroking_rgb(0.0, 0.0, 1.0);
353 assert_eq!(
354 state.graphics_state().stroke_color,
355 Color::Rgb(0.0, 0.0, 1.0)
356 );
357
358 state.save_state();
360
361 state.set_stroking_rgb(0.0, 1.0, 0.0);
363 assert_eq!(
364 state.graphics_state().stroke_color,
365 Color::Rgb(0.0, 1.0, 0.0)
366 );
367
368 state.restore_state();
370 assert_eq!(
371 state.graphics_state().stroke_color,
372 Color::Rgb(0.0, 0.0, 1.0)
373 );
374
375 state.restore_state();
377 assert_eq!(
378 state.graphics_state().stroke_color,
379 Color::Rgb(1.0, 0.0, 0.0)
380 );
381 }
382
383 #[test]
386 fn test_concat_matrix_translation() {
387 let mut state = InterpreterState::new();
388 state.concat_matrix(1.0, 0.0, 0.0, 1.0, 100.0, 200.0);
389
390 let p = state.ctm().transform_point(Point::new(0.0, 0.0));
391 assert_approx(p.x, 100.0);
392 assert_approx(p.y, 200.0);
393 }
394
395 #[test]
396 fn test_concat_matrix_scaling() {
397 let mut state = InterpreterState::new();
398 state.concat_matrix(2.0, 0.0, 0.0, 3.0, 0.0, 0.0);
399
400 let p = state.ctm().transform_point(Point::new(5.0, 10.0));
401 assert_approx(p.x, 10.0);
402 assert_approx(p.y, 30.0);
403 }
404
405 #[test]
406 fn test_concat_matrix_cumulative() {
407 let mut state = InterpreterState::new();
408
409 state.concat_matrix(2.0, 0.0, 0.0, 2.0, 0.0, 0.0);
411 state.concat_matrix(1.0, 0.0, 0.0, 1.0, 10.0, 20.0);
413
414 let p = state.ctm().transform_point(Point::new(0.0, 0.0));
418 assert_approx(p.x, 20.0);
419 assert_approx(p.y, 40.0);
420 }
421
422 #[test]
423 fn test_concat_identity_no_change() {
424 let mut state = InterpreterState::new();
425 state.concat_matrix(2.0, 0.0, 0.0, 3.0, 10.0, 20.0);
426 let ctm_before = *state.ctm();
427
428 state.concat_matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
430 assert_eq!(*state.ctm(), ctm_before);
431 }
432
433 #[test]
434 fn test_ctm_array_after_concat() {
435 let mut state = InterpreterState::new();
436 state.concat_matrix(2.0, 0.0, 0.0, 3.0, 10.0, 20.0);
437 assert_eq!(state.ctm_array(), [2.0, 0.0, 0.0, 3.0, 10.0, 20.0]);
438 }
439
440 #[test]
443 fn test_set_line_width() {
444 let mut state = InterpreterState::new();
445 state.set_line_width(3.5);
446 assert_eq!(state.graphics_state().line_width, 3.5);
447 }
448
449 #[test]
450 fn test_set_line_width_zero() {
451 let mut state = InterpreterState::new();
452 state.set_line_width(0.0);
453 assert_eq!(state.graphics_state().line_width, 0.0);
454 }
455
456 #[test]
459 fn test_set_dash_pattern() {
460 let mut state = InterpreterState::new();
461 state.set_dash_pattern(vec![3.0, 2.0], 1.0);
462
463 let dp = &state.graphics_state().dash_pattern;
464 assert_eq!(dp.dash_array, vec![3.0, 2.0]);
465 assert_eq!(dp.dash_phase, 1.0);
466 assert!(!dp.is_solid());
467 }
468
469 #[test]
470 fn test_set_dash_pattern_solid() {
471 let mut state = InterpreterState::new();
472 state.set_dash_pattern(vec![3.0, 2.0], 0.0);
473 assert!(!state.graphics_state().dash_pattern.is_solid());
474
475 state.set_dash_pattern(vec![], 0.0);
476 assert!(state.graphics_state().dash_pattern.is_solid());
477 }
478
479 #[test]
482 fn test_set_stroking_gray() {
483 let mut state = InterpreterState::new();
484 state.set_stroking_gray(0.5);
485 assert_eq!(state.graphics_state().stroke_color, Color::Gray(0.5));
486 }
487
488 #[test]
489 fn test_set_non_stroking_gray() {
490 let mut state = InterpreterState::new();
491 state.set_non_stroking_gray(0.75);
492 assert_eq!(state.graphics_state().fill_color, Color::Gray(0.75));
493 }
494
495 #[test]
498 fn test_set_stroking_rgb() {
499 let mut state = InterpreterState::new();
500 state.set_stroking_rgb(1.0, 0.0, 0.0);
501 assert_eq!(
502 state.graphics_state().stroke_color,
503 Color::Rgb(1.0, 0.0, 0.0)
504 );
505 }
506
507 #[test]
508 fn test_set_non_stroking_rgb() {
509 let mut state = InterpreterState::new();
510 state.set_non_stroking_rgb(0.0, 1.0, 0.0);
511 assert_eq!(state.graphics_state().fill_color, Color::Rgb(0.0, 1.0, 0.0));
512 }
513
514 #[test]
517 fn test_set_stroking_cmyk() {
518 let mut state = InterpreterState::new();
519 state.set_stroking_cmyk(0.1, 0.2, 0.3, 0.4);
520 assert_eq!(
521 state.graphics_state().stroke_color,
522 Color::Cmyk(0.1, 0.2, 0.3, 0.4)
523 );
524 }
525
526 #[test]
527 fn test_set_non_stroking_cmyk() {
528 let mut state = InterpreterState::new();
529 state.set_non_stroking_cmyk(0.5, 0.6, 0.7, 0.8);
530 assert_eq!(
531 state.graphics_state().fill_color,
532 Color::Cmyk(0.5, 0.6, 0.7, 0.8)
533 );
534 }
535
536 #[test]
539 fn test_set_stroking_color_1_component_is_gray() {
540 let mut state = InterpreterState::new();
541 state.set_stroking_color(&[0.5]);
542 assert_eq!(state.graphics_state().stroke_color, Color::Gray(0.5));
543 }
544
545 #[test]
546 fn test_set_stroking_color_3_components_is_rgb() {
547 let mut state = InterpreterState::new();
548 state.set_stroking_color(&[1.0, 0.0, 0.0]);
549 assert_eq!(
550 state.graphics_state().stroke_color,
551 Color::Rgb(1.0, 0.0, 0.0)
552 );
553 }
554
555 #[test]
556 fn test_set_stroking_color_4_components_is_cmyk() {
557 let mut state = InterpreterState::new();
558 state.set_stroking_color(&[0.1, 0.2, 0.3, 0.4]);
559 assert_eq!(
560 state.graphics_state().stroke_color,
561 Color::Cmyk(0.1, 0.2, 0.3, 0.4)
562 );
563 }
564
565 #[test]
566 fn test_set_stroking_color_other_component_count() {
567 let mut state = InterpreterState::new();
568 state.set_stroking_color(&[0.1, 0.2]);
569 assert_eq!(
570 state.graphics_state().stroke_color,
571 Color::Other(vec![0.1, 0.2])
572 );
573 }
574
575 #[test]
576 fn test_set_non_stroking_color_1_component() {
577 let mut state = InterpreterState::new();
578 state.set_non_stroking_color(&[0.3]);
579 assert_eq!(state.graphics_state().fill_color, Color::Gray(0.3));
580 }
581
582 #[test]
583 fn test_set_non_stroking_color_3_components() {
584 let mut state = InterpreterState::new();
585 state.set_non_stroking_color(&[0.0, 0.0, 1.0]);
586 assert_eq!(state.graphics_state().fill_color, Color::Rgb(0.0, 0.0, 1.0));
587 }
588
589 #[test]
590 fn test_set_non_stroking_color_5_components_is_other() {
591 let mut state = InterpreterState::new();
592 state.set_non_stroking_color(&[0.1, 0.2, 0.3, 0.4, 0.5]);
593 assert_eq!(
594 state.graphics_state().fill_color,
595 Color::Other(vec![0.1, 0.2, 0.3, 0.4, 0.5])
596 );
597 }
598
599 #[test]
602 fn test_stroking_and_non_stroking_independent() {
603 let mut state = InterpreterState::new();
604 state.set_stroking_rgb(1.0, 0.0, 0.0);
605 state.set_non_stroking_rgb(0.0, 0.0, 1.0);
606
607 assert_eq!(
608 state.graphics_state().stroke_color,
609 Color::Rgb(1.0, 0.0, 0.0)
610 );
611 assert_eq!(state.graphics_state().fill_color, Color::Rgb(0.0, 0.0, 1.0));
612 }
613
614 #[test]
615 fn test_color_changes_across_color_spaces() {
616 let mut state = InterpreterState::new();
617
618 state.set_stroking_gray(0.5);
620 assert_eq!(state.graphics_state().stroke_color, Color::Gray(0.5));
621
622 state.set_stroking_rgb(1.0, 0.0, 0.0);
624 assert_eq!(
625 state.graphics_state().stroke_color,
626 Color::Rgb(1.0, 0.0, 0.0)
627 );
628
629 state.set_stroking_cmyk(0.0, 1.0, 0.0, 0.0);
631 assert_eq!(
632 state.graphics_state().stroke_color,
633 Color::Cmyk(0.0, 1.0, 0.0, 0.0)
634 );
635 }
636
637 #[test]
640 fn test_full_state_save_restore_cycle() {
641 let mut state = InterpreterState::new();
642
643 state.concat_matrix(2.0, 0.0, 0.0, 2.0, 0.0, 0.0);
645 state.set_line_width(2.0);
646 state.set_stroking_rgb(1.0, 0.0, 0.0);
647 state.set_non_stroking_gray(0.5);
648 state.set_dash_pattern(vec![5.0, 3.0], 0.0);
649
650 state.save_state();
652
653 state.concat_matrix(1.0, 0.0, 0.0, 1.0, 50.0, 50.0);
655 state.set_line_width(0.5);
656 state.set_stroking_cmyk(0.0, 0.0, 0.0, 1.0);
657 state.set_non_stroking_rgb(0.0, 1.0, 0.0);
658 state.set_dash_pattern(vec![], 0.0);
659
660 assert_eq!(state.graphics_state().line_width, 0.5);
662 assert_eq!(
663 state.graphics_state().stroke_color,
664 Color::Cmyk(0.0, 0.0, 0.0, 1.0)
665 );
666 assert!(state.graphics_state().dash_pattern.is_solid());
667
668 state.restore_state();
670
671 assert_eq!(state.ctm_array(), [2.0, 0.0, 0.0, 2.0, 0.0, 0.0]);
673
674 assert_eq!(state.graphics_state().line_width, 2.0);
676 assert_eq!(
677 state.graphics_state().stroke_color,
678 Color::Rgb(1.0, 0.0, 0.0)
679 );
680 assert_eq!(state.graphics_state().fill_color, Color::Gray(0.5));
681 assert_eq!(
682 state.graphics_state().dash_pattern,
683 DashPattern::new(vec![5.0, 3.0], 0.0)
684 );
685 }
686
687 #[test]
688 fn test_multiple_unbalanced_restores_return_false() {
689 let mut state = InterpreterState::new();
690 state.save_state();
691
692 assert!(state.restore_state());
693 assert!(!state.restore_state()); assert!(!state.restore_state()); }
696
697 #[test]
698 fn test_graphics_state_mut_access() {
699 let mut state = InterpreterState::new();
700 state.graphics_state_mut().stroke_alpha = 0.5;
701 assert_eq!(state.graphics_state().stroke_alpha, 0.5);
702 }
703
704 fn assert_approx(actual: f64, expected: f64) {
707 assert!(
708 (actual - expected).abs() < 1e-10,
709 "expected {expected}, got {actual}"
710 );
711 }
712}