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;
10use 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#[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: 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 pub fn transformed(&self, content: &Geometry<f64>) -> Geometry<f64> {
45 if let Some(tx) = &self.transformation.clone() {
46 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 {
95 true
96 } else {
97 false
98 }
99 }
100
101 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>>, ) -> (MultiLineString<f64>, MultiLineString<f64>) {
109 let mut strokes = MultiLineString::new(vec![]);
110 strokes.0.push(poly.exterior().clone());
113 for interior in poly.interiors() {
114 strokes.0.push(interior.clone())
115 }
116 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 (strokes, hatches)
131 }
132
133 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>>, ) -> (MultiLineString<f64>, MultiLineString<f64>) {
141 let mut strokes = MultiLineString::new(vec![]);
142 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 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 (strokes, hatches)
166 }
167
168 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>>, ) -> (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 }
213 Geometry::GeometryCollection(collection) => {
214 let mut strokes = MultiLineString::new(vec![]);
215 let mut fills = MultiLineString::new(vec![]);
216 for item in collection {
217 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 (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 let flat_geo = Self::vectorize_flat_geo(&self.content);
257
258 let ofvec: Vec<(MultiLineString<f64>, MultiLineString<f64>)> = flat_geo
259 .iter()
260 .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 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 let outlines = match self.outline_stroke {
297 Some(stroke) => outlines
298 .outline_fill_stroke_with_hatch(
299 stroke,
300 self.pen_width,
301 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#[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 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 }
367}