1use crate::array::{VertexDist, VertexSequence};
7use crate::basics::{
8 get_close_flag, get_orientation, is_ccw, is_end_poly, is_move_to, is_oriented, is_vertex,
9 PointD, PATH_CMD_END_POLY, PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP, PATH_FLAGS_CCW,
10 PATH_FLAGS_CLOSE, PATH_FLAGS_NONE,
11};
12use crate::math::calc_polygon_area_vd;
13use crate::math_stroke::{InnerJoin, LineCap, LineJoin, MathStroke};
14
15#[derive(Debug, Clone, Copy, PartialEq)]
20enum Status {
21 Initial,
22 Ready,
23 Outline,
24 OutVertices,
25 EndPoly,
26 Stop,
27}
28
29pub struct VcgenContour {
36 stroker: MathStroke,
37 width: f64,
38 src_vertices: VertexSequence,
39 out_vertices: Vec<PointD>,
40 status: Status,
41 src_vertex: usize,
42 out_vertex: usize,
43 closed: u32,
44 orientation: u32,
45 auto_detect: bool,
46}
47
48impl VcgenContour {
49 pub fn new() -> Self {
50 Self {
51 stroker: MathStroke::new(),
52 width: 1.0,
53 src_vertices: VertexSequence::new(),
54 out_vertices: Vec::new(),
55 status: Status::Initial,
56 src_vertex: 0,
57 out_vertex: 0,
58 closed: 0,
59 orientation: 0,
60 auto_detect: false,
61 }
62 }
63
64 pub fn set_line_cap(&mut self, lc: LineCap) {
66 self.stroker.set_line_cap(lc);
67 }
68 pub fn line_cap(&self) -> LineCap {
69 self.stroker.line_cap()
70 }
71
72 pub fn set_line_join(&mut self, lj: LineJoin) {
73 self.stroker.set_line_join(lj);
74 }
75 pub fn line_join(&self) -> LineJoin {
76 self.stroker.line_join()
77 }
78
79 pub fn set_inner_join(&mut self, ij: InnerJoin) {
80 self.stroker.set_inner_join(ij);
81 }
82 pub fn inner_join(&self) -> InnerJoin {
83 self.stroker.inner_join()
84 }
85
86 pub fn set_width(&mut self, w: f64) {
87 self.width = w;
88 self.stroker.set_width(w);
89 }
90 pub fn width(&self) -> f64 {
91 self.width
92 }
93
94 pub fn set_miter_limit(&mut self, ml: f64) {
95 self.stroker.set_miter_limit(ml);
96 }
97 pub fn miter_limit(&self) -> f64 {
98 self.stroker.miter_limit()
99 }
100
101 pub fn set_miter_limit_theta(&mut self, t: f64) {
102 self.stroker.set_miter_limit_theta(t);
103 }
104
105 pub fn set_inner_miter_limit(&mut self, ml: f64) {
106 self.stroker.set_inner_miter_limit(ml);
107 }
108 pub fn inner_miter_limit(&self) -> f64 {
109 self.stroker.inner_miter_limit()
110 }
111
112 pub fn set_approximation_scale(&mut self, s: f64) {
113 self.stroker.set_approximation_scale(s);
114 }
115 pub fn approximation_scale(&self) -> f64 {
116 self.stroker.approximation_scale()
117 }
118
119 pub fn set_auto_detect_orientation(&mut self, v: bool) {
120 self.auto_detect = v;
121 }
122 pub fn auto_detect_orientation(&self) -> bool {
123 self.auto_detect
124 }
125
126 pub fn remove_all(&mut self) {
128 self.src_vertices.remove_all();
129 self.closed = 0;
130 self.orientation = 0;
131 self.status = Status::Initial;
132 }
133
134 pub fn add_vertex(&mut self, x: f64, y: f64, cmd: u32) {
135 self.status = Status::Initial;
136 if is_move_to(cmd) {
137 self.src_vertices.modify_last(VertexDist::new(x, y));
138 } else if is_vertex(cmd) {
139 self.src_vertices.add(VertexDist::new(x, y));
140 } else if is_end_poly(cmd) {
141 self.closed = get_close_flag(cmd);
142 if self.orientation == PATH_FLAGS_NONE {
143 self.orientation = get_orientation(cmd);
144 }
145 }
146 }
147
148 pub fn rewind(&mut self, _path_id: u32) {
150 if self.status == Status::Initial {
151 self.src_vertices.close(true);
152 if self.auto_detect && !is_oriented(self.orientation) {
153 let verts: Vec<VertexDist> = (0..self.src_vertices.size())
154 .map(|i| self.src_vertices[i])
155 .collect();
156 self.orientation = if calc_polygon_area_vd(&verts) > 0.0 {
157 PATH_FLAGS_CCW
158 } else {
159 crate::basics::PATH_FLAGS_CW
160 };
161 }
162 if is_oriented(self.orientation) {
163 self.stroker.set_width(if is_ccw(self.orientation) {
164 self.width
165 } else {
166 -self.width
167 });
168 }
169 }
170 self.status = Status::Ready;
171 self.src_vertex = 0;
172 }
173
174 pub fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
175 let mut cmd = PATH_CMD_LINE_TO;
178 loop {
179 match self.status {
180 Status::Initial => {
181 self.rewind(0);
182 }
184 Status::Ready => {
185 if self.src_vertices.size() < 2 + (self.closed != 0) as usize {
186 return PATH_CMD_STOP;
187 }
188 self.status = Status::Outline;
189 cmd = PATH_CMD_MOVE_TO;
190 self.src_vertex = 0;
191 self.out_vertex = 0;
192 }
194 Status::Outline => {
195 if self.src_vertex >= self.src_vertices.size() {
196 self.status = Status::EndPoly;
197 continue;
198 }
199 let v_prev = *self.src_vertices.prev(self.src_vertex);
201 let v_curr = *self.src_vertices.curr(self.src_vertex);
202 let v_next = *self.src_vertices.next(self.src_vertex);
203 self.stroker.calc_join(
204 &mut self.out_vertices,
205 &v_prev,
206 &v_curr,
207 &v_next,
208 v_prev.dist,
209 v_curr.dist,
210 );
211 self.src_vertex += 1;
212 self.status = Status::OutVertices;
213 self.out_vertex = 0;
214 }
216 Status::OutVertices => {
217 if self.out_vertex >= self.out_vertices.len() {
218 self.status = Status::Outline;
219 } else {
221 let c = self.out_vertices[self.out_vertex];
222 self.out_vertex += 1;
223 *x = c.x;
224 *y = c.y;
225 return cmd;
226 }
227 }
228 Status::EndPoly => {
229 if self.closed == 0 {
230 return PATH_CMD_STOP;
231 }
232 self.status = Status::Stop;
233 return PATH_CMD_END_POLY | PATH_FLAGS_CLOSE | PATH_FLAGS_CCW;
234 }
235 Status::Stop => {
236 return PATH_CMD_STOP;
237 }
238 }
239 }
240 }
241}
242
243impl Default for VcgenContour {
244 fn default() -> Self {
245 Self::new()
246 }
247}
248
249impl crate::conv_adaptor_vcgen::VcgenGenerator for VcgenContour {
250 fn remove_all(&mut self) {
251 self.remove_all();
252 }
253 fn add_vertex(&mut self, x: f64, y: f64, cmd: u32) {
254 self.add_vertex(x, y, cmd);
255 }
256 fn rewind(&mut self, path_id: u32) {
257 self.rewind(path_id);
258 }
259 fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
260 self.vertex(x, y)
261 }
262}
263
264#[cfg(test)]
269mod tests {
270 use super::*;
271 use crate::basics::{is_stop, PATH_FLAGS_CLOSE};
272
273 fn collect_gen_vertices(gen: &mut VcgenContour) -> Vec<(f64, f64, u32)> {
274 gen.rewind(0);
275 let mut result = Vec::new();
276 loop {
277 let (mut x, mut y) = (0.0, 0.0);
278 let cmd = gen.vertex(&mut x, &mut y);
279 if is_stop(cmd) {
280 break;
281 }
282 result.push((x, y, cmd));
283 }
284 result
285 }
286
287 #[test]
288 fn test_new_defaults() {
289 let gen = VcgenContour::new();
290 assert!((gen.width() - 1.0).abs() < 1e-10);
291 assert!(!gen.auto_detect_orientation());
292 }
293
294 #[test]
295 fn test_empty_produces_stop() {
296 let mut gen = VcgenContour::new();
297 let verts = collect_gen_vertices(&mut gen);
298 assert!(verts.is_empty());
299 }
300
301 #[test]
302 fn test_closed_square_contour() {
303 let mut gen = VcgenContour::new();
304 gen.set_width(5.0);
305 gen.set_auto_detect_orientation(true);
306
307 gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
309 gen.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
310 gen.add_vertex(100.0, 100.0, PATH_CMD_LINE_TO);
311 gen.add_vertex(0.0, 100.0, PATH_CMD_LINE_TO);
312 gen.add_vertex(0.0, 0.0, PATH_CMD_END_POLY | PATH_FLAGS_CLOSE);
313
314 let verts = collect_gen_vertices(&mut gen);
315 assert!(
316 verts.len() >= 4,
317 "Expected at least 4 contour vertices, got {}",
318 verts.len()
319 );
320 assert_eq!(verts[0].2, PATH_CMD_MOVE_TO);
322 }
323
324 #[test]
325 fn test_contour_expands_ccw_polygon() {
326 let mut gen = VcgenContour::new();
327 gen.set_width(10.0);
328 gen.set_auto_detect_orientation(true);
329
330 gen.add_vertex(50.0, 10.0, PATH_CMD_MOVE_TO);
332 gen.add_vertex(90.0, 90.0, PATH_CMD_LINE_TO);
333 gen.add_vertex(10.0, 90.0, PATH_CMD_LINE_TO);
334 gen.add_vertex(0.0, 0.0, PATH_CMD_END_POLY | PATH_FLAGS_CLOSE);
335
336 let verts = collect_gen_vertices(&mut gen);
337
338 let max_x = verts
340 .iter()
341 .filter(|v| is_vertex(v.2))
342 .map(|v| v.0)
343 .fold(f64::MIN, f64::max);
344 let min_x = verts
345 .iter()
346 .filter(|v| is_vertex(v.2))
347 .map(|v| v.0)
348 .fold(f64::MAX, f64::min);
349
350 assert!(max_x > 90.0, "Max x={} should exceed original 90", max_x);
351 assert!(
352 min_x < 10.0,
353 "Min x={} should be less than original 10",
354 min_x
355 );
356 }
357
358 #[test]
359 fn test_width_setter() {
360 let mut gen = VcgenContour::new();
361 gen.set_width(7.5);
362 assert!((gen.width() - 7.5).abs() < 1e-10);
363 }
364
365 #[test]
366 fn test_remove_all() {
367 let mut gen = VcgenContour::new();
368 gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
369 gen.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
370 gen.add_vertex(100.0, 100.0, PATH_CMD_LINE_TO);
371 gen.add_vertex(0.0, 0.0, PATH_CMD_END_POLY | PATH_FLAGS_CLOSE);
372 gen.remove_all();
373 let verts = collect_gen_vertices(&mut gen);
374 assert!(verts.is_empty());
375 }
376
377 #[test]
378 fn test_rewind_replay() {
379 let mut gen = VcgenContour::new();
380 gen.set_width(5.0);
381 gen.set_auto_detect_orientation(true);
382 gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
383 gen.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
384 gen.add_vertex(50.0, 80.0, PATH_CMD_LINE_TO);
385 gen.add_vertex(0.0, 0.0, PATH_CMD_END_POLY | PATH_FLAGS_CLOSE);
386
387 let v1 = collect_gen_vertices(&mut gen);
388 let v2 = collect_gen_vertices(&mut gen);
389 assert_eq!(v1.len(), v2.len());
390 }
391
392 #[test]
393 fn test_auto_detect_orientation() {
394 let mut gen = VcgenContour::new();
395 gen.set_auto_detect_orientation(true);
396 assert!(gen.auto_detect_orientation());
397 gen.set_auto_detect_orientation(false);
398 assert!(!gen.auto_detect_orientation());
399 }
400
401 #[test]
402 fn test_line_join_setter() {
403 let mut gen = VcgenContour::new();
404 gen.set_line_join(LineJoin::Round);
405 assert_eq!(gen.line_join(), LineJoin::Round);
406 }
407
408 #[test]
409 fn test_open_path_no_contour() {
410 let mut gen = VcgenContour::new();
411 gen.set_width(5.0);
412 gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
414 gen.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
415 gen.add_vertex(100.0, 100.0, PATH_CMD_LINE_TO);
416
417 let verts = collect_gen_vertices(&mut gen);
418 let _ = verts;
423 }
424
425 #[test]
426 fn test_end_poly_emitted_for_closed() {
427 let mut gen = VcgenContour::new();
428 gen.set_width(5.0);
429 gen.set_auto_detect_orientation(true);
430
431 gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
433 gen.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
434 gen.add_vertex(50.0, 80.0, PATH_CMD_LINE_TO);
435 gen.add_vertex(0.0, 0.0, PATH_CMD_END_POLY | PATH_FLAGS_CLOSE);
436
437 gen.rewind(0);
438 let mut found_end_poly = false;
439 loop {
440 let (mut x, mut y) = (0.0, 0.0);
441 let cmd = gen.vertex(&mut x, &mut y);
442 if is_stop(cmd) {
443 break;
444 }
445 if (cmd & PATH_CMD_END_POLY) == PATH_CMD_END_POLY {
446 found_end_poly = true;
447 assert_ne!(cmd & PATH_FLAGS_CLOSE, 0, "end_poly should have close flag");
448 }
449 }
450 assert!(found_end_poly, "Closed contour should emit end_poly");
451 }
452}