del_canvas/
canvas_svg.rs

1pub struct Canvas {
2    pub width: usize,
3    pub height: usize,
4    pub file_path: String,
5    pub tags: Vec<String>,
6}
7
8impl crate::canvas_svg::Canvas {
9    pub fn new(file_path: String, size: (usize, usize)) -> Self {
10        crate::canvas_svg::Canvas {
11            width: size.0,
12            height: size.1,
13            file_path,
14            tags: vec![],
15        }
16    }
17
18    pub fn write(&self) {
19        let mut file = std::fs::File::create(self.file_path.as_str()).expect("file not found.");
20        use std::io::Write;
21        writeln!(
22            file,
23            "<svg width=\"{}\" height=\"{}\" xmlns=\"http://www.w3.org/2000/svg\">",
24            self.width, self.height
25        )
26        .expect("cannot write.");
27        for s in &self.tags {
28            writeln!(file, "{}", s).expect("cannot write");
29        }
30        writeln!(file, "</svg>").expect("cannot write");
31    }
32
33    pub fn polyloop(
34        &mut self,
35        vtx2xy: &[f32],
36        transform_xy2pix: &[f32; 9],
37        stroke_color: Option<i32>,
38        stroke_width: Option<f32>,
39        fill: Option<i32>,
40    ) {
41        let s = format!(
42            "<polygon points=\"{}\" {} {} {} />",
43            polyloop2_to_svg(vtx2xy, transform_xy2pix),
44            if stroke_color.is_some() {
45                format!("stroke=\"#{:06X}\"", stroke_color.unwrap())
46            } else {
47                "stroke=\"none\"".to_owned()
48            },
49            if stroke_width.is_some() {
50                format!("stroke-width=\"{}\"", stroke_width.unwrap())
51            } else {
52                "".to_owned()
53            },
54            if fill.is_some() {
55                format!("fill=\"#{:06X}\"", fill.unwrap())
56            } else {
57                "fill=\"none\"".to_owned()
58            }
59        );
60        self.tags.push(s);
61    }
62
63    pub fn circle(
64        &mut self,
65        x: f32,
66        y: f32,
67        transform_xy2pix: &[f32; 9],
68        radius: f32,
69        color: &str,
70    ) {
71        let p = [x, y, 1.];
72        let q = del_geo_core::mat3_col_major::mult_vec(transform_xy2pix, &p);
73        let s = format!(
74            "<circle cx=\"{}\" cy=\"{}\" r=\"{}\" fill=\"{}\" />",
75            q[0] / q[2],
76            q[1] / q[2],
77            radius,
78            color
79        );
80        self.tags.push(s);
81    }
82
83    pub fn line(
84        &mut self,
85        x1: f32,
86        y1: f32,
87        x2: f32,
88        y2: f32,
89        transform_xy2pix: &[f32; 9],
90        stroke_width: Option<f32>,
91    ) {
92        let p1 = [x1, y1, 1.];
93        let q1 = del_geo_core::mat3_col_major::mult_vec(transform_xy2pix, &p1);
94        let p2 = [x2, y2, 1.];
95        let q2 = del_geo_core::mat3_col_major::mult_vec(transform_xy2pix, &p2);
96        let s = format!(
97            "<line x1=\"{}\" y1=\"{}\" x2=\"{}\" y2=\"{}\" stroke=\"black\" {} />",
98            q1[0] / q1[2],
99            q1[1] / q1[2],
100            q2[0] / q2[2],
101            q2[1] / q2[2],
102            if stroke_width.is_some() {
103                format!("stroke-width=\"{}\"", stroke_width.unwrap())
104            } else {
105                "".to_owned()
106            }
107        );
108        self.tags.push(s);
109    }
110}
111
112#[test]
113fn hoge() {
114    let str2 = "M 457.60409,474.77081 H 347.66161 \
115    L 208.25942,282.21963 q -15.48914,0.60741 -25.20781,0.60741 \
116    -3.94821,0 -8.50384,0 -4.55562,-0.3037 -9.41496,-0.60741 \
117    v 119.66114 q 0,38.87469 8.50384,48.28965 11.54092,13.36318 34.62277,13.36318 \
118    h 16.09655 v 11.23721 H 47.901331 V 463.5336 h 15.489133 \
119    q 26.118931,0 37.356146,-17.00768 6.37788,-9.41496 6.37788,-44.64515 V 135.83213 \
120    q 0,-38.874683 -8.50384,-48.289646 Q 86.776018,74.17931 63.390464,74.17931 H 47.901331 \
121    V 62.942096 H 197.93333 q 65.60103,0 96.5793,9.718671 31.28197,9.414964 52.84528,35.230183 \
122    21.86701,25.51152 21.86701,61.04541 0,37.96356 -24.9041,65.90474 -24.60039,27.94118 -76.53454,39.48211 \
123    l 85.03838,118.1426 q 29.15601,40.69694 50.1119,54.06011 20.95589,13.36318 54.66753,17.00768 z \
124    M 165.13281,263.08599 q 5.77046,0 10.02238,0.30371 4.25192,0 6.9853,0 58.91944,0 88.68288,-25.51151 \
125    30.06714,-25.51152 30.06714,-64.99362 0,-38.57098 -24.29668,-62.56395 -23.99297,-24.296679 \
126    -63.77879,-24.296679 -17.61509,0 -47.68223,5.770461 z";
127    let strs = svg_outline_path_from_shape(str2);
128    // dbg!(&strs);
129    let loops = svg_loops_from_outline_path(&strs);
130    // dbg!(&loops);
131    // dbg!(loops.len());
132    let (width, height) = (512usize, 512usize);
133    let mut img_data = vec![255u8; height * width];
134    // winding number
135    for i_w in 0..width {
136        for i_h in 0..height {
137            let p = [i_w as f32 + 0.5f32, i_h as f32 + 0.5f32];
138            let mut wn = 0.0f32;
139            for i_loop in 0..loops.len() {
140                use slice_of_array::SliceFlatExt;
141                let loop0 = loops[i_loop].0.flat();
142                wn += crate::rasterize::polygon2::winding_number(loop0, &p);
143            }
144            if wn.round() as i64 != 0 {
145                img_data[i_h * width + i_w] = 128;
146            }
147        }
148    }
149    // dda
150    for (vtx2xy, _seg2vtx, _is_close) in &loops {
151        let num_vtx = vtx2xy.len();
152        for i_vtx in 0..num_vtx {
153            let j_vtx = (i_vtx + 1) % num_vtx;
154            let p0 = &vtx2xy[i_vtx];
155            let p1 = &vtx2xy[j_vtx];
156            crate::rasterize::line2::draw_pixcenter(
157                &mut img_data,
158                width,
159                p0,
160                p1,
161                &[1., 0., 0., 0., 1., 0., 0., 0., 1.],
162                3.0,
163                0,
164            );
165        }
166    }
167    let file = std::fs::File::create("target/r0.png").unwrap();
168    let w = std::io::BufWriter::new(file);
169    let mut encoder = png::Encoder::new(w, width.try_into().unwrap(), height.try_into().unwrap()); // Width is 2 pixels and height is 1.
170    encoder.set_color(png::ColorType::Grayscale);
171    encoder.set_depth(png::BitDepth::Eight);
172    let mut writer = encoder.write_header().unwrap();
173    writer.write_image_data(&img_data).unwrap(); // Save
174}
175
176#[test]
177fn hoge1() {
178    let str2 = "M 457.60409,474.77081 H 347.66161 \
179    L 208.25942,282.21963 q -15.48914,0.60741 -25.20781,0.60741 \
180    -3.94821,0 -8.50384,0 -4.55562,-0.3037 -9.41496,-0.60741 \
181    v 119.66114 q 0,38.87469 8.50384,48.28965 11.54092,13.36318 34.62277,13.36318 \
182    h 16.09655 v 11.23721 H 47.901331 V 463.5336 h 15.489133 \
183    q 26.118931,0 37.356146,-17.00768 6.37788,-9.41496 6.37788,-44.64515 V 135.83213 \
184    q 0,-38.874683 -8.50384,-48.289646 Q 86.776018,74.17931 63.390464,74.17931 H 47.901331 \
185    V 62.942096 H 197.93333 q 65.60103,0 96.5793,9.718671 31.28197,9.414964 52.84528,35.230183 \
186    21.86701,25.51152 21.86701,61.04541 0,37.96356 -24.9041,65.90474 -24.60039,27.94118 -76.53454,39.48211 \
187    l 85.03838,118.1426 q 29.15601,40.69694 50.1119,54.06011 20.95589,13.36318 54.66753,17.00768 z \
188    M 165.13281,263.08599 q 5.77046,0 10.02238,0.30371 4.25192,0 6.9853,0 58.91944,0 88.68288,-25.51151 \
189    30.06714,-25.51152 30.06714,-64.99362 0,-38.57098 -24.29668,-62.56395 -23.99297,-24.296679 \
190    -63.77879,-24.296679 -17.61509,0 -47.68223,5.770461 z";
191    let strs = svg_outline_path_from_shape(str2);
192    let loops = svg_loops_from_outline_path(&strs);
193    // dbg!(&loops);
194    let (width, height) = (512usize, 512usize);
195    let mut img_data = vec![255u8; height * width];
196    for (vtx2xy, seg2vtx, is_close) in &loops {
197        let vtxp2xy = polybezier2polyloop(&vtx2xy, &seg2vtx, *is_close, 0.01);
198        for i_vtx in 0..vtxp2xy.len() {
199            let j_vtx = (i_vtx + 1) % vtxp2xy.len();
200            let p0 = vtxp2xy[i_vtx];
201            let p1 = vtxp2xy[j_vtx];
202            crate::rasterize::line2::draw_dda_pixel_coordinate(&mut img_data, width, &p0, &p1, 0);
203        }
204    }
205    let file = std::fs::File::create("target/r1.png").unwrap();
206    let w = std::io::BufWriter::new(file);
207    let mut encoder = png::Encoder::new(w, width.try_into().unwrap(), height.try_into().unwrap()); // Width is 2 pixels and height is 1.
208    encoder.set_color(png::ColorType::Grayscale);
209    encoder.set_depth(png::BitDepth::Eight);
210    let mut writer = encoder.write_header().unwrap();
211    writer.write_image_data(&img_data).unwrap(); // Save
212}
213
214pub fn polyloop2_to_svg<Real>(vtx2xy: &[Real], transform: &[Real; 9]) -> String
215where
216    Real: std::fmt::Display + Copy + num_traits::Float,
217{
218    let mut res = String::new();
219    for ivtx in 0..vtx2xy.len() / 2 {
220        let x = vtx2xy[ivtx * 2];
221        let y = vtx2xy[ivtx * 2 + 1];
222        let a = del_geo_core::mat3_col_major::transform_homogeneous(transform, &[x, y]).unwrap();
223        res += format!("{} {}", a[0], a[1]).as_str();
224        if ivtx != vtx2xy.len() / 2 - 1 {
225            res += ",";
226        }
227    }
228    res
229}
230
231pub fn svg_outline_path_from_shape(s0: &str) -> Vec<String> {
232    let s0 = s0.as_bytes();
233    let mut imark = 0;
234    let mut strs = Vec::<String>::new();
235    for i in 0..s0.len() {
236        if s0[i].is_ascii_digit() {
237            continue;
238        }
239        if s0[i] == b',' {
240            strs.push(std::str::from_utf8(&s0[imark..i]).unwrap().to_string());
241            imark = i + 1; // mark should be the beginning position of the string so move next
242        }
243        if s0[i] == b' ' {
244            // sometimes the space act as delimiter in the SVG (inkscape version)
245            if i > imark {
246                strs.push(std::str::from_utf8(&s0[imark..i]).unwrap().to_string());
247            }
248            imark = i + 1; // mark should be the beginning position of the string so move next
249        }
250        if s0[i] == b'-' {
251            if i > imark {
252                strs.push(std::str::from_utf8(&s0[imark..i]).unwrap().to_string());
253            }
254            imark = i;
255        }
256        if s0[i].is_ascii_alphabetic() {
257            if i > imark {
258                strs.push(std::str::from_utf8(&s0[imark..i]).unwrap().to_string());
259            }
260            strs.push(std::str::from_utf8(&[s0[i]]).unwrap().to_string()); // push tag
261            imark = i + 1;
262        }
263    }
264    if s0.len() > imark {
265        strs.push(
266            std::str::from_utf8(&s0[imark..s0.len()])
267                .unwrap()
268                .to_string(),
269        );
270    }
271    strs
272}
273
274#[allow(clippy::identity_op)]
275pub fn svg_loops_from_outline_path(strs: &[String]) -> Vec<(Vec<[f32; 2]>, Vec<usize>, bool)> {
276    use del_geo_core::vec2::Vec2;
277    let hoge = |s0: &str, s1: &str| [s0.parse::<f32>().unwrap(), s1.parse::<f32>().unwrap()];
278    let mut loops: Vec<(Vec<[f32; 2]>, Vec<usize>, bool)> = vec![];
279    let mut vtxl2xy: Vec<[f32; 2]> = vec![];
280    let mut seg2vtxl: Vec<usize> = vec![0];
281    assert!(strs[0] == "M" || strs[0] == "m");
282    // assert!(strs[strs.len() - 1] == "Z" || strs[strs.len() - 1] == "z");
283    let mut pos_cur = [0f32; 2];
284    let mut is = 0;
285    loop {
286        if strs[is] == "M" {
287            // start absolute
288            is += 1;
289            pos_cur = hoge(&strs[is + 0], &strs[is + 1]);
290            vtxl2xy.push(pos_cur);
291            is += 2;
292        } else if strs[is] == "m" {
293            // start relative
294            is += 1;
295            pos_cur = pos_cur.add(&hoge(&strs[is + 0], &strs[is + 1]));
296            vtxl2xy.push(pos_cur);
297            is += 2;
298        } else if strs[is] == "l" {
299            // line relative
300            is += 1;
301            loop {
302                seg2vtxl.push(vtxl2xy.len());
303                let p1 = pos_cur.add(&hoge(&strs[is + 0], &strs[is + 1]));
304                vtxl2xy.push(p1);
305                pos_cur = p1;
306                is += 2;
307                if strs[is].as_bytes()[0].is_ascii_alphabetic() {
308                    break;
309                }
310            }
311        } else if strs[is] == "L" {
312            // line absolute
313            is += 1;
314            loop {
315                seg2vtxl.push(vtxl2xy.len());
316                let p1 = hoge(&strs[is + 0], &strs[is + 1]);
317                vtxl2xy.push(p1);
318                pos_cur = p1;
319                is += 2;
320                if strs[is].as_bytes()[0].is_ascii_alphabetic() {
321                    break;
322                }
323            }
324        } else if strs[is] == "v" {
325            // vertical relative
326            seg2vtxl.push(vtxl2xy.len());
327            let p1 = pos_cur.add(&[0., strs[is + 1].parse::<f32>().unwrap()]);
328            vtxl2xy.push(p1);
329            pos_cur = p1;
330            is += 2;
331        } else if strs[is] == "V" {
332            // vertical absolute
333            seg2vtxl.push(vtxl2xy.len());
334            let p1 = [pos_cur[0], strs[is + 1].parse::<f32>().unwrap()];
335            vtxl2xy.push(p1);
336            pos_cur = p1;
337            is += 2;
338        } else if strs[is] == "H" {
339            // horizontal absolute
340            seg2vtxl.push(vtxl2xy.len());
341            let p1 = [strs[is + 1].parse::<f32>().unwrap(), pos_cur[1]];
342            vtxl2xy.push(p1);
343            pos_cur = p1;
344            is += 2;
345        } else if strs[is] == "h" {
346            // horizontal relative
347            seg2vtxl.push(vtxl2xy.len());
348            let dh = strs[is + 1].parse::<f32>().unwrap();
349            let p1 = pos_cur.add(&[dh, 0.]);
350            vtxl2xy.push(p1);
351            pos_cur = p1;
352            is += 2;
353        } else if strs[is] == "q" {
354            // relative
355            is += 1;
356            loop {
357                // loop for poly quadratic Bézeir curve
358                let pm0 = pos_cur.add(&hoge(&strs[is + 0], &strs[is + 1]));
359                let p1 = pos_cur.add(&hoge(&strs[is + 2], &strs[is + 3]));
360                vtxl2xy.push(pm0);
361                seg2vtxl.push(vtxl2xy.len());
362                vtxl2xy.push(p1);
363                pos_cur = p1;
364                is += 4;
365                if is == strs.len() {
366                    loops.push((vtxl2xy.clone(), seg2vtxl.clone(), false));
367                    break;
368                }
369                if strs[is].as_bytes()[0].is_ascii_alphabetic() {
370                    break;
371                }
372            }
373        } else if strs[is] == "Q" {
374            // absolute
375            is += 1;
376            loop {
377                // loop for poly-Bezeir curve
378                let pm0 = hoge(&strs[is + 0], &strs[is + 1]);
379                let p1 = hoge(&strs[is + 2], &strs[is + 3]);
380                vtxl2xy.push(pm0);
381                seg2vtxl.push(vtxl2xy.len());
382                vtxl2xy.push(p1);
383                pos_cur = p1;
384                is += 4;
385                if strs[is].as_bytes()[0].is_ascii_alphabetic() {
386                    break;
387                }
388            }
389        } else if strs[is] == "c" {
390            // relative
391            is += 1;
392            loop {
393                // loop for poly cubic Bézeir curve
394                let pm0 = pos_cur.add(&hoge(&strs[is + 0], &strs[is + 1]));
395                let pm1 = pos_cur.add(&hoge(&strs[is + 2], &strs[is + 3]));
396                let p1 = pos_cur.add(&hoge(&strs[is + 4], &strs[is + 5]));
397                vtxl2xy.push(pm0);
398                vtxl2xy.push(pm1);
399                seg2vtxl.push(vtxl2xy.len());
400                vtxl2xy.push(p1);
401                pos_cur = p1;
402                is += 6;
403                if is == strs.len() {
404                    loops.push((vtxl2xy.clone(), seg2vtxl.clone(), false));
405                    break;
406                }
407                if strs[is].as_bytes()[0].is_ascii_alphabetic() {
408                    break;
409                }
410            }
411        } else if strs[is] == "z" || strs[is] == "Z" {
412            let pe = vtxl2xy[0];
413            let ps = vtxl2xy[vtxl2xy.len() - 1];
414            let _dist0 = ps.sub(&pe).norm();
415            loops.push((vtxl2xy.clone(), seg2vtxl.clone(), true));
416            vtxl2xy.clear();
417            seg2vtxl = vec![0];
418            is += 1;
419        } else {
420            dbg!("error!--> ", &strs[is]);
421            break;
422        }
423        if is == strs.len() {
424            break;
425        }
426    }
427    loops
428}
429pub fn polybezier2polyloop(
430    vtx2xy: &[[f32; 2]],
431    seg2vtx: &[usize],
432    is_close: bool,
433    edge_length: f32,
434) -> Vec<[f32; 2]> {
435    use del_geo_core::vec2::Vec2;
436    let mut ret: Vec<[f32; 2]> = vec![];
437    let num_seg = seg2vtx.len() - 1;
438    for i_seg in 0..num_seg {
439        let (is_vtx, ie_vtx) = (seg2vtx[i_seg], seg2vtx[i_seg + 1]);
440        let ps = &vtx2xy[is_vtx];
441        let pe = &vtx2xy[ie_vtx];
442        if ie_vtx - is_vtx == 1 {
443            let len = pe.sub(ps).norm();
444            let ndiv = (len / edge_length) as usize;
445            for i in 0..ndiv {
446                let r = i as f32 / ndiv as f32;
447                let p = ps.scale(1f32 - r).add(&pe.scale(r));
448                ret.push(p);
449            }
450        } else if ie_vtx - is_vtx == 2 {
451            // quadratic bezier
452            let pc = &vtx2xy[is_vtx + 1];
453            let ndiv = 10;
454            for idiv in 0..ndiv {
455                let t0 = idiv as f32 / ndiv as f32;
456                let p0 = del_geo_core::bezier_quadratic::eval(ps, pc, pe, t0);
457                ret.push(p0);
458            }
459        } else if ie_vtx - is_vtx == 3 {
460            // cubic bezier
461            let pc0 = &vtx2xy[is_vtx + 1];
462            let pc1 = &vtx2xy[is_vtx + 2];
463            let samples = del_geo_core::bezier_cubic::sample_uniform_length(
464                del_geo_core::bezier_cubic::ControlPoints::<'_, f32, 2> {
465                    p0: ps,
466                    p1: pc0,
467                    p2: pc1,
468                    p3: pe,
469                },
470                edge_length,
471                true,
472                false,
473                30,
474            );
475            ret.extend(samples);
476        }
477    }
478    if is_close {
479        let ps = &vtx2xy[vtx2xy.len() - 1];
480        let pe = &vtx2xy[0];
481        let len = pe.sub(ps).norm();
482        let ndiv = (len / edge_length) as usize;
483        for i in 0..ndiv {
484            let r = i as f32 / ndiv as f32;
485            let p = ps.scale(1f32 - r).add(&pe.scale(r));
486            ret.push(p);
487        }
488    }
489    ret
490}