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
34impl Default for Color {
35 fn default() -> Self {
36 Self::black()
37 }
38}
39
40#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43pub enum FillRule {
44 #[default]
46 NonZeroWinding,
47 EvenOdd,
49}
50
51#[derive(Debug, Clone, PartialEq)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub struct DashPattern {
58 pub dash_array: Vec<f64>,
61 pub dash_phase: f64,
63}
64
65impl DashPattern {
66 pub fn new(dash_array: Vec<f64>, dash_phase: f64) -> Self {
68 Self {
69 dash_array,
70 dash_phase,
71 }
72 }
73
74 pub fn solid() -> Self {
76 Self {
77 dash_array: Vec::new(),
78 dash_phase: 0.0,
79 }
80 }
81
82 pub fn is_solid(&self) -> bool {
84 self.dash_array.is_empty()
85 }
86}
87
88impl Default for DashPattern {
89 fn default() -> Self {
90 Self::solid()
91 }
92}
93
94#[derive(Debug, Clone, PartialEq)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97pub struct GraphicsState {
98 pub line_width: f64,
100 pub stroke_color: Color,
102 pub fill_color: Color,
104 pub dash_pattern: DashPattern,
106 pub stroke_alpha: f64,
108 pub fill_alpha: f64,
110}
111
112impl Default for GraphicsState {
113 fn default() -> Self {
114 Self {
115 line_width: 1.0,
116 stroke_color: Color::black(),
117 fill_color: Color::black(),
118 dash_pattern: DashPattern::solid(),
119 stroke_alpha: 1.0,
120 fill_alpha: 1.0,
121 }
122 }
123}
124
125impl GraphicsState {
126 pub fn apply_ext_gstate(&mut self, ext: &ExtGState) {
130 if let Some(lw) = ext.line_width {
131 self.line_width = lw;
132 }
133 if let Some(ref dp) = ext.dash_pattern {
134 self.dash_pattern = dp.clone();
135 }
136 if let Some(ca) = ext.stroke_alpha {
137 self.stroke_alpha = ca;
138 }
139 if let Some(ca) = ext.fill_alpha {
140 self.fill_alpha = ca;
141 }
142 }
143
144 pub fn set_dash_pattern(&mut self, dash_array: Vec<f64>, dash_phase: f64) {
146 self.dash_pattern = DashPattern::new(dash_array, dash_phase);
147 }
148}
149
150#[derive(Debug, Clone, Default, PartialEq)]
155#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
156pub struct ExtGState {
157 pub line_width: Option<f64>,
159 pub dash_pattern: Option<DashPattern>,
161 pub stroke_alpha: Option<f64>,
163 pub fill_alpha: Option<f64>,
165 pub font: Option<(String, f64)>,
167}
168
169#[derive(Debug, Clone, PartialEq)]
171#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
172pub struct PaintedPath {
173 pub path: Path,
175 pub stroke: bool,
177 pub fill: bool,
179 pub fill_rule: FillRule,
181 pub line_width: f64,
183 pub stroke_color: Color,
185 pub fill_color: Color,
187 pub dash_pattern: DashPattern,
189 pub stroke_alpha: f64,
191 pub fill_alpha: f64,
193}
194
195impl PathBuilder {
196 fn paint(
198 &mut self,
199 gs: &GraphicsState,
200 stroke: bool,
201 fill: bool,
202 fill_rule: FillRule,
203 ) -> PaintedPath {
204 let path = self.take_path();
205 PaintedPath {
206 path,
207 stroke,
208 fill,
209 fill_rule,
210 line_width: gs.line_width,
211 stroke_color: gs.stroke_color.clone(),
212 fill_color: gs.fill_color.clone(),
213 dash_pattern: gs.dash_pattern.clone(),
214 stroke_alpha: gs.stroke_alpha,
215 fill_alpha: gs.fill_alpha,
216 }
217 }
218
219 pub fn stroke(&mut self, gs: &GraphicsState) -> PaintedPath {
224 self.paint(gs, true, false, FillRule::NonZeroWinding)
225 }
226
227 pub fn close_and_stroke(&mut self, gs: &GraphicsState) -> PaintedPath {
231 self.close_path();
232 self.stroke(gs)
233 }
234
235 pub fn fill(&mut self, gs: &GraphicsState) -> PaintedPath {
239 self.paint(gs, false, true, FillRule::NonZeroWinding)
240 }
241
242 pub fn fill_even_odd(&mut self, gs: &GraphicsState) -> PaintedPath {
244 self.paint(gs, false, true, FillRule::EvenOdd)
245 }
246
247 pub fn fill_and_stroke(&mut self, gs: &GraphicsState) -> PaintedPath {
249 self.paint(gs, true, true, FillRule::NonZeroWinding)
250 }
251
252 pub fn fill_even_odd_and_stroke(&mut self, gs: &GraphicsState) -> PaintedPath {
254 self.paint(gs, true, true, FillRule::EvenOdd)
255 }
256
257 pub fn close_fill_and_stroke(&mut self, gs: &GraphicsState) -> PaintedPath {
261 self.close_path();
262 self.fill_and_stroke(gs)
263 }
264
265 pub fn close_fill_even_odd_and_stroke(&mut self, gs: &GraphicsState) -> PaintedPath {
269 self.close_path();
270 self.fill_even_odd_and_stroke(gs)
271 }
272
273 pub fn end_path(&mut self) -> Option<PaintedPath> {
278 self.take_path();
279 None
280 }
281
282 fn take_path(&mut self) -> Path {
284 self.take_and_reset()
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::geometry::{Ctm, Point};
292 use crate::path::PathSegment;
293
294 fn default_gs() -> GraphicsState {
295 GraphicsState::default()
296 }
297
298 fn custom_gs() -> GraphicsState {
299 GraphicsState {
300 line_width: 2.5,
301 stroke_color: Color::Rgb(1.0, 0.0, 0.0),
302 fill_color: Color::Rgb(0.0, 0.0, 1.0),
303 ..GraphicsState::default()
304 }
305 }
306
307 fn build_triangle(builder: &mut PathBuilder) {
308 builder.move_to(0.0, 0.0);
309 builder.line_to(100.0, 0.0);
310 builder.line_to(50.0, 80.0);
311 }
312
313 fn build_rectangle(builder: &mut PathBuilder) {
314 builder.rectangle(10.0, 20.0, 100.0, 50.0);
315 }
316
317 #[test]
320 fn test_color_gray() {
321 let c = Color::Gray(0.5);
322 assert_eq!(c, Color::Gray(0.5));
323 }
324
325 #[test]
326 fn test_color_rgb() {
327 let c = Color::Rgb(0.5, 0.6, 0.7);
328 assert_eq!(c, Color::Rgb(0.5, 0.6, 0.7));
329 }
330
331 #[test]
332 fn test_color_cmyk() {
333 let c = Color::Cmyk(0.1, 0.2, 0.3, 0.4);
334 assert_eq!(c, Color::Cmyk(0.1, 0.2, 0.3, 0.4));
335 }
336
337 #[test]
338 fn test_color_other() {
339 let c = Color::Other(vec![0.1, 0.2, 0.3, 0.4, 0.5]);
340 if let Color::Other(ref v) = c {
341 assert_eq!(v.len(), 5);
342 } else {
343 panic!("expected Color::Other");
344 }
345 }
346
347 #[test]
348 fn test_color_black() {
349 let c = Color::black();
350 assert_eq!(c, Color::Gray(0.0));
351 }
352
353 #[test]
354 fn test_color_default_is_black() {
355 assert_eq!(Color::default(), Color::black());
356 }
357
358 #[test]
359 fn test_color_clone() {
360 let c = Color::Rgb(1.0, 0.0, 0.0);
361 let c2 = c.clone();
362 assert_eq!(c, c2);
363 }
364
365 #[test]
368 fn test_fill_rule_default() {
369 assert_eq!(FillRule::default(), FillRule::NonZeroWinding);
370 }
371
372 #[test]
375 fn test_dash_pattern_solid() {
376 let dp = DashPattern::solid();
377 assert!(dp.dash_array.is_empty());
378 assert_eq!(dp.dash_phase, 0.0);
379 assert!(dp.is_solid());
380 }
381
382 #[test]
383 fn test_dash_pattern_default_is_solid() {
384 assert_eq!(DashPattern::default(), DashPattern::solid());
385 }
386
387 #[test]
388 fn test_dash_pattern_new() {
389 let dp = DashPattern::new(vec![3.0, 2.0], 1.0);
390 assert_eq!(dp.dash_array, vec![3.0, 2.0]);
391 assert_eq!(dp.dash_phase, 1.0);
392 assert!(!dp.is_solid());
393 }
394
395 #[test]
396 fn test_dash_pattern_complex() {
397 let dp = DashPattern::new(vec![5.0, 2.0, 1.0, 2.0], 0.0);
398 assert_eq!(dp.dash_array.len(), 4);
399 assert!(!dp.is_solid());
400 }
401
402 #[test]
405 fn test_graphics_state_default() {
406 let gs = GraphicsState::default();
407 assert_eq!(gs.line_width, 1.0);
408 assert_eq!(gs.stroke_color, Color::black());
409 assert_eq!(gs.fill_color, Color::black());
410 assert!(gs.dash_pattern.is_solid());
411 assert_eq!(gs.stroke_alpha, 1.0);
412 assert_eq!(gs.fill_alpha, 1.0);
413 }
414
415 #[test]
416 fn test_set_dash_pattern() {
417 let mut gs = GraphicsState::default();
418 gs.set_dash_pattern(vec![4.0, 2.0], 0.5);
419
420 assert_eq!(gs.dash_pattern.dash_array, vec![4.0, 2.0]);
421 assert_eq!(gs.dash_pattern.dash_phase, 0.5);
422 assert!(!gs.dash_pattern.is_solid());
423 }
424
425 #[test]
426 fn test_set_dash_pattern_back_to_solid() {
427 let mut gs = GraphicsState::default();
428 gs.set_dash_pattern(vec![4.0, 2.0], 0.5);
429 assert!(!gs.dash_pattern.is_solid());
430
431 gs.set_dash_pattern(vec![], 0.0);
432 assert!(gs.dash_pattern.is_solid());
433 }
434
435 #[test]
438 fn test_ext_gstate_default_is_all_none() {
439 let ext = ExtGState::default();
440 assert!(ext.line_width.is_none());
441 assert!(ext.dash_pattern.is_none());
442 assert!(ext.stroke_alpha.is_none());
443 assert!(ext.fill_alpha.is_none());
444 assert!(ext.font.is_none());
445 }
446
447 #[test]
448 fn test_apply_ext_gstate_line_width() {
449 let mut gs = GraphicsState::default();
450 assert_eq!(gs.line_width, 1.0);
451
452 let ext = ExtGState {
453 line_width: Some(3.5),
454 ..ExtGState::default()
455 };
456 gs.apply_ext_gstate(&ext);
457
458 assert_eq!(gs.line_width, 3.5);
459 assert!(gs.dash_pattern.is_solid());
461 assert_eq!(gs.stroke_alpha, 1.0);
462 assert_eq!(gs.fill_alpha, 1.0);
463 }
464
465 #[test]
466 fn test_apply_ext_gstate_dash_pattern() {
467 let mut gs = GraphicsState::default();
468 assert!(gs.dash_pattern.is_solid());
469
470 let ext = ExtGState {
471 dash_pattern: Some(DashPattern::new(vec![6.0, 3.0], 0.0)),
472 ..ExtGState::default()
473 };
474 gs.apply_ext_gstate(&ext);
475
476 assert_eq!(gs.dash_pattern.dash_array, vec![6.0, 3.0]);
477 assert_eq!(gs.dash_pattern.dash_phase, 0.0);
478 assert_eq!(gs.line_width, 1.0);
480 }
481
482 #[test]
483 fn test_apply_ext_gstate_stroke_alpha() {
484 let mut gs = GraphicsState::default();
485 assert_eq!(gs.stroke_alpha, 1.0);
486
487 let ext = ExtGState {
488 stroke_alpha: Some(0.5),
489 ..ExtGState::default()
490 };
491 gs.apply_ext_gstate(&ext);
492
493 assert_eq!(gs.stroke_alpha, 0.5);
494 assert_eq!(gs.fill_alpha, 1.0); }
496
497 #[test]
498 fn test_apply_ext_gstate_fill_alpha() {
499 let mut gs = GraphicsState::default();
500 assert_eq!(gs.fill_alpha, 1.0);
501
502 let ext = ExtGState {
503 fill_alpha: Some(0.75),
504 ..ExtGState::default()
505 };
506 gs.apply_ext_gstate(&ext);
507
508 assert_eq!(gs.fill_alpha, 0.75);
509 assert_eq!(gs.stroke_alpha, 1.0); }
511
512 #[test]
513 fn test_apply_ext_gstate_multiple_fields() {
514 let mut gs = GraphicsState::default();
515
516 let ext = ExtGState {
517 line_width: Some(2.0),
518 dash_pattern: Some(DashPattern::new(vec![1.0, 1.0], 0.0)),
519 stroke_alpha: Some(0.8),
520 fill_alpha: Some(0.6),
521 font: Some(("Helvetica".to_string(), 14.0)),
522 };
523 gs.apply_ext_gstate(&ext);
524
525 assert_eq!(gs.line_width, 2.0);
526 assert_eq!(gs.dash_pattern.dash_array, vec![1.0, 1.0]);
527 assert_eq!(gs.stroke_alpha, 0.8);
528 assert_eq!(gs.fill_alpha, 0.6);
529 }
532
533 #[test]
534 fn test_apply_ext_gstate_none_fields_preserve_state() {
535 let mut gs = GraphicsState {
536 line_width: 5.0,
537 stroke_alpha: 0.3,
538 fill_alpha: 0.4,
539 dash_pattern: DashPattern::new(vec![2.0], 0.0),
540 ..GraphicsState::default()
541 };
542
543 let ext = ExtGState::default();
545 gs.apply_ext_gstate(&ext);
546
547 assert_eq!(gs.line_width, 5.0);
548 assert_eq!(gs.stroke_alpha, 0.3);
549 assert_eq!(gs.fill_alpha, 0.4);
550 assert_eq!(gs.dash_pattern.dash_array, vec![2.0]);
551 }
552
553 #[test]
554 fn test_apply_ext_gstate_sequential() {
555 let mut gs = GraphicsState::default();
556
557 let ext1 = ExtGState {
559 line_width: Some(2.0),
560 ..ExtGState::default()
561 };
562 gs.apply_ext_gstate(&ext1);
563 assert_eq!(gs.line_width, 2.0);
564
565 let ext2 = ExtGState {
567 stroke_alpha: Some(0.5),
568 ..ExtGState::default()
569 };
570 gs.apply_ext_gstate(&ext2);
571 assert_eq!(gs.line_width, 2.0); assert_eq!(gs.stroke_alpha, 0.5);
573 }
574
575 #[test]
576 fn test_ext_gstate_font_override() {
577 let ext = ExtGState {
578 font: Some(("CourierNew".to_string(), 10.0)),
579 ..ExtGState::default()
580 };
581 let (name, size) = ext.font.as_ref().unwrap();
582 assert_eq!(name, "CourierNew");
583 assert_eq!(*size, 10.0);
584 }
585
586 #[test]
589 fn test_stroke_produces_stroke_only() {
590 let mut builder = PathBuilder::new(Ctm::identity());
591 build_triangle(&mut builder);
592
593 let painted = builder.stroke(&default_gs());
594 assert!(painted.stroke);
595 assert!(!painted.fill);
596 assert_eq!(painted.fill_rule, FillRule::NonZeroWinding);
597 }
598
599 #[test]
600 fn test_stroke_captures_path_segments() {
601 let mut builder = PathBuilder::new(Ctm::identity());
602 build_triangle(&mut builder);
603
604 let painted = builder.stroke(&default_gs());
605 assert_eq!(painted.path.segments.len(), 3); assert_eq!(
607 painted.path.segments[0],
608 PathSegment::MoveTo(Point::new(0.0, 0.0))
609 );
610 }
611
612 #[test]
613 fn test_stroke_captures_graphics_state() {
614 let mut builder = PathBuilder::new(Ctm::identity());
615 build_triangle(&mut builder);
616
617 let gs = custom_gs();
618 let painted = builder.stroke(&gs);
619 assert_eq!(painted.line_width, 2.5);
620 assert_eq!(painted.stroke_color, Color::Rgb(1.0, 0.0, 0.0));
621 assert_eq!(painted.fill_color, Color::Rgb(0.0, 0.0, 1.0));
622 }
623
624 #[test]
625 fn test_stroke_clears_builder() {
626 let mut builder = PathBuilder::new(Ctm::identity());
627 build_triangle(&mut builder);
628 let _ = builder.stroke(&default_gs());
629
630 assert!(builder.is_empty());
631 }
632
633 #[test]
636 fn test_stroke_captures_dash_pattern() {
637 let mut builder = PathBuilder::new(Ctm::identity());
638 builder.move_to(0.0, 0.0);
639 builder.line_to(100.0, 0.0);
640
641 let gs = GraphicsState {
642 dash_pattern: DashPattern::new(vec![5.0, 3.0], 1.0),
643 ..GraphicsState::default()
644 };
645 let painted = builder.stroke(&gs);
646 assert_eq!(painted.dash_pattern.dash_array, vec![5.0, 3.0]);
647 assert_eq!(painted.dash_pattern.dash_phase, 1.0);
648 }
649
650 #[test]
651 fn test_stroke_captures_alpha() {
652 let mut builder = PathBuilder::new(Ctm::identity());
653 builder.move_to(0.0, 0.0);
654 builder.line_to(100.0, 0.0);
655
656 let gs = GraphicsState {
657 stroke_alpha: 0.7,
658 fill_alpha: 0.3,
659 ..GraphicsState::default()
660 };
661 let painted = builder.stroke(&gs);
662 assert_eq!(painted.stroke_alpha, 0.7);
663 assert_eq!(painted.fill_alpha, 0.3);
664 }
665
666 #[test]
667 fn test_stroke_default_gs_has_solid_dash_and_full_alpha() {
668 let mut builder = PathBuilder::new(Ctm::identity());
669 builder.move_to(0.0, 0.0);
670 builder.line_to(100.0, 0.0);
671
672 let painted = builder.stroke(&default_gs());
673 assert!(painted.dash_pattern.is_solid());
674 assert_eq!(painted.stroke_alpha, 1.0);
675 assert_eq!(painted.fill_alpha, 1.0);
676 }
677
678 #[test]
681 fn test_close_and_stroke_includes_closepath() {
682 let mut builder = PathBuilder::new(Ctm::identity());
683 build_triangle(&mut builder);
684
685 let painted = builder.close_and_stroke(&default_gs());
686 assert!(painted.stroke);
687 assert!(!painted.fill);
688 assert_eq!(painted.path.segments.len(), 4);
690 assert_eq!(painted.path.segments[3], PathSegment::ClosePath);
691 }
692
693 #[test]
696 fn test_fill_produces_fill_only() {
697 let mut builder = PathBuilder::new(Ctm::identity());
698 build_triangle(&mut builder);
699
700 let painted = builder.fill(&default_gs());
701 assert!(!painted.stroke);
702 assert!(painted.fill);
703 assert_eq!(painted.fill_rule, FillRule::NonZeroWinding);
704 }
705
706 #[test]
707 fn test_fill_captures_path() {
708 let mut builder = PathBuilder::new(Ctm::identity());
709 build_rectangle(&mut builder);
710
711 let painted = builder.fill(&default_gs());
712 assert_eq!(painted.path.segments.len(), 5); }
714
715 #[test]
716 fn test_fill_clears_builder() {
717 let mut builder = PathBuilder::new(Ctm::identity());
718 build_triangle(&mut builder);
719 let _ = builder.fill(&default_gs());
720
721 assert!(builder.is_empty());
722 }
723
724 #[test]
727 fn test_fill_even_odd_uses_even_odd_rule() {
728 let mut builder = PathBuilder::new(Ctm::identity());
729 build_triangle(&mut builder);
730
731 let painted = builder.fill_even_odd(&default_gs());
732 assert!(!painted.stroke);
733 assert!(painted.fill);
734 assert_eq!(painted.fill_rule, FillRule::EvenOdd);
735 }
736
737 #[test]
740 fn test_fill_and_stroke() {
741 let mut builder = PathBuilder::new(Ctm::identity());
742 build_triangle(&mut builder);
743
744 let painted = builder.fill_and_stroke(&default_gs());
745 assert!(painted.stroke);
746 assert!(painted.fill);
747 assert_eq!(painted.fill_rule, FillRule::NonZeroWinding);
748 }
749
750 #[test]
751 fn test_fill_and_stroke_captures_custom_gs() {
752 let mut builder = PathBuilder::new(Ctm::identity());
753 build_triangle(&mut builder);
754
755 let gs = custom_gs();
756 let painted = builder.fill_and_stroke(&gs);
757 assert_eq!(painted.line_width, 2.5);
758 assert_eq!(painted.stroke_color, Color::Rgb(1.0, 0.0, 0.0));
759 assert_eq!(painted.fill_color, Color::Rgb(0.0, 0.0, 1.0));
760 }
761
762 #[test]
765 fn test_fill_even_odd_and_stroke() {
766 let mut builder = PathBuilder::new(Ctm::identity());
767 build_triangle(&mut builder);
768
769 let painted = builder.fill_even_odd_and_stroke(&default_gs());
770 assert!(painted.stroke);
771 assert!(painted.fill);
772 assert_eq!(painted.fill_rule, FillRule::EvenOdd);
773 }
774
775 #[test]
778 fn test_close_fill_and_stroke() {
779 let mut builder = PathBuilder::new(Ctm::identity());
780 build_triangle(&mut builder);
781
782 let painted = builder.close_fill_and_stroke(&default_gs());
783 assert!(painted.stroke);
784 assert!(painted.fill);
785 assert_eq!(painted.fill_rule, FillRule::NonZeroWinding);
786 assert_eq!(painted.path.segments.len(), 4);
788 assert_eq!(painted.path.segments[3], PathSegment::ClosePath);
789 }
790
791 #[test]
794 fn test_close_fill_even_odd_and_stroke() {
795 let mut builder = PathBuilder::new(Ctm::identity());
796 build_triangle(&mut builder);
797
798 let painted = builder.close_fill_even_odd_and_stroke(&default_gs());
799 assert!(painted.stroke);
800 assert!(painted.fill);
801 assert_eq!(painted.fill_rule, FillRule::EvenOdd);
802 assert_eq!(painted.path.segments.len(), 4);
804 assert_eq!(painted.path.segments[3], PathSegment::ClosePath);
805 }
806
807 #[test]
810 fn test_end_path_returns_none() {
811 let mut builder = PathBuilder::new(Ctm::identity());
812 build_triangle(&mut builder);
813
814 let result = builder.end_path();
815 assert!(result.is_none());
816 }
817
818 #[test]
819 fn test_end_path_clears_builder() {
820 let mut builder = PathBuilder::new(Ctm::identity());
821 build_triangle(&mut builder);
822 let _ = builder.end_path();
823
824 assert!(builder.is_empty());
825 }
826
827 #[test]
830 fn test_paint_then_build_new_path() {
831 let mut builder = PathBuilder::new(Ctm::identity());
832
833 builder.move_to(0.0, 0.0);
835 builder.line_to(100.0, 0.0);
836 let first = builder.stroke(&default_gs());
837 assert_eq!(first.path.segments.len(), 2);
838
839 build_rectangle(&mut builder);
841 let second = builder.fill(&default_gs());
842 assert_eq!(second.path.segments.len(), 5);
843 assert!(second.fill);
844 assert!(!second.stroke);
845 }
846
847 #[test]
848 fn test_multiple_paints_independent() {
849 let mut builder = PathBuilder::new(Ctm::identity());
850
851 builder.move_to(0.0, 0.0);
853 builder.line_to(50.0, 50.0);
854 let gs1 = GraphicsState {
855 line_width: 1.0,
856 stroke_color: Color::Rgb(1.0, 0.0, 0.0),
857 fill_color: Color::black(),
858 ..GraphicsState::default()
859 };
860 let first = builder.stroke(&gs1);
861
862 builder.move_to(10.0, 10.0);
864 builder.line_to(60.0, 60.0);
865 let gs2 = GraphicsState {
866 line_width: 3.0,
867 stroke_color: Color::Rgb(0.0, 1.0, 0.0),
868 fill_color: Color::black(),
869 ..GraphicsState::default()
870 };
871 let second = builder.stroke(&gs2);
872
873 assert_eq!(first.line_width, 1.0);
875 assert_eq!(first.stroke_color, Color::Rgb(1.0, 0.0, 0.0));
876 assert_eq!(second.line_width, 3.0);
877 assert_eq!(second.stroke_color, Color::Rgb(0.0, 1.0, 0.0));
878 }
879
880 #[test]
883 fn test_stroke_with_ctm_transformed_path() {
884 let ctm = Ctm::new(2.0, 0.0, 0.0, 2.0, 10.0, 10.0);
885 let mut builder = PathBuilder::new(ctm);
886 builder.move_to(0.0, 0.0);
887 builder.line_to(50.0, 0.0);
888
889 let painted = builder.stroke(&default_gs());
890 assert_eq!(
892 painted.path.segments[0],
893 PathSegment::MoveTo(Point::new(10.0, 10.0))
894 );
895 assert_eq!(
896 painted.path.segments[1],
897 PathSegment::LineTo(Point::new(110.0, 10.0))
898 );
899 }
900
901 #[test]
904 fn test_stroke_path_with_curves() {
905 let mut builder = PathBuilder::new(Ctm::identity());
906 builder.move_to(0.0, 0.0);
907 builder.curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 0.0);
908
909 let painted = builder.stroke(&default_gs());
910 assert_eq!(painted.path.segments.len(), 2);
911 assert_eq!(
912 painted.path.segments[1],
913 PathSegment::CurveTo {
914 cp1: Point::new(10.0, 20.0),
915 cp2: Point::new(30.0, 40.0),
916 end: Point::new(50.0, 0.0),
917 }
918 );
919 assert!(painted.stroke);
920 }
921
922 #[test]
923 fn test_fill_path_with_curves() {
924 let mut builder = PathBuilder::new(Ctm::identity());
925 builder.move_to(0.0, 0.0);
926 builder.curve_to(10.0, 50.0, 90.0, 50.0, 100.0, 0.0);
927 builder.close_path();
928
929 let painted = builder.fill(&default_gs());
930 assert!(painted.fill);
931 assert!(!painted.stroke);
932 assert_eq!(painted.path.segments.len(), 3); }
934
935 #[test]
938 fn test_d_operator_sets_dash() {
939 let mut gs = GraphicsState::default();
940 assert!(gs.dash_pattern.is_solid());
941
942 gs.set_dash_pattern(vec![3.0, 2.0], 0.0);
944 assert_eq!(gs.dash_pattern.dash_array, vec![3.0, 2.0]);
945 assert_eq!(gs.dash_pattern.dash_phase, 0.0);
946 }
947
948 #[test]
949 fn test_d_operator_with_phase() {
950 let mut gs = GraphicsState::default();
951 gs.set_dash_pattern(vec![6.0, 3.0, 1.0, 3.0], 2.0);
952 assert_eq!(gs.dash_pattern.dash_array, vec![6.0, 3.0, 1.0, 3.0]);
953 assert_eq!(gs.dash_pattern.dash_phase, 2.0);
954 }
955
956 #[test]
957 fn test_d_operator_propagates_to_painted_path() {
958 let mut gs = GraphicsState::default();
959 gs.set_dash_pattern(vec![4.0, 2.0], 0.0);
960
961 let mut builder = PathBuilder::new(Ctm::identity());
962 builder.move_to(0.0, 0.0);
963 builder.line_to(100.0, 0.0);
964
965 let painted = builder.stroke(&gs);
966 assert_eq!(painted.dash_pattern.dash_array, vec![4.0, 2.0]);
967 assert!(!painted.dash_pattern.is_solid());
968 }
969
970 #[test]
973 fn test_gs_operator_line_width_propagates_to_paint() {
974 let mut gs = GraphicsState::default();
975 let ext = ExtGState {
976 line_width: Some(4.0),
977 ..ExtGState::default()
978 };
979 gs.apply_ext_gstate(&ext);
980
981 let mut builder = PathBuilder::new(Ctm::identity());
982 builder.move_to(0.0, 0.0);
983 builder.line_to(100.0, 0.0);
984
985 let painted = builder.stroke(&gs);
986 assert_eq!(painted.line_width, 4.0);
987 }
988
989 #[test]
990 fn test_gs_operator_dash_propagates_to_paint() {
991 let mut gs = GraphicsState::default();
992 let ext = ExtGState {
993 dash_pattern: Some(DashPattern::new(vec![10.0, 5.0], 0.0)),
994 ..ExtGState::default()
995 };
996 gs.apply_ext_gstate(&ext);
997
998 let mut builder = PathBuilder::new(Ctm::identity());
999 builder.move_to(0.0, 0.0);
1000 builder.line_to(100.0, 0.0);
1001
1002 let painted = builder.stroke(&gs);
1003 assert_eq!(painted.dash_pattern.dash_array, vec![10.0, 5.0]);
1004 }
1005
1006 #[test]
1007 fn test_gs_operator_opacity_propagates_to_paint() {
1008 let mut gs = GraphicsState::default();
1009 let ext = ExtGState {
1010 stroke_alpha: Some(0.5),
1011 fill_alpha: Some(0.25),
1012 ..ExtGState::default()
1013 };
1014 gs.apply_ext_gstate(&ext);
1015
1016 let mut builder = PathBuilder::new(Ctm::identity());
1017 builder.move_to(0.0, 0.0);
1018 builder.line_to(100.0, 100.0);
1019 builder.close_path();
1020
1021 let painted = builder.fill_and_stroke(&gs);
1022 assert_eq!(painted.stroke_alpha, 0.5);
1023 assert_eq!(painted.fill_alpha, 0.25);
1024 }
1025}