1use crate::error::{PdfError, Result};
10use crate::graphics::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 pattern.add_command(&format!("{} {} {} rg", color1[0], color1[1], color1[2]));
423 pattern.add_rectangle(0.0, 0.0, cell_size, cell_size);
424 pattern.fill();
425
426 pattern.add_rectangle(cell_size, cell_size, cell_size, cell_size);
427 pattern.fill();
428
429 pattern.add_command(&format!("{} {} {} rg", color2[0], color2[1], color2[2]));
431 pattern.add_rectangle(cell_size, 0.0, cell_size, cell_size);
432 pattern.fill();
433
434 pattern.add_rectangle(0.0, cell_size, cell_size, cell_size);
435 pattern.fill();
436
437 self.add_pattern(pattern)
438 }
439
440 pub fn create_stripe_pattern(
442 &mut self,
443 stripe_width: f64,
444 angle: f64, color1: [f64; 3],
446 color2: [f64; 3],
447 ) -> Result<String> {
448 let pattern_size = stripe_width * 2.0;
449 let mut pattern = TilingPattern::new(
450 String::new(),
451 PaintType::Colored,
452 TilingType::ConstantSpacing,
453 [0.0, 0.0, pattern_size, pattern_size],
454 pattern_size,
455 pattern_size,
456 );
457
458 if angle != 0.0 {
460 let rotation_matrix = PatternMatrix::rotation(angle.to_radians());
461 pattern = pattern.with_matrix(rotation_matrix);
462 }
463
464 pattern.add_command(&format!("{} {} {} rg", color1[0], color1[1], color1[2]));
466 pattern.add_rectangle(0.0, 0.0, stripe_width, pattern_size);
467 pattern.fill();
468
469 pattern.add_command(&format!("{} {} {} rg", color2[0], color2[1], color2[2]));
471 pattern.add_rectangle(stripe_width, 0.0, stripe_width, pattern_size);
472 pattern.fill();
473
474 self.add_pattern(pattern)
475 }
476
477 pub fn create_dots_pattern(
479 &mut self,
480 dot_radius: f64,
481 spacing: f64,
482 dot_color: [f64; 3],
483 background_color: [f64; 3],
484 ) -> Result<String> {
485 let pattern_size = spacing;
486 let mut pattern = TilingPattern::new(
487 String::new(),
488 PaintType::Colored,
489 TilingType::ConstantSpacing,
490 [0.0, 0.0, pattern_size, pattern_size],
491 pattern_size,
492 pattern_size,
493 );
494
495 pattern.add_command(&format!(
497 "{} {} {} rg",
498 background_color[0], background_color[1], background_color[2]
499 ));
500 pattern.add_rectangle(0.0, 0.0, pattern_size, pattern_size);
501 pattern.fill();
502
503 pattern.add_command(&format!(
505 "{} {} {} rg",
506 dot_color[0], dot_color[1], dot_color[2]
507 ));
508 pattern.add_circle(pattern_size / 2.0, pattern_size / 2.0, dot_radius);
509 pattern.fill();
510
511 self.add_pattern(pattern)
512 }
513}
514
515pub trait PatternGraphicsContext {
517 fn set_fill_pattern(&mut self, pattern_name: &str) -> Result<()>;
519
520 fn set_stroke_pattern(&mut self, pattern_name: &str) -> Result<()>;
522}
523
524impl PatternGraphicsContext for GraphicsContext {
525 fn set_fill_pattern(&mut self, pattern_name: &str) -> Result<()> {
526 self.add_command(&format!("/Pattern cs /{pattern_name} scn"));
529 Ok(())
530 }
531
532 fn set_stroke_pattern(&mut self, pattern_name: &str) -> Result<()> {
533 self.add_command(&format!("/Pattern CS /{pattern_name} SCN"));
535 Ok(())
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542
543 #[test]
544 fn test_pattern_matrix_identity() {
545 let matrix = PatternMatrix::identity();
546 assert_eq!(matrix.matrix, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
547 }
548
549 #[test]
550 fn test_pattern_matrix_translation() {
551 let matrix = PatternMatrix::translation(10.0, 20.0);
552 assert_eq!(matrix.matrix, [1.0, 0.0, 0.0, 1.0, 10.0, 20.0]);
553 }
554
555 #[test]
556 fn test_pattern_matrix_scale() {
557 let matrix = PatternMatrix::scale(2.0, 3.0);
558 assert_eq!(matrix.matrix, [2.0, 0.0, 0.0, 3.0, 0.0, 0.0]);
559 }
560
561 #[test]
562 fn test_pattern_matrix_multiply() {
563 let m1 = PatternMatrix::translation(10.0, 20.0);
564 let m2 = PatternMatrix::scale(2.0, 3.0);
565 let result = m1.multiply(&m2);
566 assert_eq!(result.matrix, [2.0, 0.0, 0.0, 3.0, 20.0, 60.0]);
567 }
568
569 #[test]
570 fn test_tiling_pattern_creation() {
571 let pattern = TilingPattern::new(
572 "TestPattern".to_string(),
573 PaintType::Colored,
574 TilingType::ConstantSpacing,
575 [0.0, 0.0, 100.0, 100.0],
576 50.0,
577 50.0,
578 );
579
580 assert_eq!(pattern.name, "TestPattern");
581 assert_eq!(pattern.paint_type, PaintType::Colored);
582 assert_eq!(pattern.tiling_type, TilingType::ConstantSpacing);
583 assert_eq!(pattern.bbox, [0.0, 0.0, 100.0, 100.0]);
584 assert_eq!(pattern.x_step, 50.0);
585 assert_eq!(pattern.y_step, 50.0);
586 }
587
588 #[test]
589 fn test_tiling_pattern_content_operations() {
590 let mut pattern = TilingPattern::new(
591 "TestPattern".to_string(),
592 PaintType::Colored,
593 TilingType::ConstantSpacing,
594 [0.0, 0.0, 100.0, 100.0],
595 100.0,
596 100.0,
597 );
598
599 pattern.add_rectangle(10.0, 10.0, 50.0, 50.0);
600 pattern.fill();
601
602 let content = String::from_utf8(pattern.content_stream).unwrap();
603 assert!(content.contains("10 10 50 50 re"));
604 assert!(content.contains("f"));
605 }
606
607 #[test]
608 fn test_tiling_pattern_circle() {
609 let mut pattern = TilingPattern::new(
610 "CirclePattern".to_string(),
611 PaintType::Colored,
612 TilingType::ConstantSpacing,
613 [0.0, 0.0, 100.0, 100.0],
614 100.0,
615 100.0,
616 );
617
618 pattern.add_circle(50.0, 50.0, 25.0);
619 pattern.stroke();
620
621 let content = String::from_utf8(pattern.content_stream).unwrap();
622 assert!(content.contains("75 50 m")); assert!(content.contains("c")); assert!(content.contains("S")); }
626
627 #[test]
628 fn test_pattern_validation_valid() {
629 let pattern = TilingPattern::new(
630 "ValidPattern".to_string(),
631 PaintType::Colored,
632 TilingType::ConstantSpacing,
633 [0.0, 0.0, 100.0, 100.0],
634 50.0,
635 50.0,
636 );
637
638 let mut pattern_with_content = pattern;
640 pattern_with_content.add_rectangle(0.0, 0.0, 50.0, 50.0);
641
642 assert!(pattern_with_content.validate().is_ok());
643 }
644
645 #[test]
646 fn test_pattern_validation_invalid_bbox() {
647 let pattern = TilingPattern::new(
648 "InvalidPattern".to_string(),
649 PaintType::Colored,
650 TilingType::ConstantSpacing,
651 [100.0, 100.0, 0.0, 0.0], 50.0,
653 50.0,
654 );
655
656 assert!(pattern.validate().is_err());
657 }
658
659 #[test]
660 fn test_pattern_validation_invalid_steps() {
661 let pattern = TilingPattern::new(
662 "InvalidPattern".to_string(),
663 PaintType::Colored,
664 TilingType::ConstantSpacing,
665 [0.0, 0.0, 100.0, 100.0],
666 0.0, 50.0,
668 );
669
670 assert!(pattern.validate().is_err());
671 }
672
673 #[test]
674 fn test_pattern_validation_empty_content() {
675 let pattern = TilingPattern::new(
676 "EmptyPattern".to_string(),
677 PaintType::Colored,
678 TilingType::ConstantSpacing,
679 [0.0, 0.0, 100.0, 100.0],
680 50.0,
681 50.0,
682 );
683
684 assert!(pattern.validate().is_err());
686 }
687
688 #[test]
689 fn test_pattern_manager_creation() {
690 let manager = PatternManager::new();
691 assert_eq!(manager.count(), 0);
692 assert!(manager.patterns().is_empty());
693 }
694
695 #[test]
696 fn test_pattern_manager_add_pattern() {
697 let mut manager = PatternManager::new();
698 let mut pattern = TilingPattern::new(
699 "TestPattern".to_string(),
700 PaintType::Colored,
701 TilingType::ConstantSpacing,
702 [0.0, 0.0, 100.0, 100.0],
703 50.0,
704 50.0,
705 );
706
707 pattern.add_rectangle(0.0, 0.0, 50.0, 50.0);
709
710 let name = manager.add_pattern(pattern).unwrap();
711 assert_eq!(name, "TestPattern");
712 assert_eq!(manager.count(), 1);
713
714 let retrieved = manager.get_pattern(&name).unwrap();
715 assert_eq!(retrieved.name, "TestPattern");
716 }
717
718 #[test]
719 fn test_pattern_manager_auto_naming() {
720 let mut manager = PatternManager::new();
721 let mut pattern = TilingPattern::new(
722 String::new(), PaintType::Colored,
724 TilingType::ConstantSpacing,
725 [0.0, 0.0, 100.0, 100.0],
726 50.0,
727 50.0,
728 );
729
730 pattern.add_rectangle(0.0, 0.0, 50.0, 50.0);
731
732 let name = manager.add_pattern(pattern).unwrap();
733 assert_eq!(name, "P1");
734
735 let mut pattern2 = TilingPattern::new(
736 String::new(),
737 PaintType::Colored,
738 TilingType::ConstantSpacing,
739 [0.0, 0.0, 100.0, 100.0],
740 50.0,
741 50.0,
742 );
743
744 pattern2.add_rectangle(0.0, 0.0, 50.0, 50.0);
745
746 let name2 = manager.add_pattern(pattern2).unwrap();
747 assert_eq!(name2, "P2");
748 }
749
750 #[test]
751 fn test_pattern_manager_checkerboard() {
752 let mut manager = PatternManager::new();
753 let name = manager
754 .create_checkerboard_pattern(
755 25.0,
756 [1.0, 0.0, 0.0], [0.0, 0.0, 1.0], )
759 .unwrap();
760
761 let pattern = manager.get_pattern(&name).unwrap();
762 assert_eq!(pattern.x_step, 50.0);
763 assert_eq!(pattern.y_step, 50.0);
764 assert!(!pattern.content_stream.is_empty());
765 }
766
767 #[test]
768 fn test_pattern_manager_stripes() {
769 let mut manager = PatternManager::new();
770 let name = manager
771 .create_stripe_pattern(
772 10.0,
773 45.0, [0.0, 1.0, 0.0], [1.0, 1.0, 0.0], )
777 .unwrap();
778
779 let pattern = manager.get_pattern(&name).unwrap();
780 assert_eq!(pattern.x_step, 20.0);
781 assert_eq!(pattern.y_step, 20.0);
782 assert_ne!(pattern.matrix.matrix, PatternMatrix::identity().matrix);
784 }
785
786 #[test]
787 fn test_pattern_manager_dots() {
788 let mut manager = PatternManager::new();
789 let name = manager
790 .create_dots_pattern(
791 5.0, 20.0, [1.0, 0.0, 1.0], [1.0, 1.0, 1.0], )
796 .unwrap();
797
798 let pattern = manager.get_pattern(&name).unwrap();
799 assert_eq!(pattern.x_step, 20.0);
800 assert_eq!(pattern.y_step, 20.0);
801
802 let content = String::from_utf8(pattern.content_stream.clone()).unwrap();
803 assert!(content.contains("c")); }
805
806 #[test]
807 fn test_pattern_pdf_dictionary_generation() {
808 let mut pattern = TilingPattern::new(
809 "TestPattern".to_string(),
810 PaintType::Colored,
811 TilingType::ConstantSpacing,
812 [0.0, 0.0, 100.0, 100.0],
813 50.0,
814 50.0,
815 );
816
817 pattern.add_rectangle(0.0, 0.0, 50.0, 50.0);
818
819 let dict = pattern.to_pdf_dictionary().unwrap();
820
821 if let Some(Object::Name(type_name)) = dict.get("Type") {
823 assert_eq!(type_name, "Pattern");
824 }
825 if let Some(Object::Integer(pattern_type)) = dict.get("PatternType") {
826 assert_eq!(*pattern_type, 1);
827 }
828 if let Some(Object::Integer(paint_type)) = dict.get("PaintType") {
829 assert_eq!(*paint_type, 1);
830 }
831 if let Some(Object::Array(bbox)) = dict.get("BBox") {
832 assert_eq!(bbox.len(), 4);
833 }
834 }
835
836 #[test]
837 fn test_pattern_manager_clear() {
838 let mut manager = PatternManager::new();
839 let mut pattern = TilingPattern::new(
840 "TestPattern".to_string(),
841 PaintType::Colored,
842 TilingType::ConstantSpacing,
843 [0.0, 0.0, 100.0, 100.0],
844 50.0,
845 50.0,
846 );
847
848 pattern.add_rectangle(0.0, 0.0, 50.0, 50.0);
849 manager.add_pattern(pattern).unwrap();
850 assert_eq!(manager.count(), 1);
851
852 manager.clear();
853 assert_eq!(manager.count(), 0);
854 assert!(manager.patterns().is_empty());
855 }
856
857 #[test]
858 fn test_pattern_type_values() {
859 assert_eq!(PatternType::Tiling as i32, 1);
860 assert_eq!(PatternType::Shading as i32, 2);
861 }
862
863 #[test]
864 fn test_tiling_type_values() {
865 assert_eq!(TilingType::ConstantSpacing as i32, 1);
866 assert_eq!(TilingType::NoDistortion as i32, 2);
867 assert_eq!(TilingType::ConstantSpacingFaster as i32, 3);
868 }
869
870 #[test]
871 fn test_paint_type_values() {
872 assert_eq!(PaintType::Colored as i32, 1);
873 assert_eq!(PaintType::Uncolored as i32, 2);
874 }
875
876 #[test]
877 fn test_pattern_matrix_rotation() {
878 let angle = std::f64::consts::PI / 2.0; let matrix = PatternMatrix::rotation(angle);
880
881 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); }
887
888 #[test]
889 fn test_pattern_matrix_complex_multiply() {
890 let translate = PatternMatrix::translation(10.0, 20.0);
891 let scale = PatternMatrix::scale(2.0, 3.0);
892 let rotate = PatternMatrix::rotation(std::f64::consts::PI / 4.0); let result = translate.multiply(&scale).multiply(&rotate);
895
896 assert_ne!(result.matrix, PatternMatrix::identity().matrix);
898 }
899
900 #[test]
901 fn test_tiling_pattern_with_matrix() {
902 let pattern = TilingPattern::new(
903 "TestPattern".to_string(),
904 PaintType::Colored,
905 TilingType::ConstantSpacing,
906 [0.0, 0.0, 100.0, 100.0],
907 50.0,
908 50.0,
909 );
910
911 let matrix = PatternMatrix::scale(2.0, 2.0);
912 let pattern_with_matrix = pattern.with_matrix(matrix);
913
914 assert_eq!(
915 pattern_with_matrix.matrix.matrix,
916 [2.0, 0.0, 0.0, 2.0, 0.0, 0.0]
917 );
918 }
919
920 #[test]
921 fn test_tiling_pattern_with_resources() {
922 let pattern = TilingPattern::new(
923 "TestPattern".to_string(),
924 PaintType::Colored,
925 TilingType::ConstantSpacing,
926 [0.0, 0.0, 100.0, 100.0],
927 50.0,
928 50.0,
929 );
930
931 let mut resources = Dictionary::new();
932 resources.set("Font", Object::Name("F1".to_string()));
933
934 let pattern_with_resources = pattern.with_resources(resources.clone());
935 assert_eq!(pattern_with_resources.resources, Some(resources));
936 }
937
938 #[test]
939 fn test_tiling_pattern_stroke() {
940 let mut pattern = TilingPattern::new(
941 "StrokePattern".to_string(),
942 PaintType::Colored,
943 TilingType::ConstantSpacing,
944 [0.0, 0.0, 100.0, 100.0],
945 100.0,
946 100.0,
947 );
948
949 pattern.add_rectangle(10.0, 10.0, 80.0, 80.0);
950 pattern.stroke();
951
952 let content = String::from_utf8(pattern.content_stream).unwrap();
953 assert!(content.contains("S"));
954 assert!(!content.contains("f")); }
956
957 #[test]
958 fn test_tiling_pattern_add_command() {
959 let mut pattern = TilingPattern::new(
960 "CommandPattern".to_string(),
961 PaintType::Colored,
962 TilingType::ConstantSpacing,
963 [0.0, 0.0, 100.0, 100.0],
964 100.0,
965 100.0,
966 );
967
968 pattern.add_command("0.5 0.5 0.5 rg");
969 pattern.add_command("2 w");
970
971 let content = String::from_utf8(pattern.content_stream).unwrap();
972 assert!(content.contains("0.5 0.5 0.5 rg"));
973 assert!(content.contains("2 w"));
974 }
975
976 #[test]
977 fn test_pattern_manager_remove_pattern() {
978 let mut manager = PatternManager::new();
979 let mut pattern = TilingPattern::new(
980 "RemovablePattern".to_string(),
981 PaintType::Colored,
982 TilingType::ConstantSpacing,
983 [0.0, 0.0, 100.0, 100.0],
984 50.0,
985 50.0,
986 );
987
988 pattern.add_rectangle(0.0, 0.0, 50.0, 50.0);
989 manager.add_pattern(pattern).unwrap();
990 assert_eq!(manager.count(), 1);
991
992 let removed = manager.remove_pattern("RemovablePattern");
993 assert!(removed.is_some());
994 assert_eq!(manager.count(), 0);
995
996 let removed_again = manager.remove_pattern("RemovablePattern");
997 assert!(removed_again.is_none());
998 }
999
1000 #[test]
1001 fn test_pattern_manager_to_resource_dictionary() {
1002 let mut manager = PatternManager::new();
1003
1004 assert_eq!(manager.to_resource_dictionary().unwrap(), "");
1006
1007 let mut pattern1 = TilingPattern::new(
1009 "P1".to_string(),
1010 PaintType::Colored,
1011 TilingType::ConstantSpacing,
1012 [0.0, 0.0, 10.0, 10.0],
1013 10.0,
1014 10.0,
1015 );
1016 pattern1.add_rectangle(0.0, 0.0, 10.0, 10.0);
1017 manager.add_pattern(pattern1).unwrap();
1018
1019 let dict = manager.to_resource_dictionary().unwrap();
1020 assert!(dict.starts_with("/Pattern <<"));
1021 assert!(dict.contains("/P1"));
1022 assert!(dict.ends_with(">>"));
1023 }
1024
1025 #[test]
1026 fn test_pattern_manager_default() {
1027 let manager = PatternManager::default();
1028 assert_eq!(manager.count(), 0);
1029 assert!(manager.patterns().is_empty());
1030 }
1031
1032 #[test]
1033 fn test_pattern_graphics_context_extension() {
1034 let mut context = GraphicsContext::new();
1035
1036 context.set_fill_pattern("TestPattern").unwrap();
1038 let commands = context.operations();
1039 assert!(commands.contains("/Pattern cs /TestPattern scn"));
1040
1041 context.set_stroke_pattern("StrokePattern").unwrap();
1043 let commands = context.operations();
1044 assert!(commands.contains("/Pattern CS /StrokePattern SCN"));
1045 }
1046
1047 #[test]
1048 fn test_tiling_pattern_uncolored() {
1049 let pattern = TilingPattern::new(
1050 "UncoloredPattern".to_string(),
1051 PaintType::Uncolored,
1052 TilingType::NoDistortion,
1053 [0.0, 0.0, 50.0, 50.0],
1054 50.0,
1055 50.0,
1056 );
1057
1058 assert_eq!(pattern.paint_type, PaintType::Uncolored);
1059 assert_eq!(pattern.tiling_type, TilingType::NoDistortion);
1060 }
1061
1062 #[test]
1063 fn test_checkerboard_pattern_content() {
1064 let mut manager = PatternManager::new();
1065 let name = manager
1066 .create_checkerboard_pattern(
1067 10.0,
1068 [1.0, 1.0, 1.0], [0.0, 0.0, 0.0], )
1071 .unwrap();
1072
1073 let pattern = manager.get_pattern(&name).unwrap();
1074 let content = String::from_utf8(pattern.content_stream.clone()).unwrap();
1075
1076 assert!(content.contains("1 1 1 rg")); assert!(content.contains("0 0 0 rg")); assert!(content.contains("re"));
1081 assert!(content.contains("f"));
1082 }
1083
1084 #[test]
1085 fn test_stripe_pattern_zero_angle() {
1086 let mut manager = PatternManager::new();
1087 let name = manager
1088 .create_stripe_pattern(
1089 5.0,
1090 0.0, [1.0, 0.0, 0.0],
1092 [0.0, 1.0, 0.0],
1093 )
1094 .unwrap();
1095
1096 let pattern = manager.get_pattern(&name).unwrap();
1097 assert_eq!(pattern.matrix.matrix, PatternMatrix::identity().matrix);
1099 }
1100
1101 #[test]
1102 fn test_dots_pattern_content() {
1103 let mut manager = PatternManager::new();
1104 let name = manager
1105 .create_dots_pattern(
1106 3.0, 10.0, [0.0, 0.0, 0.0], [1.0, 1.0, 1.0], )
1111 .unwrap();
1112
1113 let pattern = manager.get_pattern(&name).unwrap();
1114 let content = String::from_utf8(pattern.content_stream.clone()).unwrap();
1115
1116 assert!(content.contains("1 1 1 rg")); assert!(content.contains("0 0 10 10 re")); assert!(content.contains("0 0 0 rg")); assert!(content.contains("m")); assert!(content.contains("c")); }
1125
1126 #[test]
1127 fn test_pattern_validation_negative_step() {
1128 let pattern = TilingPattern::new(
1129 "NegativeStep".to_string(),
1130 PaintType::Colored,
1131 TilingType::ConstantSpacing,
1132 [0.0, 0.0, 100.0, 100.0],
1133 50.0,
1134 -50.0, );
1136
1137 assert!(pattern.validate().is_err());
1138 }
1139
1140 #[test]
1141 fn test_circle_approximation() {
1142 let mut pattern = TilingPattern::new(
1143 "CircleTest".to_string(),
1144 PaintType::Colored,
1145 TilingType::ConstantSpacing,
1146 [0.0, 0.0, 100.0, 100.0],
1147 100.0,
1148 100.0,
1149 );
1150
1151 pattern.add_circle(50.0, 50.0, 0.0); let content = String::from_utf8(pattern.content_stream.clone()).unwrap();
1153
1154 assert!(content.contains("50 50 m"));
1156 }
1157
1158 #[test]
1159 fn test_pattern_manager_get_nonexistent() {
1160 let manager = PatternManager::new();
1161 assert!(manager.get_pattern("NonExistent").is_none());
1162 }
1163
1164 #[test]
1165 fn test_pattern_type_debug_clone_eq() {
1166 let pattern_type = PatternType::Tiling;
1167
1168 let debug_str = format!("{pattern_type:?}");
1170 assert!(debug_str.contains("Tiling"));
1171
1172 let cloned = pattern_type;
1174 assert_eq!(cloned, PatternType::Tiling);
1175
1176 assert_eq!(PatternType::Tiling, PatternType::Tiling);
1178 assert_ne!(PatternType::Tiling, PatternType::Shading);
1179 }
1180
1181 #[test]
1182 fn test_tiling_pattern_debug_clone() {
1183 let pattern = TilingPattern::new(
1184 "TestPattern".to_string(),
1185 PaintType::Colored,
1186 TilingType::ConstantSpacing,
1187 [0.0, 0.0, 100.0, 100.0],
1188 50.0,
1189 50.0,
1190 );
1191
1192 let debug_str = format!("{pattern:?}");
1194 assert!(debug_str.contains("TilingPattern"));
1195 assert!(debug_str.contains("TestPattern"));
1196
1197 let cloned = pattern.clone();
1199 assert_eq!(cloned.name, pattern.name);
1200 assert_eq!(cloned.paint_type, pattern.paint_type);
1201 }
1202
1203 #[test]
1204 fn test_pattern_matrix_debug_clone_eq() {
1205 let matrix = PatternMatrix::translation(5.0, 10.0);
1206
1207 let debug_str = format!("{matrix:?}");
1209 assert!(debug_str.contains("PatternMatrix"));
1210
1211 let cloned = matrix.clone();
1213 assert_eq!(cloned.matrix, matrix.matrix);
1214
1215 assert_eq!(matrix, cloned);
1217 assert_ne!(matrix, PatternMatrix::identity());
1218 }
1219
1220 #[test]
1221 fn test_pattern_type() {
1222 assert_eq!(PatternType::Tiling as i32, 1);
1223 assert_eq!(PatternType::Shading as i32, 2);
1224 }
1225
1226 #[test]
1227 fn test_tiling_type() {
1228 assert_eq!(TilingType::ConstantSpacing as i32, 1);
1229 assert_eq!(TilingType::NoDistortion as i32, 2);
1230 assert_eq!(TilingType::ConstantSpacingFaster as i32, 3);
1231 }
1232
1233 #[test]
1234 fn test_paint_type() {
1235 assert_eq!(PaintType::Colored as i32, 1);
1236 assert_eq!(PaintType::Uncolored as i32, 2);
1237 }
1238
1239 #[test]
1240 fn test_pattern_manager() {
1241 let mut manager = PatternManager::new();
1242
1243 let mut pattern = TilingPattern::new(
1244 "P1".to_string(),
1245 PaintType::Colored,
1246 TilingType::ConstantSpacing,
1247 [0.0, 0.0, 10.0, 10.0],
1248 10.0,
1249 10.0,
1250 );
1251 pattern.content_stream = vec![b'q', b' ', b'Q']; let name = manager.add_pattern(pattern).unwrap();
1255 assert_eq!(name, "P1");
1256
1257 let mut pattern2 = TilingPattern::new(
1260 "P2".to_string(),
1261 PaintType::Uncolored,
1262 TilingType::NoDistortion,
1263 [0.0, 0.0, 20.0, 20.0],
1264 20.0,
1265 20.0,
1266 );
1267 pattern2.content_stream = vec![b'q', b' ', b'Q']; let name2 = manager.add_pattern(pattern2).unwrap();
1270 assert_eq!(name2, "P2");
1271 }
1272}