1use crate::error::{PdfError, Result};
10use crate::graphics::{Color, GraphicsContext};
11use crate::objects::{Dictionary, Object};
12use std::collections::HashMap;
13
14#[derive(Debug, Clone, Copy, PartialEq)]
16pub enum PatternType {
17 Tiling = 1,
19 Shading = 2,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq)]
25pub enum TilingType {
26 ConstantSpacing = 1,
28 NoDistortion = 2,
30 ConstantSpacingFaster = 3,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq)]
36pub enum PaintType {
37 Colored = 1,
39 Uncolored = 2,
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub struct PatternMatrix {
46 pub matrix: [f64; 6],
48}
49
50impl PatternMatrix {
51 pub fn identity() -> Self {
53 Self {
54 matrix: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
55 }
56 }
57
58 pub fn translation(tx: f64, ty: f64) -> Self {
60 Self {
61 matrix: [1.0, 0.0, 0.0, 1.0, tx, ty],
62 }
63 }
64
65 pub fn scale(sx: f64, sy: f64) -> Self {
67 Self {
68 matrix: [sx, 0.0, 0.0, sy, 0.0, 0.0],
69 }
70 }
71
72 pub fn rotation(angle: f64) -> Self {
74 let cos_a = angle.cos();
75 let sin_a = angle.sin();
76 Self {
77 matrix: [cos_a, sin_a, -sin_a, cos_a, 0.0, 0.0],
78 }
79 }
80
81 pub fn multiply(&self, other: &PatternMatrix) -> Self {
83 let a1 = self.matrix[0];
84 let b1 = self.matrix[1];
85 let c1 = self.matrix[2];
86 let d1 = self.matrix[3];
87 let e1 = self.matrix[4];
88 let f1 = self.matrix[5];
89
90 let a2 = other.matrix[0];
91 let b2 = other.matrix[1];
92 let c2 = other.matrix[2];
93 let d2 = other.matrix[3];
94 let e2 = other.matrix[4];
95 let f2 = other.matrix[5];
96
97 Self {
98 matrix: [
99 a1 * a2 + b1 * c2,
100 a1 * b2 + b1 * d2,
101 c1 * a2 + d1 * c2,
102 c1 * b2 + d1 * d2,
103 e1 * a2 + f1 * c2 + e2,
104 e1 * b2 + f1 * d2 + f2,
105 ],
106 }
107 }
108
109 pub fn to_pdf_array(&self) -> Vec<Object> {
111 self.matrix.iter().map(|&x| Object::Real(x)).collect()
112 }
113}
114
115#[derive(Debug, Clone)]
117pub struct TilingPattern {
118 pub name: String,
120 pub paint_type: PaintType,
122 pub tiling_type: TilingType,
124 pub bbox: [f64; 4],
126 pub x_step: f64,
128 pub y_step: f64,
130 pub matrix: PatternMatrix,
132 pub content_stream: Vec<u8>,
134 pub resources: Option<Dictionary>,
136}
137
138impl TilingPattern {
139 pub fn new(
141 name: String,
142 paint_type: PaintType,
143 tiling_type: TilingType,
144 bbox: [f64; 4],
145 x_step: f64,
146 y_step: f64,
147 ) -> Self {
148 Self {
149 name,
150 paint_type,
151 tiling_type,
152 bbox,
153 x_step,
154 y_step,
155 matrix: PatternMatrix::identity(),
156 content_stream: Vec::new(),
157 resources: None,
158 }
159 }
160
161 pub fn with_matrix(mut self, matrix: PatternMatrix) -> Self {
163 self.matrix = matrix;
164 self
165 }
166
167 pub fn with_content_stream(mut self, content: Vec<u8>) -> Self {
169 self.content_stream = content;
170 self
171 }
172
173 pub fn with_resources(mut self, resources: Dictionary) -> Self {
175 self.resources = Some(resources);
176 self
177 }
178
179 pub fn add_command(&mut self, command: &str) {
181 self.content_stream.extend_from_slice(command.as_bytes());
182 self.content_stream.push(b'\n');
183 }
184
185 pub fn add_rectangle(&mut self, x: f64, y: f64, width: f64, height: f64) {
187 self.add_command(&format!("{x} {y} {width} {height} re"));
188 }
189
190 pub fn add_line(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {
192 self.add_command(&format!("{x1} {y1} m"));
193 self.add_command(&format!("{x2} {y2} l"));
194 }
195
196 pub fn add_circle(&mut self, cx: f64, cy: f64, radius: f64) {
198 let k = 0.5522847498; let kr = k * radius;
200
201 self.add_command(&format!("{} {} m", cx + radius, cy));
203
204 self.add_command(&format!(
206 "{} {} {} {} {} {} c",
207 cx + radius,
208 cy + kr,
209 cx + kr,
210 cy + radius,
211 cx,
212 cy + radius
213 ));
214 self.add_command(&format!(
215 "{} {} {} {} {} {} c",
216 cx - kr,
217 cy + radius,
218 cx - radius,
219 cy + kr,
220 cx - radius,
221 cy
222 ));
223 self.add_command(&format!(
224 "{} {} {} {} {} {} c",
225 cx - radius,
226 cy - kr,
227 cx - kr,
228 cy - radius,
229 cx,
230 cy - radius
231 ));
232 self.add_command(&format!(
233 "{} {} {} {} {} {} c",
234 cx + kr,
235 cy - radius,
236 cx + radius,
237 cy - kr,
238 cx + radius,
239 cy
240 ));
241 }
242
243 pub fn stroke(&mut self) {
245 self.add_command("S");
246 }
247
248 pub fn fill(&mut self) {
250 self.add_command("f");
251 }
252
253 pub fn fill_and_stroke(&mut self) {
255 self.add_command("B");
256 }
257
258 pub fn to_pdf_dictionary(&self) -> Result<Dictionary> {
260 let mut pattern_dict = Dictionary::new();
261
262 pattern_dict.set("Type", Object::Name("Pattern".to_string()));
264 pattern_dict.set("PatternType", Object::Integer(PatternType::Tiling as i64));
265 pattern_dict.set("PaintType", Object::Integer(self.paint_type as i64));
266 pattern_dict.set("TilingType", Object::Integer(self.tiling_type as i64));
267
268 let bbox_array = vec![
270 Object::Real(self.bbox[0]),
271 Object::Real(self.bbox[1]),
272 Object::Real(self.bbox[2]),
273 Object::Real(self.bbox[3]),
274 ];
275 pattern_dict.set("BBox", Object::Array(bbox_array));
276
277 pattern_dict.set("XStep", Object::Real(self.x_step));
279 pattern_dict.set("YStep", Object::Real(self.y_step));
280
281 pattern_dict.set("Matrix", Object::Array(self.matrix.to_pdf_array()));
283
284 if let Some(ref resources) = self.resources {
286 pattern_dict.set("Resources", Object::Dictionary(resources.clone()));
287 }
288
289 pattern_dict.set("Length", Object::Integer(self.content_stream.len() as i64));
291
292 Ok(pattern_dict)
293 }
294
295 pub fn validate(&self) -> Result<()> {
297 if self.bbox[0] >= self.bbox[2] || self.bbox[1] >= self.bbox[3] {
299 return Err(PdfError::InvalidStructure(
300 "Pattern bounding box is invalid".to_string(),
301 ));
302 }
303
304 if self.x_step <= 0.0 || self.y_step <= 0.0 {
306 return Err(PdfError::InvalidStructure(
307 "Pattern step sizes must be positive".to_string(),
308 ));
309 }
310
311 if self.content_stream.is_empty() {
313 return Err(PdfError::InvalidStructure(
314 "Pattern content stream cannot be empty".to_string(),
315 ));
316 }
317
318 Ok(())
319 }
320}
321
322#[derive(Debug, Clone)]
324pub struct PatternManager {
325 patterns: HashMap<String, TilingPattern>,
327 next_id: usize,
329}
330
331impl Default for PatternManager {
332 fn default() -> Self {
333 Self::new()
334 }
335}
336
337impl PatternManager {
338 pub fn new() -> Self {
340 Self {
341 patterns: HashMap::new(),
342 next_id: 1,
343 }
344 }
345
346 pub fn add_pattern(&mut self, mut pattern: TilingPattern) -> Result<String> {
348 pattern.validate()?;
350
351 if pattern.name.is_empty() {
353 pattern.name = format!("P{next_id}", next_id = self.next_id);
354 self.next_id += 1;
355 }
356
357 let name = pattern.name.clone();
358 self.patterns.insert(name.clone(), pattern);
359 Ok(name)
360 }
361
362 pub fn get_pattern(&self, name: &str) -> Option<&TilingPattern> {
364 self.patterns.get(name)
365 }
366
367 pub fn patterns(&self) -> &HashMap<String, TilingPattern> {
369 &self.patterns
370 }
371
372 pub fn remove_pattern(&mut self, name: &str) -> Option<TilingPattern> {
374 self.patterns.remove(name)
375 }
376
377 pub fn clear(&mut self) {
379 self.patterns.clear();
380 self.next_id = 1;
381 }
382
383 pub fn count(&self) -> usize {
385 self.patterns.len()
386 }
387
388 pub fn to_resource_dictionary(&self) -> Result<String> {
390 if self.patterns.is_empty() {
391 return Ok(String::new());
392 }
393
394 let mut dict = String::from("/Pattern <<");
395
396 for name in self.patterns.keys() {
397 dict.push_str(&format!(" /{} {} 0 R", name, self.next_id));
399 }
400
401 dict.push_str(" >>");
402 Ok(dict)
403 }
404
405 pub fn create_checkerboard_pattern(
407 &mut self,
408 cell_size: f64,
409 color1: [f64; 3], color2: [f64; 3], ) -> Result<String> {
412 let mut pattern = TilingPattern::new(
413 String::new(), PaintType::Colored,
415 TilingType::ConstantSpacing,
416 [0.0, 0.0, cell_size * 2.0, cell_size * 2.0],
417 cell_size * 2.0,
418 cell_size * 2.0,
419 );
420
421 let c1 = Color::Rgb(color1[0], color1[1], color1[2]);
426 let c2 = Color::Rgb(color2[0], color2[1], color2[2]);
427 pattern.add_command(crate::graphics::color::fill_color_op(c1).as_str());
428 pattern.add_rectangle(0.0, 0.0, cell_size, cell_size);
429 pattern.fill();
430
431 pattern.add_rectangle(cell_size, cell_size, cell_size, cell_size);
432 pattern.fill();
433
434 pattern.add_command(crate::graphics::color::fill_color_op(c2).as_str());
436 pattern.add_rectangle(cell_size, 0.0, cell_size, cell_size);
437 pattern.fill();
438
439 pattern.add_rectangle(0.0, cell_size, cell_size, cell_size);
440 pattern.fill();
441
442 self.add_pattern(pattern)
443 }
444
445 pub fn create_stripe_pattern(
447 &mut self,
448 stripe_width: f64,
449 angle: f64, color1: [f64; 3],
451 color2: [f64; 3],
452 ) -> Result<String> {
453 let pattern_size = stripe_width * 2.0;
454 let mut pattern = TilingPattern::new(
455 String::new(),
456 PaintType::Colored,
457 TilingType::ConstantSpacing,
458 [0.0, 0.0, pattern_size, pattern_size],
459 pattern_size,
460 pattern_size,
461 );
462
463 if angle != 0.0 {
465 let rotation_matrix = PatternMatrix::rotation(angle.to_radians());
466 pattern = pattern.with_matrix(rotation_matrix);
467 }
468
469 let c1 = Color::Rgb(color1[0], color1[1], color1[2]);
471 let c2 = Color::Rgb(color2[0], color2[1], color2[2]);
472 pattern.add_command(crate::graphics::color::fill_color_op(c1).as_str());
473 pattern.add_rectangle(0.0, 0.0, stripe_width, pattern_size);
474 pattern.fill();
475
476 pattern.add_command(crate::graphics::color::fill_color_op(c2).as_str());
478 pattern.add_rectangle(stripe_width, 0.0, stripe_width, pattern_size);
479 pattern.fill();
480
481 self.add_pattern(pattern)
482 }
483
484 pub fn create_dots_pattern(
486 &mut self,
487 dot_radius: f64,
488 spacing: f64,
489 dot_color: [f64; 3],
490 background_color: [f64; 3],
491 ) -> Result<String> {
492 let pattern_size = spacing;
493 let mut pattern = TilingPattern::new(
494 String::new(),
495 PaintType::Colored,
496 TilingType::ConstantSpacing,
497 [0.0, 0.0, pattern_size, pattern_size],
498 pattern_size,
499 pattern_size,
500 );
501
502 let bg = Color::Rgb(
504 background_color[0],
505 background_color[1],
506 background_color[2],
507 );
508 let dot = Color::Rgb(dot_color[0], dot_color[1], dot_color[2]);
509 pattern.add_command(crate::graphics::color::fill_color_op(bg).as_str());
510 pattern.add_rectangle(0.0, 0.0, pattern_size, pattern_size);
511 pattern.fill();
512
513 pattern.add_command(crate::graphics::color::fill_color_op(dot).as_str());
515 pattern.add_circle(pattern_size / 2.0, pattern_size / 2.0, dot_radius);
516 pattern.fill();
517
518 self.add_pattern(pattern)
519 }
520}
521
522pub trait PatternGraphicsContext {
524 fn set_fill_pattern(&mut self, pattern_name: &str) -> Result<()>;
526
527 fn set_stroke_pattern(&mut self, pattern_name: &str) -> Result<()>;
529}
530
531impl PatternGraphicsContext for GraphicsContext {
532 fn set_fill_pattern(&mut self, pattern_name: &str) -> Result<()> {
533 self.add_command(&format!("/Pattern cs /{pattern_name} scn"));
536 Ok(())
537 }
538
539 fn set_stroke_pattern(&mut self, pattern_name: &str) -> Result<()> {
540 self.add_command(&format!("/Pattern CS /{pattern_name} SCN"));
542 Ok(())
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549
550 #[test]
551 fn test_pattern_matrix_identity() {
552 let matrix = PatternMatrix::identity();
553 assert_eq!(matrix.matrix, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
554 }
555
556 #[test]
557 fn test_pattern_matrix_translation() {
558 let matrix = PatternMatrix::translation(10.0, 20.0);
559 assert_eq!(matrix.matrix, [1.0, 0.0, 0.0, 1.0, 10.0, 20.0]);
560 }
561
562 #[test]
563 fn test_pattern_matrix_scale() {
564 let matrix = PatternMatrix::scale(2.0, 3.0);
565 assert_eq!(matrix.matrix, [2.0, 0.0, 0.0, 3.0, 0.0, 0.0]);
566 }
567
568 #[test]
569 fn test_pattern_matrix_multiply() {
570 let m1 = PatternMatrix::translation(10.0, 20.0);
571 let m2 = PatternMatrix::scale(2.0, 3.0);
572 let result = m1.multiply(&m2);
573 assert_eq!(result.matrix, [2.0, 0.0, 0.0, 3.0, 20.0, 60.0]);
574 }
575
576 #[test]
577 fn test_tiling_pattern_creation() {
578 let pattern = TilingPattern::new(
579 "TestPattern".to_string(),
580 PaintType::Colored,
581 TilingType::ConstantSpacing,
582 [0.0, 0.0, 100.0, 100.0],
583 50.0,
584 50.0,
585 );
586
587 assert_eq!(pattern.name, "TestPattern");
588 assert_eq!(pattern.paint_type, PaintType::Colored);
589 assert_eq!(pattern.tiling_type, TilingType::ConstantSpacing);
590 assert_eq!(pattern.bbox, [0.0, 0.0, 100.0, 100.0]);
591 assert_eq!(pattern.x_step, 50.0);
592 assert_eq!(pattern.y_step, 50.0);
593 }
594
595 #[test]
596 fn test_tiling_pattern_content_operations() {
597 let mut pattern = TilingPattern::new(
598 "TestPattern".to_string(),
599 PaintType::Colored,
600 TilingType::ConstantSpacing,
601 [0.0, 0.0, 100.0, 100.0],
602 100.0,
603 100.0,
604 );
605
606 pattern.add_rectangle(10.0, 10.0, 50.0, 50.0);
607 pattern.fill();
608
609 let content = String::from_utf8(pattern.content_stream).unwrap();
610 assert!(content.contains("10 10 50 50 re"));
611 assert!(content.contains("f"));
612 }
613
614 #[test]
615 fn test_tiling_pattern_circle() {
616 let mut pattern = TilingPattern::new(
617 "CirclePattern".to_string(),
618 PaintType::Colored,
619 TilingType::ConstantSpacing,
620 [0.0, 0.0, 100.0, 100.0],
621 100.0,
622 100.0,
623 );
624
625 pattern.add_circle(50.0, 50.0, 25.0);
626 pattern.stroke();
627
628 let content = String::from_utf8(pattern.content_stream).unwrap();
629 assert!(content.contains("75 50 m")); assert!(content.contains("c")); assert!(content.contains("S")); }
633
634 #[test]
635 fn test_pattern_validation_valid() {
636 let pattern = TilingPattern::new(
637 "ValidPattern".to_string(),
638 PaintType::Colored,
639 TilingType::ConstantSpacing,
640 [0.0, 0.0, 100.0, 100.0],
641 50.0,
642 50.0,
643 );
644
645 let mut pattern_with_content = pattern;
647 pattern_with_content.add_rectangle(0.0, 0.0, 50.0, 50.0);
648
649 assert!(pattern_with_content.validate().is_ok());
650 }
651
652 #[test]
653 fn test_pattern_validation_invalid_bbox() {
654 let pattern = TilingPattern::new(
655 "InvalidPattern".to_string(),
656 PaintType::Colored,
657 TilingType::ConstantSpacing,
658 [100.0, 100.0, 0.0, 0.0], 50.0,
660 50.0,
661 );
662
663 assert!(pattern.validate().is_err());
664 }
665
666 #[test]
667 fn test_pattern_validation_invalid_steps() {
668 let pattern = TilingPattern::new(
669 "InvalidPattern".to_string(),
670 PaintType::Colored,
671 TilingType::ConstantSpacing,
672 [0.0, 0.0, 100.0, 100.0],
673 0.0, 50.0,
675 );
676
677 assert!(pattern.validate().is_err());
678 }
679
680 #[test]
681 fn test_pattern_validation_empty_content() {
682 let pattern = TilingPattern::new(
683 "EmptyPattern".to_string(),
684 PaintType::Colored,
685 TilingType::ConstantSpacing,
686 [0.0, 0.0, 100.0, 100.0],
687 50.0,
688 50.0,
689 );
690
691 assert!(pattern.validate().is_err());
693 }
694
695 #[test]
696 fn test_pattern_manager_creation() {
697 let manager = PatternManager::new();
698 assert_eq!(manager.count(), 0);
699 assert!(manager.patterns().is_empty());
700 }
701
702 #[test]
703 fn test_pattern_manager_add_pattern() {
704 let mut manager = PatternManager::new();
705 let mut pattern = TilingPattern::new(
706 "TestPattern".to_string(),
707 PaintType::Colored,
708 TilingType::ConstantSpacing,
709 [0.0, 0.0, 100.0, 100.0],
710 50.0,
711 50.0,
712 );
713
714 pattern.add_rectangle(0.0, 0.0, 50.0, 50.0);
716
717 let name = manager.add_pattern(pattern).unwrap();
718 assert_eq!(name, "TestPattern");
719 assert_eq!(manager.count(), 1);
720
721 let retrieved = manager.get_pattern(&name).unwrap();
722 assert_eq!(retrieved.name, "TestPattern");
723 }
724
725 #[test]
726 fn test_pattern_manager_auto_naming() {
727 let mut manager = PatternManager::new();
728 let mut pattern = TilingPattern::new(
729 String::new(), PaintType::Colored,
731 TilingType::ConstantSpacing,
732 [0.0, 0.0, 100.0, 100.0],
733 50.0,
734 50.0,
735 );
736
737 pattern.add_rectangle(0.0, 0.0, 50.0, 50.0);
738
739 let name = manager.add_pattern(pattern).unwrap();
740 assert_eq!(name, "P1");
741
742 let mut pattern2 = TilingPattern::new(
743 String::new(),
744 PaintType::Colored,
745 TilingType::ConstantSpacing,
746 [0.0, 0.0, 100.0, 100.0],
747 50.0,
748 50.0,
749 );
750
751 pattern2.add_rectangle(0.0, 0.0, 50.0, 50.0);
752
753 let name2 = manager.add_pattern(pattern2).unwrap();
754 assert_eq!(name2, "P2");
755 }
756
757 #[test]
758 fn test_pattern_manager_checkerboard() {
759 let mut manager = PatternManager::new();
760 let name = manager
761 .create_checkerboard_pattern(
762 25.0,
763 [1.0, 0.0, 0.0], [0.0, 0.0, 1.0], )
766 .unwrap();
767
768 let pattern = manager.get_pattern(&name).unwrap();
769 assert_eq!(pattern.x_step, 50.0);
770 assert_eq!(pattern.y_step, 50.0);
771 assert!(!pattern.content_stream.is_empty());
772 }
773
774 #[test]
775 fn test_pattern_manager_stripes() {
776 let mut manager = PatternManager::new();
777 let name = manager
778 .create_stripe_pattern(
779 10.0,
780 45.0, [0.0, 1.0, 0.0], [1.0, 1.0, 0.0], )
784 .unwrap();
785
786 let pattern = manager.get_pattern(&name).unwrap();
787 assert_eq!(pattern.x_step, 20.0);
788 assert_eq!(pattern.y_step, 20.0);
789 assert_ne!(pattern.matrix.matrix, PatternMatrix::identity().matrix);
791 }
792
793 #[test]
794 fn test_pattern_manager_dots() {
795 let mut manager = PatternManager::new();
796 let name = manager
797 .create_dots_pattern(
798 5.0, 20.0, [1.0, 0.0, 1.0], [1.0, 1.0, 1.0], )
803 .unwrap();
804
805 let pattern = manager.get_pattern(&name).unwrap();
806 assert_eq!(pattern.x_step, 20.0);
807 assert_eq!(pattern.y_step, 20.0);
808
809 let content = String::from_utf8(pattern.content_stream.clone()).unwrap();
810 assert!(content.contains("c")); }
812
813 #[test]
814 fn test_pattern_pdf_dictionary_generation() {
815 let mut pattern = TilingPattern::new(
816 "TestPattern".to_string(),
817 PaintType::Colored,
818 TilingType::ConstantSpacing,
819 [0.0, 0.0, 100.0, 100.0],
820 50.0,
821 50.0,
822 );
823
824 pattern.add_rectangle(0.0, 0.0, 50.0, 50.0);
825
826 let dict = pattern.to_pdf_dictionary().unwrap();
827
828 if let Some(Object::Name(type_name)) = dict.get("Type") {
830 assert_eq!(type_name, "Pattern");
831 }
832 if let Some(Object::Integer(pattern_type)) = dict.get("PatternType") {
833 assert_eq!(*pattern_type, 1);
834 }
835 if let Some(Object::Integer(paint_type)) = dict.get("PaintType") {
836 assert_eq!(*paint_type, 1);
837 }
838 if let Some(Object::Array(bbox)) = dict.get("BBox") {
839 assert_eq!(bbox.len(), 4);
840 }
841 }
842
843 #[test]
844 fn test_pattern_manager_clear() {
845 let mut manager = PatternManager::new();
846 let mut pattern = TilingPattern::new(
847 "TestPattern".to_string(),
848 PaintType::Colored,
849 TilingType::ConstantSpacing,
850 [0.0, 0.0, 100.0, 100.0],
851 50.0,
852 50.0,
853 );
854
855 pattern.add_rectangle(0.0, 0.0, 50.0, 50.0);
856 manager.add_pattern(pattern).unwrap();
857 assert_eq!(manager.count(), 1);
858
859 manager.clear();
860 assert_eq!(manager.count(), 0);
861 assert!(manager.patterns().is_empty());
862 }
863
864 #[test]
865 fn test_pattern_type_values() {
866 assert_eq!(PatternType::Tiling as i32, 1);
867 assert_eq!(PatternType::Shading as i32, 2);
868 }
869
870 #[test]
871 fn test_tiling_type_values() {
872 assert_eq!(TilingType::ConstantSpacing as i32, 1);
873 assert_eq!(TilingType::NoDistortion as i32, 2);
874 assert_eq!(TilingType::ConstantSpacingFaster as i32, 3);
875 }
876
877 #[test]
878 fn test_paint_type_values() {
879 assert_eq!(PaintType::Colored as i32, 1);
880 assert_eq!(PaintType::Uncolored as i32, 2);
881 }
882
883 #[test]
884 fn test_pattern_matrix_rotation() {
885 let angle = std::f64::consts::PI / 2.0; let matrix = PatternMatrix::rotation(angle);
887
888 assert!((matrix.matrix[0]).abs() < 1e-10); assert!((matrix.matrix[1] - 1.0).abs() < 1e-10); assert!((matrix.matrix[2] + 1.0).abs() < 1e-10); assert!((matrix.matrix[3]).abs() < 1e-10); }
894
895 #[test]
896 fn test_pattern_matrix_complex_multiply() {
897 let translate = PatternMatrix::translation(10.0, 20.0);
898 let scale = PatternMatrix::scale(2.0, 3.0);
899 let rotate = PatternMatrix::rotation(std::f64::consts::PI / 4.0); let result = translate.multiply(&scale).multiply(&rotate);
902
903 assert_ne!(result.matrix, PatternMatrix::identity().matrix);
905 }
906
907 #[test]
908 fn test_tiling_pattern_with_matrix() {
909 let pattern = TilingPattern::new(
910 "TestPattern".to_string(),
911 PaintType::Colored,
912 TilingType::ConstantSpacing,
913 [0.0, 0.0, 100.0, 100.0],
914 50.0,
915 50.0,
916 );
917
918 let matrix = PatternMatrix::scale(2.0, 2.0);
919 let pattern_with_matrix = pattern.with_matrix(matrix);
920
921 assert_eq!(
922 pattern_with_matrix.matrix.matrix,
923 [2.0, 0.0, 0.0, 2.0, 0.0, 0.0]
924 );
925 }
926
927 #[test]
928 fn test_tiling_pattern_with_resources() {
929 let pattern = TilingPattern::new(
930 "TestPattern".to_string(),
931 PaintType::Colored,
932 TilingType::ConstantSpacing,
933 [0.0, 0.0, 100.0, 100.0],
934 50.0,
935 50.0,
936 );
937
938 let mut resources = Dictionary::new();
939 resources.set("Font", Object::Name("F1".to_string()));
940
941 let pattern_with_resources = pattern.with_resources(resources.clone());
942 assert_eq!(pattern_with_resources.resources, Some(resources));
943 }
944
945 #[test]
946 fn test_tiling_pattern_stroke() {
947 let mut pattern = TilingPattern::new(
948 "StrokePattern".to_string(),
949 PaintType::Colored,
950 TilingType::ConstantSpacing,
951 [0.0, 0.0, 100.0, 100.0],
952 100.0,
953 100.0,
954 );
955
956 pattern.add_rectangle(10.0, 10.0, 80.0, 80.0);
957 pattern.stroke();
958
959 let content = String::from_utf8(pattern.content_stream).unwrap();
960 assert!(content.contains("S"));
961 assert!(!content.contains("f")); }
963
964 #[test]
965 fn test_tiling_pattern_add_command() {
966 let mut pattern = TilingPattern::new(
967 "CommandPattern".to_string(),
968 PaintType::Colored,
969 TilingType::ConstantSpacing,
970 [0.0, 0.0, 100.0, 100.0],
971 100.0,
972 100.0,
973 );
974
975 pattern.add_command("0.5 0.5 0.5 rg");
976 pattern.add_command("2 w");
977
978 let content = String::from_utf8(pattern.content_stream).unwrap();
979 assert!(content.contains("0.5 0.5 0.5 rg"));
980 assert!(content.contains("2 w"));
981 }
982
983 #[test]
984 fn test_pattern_manager_remove_pattern() {
985 let mut manager = PatternManager::new();
986 let mut pattern = TilingPattern::new(
987 "RemovablePattern".to_string(),
988 PaintType::Colored,
989 TilingType::ConstantSpacing,
990 [0.0, 0.0, 100.0, 100.0],
991 50.0,
992 50.0,
993 );
994
995 pattern.add_rectangle(0.0, 0.0, 50.0, 50.0);
996 manager.add_pattern(pattern).unwrap();
997 assert_eq!(manager.count(), 1);
998
999 let removed = manager.remove_pattern("RemovablePattern");
1000 assert!(removed.is_some());
1001 assert_eq!(manager.count(), 0);
1002
1003 let removed_again = manager.remove_pattern("RemovablePattern");
1004 assert!(removed_again.is_none());
1005 }
1006
1007 #[test]
1008 fn test_pattern_manager_to_resource_dictionary() {
1009 let mut manager = PatternManager::new();
1010
1011 assert_eq!(manager.to_resource_dictionary().unwrap(), "");
1013
1014 let mut pattern1 = TilingPattern::new(
1016 "P1".to_string(),
1017 PaintType::Colored,
1018 TilingType::ConstantSpacing,
1019 [0.0, 0.0, 10.0, 10.0],
1020 10.0,
1021 10.0,
1022 );
1023 pattern1.add_rectangle(0.0, 0.0, 10.0, 10.0);
1024 manager.add_pattern(pattern1).unwrap();
1025
1026 let dict = manager.to_resource_dictionary().unwrap();
1027 assert!(dict.starts_with("/Pattern <<"));
1028 assert!(dict.contains("/P1"));
1029 assert!(dict.ends_with(">>"));
1030 }
1031
1032 #[test]
1033 fn test_pattern_manager_default() {
1034 let manager = PatternManager::default();
1035 assert_eq!(manager.count(), 0);
1036 assert!(manager.patterns().is_empty());
1037 }
1038
1039 #[test]
1040 fn test_pattern_graphics_context_extension() {
1041 let mut context = GraphicsContext::new();
1042
1043 context.set_fill_pattern("TestPattern").unwrap();
1045 let commands = context.operations();
1046 assert!(commands.contains("/Pattern cs /TestPattern scn"));
1047
1048 context.set_stroke_pattern("StrokePattern").unwrap();
1050 let commands = context.operations();
1051 assert!(commands.contains("/Pattern CS /StrokePattern SCN"));
1052 }
1053
1054 #[test]
1055 fn test_tiling_pattern_uncolored() {
1056 let pattern = TilingPattern::new(
1057 "UncoloredPattern".to_string(),
1058 PaintType::Uncolored,
1059 TilingType::NoDistortion,
1060 [0.0, 0.0, 50.0, 50.0],
1061 50.0,
1062 50.0,
1063 );
1064
1065 assert_eq!(pattern.paint_type, PaintType::Uncolored);
1066 assert_eq!(pattern.tiling_type, TilingType::NoDistortion);
1067 }
1068
1069 #[test]
1070 fn test_checkerboard_pattern_content() {
1071 let mut manager = PatternManager::new();
1072 let name = manager
1073 .create_checkerboard_pattern(
1074 10.0,
1075 [1.0, 1.0, 1.0], [0.0, 0.0, 0.0], )
1078 .unwrap();
1079
1080 let pattern = manager.get_pattern(&name).unwrap();
1081 let content = String::from_utf8(pattern.content_stream.clone()).unwrap();
1082
1083 assert!(content.contains("1.000 1.000 1.000 rg")); assert!(content.contains("0.000 0.000 0.000 rg")); assert!(content.contains("re"));
1091 assert!(content.contains("f"));
1092 }
1093
1094 #[test]
1095 fn test_stripe_pattern_zero_angle() {
1096 let mut manager = PatternManager::new();
1097 let name = manager
1098 .create_stripe_pattern(
1099 5.0,
1100 0.0, [1.0, 0.0, 0.0],
1102 [0.0, 1.0, 0.0],
1103 )
1104 .unwrap();
1105
1106 let pattern = manager.get_pattern(&name).unwrap();
1107 assert_eq!(pattern.matrix.matrix, PatternMatrix::identity().matrix);
1109 }
1110
1111 #[test]
1112 fn test_dots_pattern_content() {
1113 let mut manager = PatternManager::new();
1114 let name = manager
1115 .create_dots_pattern(
1116 3.0, 10.0, [0.0, 0.0, 0.0], [1.0, 1.0, 1.0], )
1121 .unwrap();
1122
1123 let pattern = manager.get_pattern(&name).unwrap();
1124 let content = String::from_utf8(pattern.content_stream.clone()).unwrap();
1125
1126 assert!(content.contains("1.000 1.000 1.000 rg")); assert!(content.contains("0 0 10 10 re")); assert!(content.contains("0.000 0.000 0.000 rg")); assert!(content.contains("m")); assert!(content.contains("c")); }
1137
1138 #[test]
1139 fn test_pattern_validation_negative_step() {
1140 let pattern = TilingPattern::new(
1141 "NegativeStep".to_string(),
1142 PaintType::Colored,
1143 TilingType::ConstantSpacing,
1144 [0.0, 0.0, 100.0, 100.0],
1145 50.0,
1146 -50.0, );
1148
1149 assert!(pattern.validate().is_err());
1150 }
1151
1152 #[test]
1153 fn test_circle_approximation() {
1154 let mut pattern = TilingPattern::new(
1155 "CircleTest".to_string(),
1156 PaintType::Colored,
1157 TilingType::ConstantSpacing,
1158 [0.0, 0.0, 100.0, 100.0],
1159 100.0,
1160 100.0,
1161 );
1162
1163 pattern.add_circle(50.0, 50.0, 0.0); let content = String::from_utf8(pattern.content_stream.clone()).unwrap();
1165
1166 assert!(content.contains("50 50 m"));
1168 }
1169
1170 #[test]
1171 fn test_pattern_manager_get_nonexistent() {
1172 let manager = PatternManager::new();
1173 assert!(manager.get_pattern("NonExistent").is_none());
1174 }
1175
1176 #[test]
1177 fn test_pattern_type_debug_clone_eq() {
1178 let pattern_type = PatternType::Tiling;
1179
1180 let debug_str = format!("{pattern_type:?}");
1182 assert!(debug_str.contains("Tiling"));
1183
1184 let cloned = pattern_type;
1186 assert_eq!(cloned, PatternType::Tiling);
1187
1188 assert_eq!(PatternType::Tiling, PatternType::Tiling);
1190 assert_ne!(PatternType::Tiling, PatternType::Shading);
1191 }
1192
1193 #[test]
1194 fn test_tiling_pattern_debug_clone() {
1195 let pattern = TilingPattern::new(
1196 "TestPattern".to_string(),
1197 PaintType::Colored,
1198 TilingType::ConstantSpacing,
1199 [0.0, 0.0, 100.0, 100.0],
1200 50.0,
1201 50.0,
1202 );
1203
1204 let debug_str = format!("{pattern:?}");
1206 assert!(debug_str.contains("TilingPattern"));
1207 assert!(debug_str.contains("TestPattern"));
1208
1209 let cloned = pattern.clone();
1211 assert_eq!(cloned.name, pattern.name);
1212 assert_eq!(cloned.paint_type, pattern.paint_type);
1213 }
1214
1215 #[test]
1216 fn test_pattern_matrix_debug_clone_eq() {
1217 let matrix = PatternMatrix::translation(5.0, 10.0);
1218
1219 let debug_str = format!("{matrix:?}");
1221 assert!(debug_str.contains("PatternMatrix"));
1222
1223 let cloned = matrix.clone();
1225 assert_eq!(cloned.matrix, matrix.matrix);
1226
1227 assert_eq!(matrix, cloned);
1229 assert_ne!(matrix, PatternMatrix::identity());
1230 }
1231
1232 #[test]
1233 fn test_pattern_type() {
1234 assert_eq!(PatternType::Tiling as i32, 1);
1235 assert_eq!(PatternType::Shading as i32, 2);
1236 }
1237
1238 #[test]
1239 fn test_tiling_type() {
1240 assert_eq!(TilingType::ConstantSpacing as i32, 1);
1241 assert_eq!(TilingType::NoDistortion as i32, 2);
1242 assert_eq!(TilingType::ConstantSpacingFaster as i32, 3);
1243 }
1244
1245 #[test]
1246 fn test_paint_type() {
1247 assert_eq!(PaintType::Colored as i32, 1);
1248 assert_eq!(PaintType::Uncolored as i32, 2);
1249 }
1250
1251 #[test]
1252 fn test_pattern_manager() {
1253 let mut manager = PatternManager::new();
1254
1255 let mut pattern = TilingPattern::new(
1256 "P1".to_string(),
1257 PaintType::Colored,
1258 TilingType::ConstantSpacing,
1259 [0.0, 0.0, 10.0, 10.0],
1260 10.0,
1261 10.0,
1262 );
1263 pattern.content_stream = vec![b'q', b' ', b'Q']; let name = manager.add_pattern(pattern).unwrap();
1267 assert_eq!(name, "P1");
1268
1269 let mut pattern2 = TilingPattern::new(
1272 "P2".to_string(),
1273 PaintType::Uncolored,
1274 TilingType::NoDistortion,
1275 [0.0, 0.0, 20.0, 20.0],
1276 20.0,
1277 20.0,
1278 );
1279 pattern2.content_stream = vec![b'q', b' ', b'Q']; let name2 = manager.add_pattern(pattern2).unwrap();
1282 assert_eq!(name2, "P2");
1283 }
1284}