Skip to main content

roughr/
generator.rs

1use std::fmt::{Display, Write};
2use std::ops::MulAssign;
3
4use euclid::default::Point2D;
5use euclid::Trig;
6use num_traits::{Float, FromPrimitive};
7use points_on_curve::{curve_to_bezier, points_on_bezier_curves};
8use svgtypes::PathSegment;
9
10use crate::core::{
11    Drawable, FillStyle, OpSet, OpSetType, OpType, Options, OptionsBuilder, PathInfo, _c,
12};
13use crate::geometry::{convert_bezier_quadratic_to_cubic, BezierQuadratic};
14use crate::points_on_path::{points_on_path, points_on_segments};
15use crate::renderer::{
16    bezier_cubic, bezier_quadratic, curve, ellipse_with_params, generate_ellipse_params, line,
17    linear_path, pattern_fill_arc, pattern_fill_polygons, rectangle, solid_fill_polygon, svg_path,
18    svg_segments,
19};
20
21pub struct Generator {
22    default_options: Options,
23}
24
25impl Default for Generator {
26    fn default() -> Self {
27        Self {
28            default_options: OptionsBuilder::default()
29                .seed(345_u64)
30                .build()
31                .expect("failed to build default options"),
32        }
33    }
34}
35
36impl Generator {
37    #[allow(dead_code)]
38    fn new(options: Options) -> Self {
39        Generator {
40            default_options: options,
41        }
42    }
43
44    fn d<T, F>(&self, name: T, op_sets: &[OpSet<F>], options: &Option<Options>) -> Drawable<F>
45    where
46        T: Into<String>,
47        F: Float + Trig + FromPrimitive,
48    {
49        Drawable {
50            shape: name.into(),
51            options: options
52                .clone()
53                .unwrap_or_else(|| self.default_options.clone()),
54            sets: Vec::from_iter(op_sets.iter().cloned()),
55        }
56    }
57
58    pub fn line<F>(&self, x1: F, y1: F, x2: F, y2: F, options: &Option<Options>) -> Drawable<F>
59    where
60        F: Float + Trig + FromPrimitive,
61    {
62        self.d(
63            "line",
64            &[line(
65                x1,
66                y1,
67                x2,
68                y2,
69                &mut options
70                    .clone()
71                    .unwrap_or_else(|| self.default_options.clone()),
72            )],
73            options,
74        )
75    }
76
77    pub fn rectangle<F>(
78        &self,
79        x: F,
80        y: F,
81        width: F,
82        height: F,
83        options: &Option<Options>,
84    ) -> Drawable<F>
85    where
86        F: Float + Trig + FromPrimitive,
87    {
88        let mut paths = vec![];
89        let mut options = options
90            .clone()
91            .unwrap_or_else(|| self.default_options.clone());
92        let outline = rectangle(x, y, width, height, &mut options);
93        if options.fill.is_some() {
94            let points = vec![
95                Point2D::new(x, y),
96                Point2D::new(x + width, y),
97                Point2D::new(x + width, y + height),
98                Point2D::new(x, y + height),
99            ];
100            if options.fill_style == Some(FillStyle::Solid) {
101                paths.push(solid_fill_polygon(&vec![points], &mut options));
102            } else {
103                paths.push(pattern_fill_polygons(vec![points], &mut options));
104            }
105        }
106        if options.stroke.is_some() {
107            paths.push(outline);
108        }
109
110        self.d("rectangle", &paths, &Some(options))
111    }
112
113    pub fn ellipse<F>(
114        &self,
115        x: F,
116        y: F,
117        width: F,
118        height: F,
119        options: &Option<Options>,
120    ) -> Drawable<F>
121    where
122        F: Float + Trig + FromPrimitive,
123    {
124        let mut paths = vec![];
125        let mut options = options
126            .clone()
127            .unwrap_or_else(|| self.default_options.clone());
128        let ellipse_params = generate_ellipse_params(width, height, &mut options);
129        let ellipse_response = ellipse_with_params(x, y, &mut options, &ellipse_params);
130        if options.fill.is_some() {
131            if options.fill_style == Some(FillStyle::Solid) {
132                let mut shape = ellipse_with_params(x, y, &mut options, &ellipse_params).opset;
133                shape.op_set_type = OpSetType::FillPath;
134                paths.push(shape);
135            } else {
136                paths.push(pattern_fill_polygons(
137                    vec![ellipse_response.estimated_points],
138                    &mut options,
139                ));
140            }
141        }
142        if options.stroke.is_some() {
143            paths.push(ellipse_response.opset);
144        }
145        self.d("ellipse", &paths, &Some(options))
146    }
147
148    pub fn circle<F>(&self, x: F, y: F, diameter: F, options: &Option<Options>) -> Drawable<F>
149    where
150        F: Float + Trig + FromPrimitive,
151    {
152        let mut shape = self.ellipse(x, y, diameter, diameter, options);
153        shape.shape = "circle".into();
154        shape
155    }
156
157    pub fn linear_path<F>(
158        &self,
159        points: &[Point2D<F>],
160        close: bool,
161        options: &Option<Options>,
162    ) -> Drawable<F>
163    where
164        F: Float + Trig + FromPrimitive,
165    {
166        let mut options = options
167            .clone()
168            .unwrap_or_else(|| self.default_options.clone());
169        self.d(
170            "linear_path",
171            &[linear_path(points, close, &mut options)],
172            &Some(options),
173        )
174    }
175
176    #[allow(clippy::too_many_arguments)]
177    pub fn arc<F>(
178        &self,
179        x: F,
180        y: F,
181        width: F,
182        height: F,
183        start: F,
184        stop: F,
185        closed: bool,
186        options: &Option<Options>,
187    ) -> Drawable<F>
188    where
189        F: Float + Trig + FromPrimitive,
190    {
191        let mut options = options
192            .clone()
193            .unwrap_or_else(|| self.default_options.clone());
194        let mut paths = vec![];
195        let outline =
196            crate::renderer::arc(x, y, width, height, start, stop, closed, true, &mut options);
197        if closed && options.fill.is_some() {
198            if options.fill_style == Some(FillStyle::Solid) {
199                options.disable_multi_stroke = Some(true);
200                let mut shape = crate::renderer::arc(
201                    x,
202                    y,
203                    width,
204                    height,
205                    start,
206                    stop,
207                    true,
208                    false,
209                    &mut options,
210                );
211                shape.op_set_type = OpSetType::FillPath;
212                paths.push(shape);
213            } else {
214                paths.push(pattern_fill_arc(
215                    x,
216                    y,
217                    width,
218                    height,
219                    start,
220                    stop,
221                    &mut options,
222                ));
223            }
224        }
225        if options.stroke.is_some() {
226            paths.push(outline);
227        }
228        self.d("arc", &paths, &Some(options))
229    }
230
231    pub fn bezier_quadratic<F>(
232        &self,
233        start: Point2D<F>,
234        cp: Point2D<F>,
235        end: Point2D<F>,
236        options: &Option<Options>,
237    ) -> Drawable<F>
238    where
239        F: Float + Trig + FromPrimitive + MulAssign + Display,
240    {
241        let mut paths = vec![];
242        let mut options = options
243            .clone()
244            .unwrap_or_else(|| self.default_options.clone());
245
246        let outline = bezier_quadratic(start, cp, end, &mut options);
247
248        if options.fill.is_some() {
249            // The fill algorithms expect at least 4 points of a cubic curve, else they panic
250            let cubic = convert_bezier_quadratic_to_cubic(BezierQuadratic { start, cp, end });
251            let crv = vec![cubic.start, cubic.cp1, cubic.cp2, cubic.end];
252
253            let poly_points = points_on_bezier_curves(
254                &crv,
255                _c(10.0),
256                Some(_c::<F>(1.0) + _c::<F>(options.roughness.unwrap_or(0.0)) / _c(2.0)),
257            );
258            if options.fill_style == Some(FillStyle::Solid) {
259                paths.push(solid_fill_polygon(&vec![poly_points], &mut options));
260            } else {
261                paths.push(pattern_fill_polygons(&mut vec![poly_points], &mut options));
262            }
263        }
264
265        if options.stroke.is_some() {
266            paths.push(outline);
267        }
268
269        self.d("curve", &paths, &Some(options))
270    }
271
272    pub fn bezier_cubic<F>(
273        &self,
274        start: Point2D<F>,
275        cp1: Point2D<F>,
276        cp2: Point2D<F>,
277        end: Point2D<F>,
278        options: &Option<Options>,
279    ) -> Drawable<F>
280    where
281        F: Float + Trig + FromPrimitive + MulAssign + Display,
282    {
283        let mut paths = vec![];
284        let mut options = options
285            .clone()
286            .unwrap_or_else(|| self.default_options.clone());
287
288        let outline = bezier_cubic(start, cp1, cp2, end, &mut options);
289
290        if options.fill.is_some() {
291            let crv = vec![start, cp1, cp2, end];
292
293            let poly_points = points_on_bezier_curves(
294                &crv,
295                _c(10.0),
296                Some(_c::<F>(1.0) + _c::<F>(options.roughness.unwrap_or(0.0)) / _c(2.0)),
297            );
298            if options.fill_style == Some(FillStyle::Solid) {
299                paths.push(solid_fill_polygon(&vec![poly_points], &mut options));
300            } else {
301                paths.push(pattern_fill_polygons(&mut vec![poly_points], &mut options));
302            }
303        }
304
305        if options.stroke.is_some() {
306            paths.push(outline);
307        }
308
309        self.d("curve", &paths, &Some(options))
310    }
311
312    pub fn curve<F>(&self, points: &[Point2D<F>], options: &Option<Options>) -> Drawable<F>
313    where
314        F: Float + Trig + FromPrimitive + MulAssign + Display,
315    {
316        let mut paths = vec![];
317        let mut options = options
318            .clone()
319            .unwrap_or_else(|| self.default_options.clone());
320        let outline = curve(points, &mut options);
321        if options.fill.is_some() && points.len() >= 3 {
322            let curve = curve_to_bezier(points, _c(0.0));
323            if let Some(crv) = curve {
324                let poly_points = points_on_bezier_curves(
325                    &crv,
326                    _c(10.0),
327                    Some(_c::<F>(1.0) + _c::<F>(options.roughness.unwrap_or(0.0)) / _c(2.0)),
328                );
329                if options.fill_style == Some(FillStyle::Solid) {
330                    paths.push(solid_fill_polygon(&vec![poly_points], &mut options));
331                } else {
332                    paths.push(pattern_fill_polygons(&mut vec![poly_points], &mut options));
333                }
334            }
335        }
336
337        if options.stroke.is_some() {
338            paths.push(outline);
339        }
340
341        self.d("curve", &paths, &Some(options))
342    }
343
344    pub fn polygon<F>(&self, points: &[Point2D<F>], options: &Option<Options>) -> Drawable<F>
345    where
346        F: Float + Trig + FromPrimitive + MulAssign + Display,
347    {
348        let mut options = options
349            .clone()
350            .unwrap_or_else(|| self.default_options.clone());
351        let mut paths = vec![];
352        let outline = linear_path(points, true, &mut options);
353        if options.fill.is_some() {
354            if options.fill_style == Some(FillStyle::Solid) {
355                paths.push(solid_fill_polygon(&vec![points.to_vec()], &mut options));
356            } else {
357                paths.push(pattern_fill_polygons(
358                    &mut vec![points.to_vec()],
359                    &mut options,
360                ));
361            }
362        }
363        if options.stroke.is_some() {
364            paths.push(outline);
365        }
366        self.d("polygon", &paths, &Some(options))
367    }
368
369    pub fn path<F>(&self, d: String, options: &Option<Options>) -> Drawable<F>
370    where
371        F: Float + Trig + FromPrimitive + MulAssign + Display,
372    {
373        let mut options = options.clone().unwrap_or(self.default_options.clone());
374        let mut paths = vec![];
375        if d.is_empty() {
376            self.d("path", &paths, &Some(options))
377        } else {
378            let simplified = options.simplification.map(|a| a < 1.0).unwrap_or(false);
379            let distance = if simplified {
380                _c::<F>(4.0) - _c::<F>(4.0) * _c::<F>(options.simplification.unwrap())
381            } else {
382                (_c::<F>(1.0) + _c::<F>(options.roughness.unwrap_or(1.0))) / _c::<F>(2.0)
383            };
384
385            let sets = points_on_path(d.clone(), Some(_c(1.0)), Some(distance));
386            if options.fill.is_some() {
387                if options.fill_style == Some(FillStyle::Solid) {
388                    paths.push(solid_fill_polygon(&sets, &mut options));
389                } else {
390                    paths.push(pattern_fill_polygons(sets.clone(), &mut options));
391                }
392            }
393
394            if options.stroke.is_some() {
395                if simplified {
396                    sets.iter()
397                        .for_each(|s| paths.push(linear_path(s, false, &mut options)));
398                } else {
399                    paths.push(svg_path(d, &mut options));
400                }
401            }
402
403            self.d("path", &paths, &Some(options))
404        }
405    }
406
407    pub fn path_from_segments<F>(
408        &self,
409        segments: Vec<PathSegment>,
410        options: &Option<Options>,
411    ) -> Drawable<F>
412    where
413        F: Float + Trig + FromPrimitive + MulAssign + Display,
414    {
415        let mut options = options.clone().unwrap_or(self.default_options.clone());
416        let mut paths = vec![];
417        if segments.is_empty() {
418            self.d("path", &paths, &Some(options))
419        } else {
420            let simplified = options.simplification.map(|a| a < 1.0).unwrap_or(false);
421            let distance = if simplified {
422                _c::<F>(4.0) - _c::<F>(4.0) * _c::<F>(options.simplification.unwrap())
423            } else {
424                (_c::<F>(1.0) + _c::<F>(options.roughness.unwrap_or(1.0))) / _c::<F>(2.0)
425            };
426
427            let sets = points_on_segments(segments.clone(), Some(_c(1.0)), Some(distance));
428            if options.fill.is_some() {
429                if options.fill_style == Some(FillStyle::Solid) {
430                    paths.push(solid_fill_polygon(&sets, &mut options));
431                } else {
432                    paths.push(pattern_fill_polygons(sets.clone(), &mut options));
433                }
434            }
435
436            if options.stroke.is_some() {
437                if simplified {
438                    sets.iter()
439                        .for_each(|s| paths.push(linear_path(s, false, &mut options)));
440                } else {
441                    paths.push(svg_segments(segments, &mut options));
442                }
443            }
444
445            self.d("path", &paths, &Some(options))
446        }
447    }
448
449    pub fn ops_to_path<F>(mut drawing: OpSet<F>, fixed_decimals: Option<u32>) -> String
450    where
451        F: Float + FromPrimitive + Trig + Display,
452    {
453        let mut path = String::new();
454
455        for item in drawing.ops.iter_mut() {
456            if let Some(fd) = fixed_decimals {
457                let pow: u32 = 10u32.pow(fd);
458                item.data.iter_mut().for_each(|p| {
459                    *p = (*p * F::from(pow).unwrap()).round() / F::from(pow).unwrap();
460                });
461            }
462
463            match item.op {
464                OpType::Move => {
465                    write!(&mut path, "L{} {} ", item.data[0], item.data[1])
466                        .expect("Failed to write path string");
467                }
468                OpType::BCurveTo => {
469                    write!(
470                        &mut path,
471                        "C{} {}, {} {}, {} {} ",
472                        item.data[0],
473                        item.data[1],
474                        item.data[2],
475                        item.data[3],
476                        item.data[4],
477                        item.data[5]
478                    )
479                    .expect("Failed to write path string");
480                }
481                OpType::LineTo => {
482                    write!(&mut path, "L{} {}, ", item.data[0], item.data[1])
483                        .expect("Failed to write path string");
484                }
485            }
486        }
487
488        path
489    }
490
491    pub fn to_paths<F>(drawable: Drawable<F>) -> Vec<PathInfo>
492    where
493        F: Float + FromPrimitive + Trig + Display,
494    {
495        let sets = drawable.sets;
496        let o = drawable.options;
497        let mut path_infos = vec![];
498        for drawing in sets.iter() {
499            let path_info = match drawing.op_set_type {
500                OpSetType::Path => PathInfo {
501                    d: Self::ops_to_path(drawing.clone(), None),
502                    stroke: o.stroke,
503                    stroke_width: o.stroke_width,
504                    fill: None,
505                },
506                OpSetType::FillPath => PathInfo {
507                    d: Self::ops_to_path(drawing.clone(), None),
508                    stroke: None,
509                    stroke_width: Some(0.0f32),
510                    fill: o.fill,
511                },
512                OpSetType::FillSketch => {
513                    let fill_weight = if o.fill_weight.unwrap_or(0.0) < 0.0 {
514                        o.stroke_width.unwrap_or(0.0) / 2.0
515                    } else {
516                        o.fill_weight.unwrap_or(0.0)
517                    };
518                    PathInfo {
519                        d: Self::ops_to_path(drawing.clone(), None),
520                        stroke: o.fill,
521                        stroke_width: Some(fill_weight),
522                        fill: None,
523                    }
524                }
525            };
526            path_infos.push(path_info);
527        }
528        path_infos
529    }
530}