aoer_plotty_rs/context/
operation.rs

1use crate::context::line_filter::LineFilter;
2use crate::prelude::{Hatch, HatchPattern, LineHatch, OutlineFillStroke};
3use geo::coord;
4use geo::map_coords::MapCoords;
5use geo::Coord;
6use geo_types::{Geometry, MultiLineString, MultiPolygon, Polygon};
7use geos::{Geom, GeometryTypes};
8use std::borrow::BorrowMut;
9use std::sync::Arc;
10// use geos::GeometryTypes::Point;
11use crate::geo_types::clip::try_to_geos_geometry;
12use geo::simplify::Simplify;
13pub use kurbo::BezPath;
14pub use kurbo::Point as BezPoint;
15use nalgebra::{Affine2, Point2 as NPoint2};
16use serde::{Deserialize, Serialize};
17
18/// Operations are private items used to store the operation stack
19/// consisting of a combination of Geometry and Context state.
20#[derive(Clone, Debug)]
21pub struct Operation {
22    pub(crate) accuracy: f64,
23    pub(crate) content: Geometry<f64>,
24    pub(crate) rendered: (MultiLineString<f64>, MultiLineString<f64>),
25    pub(crate) transformation: Option<Affine2<f64>>,
26    pub(crate) stroke_color: Option<String>,
27    pub(crate) outline_stroke: Option<f64>,
28    pub(crate) fill_color: Option<String>,
29    pub(crate) line_join: String,
30    pub(crate) line_cap: String,
31    pub(crate) pen_width: f64,
32    pub(crate) mask: Option<Geometry<f64>>,
33    pub(crate) clip_previous: bool,
34    //pub(crate) hatch_pattern: Hatches,
35    pub(crate) hatch_pattern: Arc<Box<dyn HatchPattern>>,
36    pub(crate) hatch_angle: f64,
37    pub(crate) hatch_scale: Option<f64>,
38    pub(crate) stroke_filter: Option<Arc<Box<dyn LineFilter>>>,
39    pub(crate) hatch_filter: Option<Arc<Box<dyn LineFilter>>>,
40}
41
42impl Operation {
43    /// Transform content by my transformation
44    pub fn transformed(&self, content: &Geometry<f64>) -> Geometry<f64> {
45        if let Some(tx) = &self.transformation.clone() {
46            // let mut content = content.clone();
47            content.map_coords(|xy| Operation::xform_coord(&xy, tx))
48        } else {
49            content.clone()
50        }
51    }
52
53    pub fn render(mut self) -> Self {
54        if let Some(tx) = &self.transformation {
55            self.content = self
56                .content
57                .map_coords(|xy| Operation::xform_coord(&xy, tx));
58        }
59        self.content = match &self.mask {
60            Some(mask) => {
61                let ggeo = try_to_geos_geometry(&self.content).unwrap_or(
62                    geos::Geometry::create_empty_collection(GeometryTypes::GeometryCollection)
63                        .unwrap(),
64                );
65                let mggeo = try_to_geos_geometry(mask).unwrap_or(
66                    geos::Geometry::create_empty_collection(GeometryTypes::GeometryCollection)
67                        .unwrap(),
68                );
69                let masked_geo = ggeo.intersection(&mggeo).unwrap_or(
70                    geos::Geometry::create_empty_collection(GeometryTypes::GeometryCollection)
71                        .unwrap(),
72                );
73                geo_types::Geometry::<f64>::try_from(masked_geo)
74                    .unwrap_or(Geometry::GeometryCollection::<f64>(Default::default()))
75            }
76            None => self.content,
77        };
78
79        self.rendered = self.render_to_lines();
80        self
81    }
82
83    pub fn consistent(&self, other: &Operation) -> bool {
84        if self.stroke_color == other.stroke_color
85            && self.outline_stroke == other.outline_stroke
86            && self.fill_color == other.fill_color
87            && self.line_join == other.line_join
88            && self.line_cap == other.line_cap
89            && self.pen_width == other.pen_width
90            && self.hatch_angle == other.hatch_angle
91            && self.hatch_scale == other.hatch_scale
92            && self.clip_previous == other.clip_previous
93        // &&
94        {
95            true
96        } else {
97            false
98        }
99    }
100
101    /// Helper function for converting polygons into sets of strings.
102    fn poly2lines(
103        poly: &Polygon<f64>,
104        pen_width: f64,
105        hatch_angle: f64,
106        hatch_scale: Option<f64>,
107        hatch_pattern: Arc<Box<dyn HatchPattern>>, //Hatches,
108    ) -> (MultiLineString<f64>, MultiLineString<f64>) {
109        let mut strokes = MultiLineString::new(vec![]);
110        // let mut fills = MultiLineString::new(vec![]);
111        // Push the exterior
112        strokes.0.push(poly.exterior().clone());
113        for interior in poly.interiors() {
114            strokes.0.push(interior.clone())
115        }
116        // let hatch_pattern = hatch_pattern.deref();
117        // println!("Hatching with pattern: {:?}", &hatch_pattern);
118        let hatches = poly
119            .hatch(
120                hatch_pattern,
121                hatch_angle,
122                match hatch_scale {
123                    Some(scale) => scale,
124                    None => pen_width,
125                },
126                pen_width,
127            )
128            .unwrap_or(MultiLineString::new(vec![]));
129        // fills.0.append(&mut hatches.0.clone());
130        (strokes, hatches)
131    }
132
133    /// Helper function for converting multipolygons into sets of strings.
134    fn mpoly2lines(
135        mpoly: &MultiPolygon<f64>,
136        pen_width: f64,
137        hatch_angle: f64,
138        hatch_scale: Option<f64>,
139        hatch_pattern: Arc<Box<dyn HatchPattern>>, //Hatches,
140    ) -> (MultiLineString<f64>, MultiLineString<f64>) {
141        let mut strokes = MultiLineString::new(vec![]);
142        // let mut fills = MultiLineString::new(vec![]);
143        // Push the exterior
144        for poly in mpoly {
145            strokes.0.push(poly.exterior().clone());
146            for interior in poly.interiors() {
147                strokes.0.push(interior.clone())
148            }
149        }
150        // let hatch_pattern = hatch_pattern.deref();
151        // println!("Hatching with pattern: {:?}", &hatch_pattern);
152        let hatches = mpoly
153            .hatch(
154                hatch_pattern,
155                hatch_angle,
156                match hatch_scale {
157                    Some(scale) => scale,
158                    None => pen_width,
159                },
160                pen_width,
161            )
162            .unwrap_or(MultiLineString::new(vec![]));
163        // fills.0.append(&mut hatches.0.clone());
164        // println!("HATCHES in Mpoly2lines: {:?}", hatches);
165        (strokes, hatches)
166    }
167
168    /// Helper to transform geometry when we have an affine transform set.
169    //pub fn xform_coord((x, y): &(f64, f64), affine: &Affine2<f64>) -> (f64, f64) {
170    pub fn xform_coord(xy: &Coord, affine: &Affine2<f64>) -> Coord {
171        let out = affine * NPoint2::new(xy.x, xy.y);
172        coord!(x: out.x, y: out.y)
173    }
174
175    fn help_render_geo(
176        txgeo: &Geometry<f64>,
177        pen_width: f64,
178        hatch_angle: f64,
179        hatch_scale: Option<f64>,
180        hatch_pattern: Arc<Box<dyn HatchPattern>>, //Hatches,
181    ) -> (MultiLineString<f64>, MultiLineString<f64>) {
182        match txgeo {
183            Geometry::MultiLineString(mls) => (mls.clone(), MultiLineString::new(vec![])),
184            Geometry::LineString(ls) => (
185                MultiLineString::new(vec![ls.clone()]),
186                MultiLineString::new(vec![]),
187            ),
188            Geometry::Polygon(poly) => Self::poly2lines(
189                &poly,
190                pen_width,
191                hatch_angle,
192                hatch_scale,
193                hatch_pattern.clone(),
194            ),
195            Geometry::MultiPolygon(polys) => {
196                Self::mpoly2lines(
197                    &polys,
198                    pen_width,
199                    hatch_angle,
200                    hatch_scale,
201                    hatch_pattern.clone(),
202                )
203                // let mut strokes = MultiLineString::new(vec![]);
204                // let mut fills = MultiLineString::new(vec![]);
205                // for poly in polys {
206                //     let (new_strokes, new_fills) =
207                //         Self::poly2lines(&poly, pen_width, hatch_angle, hatch_pattern.clone());
208                //     strokes.0.append(&mut new_strokes.0.clone());
209                //     fills.0.append(&mut new_fills.0.clone());
210                // }
211                // (strokes, fills)
212            }
213            Geometry::GeometryCollection(collection) => {
214                let mut strokes = MultiLineString::new(vec![]);
215                let mut fills = MultiLineString::new(vec![]);
216                for item in collection {
217                    // println!("Adding geo collection item: {:?}", &item);
218                    let (mut tmpstrokes, mut tmpfills) = Operation::help_render_geo(
219                        item,
220                        pen_width,
221                        hatch_angle,
222                        hatch_scale,
223                        hatch_pattern.clone(),
224                    );
225                    strokes.0.append(tmpstrokes.0.borrow_mut());
226                    fills.0.append(tmpfills.0.borrow_mut());
227                }
228                // println!("Got strokes, fills of: \n{:?}, \n{:?}\n\n", &strokes, &fills);
229                (strokes, fills)
230            }
231
232            _ => (MultiLineString::new(vec![]), MultiLineString::new(vec![])),
233        }
234    }
235
236    fn vectorize_flat_geo(geo: &Geometry<f64>) -> Vec<Geometry<f64>> {
237        let mut out: Vec<Geometry<f64>> = vec![];
238        out.append(&mut match geo {
239            Geometry::Point(p) => vec![Geometry::Point(p.clone())],
240            Geometry::Line(l) => vec![Geometry::Line(l.clone())],
241            Geometry::LineString(ls) => vec![Geometry::LineString(ls.clone())],
242            Geometry::Polygon(p) => vec![Geometry::Polygon(p.clone())],
243            Geometry::MultiPoint(mp) => vec![Geometry::MultiPoint(mp.clone())],
244            Geometry::MultiLineString(mls) => vec![Geometry::MultiLineString(mls.clone())],
245            Geometry::MultiPolygon(mp) => vec![Geometry::MultiPolygon(mp.clone())],
246            Geometry::GeometryCollection(coll) => coll.iter().map(|gc| gc.to_owned()).collect(),
247            Geometry::Rect(r) => vec![Geometry::Rect(r.clone())],
248            Geometry::Triangle(t) => vec![Geometry::Triangle(t.clone())],
249        });
250        out
251    }
252
253    pub fn render_to_lines(&self) -> (MultiLineString<f64>, MultiLineString<f64>) {
254        // Get the transformed geo, or just this geo at 1:1
255        // First let's flatten that shit out.
256        let flat_geo = Self::vectorize_flat_geo(&self.content);
257
258        let ofvec: Vec<(MultiLineString<f64>, MultiLineString<f64>)> = flat_geo
259            .iter()
260            //.par_iter()
261            .map(|g| {
262                Self::help_render_geo(
263                    &g,
264                    self.pen_width,
265                    self.hatch_angle,
266                    self.hatch_scale,
267                    self.hatch_pattern.clone(),
268                )
269            })
270            .collect();
271        let (mut outlines, mut fills) =
272            (MultiLineString::new(vec![]), MultiLineString::new(vec![]));
273        for (mut outline, mut fill) in ofvec {
274            outlines.0.append(&mut outline.0);
275            fills.0.append(&mut fill.0);
276        }
277        // println!(
278        //     "Output fills for hatch {:?}: {:?}",
279        //     &self.hatch_pattern, &fills
280        // );
281        if self.stroke_color.is_none() {
282            outlines.0 = Vec::new();
283        }
284        if self.fill_color.is_none() {
285            fills.0 = Vec::new();
286        }
287
288        if let Some(filter) = &self.stroke_filter {
289            outlines = filter.apply(&outlines);
290        }
291        if let Some(filter) = &self.hatch_filter {
292            fills = filter.apply(&fills);
293        }
294
295        // Finally, if we have outline stroke, then outline the existing strokes.
296        let outlines = match self.outline_stroke {
297            Some(stroke) => outlines
298                .outline_fill_stroke_with_hatch(
299                    stroke,
300                    self.pen_width,
301                    //Hatches::line(),
302                    Arc::new(Box::new(LineHatch {})),
303                    self.hatch_angle,
304                )
305                .unwrap_or(outlines),
306            None => outlines,
307        };
308        (
309            outlines.simplify(&self.accuracy),
310            fills.simplify(&self.accuracy),
311        )
312    }
313}
314
315/// OPLayer is an operation layer, rendered into lines for drawing.
316#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct OPLayer {
318    pub(crate) stroke_lines: MultiLineString<f64>,
319    pub(crate) fill_lines: MultiLineString<f64>,
320    pub(crate) stroke: Option<String>,
321    pub(crate) fill: Option<String>,
322    pub(crate) stroke_width: f64,
323    pub(crate) stroke_linejoin: String,
324    pub(crate) stroke_linecap: String,
325}
326
327impl OPLayer {
328    pub fn to_lines(&self) -> (MultiLineString<f64>, MultiLineString<f64>) {
329        (self.stroke_lines.clone(), self.fill_lines.clone())
330    }
331
332    pub fn stroke(&self) -> Option<String> {
333        self.stroke.clone()
334    }
335    pub fn fill(&self) -> Option<String> {
336        self.fill.clone()
337    }
338
339    pub fn stroke_width(&self) -> f64 {
340        self.stroke_width.clone()
341    }
342}
343
344#[cfg(test)]
345pub mod test {
346    use geo::LineString;
347    use geo_types::Coord;
348
349    use crate::context::line_filter::SketchyLineFilter;
350
351    use super::*;
352
353    #[test]
354    pub fn test_stroke_filter() {
355        let foo = SketchyLineFilter::new(0.1, 3.);
356        // let geo: Geometry<f64> =
357        //     Line::new(Coord { x: 0.0, y: 0.0 }, Coord { x: 50.0, y: 50.0 }).into();
358        let mls = MultiLineString(vec![LineString::new(vec![
359            Coord { x: 0.0, y: 0.0 },
360            Coord { x: 50.0, y: 50.0 },
361        ])]);
362        let _new_mls = foo.apply(&mls);
363        // println!("New MLS: {:?}", &new_mls);
364        // let geo = foo.apply(&geo);
365        // println!("geo: {:?}", &geo);
366    }
367}