1use crate::error::{PdfError, Result};
10use crate::graphics::Color;
11use crate::objects::{Dictionary, Object};
12use std::collections::HashMap;
13
14#[derive(Debug, Clone, Copy, PartialEq)]
16pub enum ShadingType {
17 FunctionBased = 1,
19 Axial = 2,
21 Radial = 3,
23 FreeFormGouraud = 4,
25 LatticeFormGouraud = 5,
27 CoonsPatch = 6,
29 TensorProductPatch = 7,
31}
32
33#[derive(Debug, Clone, PartialEq)]
35pub struct ColorStop {
36 pub position: f64,
38 pub color: Color,
40}
41
42impl ColorStop {
43 pub fn new(position: f64, color: Color) -> Self {
45 Self {
46 position: position.clamp(0.0, 1.0),
47 color,
48 }
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq)]
54pub struct Point {
55 pub x: f64,
56 pub y: f64,
57}
58
59impl Point {
60 pub fn new(x: f64, y: f64) -> Self {
62 Self { x, y }
63 }
64}
65
66#[derive(Debug, Clone)]
68pub struct AxialShading {
69 pub name: String,
71 pub start_point: Point,
73 pub end_point: Point,
75 pub color_stops: Vec<ColorStop>,
77 pub extend_start: bool,
79 pub extend_end: bool,
81}
82
83impl AxialShading {
84 pub fn new(
86 name: String,
87 start_point: Point,
88 end_point: Point,
89 color_stops: Vec<ColorStop>,
90 ) -> Self {
91 Self {
92 name,
93 start_point,
94 end_point,
95 color_stops,
96 extend_start: false,
97 extend_end: false,
98 }
99 }
100
101 pub fn with_extend(mut self, extend_start: bool, extend_end: bool) -> Self {
103 self.extend_start = extend_start;
104 self.extend_end = extend_end;
105 self
106 }
107
108 pub fn linear_gradient(
110 name: String,
111 start_point: Point,
112 end_point: Point,
113 start_color: Color,
114 end_color: Color,
115 ) -> Self {
116 let color_stops = vec![
117 ColorStop::new(0.0, start_color),
118 ColorStop::new(1.0, end_color),
119 ];
120
121 Self::new(name, start_point, end_point, color_stops)
122 }
123
124 pub fn to_pdf_dictionary(&self) -> Result<Dictionary> {
126 let mut shading_dict = Dictionary::new();
127
128 shading_dict.set("ShadingType", Object::Integer(ShadingType::Axial as i64));
130
131 let coords = vec![
133 Object::Real(self.start_point.x),
134 Object::Real(self.start_point.y),
135 Object::Real(self.end_point.x),
136 Object::Real(self.end_point.y),
137 ];
138 shading_dict.set("Coords", Object::Array(coords));
139
140 shading_dict.set("Function", Object::Integer(1)); let extend = vec![
146 Object::Boolean(self.extend_start),
147 Object::Boolean(self.extend_end),
148 ];
149 shading_dict.set("Extend", Object::Array(extend));
150
151 Ok(shading_dict)
152 }
153
154 pub fn validate(&self) -> Result<()> {
156 if self.color_stops.is_empty() {
157 return Err(PdfError::InvalidStructure(
158 "Axial shading must have at least one color stop".to_string(),
159 ));
160 }
161
162 for window in self.color_stops.windows(2) {
164 if window[0].position > window[1].position {
165 return Err(PdfError::InvalidStructure(
166 "Color stops must be in ascending order".to_string(),
167 ));
168 }
169 }
170
171 if (self.start_point.x - self.end_point.x).abs() < f64::EPSILON
173 && (self.start_point.y - self.end_point.y).abs() < f64::EPSILON
174 {
175 return Err(PdfError::InvalidStructure(
176 "Start and end points cannot be the same".to_string(),
177 ));
178 }
179
180 Ok(())
181 }
182}
183
184#[derive(Debug, Clone)]
186pub struct RadialShading {
187 pub name: String,
189 pub start_center: Point,
191 pub start_radius: f64,
193 pub end_center: Point,
195 pub end_radius: f64,
197 pub color_stops: Vec<ColorStop>,
199 pub extend_start: bool,
201 pub extend_end: bool,
203}
204
205impl RadialShading {
206 pub fn new(
208 name: String,
209 start_center: Point,
210 start_radius: f64,
211 end_center: Point,
212 end_radius: f64,
213 color_stops: Vec<ColorStop>,
214 ) -> Self {
215 Self {
216 name,
217 start_center,
218 start_radius: start_radius.max(0.0),
219 end_center,
220 end_radius: end_radius.max(0.0),
221 color_stops,
222 extend_start: false,
223 extend_end: false,
224 }
225 }
226
227 pub fn with_extend(mut self, extend_start: bool, extend_end: bool) -> Self {
229 self.extend_start = extend_start;
230 self.extend_end = extend_end;
231 self
232 }
233
234 pub fn radial_gradient(
236 name: String,
237 center: Point,
238 start_radius: f64,
239 end_radius: f64,
240 start_color: Color,
241 end_color: Color,
242 ) -> Self {
243 let color_stops = vec![
244 ColorStop::new(0.0, start_color),
245 ColorStop::new(1.0, end_color),
246 ];
247
248 Self::new(name, center, start_radius, center, end_radius, color_stops)
249 }
250
251 pub fn to_pdf_dictionary(&self) -> Result<Dictionary> {
253 let mut shading_dict = Dictionary::new();
254
255 shading_dict.set("ShadingType", Object::Integer(ShadingType::Radial as i64));
257
258 let coords = vec![
260 Object::Real(self.start_center.x),
261 Object::Real(self.start_center.y),
262 Object::Real(self.start_radius),
263 Object::Real(self.end_center.x),
264 Object::Real(self.end_center.y),
265 Object::Real(self.end_radius),
266 ];
267 shading_dict.set("Coords", Object::Array(coords));
268
269 shading_dict.set("Function", Object::Integer(1)); let extend = vec![
274 Object::Boolean(self.extend_start),
275 Object::Boolean(self.extend_end),
276 ];
277 shading_dict.set("Extend", Object::Array(extend));
278
279 Ok(shading_dict)
280 }
281
282 pub fn validate(&self) -> Result<()> {
284 if self.color_stops.is_empty() {
285 return Err(PdfError::InvalidStructure(
286 "Radial shading must have at least one color stop".to_string(),
287 ));
288 }
289
290 for window in self.color_stops.windows(2) {
292 if window[0].position > window[1].position {
293 return Err(PdfError::InvalidStructure(
294 "Color stops must be in ascending order".to_string(),
295 ));
296 }
297 }
298
299 if self.start_radius < 0.0 || self.end_radius < 0.0 {
301 return Err(PdfError::InvalidStructure(
302 "Radii cannot be negative".to_string(),
303 ));
304 }
305
306 Ok(())
307 }
308}
309
310#[derive(Debug, Clone)]
312pub struct FunctionBasedShading {
313 pub name: String,
315 pub domain: [f64; 4],
317 pub matrix: Option<[f64; 6]>,
319 pub function_id: u32,
321}
322
323impl FunctionBasedShading {
324 pub fn new(name: String, domain: [f64; 4], function_id: u32) -> Self {
326 Self {
327 name,
328 domain,
329 matrix: None,
330 function_id,
331 }
332 }
333
334 pub fn with_matrix(mut self, matrix: [f64; 6]) -> Self {
336 self.matrix = Some(matrix);
337 self
338 }
339
340 pub fn to_pdf_dictionary(&self) -> Result<Dictionary> {
342 let mut shading_dict = Dictionary::new();
343
344 shading_dict.set(
346 "ShadingType",
347 Object::Integer(ShadingType::FunctionBased as i64),
348 );
349
350 let domain = vec![
352 Object::Real(self.domain[0]),
353 Object::Real(self.domain[1]),
354 Object::Real(self.domain[2]),
355 Object::Real(self.domain[3]),
356 ];
357 shading_dict.set("Domain", Object::Array(domain));
358
359 if let Some(matrix) = self.matrix {
361 let matrix_objects: Vec<Object> = matrix.iter().map(|&x| Object::Real(x)).collect();
362 shading_dict.set("Matrix", Object::Array(matrix_objects));
363 }
364
365 shading_dict.set("Function", Object::Integer(self.function_id as i64));
367
368 Ok(shading_dict)
369 }
370
371 pub fn validate(&self) -> Result<()> {
373 if self.domain[0] >= self.domain[1] || self.domain[2] >= self.domain[3] {
375 return Err(PdfError::InvalidStructure(
376 "Invalid domain: min values must be less than max values".to_string(),
377 ));
378 }
379
380 Ok(())
381 }
382}
383
384#[derive(Debug, Clone)]
386pub struct ShadingPattern {
387 pub name: String,
389 pub shading: ShadingDefinition,
391 pub matrix: Option<[f64; 6]>,
393}
394
395#[derive(Debug, Clone)]
397pub enum ShadingDefinition {
398 Axial(AxialShading),
400 Radial(RadialShading),
402 FunctionBased(FunctionBasedShading),
404}
405
406impl ShadingDefinition {
407 pub fn name(&self) -> &str {
409 match self {
410 ShadingDefinition::Axial(shading) => &shading.name,
411 ShadingDefinition::Radial(shading) => &shading.name,
412 ShadingDefinition::FunctionBased(shading) => &shading.name,
413 }
414 }
415
416 pub fn validate(&self) -> Result<()> {
418 match self {
419 ShadingDefinition::Axial(shading) => shading.validate(),
420 ShadingDefinition::Radial(shading) => shading.validate(),
421 ShadingDefinition::FunctionBased(shading) => shading.validate(),
422 }
423 }
424
425 pub fn to_pdf_dictionary(&self) -> Result<Dictionary> {
427 match self {
428 ShadingDefinition::Axial(shading) => shading.to_pdf_dictionary(),
429 ShadingDefinition::Radial(shading) => shading.to_pdf_dictionary(),
430 ShadingDefinition::FunctionBased(shading) => shading.to_pdf_dictionary(),
431 }
432 }
433}
434
435impl ShadingPattern {
436 pub fn new(name: String, shading: ShadingDefinition) -> Self {
438 Self {
439 name,
440 shading,
441 matrix: None,
442 }
443 }
444
445 pub fn with_matrix(mut self, matrix: [f64; 6]) -> Self {
447 self.matrix = Some(matrix);
448 self
449 }
450
451 pub fn to_pdf_pattern_dictionary(&self) -> Result<Dictionary> {
453 let mut pattern_dict = Dictionary::new();
454
455 pattern_dict.set("Type", Object::Name("Pattern".to_string()));
457 pattern_dict.set("PatternType", Object::Integer(2)); pattern_dict.set("Shading", Object::Integer(1)); if let Some(matrix) = self.matrix {
464 let matrix_objects: Vec<Object> = matrix.iter().map(|&x| Object::Real(x)).collect();
465 pattern_dict.set("Matrix", Object::Array(matrix_objects));
466 }
467
468 Ok(pattern_dict)
469 }
470
471 pub fn validate(&self) -> Result<()> {
473 self.shading.validate()
474 }
475}
476
477#[derive(Debug, Clone)]
479pub struct ShadingManager {
480 shadings: HashMap<String, ShadingDefinition>,
482 patterns: HashMap<String, ShadingPattern>,
484 next_id: usize,
486}
487
488impl Default for ShadingManager {
489 fn default() -> Self {
490 Self::new()
491 }
492}
493
494impl ShadingManager {
495 pub fn new() -> Self {
497 Self {
498 shadings: HashMap::new(),
499 patterns: HashMap::new(),
500 next_id: 1,
501 }
502 }
503
504 pub fn add_shading(&mut self, mut shading: ShadingDefinition) -> Result<String> {
506 shading.validate()?;
508
509 let name = shading.name().to_string();
510
511 let final_name = if name.is_empty() || self.shadings.contains_key(&name) {
513 let auto_name = format!("Sh{}", self.next_id);
514 self.next_id += 1;
515
516 match &mut shading {
518 ShadingDefinition::Axial(s) => s.name = auto_name.clone(),
519 ShadingDefinition::Radial(s) => s.name = auto_name.clone(),
520 ShadingDefinition::FunctionBased(s) => s.name = auto_name.clone(),
521 }
522
523 auto_name
524 } else {
525 name
526 };
527
528 self.shadings.insert(final_name.clone(), shading);
529 Ok(final_name)
530 }
531
532 pub fn add_shading_pattern(&mut self, mut pattern: ShadingPattern) -> Result<String> {
534 pattern.validate()?;
536
537 if pattern.name.is_empty() || self.patterns.contains_key(&pattern.name) {
539 pattern.name = format!("SP{}", self.next_id);
540 self.next_id += 1;
541 }
542
543 let name = pattern.name.clone();
544 self.patterns.insert(name.clone(), pattern);
545 Ok(name)
546 }
547
548 pub fn get_shading(&self, name: &str) -> Option<&ShadingDefinition> {
550 self.shadings.get(name)
551 }
552
553 pub fn get_pattern(&self, name: &str) -> Option<&ShadingPattern> {
555 self.patterns.get(name)
556 }
557
558 pub fn shadings(&self) -> &HashMap<String, ShadingDefinition> {
560 &self.shadings
561 }
562
563 pub fn patterns(&self) -> &HashMap<String, ShadingPattern> {
565 &self.patterns
566 }
567
568 pub fn clear(&mut self) {
570 self.shadings.clear();
571 self.patterns.clear();
572 self.next_id = 1;
573 }
574
575 pub fn shading_count(&self) -> usize {
577 self.shadings.len()
578 }
579
580 pub fn pattern_count(&self) -> usize {
582 self.patterns.len()
583 }
584
585 pub fn total_count(&self) -> usize {
587 self.shading_count() + self.pattern_count()
588 }
589
590 pub fn create_linear_gradient(
592 &mut self,
593 start_point: Point,
594 end_point: Point,
595 start_color: Color,
596 end_color: Color,
597 ) -> Result<String> {
598 let shading = ShadingDefinition::Axial(AxialShading::linear_gradient(
599 String::new(), start_point,
601 end_point,
602 start_color,
603 end_color,
604 ));
605
606 self.add_shading(shading)
607 }
608
609 pub fn create_radial_gradient(
611 &mut self,
612 center: Point,
613 start_radius: f64,
614 end_radius: f64,
615 start_color: Color,
616 end_color: Color,
617 ) -> Result<String> {
618 let shading = ShadingDefinition::Radial(RadialShading::radial_gradient(
619 String::new(), center,
621 start_radius,
622 end_radius,
623 start_color,
624 end_color,
625 ));
626
627 self.add_shading(shading)
628 }
629
630 pub fn to_resource_dictionary(&self) -> Result<String> {
632 if self.shadings.is_empty() && self.patterns.is_empty() {
633 return Ok(String::new());
634 }
635
636 let mut dict = String::new();
637
638 if !self.shadings.is_empty() {
640 dict.push_str("/Shading <<");
641 for name in self.shadings.keys() {
642 dict.push_str(&format!(" /{} {} 0 R", name, self.next_id));
643 }
644 dict.push_str(" >>");
645 }
646
647 if !self.patterns.is_empty() {
649 if !dict.is_empty() {
650 dict.push('\n');
651 }
652 dict.push_str("/Pattern <<");
653 for name in self.patterns.keys() {
654 dict.push_str(&format!(" /{} {} 0 R", name, self.next_id));
655 }
656 dict.push_str(" >>");
657 }
658
659 Ok(dict)
660 }
661}
662
663#[cfg(test)]
664mod tests {
665 use super::*;
666
667 #[test]
668 fn test_color_stop_creation() {
669 let stop = ColorStop::new(0.5, Color::red());
670 assert_eq!(stop.position, 0.5);
671 assert_eq!(stop.color, Color::red());
672
673 let stop_clamped = ColorStop::new(1.5, Color::blue());
675 assert_eq!(stop_clamped.position, 1.0);
676 }
677
678 #[test]
679 fn test_point_creation() {
680 let point = Point::new(10.0, 20.0);
681 assert_eq!(point.x, 10.0);
682 assert_eq!(point.y, 20.0);
683 }
684
685 #[test]
686 fn test_axial_shading_creation() {
687 let start = Point::new(0.0, 0.0);
688 let end = Point::new(100.0, 100.0);
689 let stops = vec![
690 ColorStop::new(0.0, Color::red()),
691 ColorStop::new(1.0, Color::blue()),
692 ];
693
694 let shading = AxialShading::new("TestGradient".to_string(), start, end, stops);
695 assert_eq!(shading.name, "TestGradient");
696 assert_eq!(shading.start_point, start);
697 assert_eq!(shading.end_point, end);
698 assert_eq!(shading.color_stops.len(), 2);
699 assert!(!shading.extend_start);
700 assert!(!shading.extend_end);
701 }
702
703 #[test]
704 fn test_axial_shading_linear_gradient() {
705 let start = Point::new(0.0, 0.0);
706 let end = Point::new(100.0, 0.0);
707 let shading = AxialShading::linear_gradient(
708 "LinearGrad".to_string(),
709 start,
710 end,
711 Color::red(),
712 Color::blue(),
713 );
714
715 assert_eq!(shading.color_stops.len(), 2);
716 assert_eq!(shading.color_stops[0].position, 0.0);
717 assert_eq!(shading.color_stops[1].position, 1.0);
718 }
719
720 #[test]
721 fn test_axial_shading_with_extend() {
722 let start = Point::new(0.0, 0.0);
723 let end = Point::new(100.0, 0.0);
724 let shading = AxialShading::linear_gradient(
725 "ExtendedGrad".to_string(),
726 start,
727 end,
728 Color::red(),
729 Color::blue(),
730 )
731 .with_extend(true, true);
732
733 assert!(shading.extend_start);
734 assert!(shading.extend_end);
735 }
736
737 #[test]
738 fn test_axial_shading_validation_valid() {
739 let start = Point::new(0.0, 0.0);
740 let end = Point::new(100.0, 0.0);
741 let shading = AxialShading::linear_gradient(
742 "ValidGrad".to_string(),
743 start,
744 end,
745 Color::red(),
746 Color::blue(),
747 );
748
749 assert!(shading.validate().is_ok());
750 }
751
752 #[test]
753 fn test_axial_shading_validation_no_stops() {
754 let start = Point::new(0.0, 0.0);
755 let end = Point::new(100.0, 0.0);
756 let shading = AxialShading::new("EmptyGrad".to_string(), start, end, Vec::new());
757
758 assert!(shading.validate().is_err());
759 }
760
761 #[test]
762 fn test_axial_shading_validation_same_points() {
763 let point = Point::new(50.0, 50.0);
764 let shading = AxialShading::linear_gradient(
765 "SamePointGrad".to_string(),
766 point,
767 point,
768 Color::red(),
769 Color::blue(),
770 );
771
772 assert!(shading.validate().is_err());
773 }
774
775 #[test]
776 fn test_radial_shading_creation() {
777 let center = Point::new(50.0, 50.0);
778 let stops = vec![
779 ColorStop::new(0.0, Color::red()),
780 ColorStop::new(1.0, Color::blue()),
781 ];
782
783 let shading =
784 RadialShading::new("RadialGrad".to_string(), center, 10.0, center, 50.0, stops);
785
786 assert_eq!(shading.name, "RadialGrad");
787 assert_eq!(shading.start_center, center);
788 assert_eq!(shading.start_radius, 10.0);
789 assert_eq!(shading.end_radius, 50.0);
790 }
791
792 #[test]
793 fn test_radial_shading_gradient() {
794 let center = Point::new(50.0, 50.0);
795 let shading = RadialShading::radial_gradient(
796 "SimpleRadial".to_string(),
797 center,
798 0.0,
799 25.0,
800 Color::white(),
801 Color::black(),
802 );
803
804 assert_eq!(shading.color_stops.len(), 2);
805 assert_eq!(shading.start_radius, 0.0);
806 assert_eq!(shading.end_radius, 25.0);
807 }
808
809 #[test]
810 fn test_radial_shading_radius_clamping() {
811 let center = Point::new(50.0, 50.0);
812 let stops = vec![ColorStop::new(0.0, Color::red())];
813
814 let shading = RadialShading::new(
815 "ClampedRadial".to_string(),
816 center,
817 -5.0, center,
819 10.0,
820 stops,
821 );
822
823 assert_eq!(shading.start_radius, 0.0);
824 }
825
826 #[test]
827 fn test_radial_shading_validation_valid() {
828 let center = Point::new(50.0, 50.0);
829 let shading = RadialShading::radial_gradient(
830 "ValidRadial".to_string(),
831 center,
832 0.0,
833 25.0,
834 Color::red(),
835 Color::blue(),
836 );
837
838 assert!(shading.validate().is_ok());
839 }
840
841 #[test]
842 fn test_function_based_shading_creation() {
843 let domain = [0.0, 1.0, 0.0, 1.0];
844 let shading = FunctionBasedShading::new("FuncShading".to_string(), domain, 1);
845
846 assert_eq!(shading.name, "FuncShading");
847 assert_eq!(shading.domain, domain);
848 assert_eq!(shading.function_id, 1);
849 assert!(shading.matrix.is_none());
850 }
851
852 #[test]
853 fn test_function_based_shading_with_matrix() {
854 let domain = [0.0, 1.0, 0.0, 1.0];
855 let matrix = [2.0, 0.0, 0.0, 2.0, 10.0, 20.0];
856 let shading =
857 FunctionBasedShading::new("FuncShading".to_string(), domain, 1).with_matrix(matrix);
858
859 assert_eq!(shading.matrix, Some(matrix));
860 }
861
862 #[test]
863 fn test_function_based_shading_validation_valid() {
864 let domain = [0.0, 1.0, 0.0, 1.0];
865 let shading = FunctionBasedShading::new("ValidFunc".to_string(), domain, 1);
866
867 assert!(shading.validate().is_ok());
868 }
869
870 #[test]
871 fn test_function_based_shading_validation_invalid_domain() {
872 let domain = [1.0, 0.0, 0.0, 1.0]; let shading = FunctionBasedShading::new("InvalidFunc".to_string(), domain, 1);
874
875 assert!(shading.validate().is_err());
876 }
877
878 #[test]
879 fn test_shading_pattern_creation() {
880 let start = Point::new(0.0, 0.0);
881 let end = Point::new(100.0, 0.0);
882 let axial = AxialShading::linear_gradient(
883 "PatternGrad".to_string(),
884 start,
885 end,
886 Color::red(),
887 Color::blue(),
888 );
889 let shading = ShadingDefinition::Axial(axial);
890 let pattern = ShadingPattern::new("Pattern1".to_string(), shading);
891
892 assert_eq!(pattern.name, "Pattern1");
893 assert!(pattern.matrix.is_none());
894 }
895
896 #[test]
897 fn test_shading_pattern_with_matrix() {
898 let start = Point::new(0.0, 0.0);
899 let end = Point::new(100.0, 0.0);
900 let axial = AxialShading::linear_gradient(
901 "PatternGrad".to_string(),
902 start,
903 end,
904 Color::red(),
905 Color::blue(),
906 );
907 let shading = ShadingDefinition::Axial(axial);
908 let matrix = [1.0, 0.0, 0.0, 1.0, 50.0, 50.0];
909 let pattern = ShadingPattern::new("Pattern1".to_string(), shading).with_matrix(matrix);
910
911 assert_eq!(pattern.matrix, Some(matrix));
912 }
913
914 #[test]
915 fn test_shading_manager_creation() {
916 let manager = ShadingManager::new();
917 assert_eq!(manager.shading_count(), 0);
918 assert_eq!(manager.pattern_count(), 0);
919 assert_eq!(manager.total_count(), 0);
920 }
921
922 #[test]
923 fn test_shading_manager_add_shading() {
924 let mut manager = ShadingManager::new();
925 let start = Point::new(0.0, 0.0);
926 let end = Point::new(100.0, 0.0);
927 let axial = AxialShading::linear_gradient(
928 "TestGrad".to_string(),
929 start,
930 end,
931 Color::red(),
932 Color::blue(),
933 );
934 let shading = ShadingDefinition::Axial(axial);
935
936 let name = manager.add_shading(shading).unwrap();
937 assert_eq!(name, "TestGrad");
938 assert_eq!(manager.shading_count(), 1);
939
940 let retrieved = manager.get_shading(&name).unwrap();
941 assert_eq!(retrieved.name(), "TestGrad");
942 }
943
944 #[test]
945 fn test_shading_manager_auto_naming() {
946 let mut manager = ShadingManager::new();
947 let start = Point::new(0.0, 0.0);
948 let end = Point::new(100.0, 0.0);
949 let axial = AxialShading::linear_gradient(
950 String::new(), start,
952 end,
953 Color::red(),
954 Color::blue(),
955 );
956 let shading = ShadingDefinition::Axial(axial);
957
958 let name = manager.add_shading(shading).unwrap();
959 assert_eq!(name, "Sh1");
960
961 let axial2 = AxialShading::linear_gradient(
963 String::new(),
964 start,
965 end,
966 Color::green(),
967 Color::yellow(),
968 );
969 let shading2 = ShadingDefinition::Axial(axial2);
970
971 let name2 = manager.add_shading(shading2).unwrap();
972 assert_eq!(name2, "Sh2");
973 }
974
975 #[test]
976 fn test_shading_manager_create_gradients() {
977 let mut manager = ShadingManager::new();
978
979 let linear_name = manager
980 .create_linear_gradient(
981 Point::new(0.0, 0.0),
982 Point::new(100.0, 0.0),
983 Color::red(),
984 Color::blue(),
985 )
986 .unwrap();
987
988 let radial_name = manager
989 .create_radial_gradient(
990 Point::new(50.0, 50.0),
991 0.0,
992 25.0,
993 Color::white(),
994 Color::black(),
995 )
996 .unwrap();
997
998 assert_eq!(manager.shading_count(), 2);
999 assert!(manager.get_shading(&linear_name).is_some());
1000 assert!(manager.get_shading(&radial_name).is_some());
1001 }
1002
1003 #[test]
1004 fn test_shading_manager_clear() {
1005 let mut manager = ShadingManager::new();
1006
1007 manager
1008 .create_linear_gradient(
1009 Point::new(0.0, 0.0),
1010 Point::new(100.0, 0.0),
1011 Color::red(),
1012 Color::blue(),
1013 )
1014 .unwrap();
1015
1016 assert_eq!(manager.shading_count(), 1);
1017
1018 manager.clear();
1019 assert_eq!(manager.shading_count(), 0);
1020 assert_eq!(manager.total_count(), 0);
1021 }
1022
1023 #[test]
1024 fn test_axial_shading_pdf_dictionary() {
1025 let start = Point::new(0.0, 0.0);
1026 let end = Point::new(100.0, 50.0);
1027 let shading = AxialShading::linear_gradient(
1028 "TestPDF".to_string(),
1029 start,
1030 end,
1031 Color::red(),
1032 Color::blue(),
1033 )
1034 .with_extend(true, false);
1035
1036 let dict = shading.to_pdf_dictionary().unwrap();
1037
1038 if let Some(Object::Integer(shading_type)) = dict.get("ShadingType") {
1039 assert_eq!(*shading_type, 2); }
1041
1042 if let Some(Object::Array(coords)) = dict.get("Coords") {
1043 assert_eq!(coords.len(), 4);
1044 }
1045
1046 if let Some(Object::Array(extend)) = dict.get("Extend") {
1047 assert_eq!(extend.len(), 2);
1048 if let (Object::Boolean(start_extend), Object::Boolean(end_extend)) =
1049 (&extend[0], &extend[1])
1050 {
1051 assert!(*start_extend);
1052 assert!(!(*end_extend));
1053 }
1054 }
1055 }
1056
1057 #[test]
1058 fn test_radial_shading_pdf_dictionary() {
1059 let center = Point::new(50.0, 50.0);
1060 let shading = RadialShading::radial_gradient(
1061 "TestRadialPDF".to_string(),
1062 center,
1063 10.0,
1064 30.0,
1065 Color::yellow(),
1066 Color::red(),
1067 );
1068
1069 let dict = shading.to_pdf_dictionary().unwrap();
1070
1071 if let Some(Object::Integer(shading_type)) = dict.get("ShadingType") {
1072 assert_eq!(*shading_type, 3); }
1074
1075 if let Some(Object::Array(coords)) = dict.get("Coords") {
1076 assert_eq!(coords.len(), 6); }
1078 }
1079}