oxidize_pdf/graphics/
path.rs

1#[derive(Debug, Clone, Copy, PartialEq)]
2#[repr(u8)]
3pub enum LineCap {
4    Butt = 0,
5    Round = 1,
6    Square = 2,
7}
8
9#[derive(Debug, Clone, Copy, PartialEq)]
10#[repr(u8)]
11pub enum LineJoin {
12    Miter = 0,
13    Round = 1,
14    Bevel = 2,
15}
16
17pub struct PathBuilder {
18    commands: Vec<PathCommand>,
19}
20
21#[derive(Debug, Clone)]
22#[allow(dead_code)]
23pub(crate) enum PathCommand {
24    MoveTo(f64, f64),
25    LineTo(f64, f64),
26    CurveTo(f64, f64, f64, f64, f64, f64),
27    ClosePath,
28}
29
30impl Default for PathBuilder {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36#[allow(dead_code)]
37impl PathBuilder {
38    pub fn new() -> Self {
39        Self {
40            commands: Vec::new(),
41        }
42    }
43
44    pub fn move_to(mut self, x: f64, y: f64) -> Self {
45        self.commands.push(PathCommand::MoveTo(x, y));
46        self
47    }
48
49    pub fn line_to(mut self, x: f64, y: f64) -> Self {
50        self.commands.push(PathCommand::LineTo(x, y));
51        self
52    }
53
54    pub fn curve_to(mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> Self {
55        self.commands
56            .push(PathCommand::CurveTo(x1, y1, x2, y2, x3, y3));
57        self
58    }
59
60    pub fn close(mut self) -> Self {
61        self.commands.push(PathCommand::ClosePath);
62        self
63    }
64
65    pub(crate) fn build(self) -> Vec<PathCommand> {
66        self.commands
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    
74    #[test]
75    fn test_line_cap_values() {
76        assert_eq!(LineCap::Butt as u8, 0);
77        assert_eq!(LineCap::Round as u8, 1);
78        assert_eq!(LineCap::Square as u8, 2);
79    }
80    
81    #[test]
82    fn test_line_cap_equality() {
83        assert_eq!(LineCap::Butt, LineCap::Butt);
84        assert_ne!(LineCap::Butt, LineCap::Round);
85        assert_ne!(LineCap::Round, LineCap::Square);
86    }
87    
88    #[test]
89    fn test_line_cap_debug() {
90        let butt = LineCap::Butt;
91        let debug_str = format!("{:?}", butt);
92        assert_eq!(debug_str, "Butt");
93        
94        let round = LineCap::Round;
95        assert_eq!(format!("{:?}", round), "Round");
96        
97        let square = LineCap::Square;
98        assert_eq!(format!("{:?}", square), "Square");
99    }
100    
101    #[test]
102    fn test_line_cap_clone() {
103        let cap = LineCap::Round;
104        let cap_clone = cap.clone();
105        assert_eq!(cap, cap_clone);
106    }
107    
108    #[test]
109    fn test_line_cap_copy() {
110        let cap = LineCap::Square;
111        let cap_copy = cap; // Copy semantics
112        assert_eq!(cap, cap_copy);
113        
114        // Both should still be usable
115        assert_eq!(cap, LineCap::Square);
116        assert_eq!(cap_copy, LineCap::Square);
117    }
118    
119    #[test]
120    fn test_line_join_values() {
121        assert_eq!(LineJoin::Miter as u8, 0);
122        assert_eq!(LineJoin::Round as u8, 1);
123        assert_eq!(LineJoin::Bevel as u8, 2);
124    }
125    
126    #[test]
127    fn test_line_join_equality() {
128        assert_eq!(LineJoin::Miter, LineJoin::Miter);
129        assert_ne!(LineJoin::Miter, LineJoin::Round);
130        assert_ne!(LineJoin::Round, LineJoin::Bevel);
131    }
132    
133    #[test]
134    fn test_line_join_debug() {
135        let miter = LineJoin::Miter;
136        let debug_str = format!("{:?}", miter);
137        assert_eq!(debug_str, "Miter");
138        
139        let round = LineJoin::Round;
140        assert_eq!(format!("{:?}", round), "Round");
141        
142        let bevel = LineJoin::Bevel;
143        assert_eq!(format!("{:?}", bevel), "Bevel");
144    }
145    
146    #[test]
147    fn test_line_join_clone() {
148        let join = LineJoin::Bevel;
149        let join_clone = join.clone();
150        assert_eq!(join, join_clone);
151    }
152    
153    #[test]
154    fn test_line_join_copy() {
155        let join = LineJoin::Miter;
156        let join_copy = join; // Copy semantics
157        assert_eq!(join, join_copy);
158        
159        // Both should still be usable
160        assert_eq!(join, LineJoin::Miter);
161        assert_eq!(join_copy, LineJoin::Miter);
162    }
163    
164    #[test]
165    fn test_path_builder_new() {
166        let builder = PathBuilder::new();
167        let commands = builder.build();
168        assert!(commands.is_empty());
169    }
170    
171    #[test]
172    fn test_path_builder_default() {
173        let builder = PathBuilder::default();
174        let commands = builder.build();
175        assert!(commands.is_empty());
176    }
177    
178    #[test]
179    fn test_path_builder_move_to() {
180        let builder = PathBuilder::new()
181            .move_to(10.0, 20.0);
182        let commands = builder.build();
183        
184        assert_eq!(commands.len(), 1);
185        match &commands[0] {
186            PathCommand::MoveTo(x, y) => {
187                assert_eq!(*x, 10.0);
188                assert_eq!(*y, 20.0);
189            }
190            _ => panic!("Expected MoveTo command"),
191        }
192    }
193    
194    #[test]
195    fn test_path_builder_line_to() {
196        let builder = PathBuilder::new()
197            .line_to(30.0, 40.0);
198        let commands = builder.build();
199        
200        assert_eq!(commands.len(), 1);
201        match &commands[0] {
202            PathCommand::LineTo(x, y) => {
203                assert_eq!(*x, 30.0);
204                assert_eq!(*y, 40.0);
205            }
206            _ => panic!("Expected LineTo command"),
207        }
208    }
209    
210    #[test]
211    fn test_path_builder_curve_to() {
212        let builder = PathBuilder::new()
213            .curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0);
214        let commands = builder.build();
215        
216        assert_eq!(commands.len(), 1);
217        match &commands[0] {
218            PathCommand::CurveTo(x1, y1, x2, y2, x3, y3) => {
219                assert_eq!(*x1, 10.0);
220                assert_eq!(*y1, 20.0);
221                assert_eq!(*x2, 30.0);
222                assert_eq!(*y2, 40.0);
223                assert_eq!(*x3, 50.0);
224                assert_eq!(*y3, 60.0);
225            }
226            _ => panic!("Expected CurveTo command"),
227        }
228    }
229    
230    #[test]
231    fn test_path_builder_close() {
232        let builder = PathBuilder::new()
233            .close();
234        let commands = builder.build();
235        
236        assert_eq!(commands.len(), 1);
237        match &commands[0] {
238            PathCommand::ClosePath => {}
239            _ => panic!("Expected ClosePath command"),
240        }
241    }
242    
243    #[test]
244    fn test_path_builder_complex_path() {
245        let builder = PathBuilder::new()
246            .move_to(0.0, 0.0)
247            .line_to(100.0, 0.0)
248            .line_to(100.0, 100.0)
249            .line_to(0.0, 100.0)
250            .close();
251        
252        let commands = builder.build();
253        assert_eq!(commands.len(), 5);
254        
255        match &commands[0] {
256            PathCommand::MoveTo(x, y) => {
257                assert_eq!(*x, 0.0);
258                assert_eq!(*y, 0.0);
259            }
260            _ => panic!("Expected MoveTo at index 0"),
261        }
262        
263        match &commands[1] {
264            PathCommand::LineTo(x, y) => {
265                assert_eq!(*x, 100.0);
266                assert_eq!(*y, 0.0);
267            }
268            _ => panic!("Expected LineTo at index 1"),
269        }
270        
271        match &commands[4] {
272            PathCommand::ClosePath => {}
273            _ => panic!("Expected ClosePath at index 4"),
274        }
275    }
276    
277    #[test]
278    fn test_path_builder_bezier_curve() {
279        let builder = PathBuilder::new()
280            .move_to(0.0, 0.0)
281            .curve_to(50.0, 0.0, 100.0, 50.0, 100.0, 100.0)
282            .curve_to(100.0, 150.0, 50.0, 200.0, 0.0, 200.0);
283        
284        let commands = builder.build();
285        assert_eq!(commands.len(), 3);
286        
287        match &commands[1] {
288            PathCommand::CurveTo(x1, y1, x2, y2, x3, y3) => {
289                assert_eq!(*x1, 50.0);
290                assert_eq!(*y1, 0.0);
291                assert_eq!(*x2, 100.0);
292                assert_eq!(*y2, 50.0);
293                assert_eq!(*x3, 100.0);
294                assert_eq!(*y3, 100.0);
295            }
296            _ => panic!("Expected CurveTo at index 1"),
297        }
298    }
299    
300    #[test]
301    fn test_path_command_debug() {
302        let move_cmd = PathCommand::MoveTo(10.0, 20.0);
303        let debug_str = format!("{:?}", move_cmd);
304        assert!(debug_str.contains("MoveTo"));
305        assert!(debug_str.contains("10.0"));
306        assert!(debug_str.contains("20.0"));
307        
308        let line_cmd = PathCommand::LineTo(30.0, 40.0);
309        let line_debug = format!("{:?}", line_cmd);
310        assert!(line_debug.contains("LineTo"));
311        
312        let curve_cmd = PathCommand::CurveTo(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
313        let curve_debug = format!("{:?}", curve_cmd);
314        assert!(curve_debug.contains("CurveTo"));
315        
316        let close_cmd = PathCommand::ClosePath;
317        let close_debug = format!("{:?}", close_cmd);
318        assert!(close_debug.contains("ClosePath"));
319    }
320    
321    #[test]
322    fn test_path_command_clone() {
323        let move_cmd = PathCommand::MoveTo(10.0, 20.0);
324        let move_clone = move_cmd.clone();
325        match (move_cmd, move_clone) {
326            (PathCommand::MoveTo(x1, y1), PathCommand::MoveTo(x2, y2)) => {
327                assert_eq!(x1, x2);
328                assert_eq!(y1, y2);
329            }
330            _ => panic!("Clone failed"),
331        }
332        
333        let close_cmd = PathCommand::ClosePath;
334        let close_clone = close_cmd.clone();
335        match close_clone {
336            PathCommand::ClosePath => {}
337            _ => panic!("Clone failed for ClosePath"),
338        }
339    }
340    
341    #[test]
342    fn test_path_builder_empty_path() {
343        let builder = PathBuilder::new();
344        let commands = builder.build();
345        assert_eq!(commands.len(), 0);
346    }
347    
348    #[test]
349    fn test_path_builder_single_command() {
350        // Test each command type in isolation
351        let move_builder = PathBuilder::new().move_to(5.0, 10.0);
352        assert_eq!(move_builder.build().len(), 1);
353        
354        let line_builder = PathBuilder::new().line_to(15.0, 20.0);
355        assert_eq!(line_builder.build().len(), 1);
356        
357        let curve_builder = PathBuilder::new().curve_to(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
358        assert_eq!(curve_builder.build().len(), 1);
359        
360        let close_builder = PathBuilder::new().close();
361        assert_eq!(close_builder.build().len(), 1);
362    }
363    
364    #[test]
365    fn test_path_builder_negative_coordinates() {
366        let builder = PathBuilder::new()
367            .move_to(-10.0, -20.0)
368            .line_to(-30.0, -40.0)
369            .curve_to(-1.0, -2.0, -3.0, -4.0, -5.0, -6.0);
370        
371        let commands = builder.build();
372        assert_eq!(commands.len(), 3);
373        
374        match &commands[0] {
375            PathCommand::MoveTo(x, y) => {
376                assert_eq!(*x, -10.0);
377                assert_eq!(*y, -20.0);
378            }
379            _ => panic!("Expected MoveTo"),
380        }
381    }
382    
383    #[test]
384    fn test_path_builder_zero_values() {
385        let builder = PathBuilder::new()
386            .move_to(0.0, 0.0)
387            .line_to(0.0, 0.0)
388            .curve_to(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
389        
390        let commands = builder.build();
391        assert_eq!(commands.len(), 3);
392        
393        // All commands should have zero values
394        match &commands[0] {
395            PathCommand::MoveTo(x, y) => {
396                assert_eq!(*x, 0.0);
397                assert_eq!(*y, 0.0);
398            }
399            _ => panic!("Expected MoveTo"),
400        }
401    }
402    
403    #[test]
404    fn test_path_builder_large_values() {
405        let large_val = 1e6;
406        let builder = PathBuilder::new()
407            .move_to(large_val, large_val)
408            .line_to(large_val * 2.0, large_val * 2.0);
409        
410        let commands = builder.build();
411        match &commands[0] {
412            PathCommand::MoveTo(x, y) => {
413                assert_eq!(*x, large_val);
414                assert_eq!(*y, large_val);
415            }
416            _ => panic!("Expected MoveTo"),
417        }
418    }
419}