1use crate::array::{shorten_path, VertexDist, VertexSequence};
7use crate::basics::{
8 get_close_flag, is_move_to, is_vertex, PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP,
9};
10
11const MAX_DASHES: usize = 32;
12
13#[derive(Debug, Clone, Copy, PartialEq)]
18enum Status {
19 Initial,
20 Ready,
21 Polyline,
22 Stop,
23}
24
25pub struct VcgenDash {
32 dashes: [f64; MAX_DASHES],
33 total_dash_len: f64,
34 num_dashes: usize,
35 dash_start: f64,
36 shorten: f64,
37 curr_dash_start: f64,
38 curr_dash: usize,
39 curr_rest: f64,
40 v1_idx: usize,
41 v2_idx: usize,
42 src_vertices: VertexSequence,
43 closed: u32,
44 status: Status,
45 src_vertex: usize,
46}
47
48impl VcgenDash {
49 pub fn new() -> Self {
50 Self {
51 dashes: [0.0; MAX_DASHES],
52 total_dash_len: 0.0,
53 num_dashes: 0,
54 dash_start: 0.0,
55 shorten: 0.0,
56 curr_dash_start: 0.0,
57 curr_dash: 0,
58 curr_rest: 0.0,
59 v1_idx: 0,
60 v2_idx: 0,
61 src_vertices: VertexSequence::new(),
62 closed: 0,
63 status: Status::Initial,
64 src_vertex: 0,
65 }
66 }
67
68 pub fn remove_all_dashes(&mut self) {
69 self.total_dash_len = 0.0;
70 self.num_dashes = 0;
71 self.curr_dash_start = 0.0;
72 self.curr_dash = 0;
73 }
74
75 pub fn add_dash(&mut self, dash_len: f64, gap_len: f64) {
76 if self.num_dashes < MAX_DASHES {
77 self.total_dash_len += dash_len + gap_len;
78 self.dashes[self.num_dashes] = dash_len;
79 self.num_dashes += 1;
80 self.dashes[self.num_dashes] = gap_len;
81 self.num_dashes += 1;
82 }
83 }
84
85 pub fn dash_start(&mut self, ds: f64) {
86 self.dash_start = ds;
87 self.calc_dash_start(ds.abs());
88 }
89
90 fn calc_dash_start(&mut self, mut ds: f64) {
91 self.curr_dash = 0;
92 self.curr_dash_start = 0.0;
93 while ds > 0.0 {
94 if ds > self.dashes[self.curr_dash] {
95 ds -= self.dashes[self.curr_dash];
96 self.curr_dash += 1;
97 self.curr_dash_start = 0.0;
98 if self.curr_dash >= self.num_dashes {
99 self.curr_dash = 0;
100 }
101 } else {
102 self.curr_dash_start = ds;
103 ds = 0.0;
104 }
105 }
106 }
107
108 pub fn set_shorten(&mut self, s: f64) {
109 self.shorten = s;
110 }
111
112 pub fn shorten(&self) -> f64 {
113 self.shorten
114 }
115
116 pub fn remove_all(&mut self) {
118 self.status = Status::Initial;
119 self.src_vertices.remove_all();
120 self.closed = 0;
121 }
122
123 pub fn add_vertex(&mut self, x: f64, y: f64, cmd: u32) {
124 self.status = Status::Initial;
125 if is_move_to(cmd) {
126 self.src_vertices.modify_last(VertexDist::new(x, y));
127 } else if is_vertex(cmd) {
128 self.src_vertices.add(VertexDist::new(x, y));
129 } else {
130 self.closed = get_close_flag(cmd);
131 }
132 }
133
134 pub fn rewind(&mut self, _path_id: u32) {
136 if self.status == Status::Initial {
137 self.src_vertices.close(self.closed != 0);
138 shorten_path(&mut self.src_vertices, self.shorten, self.closed);
139 }
140 self.status = Status::Ready;
141 self.src_vertex = 0;
142 }
143
144 pub fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
145 let mut cmd = PATH_CMD_MOVE_TO;
149 loop {
150 if crate::basics::is_stop(cmd) {
151 break;
152 }
153 match self.status {
154 Status::Initial => {
155 self.rewind(0);
156 }
158 Status::Ready => {
159 if self.num_dashes < 2 || self.src_vertices.size() < 2 {
160 cmd = PATH_CMD_STOP;
161 continue; }
163 self.status = Status::Polyline;
164 self.src_vertex = 1;
165 self.v1_idx = 0;
166 self.v2_idx = 1;
167 self.curr_rest = self.src_vertices[0].dist;
168 *x = self.src_vertices[0].x;
169 *y = self.src_vertices[0].y;
170 if self.dash_start >= 0.0 {
171 self.calc_dash_start(self.dash_start);
172 }
173 return PATH_CMD_MOVE_TO;
174 }
175 Status::Polyline => {
176 let dash_rest = self.dashes[self.curr_dash] - self.curr_dash_start;
177
178 cmd = if (self.curr_dash & 1) != 0 {
179 PATH_CMD_MOVE_TO
180 } else {
181 PATH_CMD_LINE_TO
182 };
183
184 let v1 = self.src_vertices[self.v1_idx];
185 let v2 = self.src_vertices[self.v2_idx];
186
187 if self.curr_rest > dash_rest {
188 self.curr_rest -= dash_rest;
189 self.curr_dash += 1;
190 if self.curr_dash >= self.num_dashes {
191 self.curr_dash = 0;
192 }
193 self.curr_dash_start = 0.0;
194 *x = v2.x - (v2.x - v1.x) * self.curr_rest / v1.dist;
195 *y = v2.y - (v2.y - v1.y) * self.curr_rest / v1.dist;
196 } else {
197 self.curr_dash_start += self.curr_rest;
198 *x = v2.x;
199 *y = v2.y;
200 self.src_vertex += 1;
201 self.v1_idx = self.v2_idx;
202 self.curr_rest = self.src_vertices[self.v1_idx].dist;
203 if self.closed != 0 {
204 if self.src_vertex > self.src_vertices.size() {
205 self.status = Status::Stop;
206 } else {
207 self.v2_idx = if self.src_vertex >= self.src_vertices.size() {
208 0
209 } else {
210 self.src_vertex
211 };
212 }
213 } else if self.src_vertex >= self.src_vertices.size() {
214 self.status = Status::Stop;
215 } else {
216 self.v2_idx = self.src_vertex;
217 }
218 }
219 return cmd;
220 }
221 Status::Stop => {
222 cmd = PATH_CMD_STOP;
223 continue; }
225 }
226 }
227 PATH_CMD_STOP
228 }
229}
230
231impl Default for VcgenDash {
232 fn default() -> Self {
233 Self::new()
234 }
235}
236
237impl crate::conv_adaptor_vcgen::VcgenGenerator for VcgenDash {
238 fn remove_all(&mut self) {
239 self.remove_all();
240 }
241 fn add_vertex(&mut self, x: f64, y: f64, cmd: u32) {
242 self.add_vertex(x, y, cmd);
243 }
244 fn rewind(&mut self, path_id: u32) {
245 self.rewind(path_id);
246 }
247 fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
248 self.vertex(x, y)
249 }
250}
251
252#[cfg(test)]
257mod tests {
258 use super::*;
259 use crate::basics::{is_stop, is_vertex, PATH_CMD_MOVE_TO};
260
261 fn collect_gen_vertices(gen: &mut VcgenDash) -> Vec<(f64, f64, u32)> {
262 gen.rewind(0);
263 let mut result = Vec::new();
264 loop {
265 let (mut x, mut y) = (0.0, 0.0);
266 let cmd = gen.vertex(&mut x, &mut y);
267 if is_stop(cmd) {
268 break;
269 }
270 result.push((x, y, cmd));
271 }
272 result
273 }
274
275 #[test]
276 fn test_new_defaults() {
277 let gen = VcgenDash::new();
278 assert!((gen.shorten() - 0.0).abs() < 1e-10);
279 }
280
281 #[test]
282 fn test_empty_produces_stop() {
283 let mut gen = VcgenDash::new();
284 gen.add_dash(10.0, 5.0);
285 let verts = collect_gen_vertices(&mut gen);
286 assert!(verts.is_empty());
287 }
288
289 #[test]
290 fn test_no_dashes_produces_stop() {
291 let mut gen = VcgenDash::new();
292 gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
294 gen.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
295 let verts = collect_gen_vertices(&mut gen);
296 assert!(verts.is_empty(), "No dashes → no output");
297 }
298
299 #[test]
300 fn test_basic_dash_pattern() {
301 let mut gen = VcgenDash::new();
302 gen.add_dash(20.0, 10.0); gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
304 gen.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
305
306 let verts = collect_gen_vertices(&mut gen);
307 assert!(!verts.is_empty(), "Should produce dash vertices");
308
309 assert_eq!(verts[0].2, PATH_CMD_MOVE_TO);
311 assert!((verts[0].0 - 0.0).abs() < 1e-10);
312 }
313
314 #[test]
315 fn test_dash_has_gaps() {
316 let mut gen = VcgenDash::new();
317 gen.add_dash(20.0, 10.0);
318 gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
319 gen.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
320
321 let verts = collect_gen_vertices(&mut gen);
322
323 let move_count = verts.iter().filter(|v| v.2 == PATH_CMD_MOVE_TO).count();
325 assert!(
326 move_count >= 2,
327 "Expected multiple dash segments, got {} move_to",
328 move_count
329 );
330 }
331
332 #[test]
333 fn test_dash_vertices_on_line() {
334 let mut gen = VcgenDash::new();
335 gen.add_dash(25.0, 10.0);
336 gen.add_vertex(0.0, 50.0, PATH_CMD_MOVE_TO);
337 gen.add_vertex(100.0, 50.0, PATH_CMD_LINE_TO);
338
339 let verts = collect_gen_vertices(&mut gen);
340
341 for v in &verts {
343 if is_vertex(v.2) {
344 assert!((v.1 - 50.0).abs() < 1e-10, "y={} should be 50", v.1);
345 }
346 }
347 }
348
349 #[test]
350 fn test_dash_rewind_replay() {
351 let mut gen = VcgenDash::new();
352 gen.add_dash(15.0, 5.0);
353 gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
354 gen.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
355
356 let v1 = collect_gen_vertices(&mut gen);
357 let v2 = collect_gen_vertices(&mut gen);
358 assert_eq!(v1.len(), v2.len());
359 }
360
361 #[test]
362 fn test_remove_all() {
363 let mut gen = VcgenDash::new();
364 gen.add_dash(10.0, 5.0);
365 gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
366 gen.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
367 gen.remove_all();
368 let verts = collect_gen_vertices(&mut gen);
369 assert!(verts.is_empty());
370 }
371
372 #[test]
373 fn test_remove_all_dashes() {
374 let mut gen = VcgenDash::new();
375 gen.add_dash(10.0, 5.0);
376 gen.remove_all_dashes();
377 gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
378 gen.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
379 let verts = collect_gen_vertices(&mut gen);
380 assert!(verts.is_empty(), "Removed dashes → no output");
381 }
382
383 #[test]
384 fn test_dash_start_offset() {
385 let mut gen1 = VcgenDash::new();
386 gen1.add_dash(20.0, 10.0);
387 gen1.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
388 gen1.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
389 let v1 = collect_gen_vertices(&mut gen1);
390
391 let mut gen2 = VcgenDash::new();
392 gen2.add_dash(20.0, 10.0);
393 gen2.dash_start(15.0); gen2.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
395 gen2.add_vertex(100.0, 0.0, PATH_CMD_LINE_TO);
396 let v2 = collect_gen_vertices(&mut gen2);
397
398 assert_ne!(
400 v1.len(),
401 v2.len(),
402 "Different dash start should change vertex count"
403 );
404 }
405
406 #[test]
407 fn test_multiple_dash_gaps() {
408 let mut gen = VcgenDash::new();
409 gen.add_dash(10.0, 5.0);
410 gen.add_dash(20.0, 5.0);
411 gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
412 gen.add_vertex(200.0, 0.0, PATH_CMD_LINE_TO);
413
414 let verts = collect_gen_vertices(&mut gen);
415 assert!(!verts.is_empty());
416
417 let line_count = verts.iter().filter(|v| v.2 == PATH_CMD_LINE_TO).count();
419 assert!(
420 line_count >= 3,
421 "Expected multiple line segments, got {}",
422 line_count
423 );
424 }
425
426 #[test]
427 fn test_shorten_setter() {
428 let mut gen = VcgenDash::new();
429 gen.set_shorten(5.0);
430 assert!((gen.shorten() - 5.0).abs() < 1e-10);
431 }
432
433 #[test]
434 fn test_diagonal_dash() {
435 let mut gen = VcgenDash::new();
436 gen.add_dash(10.0, 5.0);
437 gen.add_vertex(0.0, 0.0, PATH_CMD_MOVE_TO);
438 gen.add_vertex(100.0, 100.0, PATH_CMD_LINE_TO);
439
440 let verts = collect_gen_vertices(&mut gen);
441 assert!(!verts.is_empty());
442
443 for v in &verts {
445 if is_vertex(v.2) {
446 assert!(
447 (v.0 - v.1).abs() < 1e-10,
448 "Point ({}, {}) should be on y=x diagonal",
449 v.0,
450 v.1
451 );
452 }
453 }
454 }
455}