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;
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;
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().move_to(10.0, 20.0);
181        let commands = builder.build();
182
183        assert_eq!(commands.len(), 1);
184        match &commands[0] {
185            PathCommand::MoveTo(x, y) => {
186                assert_eq!(*x, 10.0);
187                assert_eq!(*y, 20.0);
188            }
189            _ => panic!("Expected MoveTo command"),
190        }
191    }
192
193    #[test]
194    fn test_path_builder_line_to() {
195        let builder = PathBuilder::new().line_to(30.0, 40.0);
196        let commands = builder.build();
197
198        assert_eq!(commands.len(), 1);
199        match &commands[0] {
200            PathCommand::LineTo(x, y) => {
201                assert_eq!(*x, 30.0);
202                assert_eq!(*y, 40.0);
203            }
204            _ => panic!("Expected LineTo command"),
205        }
206    }
207
208    #[test]
209    fn test_path_builder_curve_to() {
210        let builder = PathBuilder::new().curve_to(10.0, 20.0, 30.0, 40.0, 50.0, 60.0);
211        let commands = builder.build();
212
213        assert_eq!(commands.len(), 1);
214        match &commands[0] {
215            PathCommand::CurveTo(x1, y1, x2, y2, x3, y3) => {
216                assert_eq!(*x1, 10.0);
217                assert_eq!(*y1, 20.0);
218                assert_eq!(*x2, 30.0);
219                assert_eq!(*y2, 40.0);
220                assert_eq!(*x3, 50.0);
221                assert_eq!(*y3, 60.0);
222            }
223            _ => panic!("Expected CurveTo command"),
224        }
225    }
226
227    #[test]
228    fn test_path_builder_close() {
229        let builder = PathBuilder::new().close();
230        let commands = builder.build();
231
232        assert_eq!(commands.len(), 1);
233        match &commands[0] {
234            PathCommand::ClosePath => {}
235            _ => panic!("Expected ClosePath command"),
236        }
237    }
238
239    #[test]
240    fn test_path_builder_complex_path() {
241        let builder = PathBuilder::new()
242            .move_to(0.0, 0.0)
243            .line_to(100.0, 0.0)
244            .line_to(100.0, 100.0)
245            .line_to(0.0, 100.0)
246            .close();
247
248        let commands = builder.build();
249        assert_eq!(commands.len(), 5);
250
251        match &commands[0] {
252            PathCommand::MoveTo(x, y) => {
253                assert_eq!(*x, 0.0);
254                assert_eq!(*y, 0.0);
255            }
256            _ => panic!("Expected MoveTo at index 0"),
257        }
258
259        match &commands[1] {
260            PathCommand::LineTo(x, y) => {
261                assert_eq!(*x, 100.0);
262                assert_eq!(*y, 0.0);
263            }
264            _ => panic!("Expected LineTo at index 1"),
265        }
266
267        match &commands[4] {
268            PathCommand::ClosePath => {}
269            _ => panic!("Expected ClosePath at index 4"),
270        }
271    }
272
273    #[test]
274    fn test_path_builder_bezier_curve() {
275        let builder = PathBuilder::new()
276            .move_to(0.0, 0.0)
277            .curve_to(50.0, 0.0, 100.0, 50.0, 100.0, 100.0)
278            .curve_to(100.0, 150.0, 50.0, 200.0, 0.0, 200.0);
279
280        let commands = builder.build();
281        assert_eq!(commands.len(), 3);
282
283        match &commands[1] {
284            PathCommand::CurveTo(x1, y1, x2, y2, x3, y3) => {
285                assert_eq!(*x1, 50.0);
286                assert_eq!(*y1, 0.0);
287                assert_eq!(*x2, 100.0);
288                assert_eq!(*y2, 50.0);
289                assert_eq!(*x3, 100.0);
290                assert_eq!(*y3, 100.0);
291            }
292            _ => panic!("Expected CurveTo at index 1"),
293        }
294    }
295
296    #[test]
297    fn test_path_command_debug() {
298        let move_cmd = PathCommand::MoveTo(10.0, 20.0);
299        let debug_str = format!("{:?}", move_cmd);
300        assert!(debug_str.contains("MoveTo"));
301        assert!(debug_str.contains("10.0"));
302        assert!(debug_str.contains("20.0"));
303
304        let line_cmd = PathCommand::LineTo(30.0, 40.0);
305        let line_debug = format!("{:?}", line_cmd);
306        assert!(line_debug.contains("LineTo"));
307
308        let curve_cmd = PathCommand::CurveTo(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
309        let curve_debug = format!("{:?}", curve_cmd);
310        assert!(curve_debug.contains("CurveTo"));
311
312        let close_cmd = PathCommand::ClosePath;
313        let close_debug = format!("{:?}", close_cmd);
314        assert!(close_debug.contains("ClosePath"));
315    }
316
317    #[test]
318    fn test_path_command_clone() {
319        let move_cmd = PathCommand::MoveTo(10.0, 20.0);
320        let move_clone = move_cmd.clone();
321        match (move_cmd, move_clone) {
322            (PathCommand::MoveTo(x1, y1), PathCommand::MoveTo(x2, y2)) => {
323                assert_eq!(x1, x2);
324                assert_eq!(y1, y2);
325            }
326            _ => panic!("Clone failed"),
327        }
328
329        let close_cmd = PathCommand::ClosePath;
330        let close_clone = close_cmd.clone();
331        match close_clone {
332            PathCommand::ClosePath => {}
333            _ => panic!("Clone failed for ClosePath"),
334        }
335    }
336
337    #[test]
338    fn test_path_builder_empty_path() {
339        let builder = PathBuilder::new();
340        let commands = builder.build();
341        assert_eq!(commands.len(), 0);
342    }
343
344    #[test]
345    fn test_path_builder_single_command() {
346        // Test each command type in isolation
347        let move_builder = PathBuilder::new().move_to(5.0, 10.0);
348        assert_eq!(move_builder.build().len(), 1);
349
350        let line_builder = PathBuilder::new().line_to(15.0, 20.0);
351        assert_eq!(line_builder.build().len(), 1);
352
353        let curve_builder = PathBuilder::new().curve_to(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
354        assert_eq!(curve_builder.build().len(), 1);
355
356        let close_builder = PathBuilder::new().close();
357        assert_eq!(close_builder.build().len(), 1);
358    }
359
360    #[test]
361    fn test_path_builder_negative_coordinates() {
362        let builder = PathBuilder::new()
363            .move_to(-10.0, -20.0)
364            .line_to(-30.0, -40.0)
365            .curve_to(-1.0, -2.0, -3.0, -4.0, -5.0, -6.0);
366
367        let commands = builder.build();
368        assert_eq!(commands.len(), 3);
369
370        match &commands[0] {
371            PathCommand::MoveTo(x, y) => {
372                assert_eq!(*x, -10.0);
373                assert_eq!(*y, -20.0);
374            }
375            _ => panic!("Expected MoveTo"),
376        }
377    }
378
379    #[test]
380    fn test_path_builder_zero_values() {
381        let builder = PathBuilder::new()
382            .move_to(0.0, 0.0)
383            .line_to(0.0, 0.0)
384            .curve_to(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
385
386        let commands = builder.build();
387        assert_eq!(commands.len(), 3);
388
389        // All commands should have zero values
390        match &commands[0] {
391            PathCommand::MoveTo(x, y) => {
392                assert_eq!(*x, 0.0);
393                assert_eq!(*y, 0.0);
394            }
395            _ => panic!("Expected MoveTo"),
396        }
397    }
398
399    #[test]
400    fn test_path_builder_large_values() {
401        let large_val = 1e6;
402        let builder = PathBuilder::new()
403            .move_to(large_val, large_val)
404            .line_to(large_val * 2.0, large_val * 2.0);
405
406        let commands = builder.build();
407        match &commands[0] {
408            PathCommand::MoveTo(x, y) => {
409                assert_eq!(*x, large_val);
410                assert_eq!(*y, large_val);
411            }
412            _ => panic!("Expected MoveTo"),
413        }
414    }
415}