1use crate::path::{Path, PathBuilder};
9
10#[derive(Debug, Clone, PartialEq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub enum Color {
17 Gray(f32),
19 Rgb(f32, f32, f32),
21 Cmyk(f32, f32, f32, f32),
23 Other(Vec<f32>),
25}
26
27impl Color {
28 pub fn black() -> Self {
30 Self::Gray(0.0)
31 }
32
33 pub fn to_rgb(&self) -> Option<(f32, f32, f32)> {
37 match self {
38 Color::Gray(g) => Some((*g, *g, *g)),
39 Color::Rgb(r, g, b) => Some((*r, *g, *b)),
40 Color::Cmyk(c, m, y, k) => {
41 let r = (1.0 - c) * (1.0 - k);
43 let g = (1.0 - m) * (1.0 - k);
44 let b = (1.0 - y) * (1.0 - k);
45 Some((r, g, b))
46 }
47 Color::Other(_) => None,
48 }
49 }
50}
51
52impl Default for Color {
53 fn default() -> Self {
54 Self::black()
55 }
56}
57
58#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
61pub enum FillRule {
62 #[default]
64 NonZeroWinding,
65 EvenOdd,
67}
68
69#[derive(Debug, Clone, PartialEq)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
75pub struct DashPattern {
76 pub dash_array: Vec<f64>,
79 pub dash_phase: f64,
81}
82
83impl DashPattern {
84 pub fn new(dash_array: Vec<f64>, dash_phase: f64) -> Self {
86 Self {
87 dash_array,
88 dash_phase,
89 }
90 }
91
92 pub fn solid() -> Self {
94 Self {
95 dash_array: Vec::new(),
96 dash_phase: 0.0,
97 }
98 }
99
100 pub fn is_solid(&self) -> bool {
102 self.dash_array.is_empty()
103 }
104}
105
106impl Default for DashPattern {
107 fn default() -> Self {
108 Self::solid()
109 }
110}
111
112#[derive(Debug, Clone, PartialEq)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
115pub struct GraphicsState {
116 pub line_width: f64,
118 pub stroke_color: Color,
120 pub fill_color: Color,
122 pub dash_pattern: DashPattern,
124 pub stroke_alpha: f64,
126 pub fill_alpha: f64,
128}
129
130impl Default for GraphicsState {
131 fn default() -> Self {
132 Self {
133 line_width: 1.0,
134 stroke_color: Color::black(),
135 fill_color: Color::black(),
136 dash_pattern: DashPattern::solid(),
137 stroke_alpha: 1.0,
138 fill_alpha: 1.0,
139 }
140 }
141}
142
143impl GraphicsState {
144 pub fn apply_ext_gstate(&mut self, ext: &ExtGState) {
148 if let Some(lw) = ext.line_width {
149 self.line_width = lw;
150 }
151 if let Some(ref dp) = ext.dash_pattern {
152 self.dash_pattern = dp.clone();
153 }
154 if let Some(ca) = ext.stroke_alpha {
155 self.stroke_alpha = ca;
156 }
157 if let Some(ca) = ext.fill_alpha {
158 self.fill_alpha = ca;
159 }
160 }
161
162 pub fn set_dash_pattern(&mut self, dash_array: Vec<f64>, dash_phase: f64) {
164 self.dash_pattern = DashPattern::new(dash_array, dash_phase);
165 }
166}
167
168#[derive(Debug, Clone, Default, PartialEq)]
173#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
174pub struct ExtGState {
175 pub line_width: Option<f64>,
177 pub dash_pattern: Option<DashPattern>,
179 pub stroke_alpha: Option<f64>,
181 pub fill_alpha: Option<f64>,
183 pub font: Option<(String, f64)>,
185}
186
187#[derive(Debug, Clone, PartialEq)]
189#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
190pub struct PaintedPath {
191 pub path: Path,
193 pub stroke: bool,
195 pub fill: bool,
197 pub fill_rule: FillRule,
199 pub line_width: f64,
201 pub stroke_color: Color,
203 pub fill_color: Color,
205 pub dash_pattern: DashPattern,
207 pub stroke_alpha: f64,
209 pub fill_alpha: f64,
211}
212
213impl PathBuilder {
214 fn paint(
216 &mut self,
217 gs: &GraphicsState,
218 stroke: bool,
219 fill: bool,
220 fill_rule: FillRule,
221 ) -> PaintedPath {
222 let path = self.take_path();
223 PaintedPath {
224 path,
225 stroke,
226 fill,
227 fill_rule,
228 line_width: gs.line_width,
229 stroke_color: gs.stroke_color.clone(),
230 fill_color: gs.fill_color.clone(),
231 dash_pattern: gs.dash_pattern.clone(),
232 stroke_alpha: gs.stroke_alpha,
233 fill_alpha: gs.fill_alpha,
234 }
235 }
236
237 pub fn stroke(&mut self, gs: &GraphicsState) -> PaintedPath {
242 self.paint(gs, true, false, FillRule::NonZeroWinding)
243 }
244
245 pub fn close_and_stroke(&mut self, gs: &GraphicsState) -> PaintedPath {
249 self.close_path();
250 self.stroke(gs)
251 }
252
253 pub fn fill(&mut self, gs: &GraphicsState) -> PaintedPath {
257 self.paint(gs, false, true, FillRule::NonZeroWinding)
258 }
259
260 pub fn fill_even_odd(&mut self, gs: &GraphicsState) -> PaintedPath {
262 self.paint(gs, false, true, FillRule::EvenOdd)
263 }
264
265 pub fn fill_and_stroke(&mut self, gs: &GraphicsState) -> PaintedPath {
267 self.paint(gs, true, true, FillRule::NonZeroWinding)
268 }
269
270 pub fn fill_even_odd_and_stroke(&mut self, gs: &GraphicsState) -> PaintedPath {
272 self.paint(gs, true, true, FillRule::EvenOdd)
273 }
274
275 pub fn close_fill_and_stroke(&mut self, gs: &GraphicsState) -> PaintedPath {
279 self.close_path();
280 self.fill_and_stroke(gs)
281 }
282
283 pub fn close_fill_even_odd_and_stroke(&mut self, gs: &GraphicsState) -> PaintedPath {
287 self.close_path();
288 self.fill_even_odd_and_stroke(gs)
289 }
290
291 pub fn end_path(&mut self) -> Option<PaintedPath> {
296 self.take_path();
297 None
298 }
299
300 fn take_path(&mut self) -> Path {
302 self.take_and_reset()
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309 use crate::geometry::{Ctm, Point};
310 use crate::path::PathSegment;
311
312 fn default_gs() -> GraphicsState {
313 GraphicsState::default()
314 }
315
316 fn custom_gs() -> GraphicsState {
317 GraphicsState {
318 line_width: 2.5,
319 stroke_color: Color::Rgb(1.0, 0.0, 0.0),
320 fill_color: Color::Rgb(0.0, 0.0, 1.0),
321 ..GraphicsState::default()
322 }
323 }
324
325 fn build_triangle(builder: &mut PathBuilder) {
326 builder.move_to(0.0, 0.0);
327 builder.line_to(100.0, 0.0);
328 builder.line_to(50.0, 80.0);
329 }
330
331 fn build_rectangle(builder: &mut PathBuilder) {
332 builder.rectangle(10.0, 20.0, 100.0, 50.0);
333 }
334
335 #[test]
338 fn test_gray_to_rgb() {
339 let c = Color::Gray(0.5);
340 assert_eq!(c.to_rgb(), Some((0.5, 0.5, 0.5)));
341 }
342
343 #[test]
344 fn test_gray_black_to_rgb() {
345 let c = Color::Gray(0.0);
346 assert_eq!(c.to_rgb(), Some((0.0, 0.0, 0.0)));
347 }
348
349 #[test]
350 fn test_gray_white_to_rgb() {
351 let c = Color::Gray(1.0);
352 assert_eq!(c.to_rgb(), Some((1.0, 1.0, 1.0)));
353 }
354
355 #[test]
356 fn test_rgb_to_rgb_identity() {
357 let c = Color::Rgb(0.2, 0.4, 0.6);
358 assert_eq!(c.to_rgb(), Some((0.2, 0.4, 0.6)));
359 }
360
361 #[test]
362 fn test_cmyk_to_rgb() {
363 let c = Color::Cmyk(1.0, 0.0, 0.0, 0.0);
365 let (r, g, b) = c.to_rgb().unwrap();
366 assert!((r - 0.0).abs() < 0.01, "r={r}");
367 assert!((g - 1.0).abs() < 0.01, "g={g}");
368 assert!((b - 1.0).abs() < 0.01, "b={b}");
369 }
370
371 #[test]
372 fn test_cmyk_black_to_rgb() {
373 let c = Color::Cmyk(0.0, 0.0, 0.0, 1.0);
375 let (r, g, b) = c.to_rgb().unwrap();
376 assert!((r - 0.0).abs() < 0.01, "r={r}");
377 assert!((g - 0.0).abs() < 0.01, "g={g}");
378 assert!((b - 0.0).abs() < 0.01, "b={b}");
379 }
380
381 #[test]
382 fn test_cmyk_white_to_rgb() {
383 let c = Color::Cmyk(0.0, 0.0, 0.0, 0.0);
385 let (r, g, b) = c.to_rgb().unwrap();
386 assert!((r - 1.0).abs() < 0.01, "r={r}");
387 assert!((g - 1.0).abs() < 0.01, "g={g}");
388 assert!((b - 1.0).abs() < 0.01, "b={b}");
389 }
390
391 #[test]
392 fn test_other_to_rgb_returns_none() {
393 let c = Color::Other(vec![0.1, 0.2]);
394 assert_eq!(c.to_rgb(), None);
395 }
396
397 #[test]
400 fn test_color_gray() {
401 let c = Color::Gray(0.5);
402 assert_eq!(c, Color::Gray(0.5));
403 }
404
405 #[test]
406 fn test_color_rgb() {
407 let c = Color::Rgb(0.5, 0.6, 0.7);
408 assert_eq!(c, Color::Rgb(0.5, 0.6, 0.7));
409 }
410
411 #[test]
412 fn test_color_cmyk() {
413 let c = Color::Cmyk(0.1, 0.2, 0.3, 0.4);
414 assert_eq!(c, Color::Cmyk(0.1, 0.2, 0.3, 0.4));
415 }
416
417 #[test]
418 fn test_color_other() {
419 let c = Color::Other(vec![0.1, 0.2, 0.3, 0.4, 0.5]);
420 if let Color::Other(ref v) = c {
421 assert_eq!(v.len(), 5);
422 } else {
423 panic!("expected Color::Other");
424 }
425 }
426
427 #[test]
428 fn test_color_black() {
429 let c = Color::black();
430 assert_eq!(c, Color::Gray(0.0));
431 }
432
433 #[test]
434 fn test_color_default_is_black() {
435 assert_eq!(Color::default(), Color::black());
436 }
437
438 #[test]
439 fn test_color_clone() {
440 let c = Color::Rgb(1.0, 0.0, 0.0);
441 let c2 = c.clone();
442 assert_eq!(c, c2);
443 }
444
445 #[test]
448 fn test_fill_rule_default() {
449 assert_eq!(FillRule::default(), FillRule::NonZeroWinding);
450 }
451
452 #[test]
455 fn test_dash_pattern_solid() {
456 let dp = DashPattern::solid();
457 assert!(dp.dash_array.is_empty());
458 assert_eq!(dp.dash_phase, 0.0);
459 assert!(dp.is_solid());
460 }
461
462 #[test]
463 fn test_dash_pattern_default_is_solid() {
464 assert_eq!(DashPattern::default(), DashPattern::solid());
465 }
466
467 #[test]
468 fn test_dash_pattern_new() {
469 let dp = DashPattern::new(vec![3.0, 2.0], 1.0);
470 assert_eq!(dp.dash_array, vec![3.0, 2.0]);
471 assert_eq!(dp.dash_phase, 1.0);
472 assert!(!dp.is_solid());
473 }
474
475 #[test]
476 fn test_dash_pattern_complex() {
477 let dp = DashPattern::new(vec![5.0, 2.0, 1.0, 2.0], 0.0);
478 assert_eq!(dp.dash_array.len(), 4);
479 assert!(!dp.is_solid());
480 }
481
482 #[test]
485 fn test_graphics_state_default() {
486 let gs = GraphicsState::default();
487 assert_eq!(gs.line_width, 1.0);
488 assert_eq!(gs.stroke_color, Color::black());
489 assert_eq!(gs.fill_color, Color::black());
490 assert!(gs.dash_pattern.is_solid());
491 assert_eq!(gs.stroke_alpha, 1.0);
492 assert_eq!(gs.fill_alpha, 1.0);
493 }
494
495 #[test]
496 fn test_set_dash_pattern() {
497 let mut gs = GraphicsState::default();
498 gs.set_dash_pattern(vec![4.0, 2.0], 0.5);
499
500 assert_eq!(gs.dash_pattern.dash_array, vec![4.0, 2.0]);
501 assert_eq!(gs.dash_pattern.dash_phase, 0.5);
502 assert!(!gs.dash_pattern.is_solid());
503 }
504
505 #[test]
506 fn test_set_dash_pattern_back_to_solid() {
507 let mut gs = GraphicsState::default();
508 gs.set_dash_pattern(vec![4.0, 2.0], 0.5);
509 assert!(!gs.dash_pattern.is_solid());
510
511 gs.set_dash_pattern(vec![], 0.0);
512 assert!(gs.dash_pattern.is_solid());
513 }
514
515 #[test]
518 fn test_ext_gstate_default_is_all_none() {
519 let ext = ExtGState::default();
520 assert!(ext.line_width.is_none());
521 assert!(ext.dash_pattern.is_none());
522 assert!(ext.stroke_alpha.is_none());
523 assert!(ext.fill_alpha.is_none());
524 assert!(ext.font.is_none());
525 }
526
527 #[test]
528 fn test_apply_ext_gstate_line_width() {
529 let mut gs = GraphicsState::default();
530 assert_eq!(gs.line_width, 1.0);
531
532 let ext = ExtGState {
533 line_width: Some(3.5),
534 ..ExtGState::default()
535 };
536 gs.apply_ext_gstate(&ext);
537
538 assert_eq!(gs.line_width, 3.5);
539 assert!(gs.dash_pattern.is_solid());
541 assert_eq!(gs.stroke_alpha, 1.0);
542 assert_eq!(gs.fill_alpha, 1.0);
543 }
544
545 #[test]
546 fn test_apply_ext_gstate_dash_pattern() {
547 let mut gs = GraphicsState::default();
548 assert!(gs.dash_pattern.is_solid());
549
550 let ext = ExtGState {
551 dash_pattern: Some(DashPattern::new(vec![6.0, 3.0], 0.0)),
552 ..ExtGState::default()
553 };
554 gs.apply_ext_gstate(&ext);
555
556 assert_eq!(gs.dash_pattern.dash_array, vec![6.0, 3.0]);
557 assert_eq!(gs.dash_pattern.dash_phase, 0.0);
558 assert_eq!(gs.line_width, 1.0);
560 }
561
562 #[test]
563 fn test_apply_ext_gstate_stroke_alpha() {
564 let mut gs = GraphicsState::default();
565 assert_eq!(gs.stroke_alpha, 1.0);
566
567 let ext = ExtGState {
568 stroke_alpha: Some(0.5),
569 ..ExtGState::default()
570 };
571 gs.apply_ext_gstate(&ext);
572
573 assert_eq!(gs.stroke_alpha, 0.5);
574 assert_eq!(gs.fill_alpha, 1.0); }
576
577 #[test]
578 fn test_apply_ext_gstate_fill_alpha() {
579 let mut gs = GraphicsState::default();
580 assert_eq!(gs.fill_alpha, 1.0);
581
582 let ext = ExtGState {
583 fill_alpha: Some(0.75),
584 ..ExtGState::default()
585 };
586 gs.apply_ext_gstate(&ext);
587
588 assert_eq!(gs.fill_alpha, 0.75);
589 assert_eq!(gs.stroke_alpha, 1.0); }
591
592 #[test]
593 fn test_apply_ext_gstate_multiple_fields() {
594 let mut gs = GraphicsState::default();
595
596 let ext = ExtGState {
597 line_width: Some(2.0),
598 dash_pattern: Some(DashPattern::new(vec![1.0, 1.0], 0.0)),
599 stroke_alpha: Some(0.8),
600 fill_alpha: Some(0.6),
601 font: Some(("Helvetica".to_string(), 14.0)),
602 };
603 gs.apply_ext_gstate(&ext);
604
605 assert_eq!(gs.line_width, 2.0);
606 assert_eq!(gs.dash_pattern.dash_array, vec![1.0, 1.0]);
607 assert_eq!(gs.stroke_alpha, 0.8);
608 assert_eq!(gs.fill_alpha, 0.6);
609 }
612
613 #[test]
614 fn test_apply_ext_gstate_none_fields_preserve_state() {
615 let mut gs = GraphicsState {
616 line_width: 5.0,
617 stroke_alpha: 0.3,
618 fill_alpha: 0.4,
619 dash_pattern: DashPattern::new(vec![2.0], 0.0),
620 ..GraphicsState::default()
621 };
622
623 let ext = ExtGState::default();
625 gs.apply_ext_gstate(&ext);
626
627 assert_eq!(gs.line_width, 5.0);
628 assert_eq!(gs.stroke_alpha, 0.3);
629 assert_eq!(gs.fill_alpha, 0.4);
630 assert_eq!(gs.dash_pattern.dash_array, vec![2.0]);
631 }
632
633 #[test]
634 fn test_apply_ext_gstate_sequential() {
635 let mut gs = GraphicsState::default();
636
637 let ext1 = ExtGState {
639 line_width: Some(2.0),
640 ..ExtGState::default()
641 };
642 gs.apply_ext_gstate(&ext1);
643 assert_eq!(gs.line_width, 2.0);
644
645 let ext2 = ExtGState {
647 stroke_alpha: Some(0.5),
648 ..ExtGState::default()
649 };
650 gs.apply_ext_gstate(&ext2);
651 assert_eq!(gs.line_width, 2.0); assert_eq!(gs.stroke_alpha, 0.5);
653 }
654
655 #[test]
656 fn test_ext_gstate_font_override() {
657 let ext = ExtGState {
658 font: Some(("CourierNew".to_string(), 10.0)),
659 ..ExtGState::default()
660 };
661 let (name, size) = ext.font.as_ref().unwrap();
662 assert_eq!(name, "CourierNew");
663 assert_eq!(*size, 10.0);
664 }
665
666 #[test]
669 fn test_stroke_produces_stroke_only() {
670 let mut builder = PathBuilder::new(Ctm::identity());
671 build_triangle(&mut builder);
672
673 let painted = builder.stroke(&default_gs());
674 assert!(painted.stroke);
675 assert!(!painted.fill);
676 assert_eq!(painted.fill_rule, FillRule::NonZeroWinding);
677 }
678
679 #[test]
680 fn test_stroke_captures_path_segments() {
681 let mut builder = PathBuilder::new(Ctm::identity());
682 build_triangle(&mut builder);
683
684 let painted = builder.stroke(&default_gs());
685 assert_eq!(painted.path.segments.len(), 3); assert_eq!(
687 painted.path.segments[0],
688 PathSegment::MoveTo(Point::new(0.0, 0.0))
689 );
690 }
691
692 #[test]
693 fn test_stroke_captures_graphics_state() {
694 let mut builder = PathBuilder::new(Ctm::identity());
695 build_triangle(&mut builder);
696
697 let gs = custom_gs();
698 let painted = builder.stroke(&gs);
699 assert_eq!(painted.line_width, 2.5);
700 assert_eq!(painted.stroke_color, Color::Rgb(1.0, 0.0, 0.0));
701 assert_eq!(painted.fill_color, Color::Rgb(0.0, 0.0, 1.0));
702 }
703
704 #[test]
705 fn test_stroke_clears_builder() {
706 let mut builder = PathBuilder::new(Ctm::identity());
707 build_triangle(&mut builder);
708 let _ = builder.stroke(&default_gs());
709
710 assert!(builder.is_empty());
711 }
712
713 #[test]
716 fn test_stroke_captures_dash_pattern() {
717 let mut builder = PathBuilder::new(Ctm::identity());
718 builder.move_to(0.0, 0.0);
719 builder.line_to(100.0, 0.0);
720
721 let gs = GraphicsState {
722 dash_pattern: DashPattern::new(vec![5.0, 3.0], 1.0),
723 ..GraphicsState::default()
724 };
725 let painted = builder.stroke(&gs);
726 assert_eq!(painted.dash_pattern.dash_array, vec![5.0, 3.0]);
727 assert_eq!(painted.dash_pattern.dash_phase, 1.0);
728 }
729
730 #[test]
731 fn test_stroke_captures_alpha() {
732 let mut builder = PathBuilder::new(Ctm::identity());
733 builder.move_to(0.0, 0.0);
734 builder.line_to(100.0, 0.0);
735
736 let gs = GraphicsState {
737 stroke_alpha: 0.7,
738 fill_alpha: 0.3,
739 ..GraphicsState::default()
740 };
741 let painted = builder.stroke(&gs);
742 assert_eq!(painted.stroke_alpha, 0.7);
743 assert_eq!(painted.fill_alpha, 0.3);
744 }
745
746 #[test]
747 fn test_stroke_default_gs_has_solid_dash_and_full_alpha() {
748 let mut builder = PathBuilder::new(Ctm::identity());
749 builder.move_to(0.0, 0.0);
750 builder.line_to(100.0, 0.0);
751
752 let painted = builder.stroke(&default_gs());
753 assert!(painted.dash_pattern.is_solid());
754 assert_eq!(painted.stroke_alpha, 1.0);
755 assert_eq!(painted.fill_alpha, 1.0);
756 }
757
758 #[test]
761 fn test_close_and_stroke_includes_closepath() {
762 let mut builder = PathBuilder::new(Ctm::identity());
763 build_triangle(&mut builder);
764
765 let painted = builder.close_and_stroke(&default_gs());
766 assert!(painted.stroke);
767 assert!(!painted.fill);
768 assert_eq!(painted.path.segments.len(), 4);
770 assert_eq!(painted.path.segments[3], PathSegment::ClosePath);
771 }
772
773 #[test]
776 fn test_fill_produces_fill_only() {
777 let mut builder = PathBuilder::new(Ctm::identity());
778 build_triangle(&mut builder);
779
780 let painted = builder.fill(&default_gs());
781 assert!(!painted.stroke);
782 assert!(painted.fill);
783 assert_eq!(painted.fill_rule, FillRule::NonZeroWinding);
784 }
785
786 #[test]
787 fn test_fill_captures_path() {
788 let mut builder = PathBuilder::new(Ctm::identity());
789 build_rectangle(&mut builder);
790
791 let painted = builder.fill(&default_gs());
792 assert_eq!(painted.path.segments.len(), 5); }
794
795 #[test]
796 fn test_fill_clears_builder() {
797 let mut builder = PathBuilder::new(Ctm::identity());
798 build_triangle(&mut builder);
799 let _ = builder.fill(&default_gs());
800
801 assert!(builder.is_empty());
802 }
803
804 #[test]
807 fn test_fill_even_odd_uses_even_odd_rule() {
808 let mut builder = PathBuilder::new(Ctm::identity());
809 build_triangle(&mut builder);
810
811 let painted = builder.fill_even_odd(&default_gs());
812 assert!(!painted.stroke);
813 assert!(painted.fill);
814 assert_eq!(painted.fill_rule, FillRule::EvenOdd);
815 }
816
817 #[test]
820 fn test_fill_and_stroke() {
821 let mut builder = PathBuilder::new(Ctm::identity());
822 build_triangle(&mut builder);
823
824 let painted = builder.fill_and_stroke(&default_gs());
825 assert!(painted.stroke);
826 assert!(painted.fill);
827 assert_eq!(painted.fill_rule, FillRule::NonZeroWinding);
828 }
829
830 #[test]
831 fn test_fill_and_stroke_captures_custom_gs() {
832 let mut builder = PathBuilder::new(Ctm::identity());
833 build_triangle(&mut builder);
834
835 let gs = custom_gs();
836 let painted = builder.fill_and_stroke(&gs);
837 assert_eq!(painted.line_width, 2.5);
838 assert_eq!(painted.stroke_color, Color::Rgb(1.0, 0.0, 0.0));
839 assert_eq!(painted.fill_color, Color::Rgb(0.0, 0.0, 1.0));
840 }
841
842 #[test]
845 fn test_fill_even_odd_and_stroke() {
846 let mut builder = PathBuilder::new(Ctm::identity());
847 build_triangle(&mut builder);
848
849 let painted = builder.fill_even_odd_and_stroke(&default_gs());
850 assert!(painted.stroke);
851 assert!(painted.fill);
852 assert_eq!(painted.fill_rule, FillRule::EvenOdd);
853 }
854
855 #[test]
858 fn test_close_fill_and_stroke() {
859 let mut builder = PathBuilder::new(Ctm::identity());
860 build_triangle(&mut builder);
861
862 let painted = builder.close_fill_and_stroke(&default_gs());
863 assert!(painted.stroke);
864 assert!(painted.fill);
865 assert_eq!(painted.fill_rule, FillRule::NonZeroWinding);
866 assert_eq!(painted.path.segments.len(), 4);
868 assert_eq!(painted.path.segments[3], PathSegment::ClosePath);
869 }
870
871 #[test]
874 fn test_close_fill_even_odd_and_stroke() {
875 let mut builder = PathBuilder::new(Ctm::identity());
876 build_triangle(&mut builder);
877
878 let painted = builder.close_fill_even_odd_and_stroke(&default_gs());
879 assert!(painted.stroke);
880 assert!(painted.fill);
881 assert_eq!(painted.fill_rule, FillRule::EvenOdd);
882 assert_eq!(painted.path.segments.len(), 4);
884 assert_eq!(painted.path.segments[3], PathSegment::ClosePath);
885 }
886
887 #[test]
890 fn test_end_path_returns_none() {
891 let mut builder = PathBuilder::new(Ctm::identity());
892 build_triangle(&mut builder);
893
894 let result = builder.end_path();
895 assert!(result.is_none());
896 }
897
898 #[test]
899 fn test_end_path_clears_builder() {
900 let mut builder = PathBuilder::new(Ctm::identity());
901 build_triangle(&mut builder);
902 let _ = builder.end_path();
903
904 assert!(builder.is_empty());
905 }
906
907 #[test]
910 fn test_paint_then_build_new_path() {
911 let mut builder = PathBuilder::new(Ctm::identity());
912
913 builder.move_to(0.0, 0.0);
915 builder.line_to(100.0, 0.0);
916 let first = builder.stroke(&default_gs());
917 assert_eq!(first.path.segments.len(), 2);
918
919 build_rectangle(&mut builder);
921 let second = builder.fill(&default_gs());
922 assert_eq!(second.path.segments.len(), 5);
923 assert!(second.fill);
924 assert!(!second.stroke);
925 }
926
927 #[test]
928 fn test_multiple_paints_independent() {
929 let mut builder = PathBuilder::new(Ctm::identity());
930
931 builder.move_to(0.0, 0.0);
933 builder.line_to(50.0, 50.0);
934 let gs1 = GraphicsState {
935 line_width: 1.0,
936 stroke_color: Color::Rgb(1.0, 0.0, 0.0),
937 fill_color: Color::black(),
938 ..GraphicsState::default()
939 };
940 let first = builder.stroke(&gs1);
941
942 builder.move_to(10.0, 10.0);
944 builder.line_to(60.0, 60.0);
945 let gs2 = GraphicsState {
946 line_width: 3.0,
947 stroke_color: Color::Rgb(0.0, 1.0, 0.0),
948 fill_color: Color::black(),
949 ..GraphicsState::default()
950 };
951 let second = builder.stroke(&gs2);
952
953 assert_eq!(first.line_width, 1.0);
955 assert_eq!(first.stroke_color, Color::Rgb(1.0, 0.0, 0.0));
956 assert_eq!(second.line_width, 3.0);
957 assert_eq!(second.stroke_color, Color::Rgb(0.0, 1.0, 0.0));
958 }
959
960 #[test]
963 fn test_stroke_with_ctm_transformed_path() {
964 let ctm = Ctm::new(2.0, 0.0, 0.0, 2.0, 10.0, 10.0);
965 let mut builder = PathBuilder::new(ctm);
966 builder.move_to(0.0, 0.0);
967 builder.line_to(50.0, 0.0);
968
969 let painted = builder.stroke(&default_gs());
970 assert_eq!(
972 painted.path.segments[0],
973 PathSegment::MoveTo(Point::new(10.0, 10.0))
974 );
975 assert_eq!(
976 painted.path.segments[1],
977 PathSegment::LineTo(Point::new(110.0, 10.0))
978 );
979 }
980
981 #[test]
984 fn test_stroke_path_with_curves() {
985 let mut builder = PathBuilder::new(Ctm::identity());
986 builder.move_to(0.0, 0.0);
987 builder.curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 0.0);
988
989 let painted = builder.stroke(&default_gs());
990 assert_eq!(painted.path.segments.len(), 2);
991 assert_eq!(
992 painted.path.segments[1],
993 PathSegment::CurveTo {
994 cp1: Point::new(10.0, 20.0),
995 cp2: Point::new(30.0, 40.0),
996 end: Point::new(50.0, 0.0),
997 }
998 );
999 assert!(painted.stroke);
1000 }
1001
1002 #[test]
1003 fn test_fill_path_with_curves() {
1004 let mut builder = PathBuilder::new(Ctm::identity());
1005 builder.move_to(0.0, 0.0);
1006 builder.curve_to(10.0, 50.0, 90.0, 50.0, 100.0, 0.0);
1007 builder.close_path();
1008
1009 let painted = builder.fill(&default_gs());
1010 assert!(painted.fill);
1011 assert!(!painted.stroke);
1012 assert_eq!(painted.path.segments.len(), 3); }
1014
1015 #[test]
1018 fn test_d_operator_sets_dash() {
1019 let mut gs = GraphicsState::default();
1020 assert!(gs.dash_pattern.is_solid());
1021
1022 gs.set_dash_pattern(vec![3.0, 2.0], 0.0);
1024 assert_eq!(gs.dash_pattern.dash_array, vec![3.0, 2.0]);
1025 assert_eq!(gs.dash_pattern.dash_phase, 0.0);
1026 }
1027
1028 #[test]
1029 fn test_d_operator_with_phase() {
1030 let mut gs = GraphicsState::default();
1031 gs.set_dash_pattern(vec![6.0, 3.0, 1.0, 3.0], 2.0);
1032 assert_eq!(gs.dash_pattern.dash_array, vec![6.0, 3.0, 1.0, 3.0]);
1033 assert_eq!(gs.dash_pattern.dash_phase, 2.0);
1034 }
1035
1036 #[test]
1037 fn test_d_operator_propagates_to_painted_path() {
1038 let mut gs = GraphicsState::default();
1039 gs.set_dash_pattern(vec![4.0, 2.0], 0.0);
1040
1041 let mut builder = PathBuilder::new(Ctm::identity());
1042 builder.move_to(0.0, 0.0);
1043 builder.line_to(100.0, 0.0);
1044
1045 let painted = builder.stroke(&gs);
1046 assert_eq!(painted.dash_pattern.dash_array, vec![4.0, 2.0]);
1047 assert!(!painted.dash_pattern.is_solid());
1048 }
1049
1050 #[test]
1053 fn test_gs_operator_line_width_propagates_to_paint() {
1054 let mut gs = GraphicsState::default();
1055 let ext = ExtGState {
1056 line_width: Some(4.0),
1057 ..ExtGState::default()
1058 };
1059 gs.apply_ext_gstate(&ext);
1060
1061 let mut builder = PathBuilder::new(Ctm::identity());
1062 builder.move_to(0.0, 0.0);
1063 builder.line_to(100.0, 0.0);
1064
1065 let painted = builder.stroke(&gs);
1066 assert_eq!(painted.line_width, 4.0);
1067 }
1068
1069 #[test]
1070 fn test_gs_operator_dash_propagates_to_paint() {
1071 let mut gs = GraphicsState::default();
1072 let ext = ExtGState {
1073 dash_pattern: Some(DashPattern::new(vec![10.0, 5.0], 0.0)),
1074 ..ExtGState::default()
1075 };
1076 gs.apply_ext_gstate(&ext);
1077
1078 let mut builder = PathBuilder::new(Ctm::identity());
1079 builder.move_to(0.0, 0.0);
1080 builder.line_to(100.0, 0.0);
1081
1082 let painted = builder.stroke(&gs);
1083 assert_eq!(painted.dash_pattern.dash_array, vec![10.0, 5.0]);
1084 }
1085
1086 #[test]
1087 fn test_gs_operator_opacity_propagates_to_paint() {
1088 let mut gs = GraphicsState::default();
1089 let ext = ExtGState {
1090 stroke_alpha: Some(0.5),
1091 fill_alpha: Some(0.25),
1092 ..ExtGState::default()
1093 };
1094 gs.apply_ext_gstate(&ext);
1095
1096 let mut builder = PathBuilder::new(Ctm::identity());
1097 builder.move_to(0.0, 0.0);
1098 builder.line_to(100.0, 100.0);
1099 builder.close_path();
1100
1101 let painted = builder.fill_and_stroke(&gs);
1102 assert_eq!(painted.stroke_alpha, 0.5);
1103 assert_eq!(painted.fill_alpha, 0.25);
1104 }
1105}