use crate::context::line_filter::LineFilter;
use crate::prelude::{Hatch, HatchPattern, LineHatch, OutlineFillStroke};
use geo::coord;
use geo::map_coords::MapCoords;
use geo::Coord;
use geo_types::{Geometry, MultiLineString, MultiPolygon, Polygon};
use geos::{Geom, GeometryTypes};
use std::borrow::BorrowMut;
use std::sync::Arc;
use crate::geo_types::clip::try_to_geos_geometry;
use geo::simplify::Simplify;
pub use kurbo::BezPath;
pub use kurbo::Point as BezPoint;
use nalgebra::{Affine2, Point2 as NPoint2};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug)]
pub struct Operation {
pub(crate) accuracy: f64,
pub(crate) content: Geometry<f64>,
pub(crate) rendered: (MultiLineString<f64>, MultiLineString<f64>),
pub(crate) transformation: Option<Affine2<f64>>,
pub(crate) stroke_color: Option<String>,
pub(crate) outline_stroke: Option<f64>,
pub(crate) fill_color: Option<String>,
pub(crate) line_join: String,
pub(crate) line_cap: String,
pub(crate) pen_width: f64,
pub(crate) mask: Option<Geometry<f64>>,
pub(crate) clip_previous: bool,
pub(crate) hatch_pattern: Arc<Box<dyn HatchPattern>>,
pub(crate) hatch_angle: f64,
pub(crate) hatch_scale: Option<f64>,
pub(crate) stroke_filter: Option<Arc<Box<dyn LineFilter>>>,
pub(crate) hatch_filter: Option<Arc<Box<dyn LineFilter>>>,
}
impl Operation {
pub fn transformed(&self, content: &Geometry<f64>) -> Geometry<f64> {
if let Some(tx) = &self.transformation.clone() {
content.map_coords(|xy| Operation::xform_coord(&xy, tx))
} else {
content.clone()
}
}
pub fn render(mut self) -> Self {
if let Some(tx) = &self.transformation {
self.content = self
.content
.map_coords(|xy| Operation::xform_coord(&xy, tx));
}
self.content = match &self.mask {
Some(mask) => {
let ggeo = try_to_geos_geometry(&self.content).unwrap_or(
geos::Geometry::create_empty_collection(GeometryTypes::GeometryCollection)
.unwrap(),
);
let mggeo = try_to_geos_geometry(mask).unwrap_or(
geos::Geometry::create_empty_collection(GeometryTypes::GeometryCollection)
.unwrap(),
);
let masked_geo = ggeo.intersection(&mggeo).unwrap_or(
geos::Geometry::create_empty_collection(GeometryTypes::GeometryCollection)
.unwrap(),
);
geo_types::Geometry::<f64>::try_from(masked_geo)
.unwrap_or(Geometry::GeometryCollection::<f64>(Default::default()))
}
None => self.content,
};
self.rendered = self.render_to_lines();
self
}
pub fn consistent(&self, other: &Operation) -> bool {
if self.stroke_color == other.stroke_color
&& self.outline_stroke == other.outline_stroke
&& self.fill_color == other.fill_color
&& self.line_join == other.line_join
&& self.line_cap == other.line_cap
&& self.pen_width == other.pen_width
&& self.hatch_angle == other.hatch_angle
&& self.hatch_scale == other.hatch_scale
&& self.clip_previous == other.clip_previous
{
true
} else {
false
}
}
fn poly2lines(
poly: &Polygon<f64>,
pen_width: f64,
hatch_angle: f64,
hatch_scale: Option<f64>,
hatch_pattern: Arc<Box<dyn HatchPattern>>, ) -> (MultiLineString<f64>, MultiLineString<f64>) {
let mut strokes = MultiLineString::new(vec![]);
strokes.0.push(poly.exterior().clone());
for interior in poly.interiors() {
strokes.0.push(interior.clone())
}
let hatches = poly
.hatch(
hatch_pattern,
hatch_angle,
match hatch_scale {
Some(scale) => scale,
None => pen_width,
},
pen_width,
)
.unwrap_or(MultiLineString::new(vec![]));
(strokes, hatches)
}
fn mpoly2lines(
mpoly: &MultiPolygon<f64>,
pen_width: f64,
hatch_angle: f64,
hatch_scale: Option<f64>,
hatch_pattern: Arc<Box<dyn HatchPattern>>, ) -> (MultiLineString<f64>, MultiLineString<f64>) {
let mut strokes = MultiLineString::new(vec![]);
for poly in mpoly {
strokes.0.push(poly.exterior().clone());
for interior in poly.interiors() {
strokes.0.push(interior.clone())
}
}
let hatches = mpoly
.hatch(
hatch_pattern,
hatch_angle,
match hatch_scale {
Some(scale) => scale,
None => pen_width,
},
pen_width,
)
.unwrap_or(MultiLineString::new(vec![]));
(strokes, hatches)
}
pub fn xform_coord(xy: &Coord, affine: &Affine2<f64>) -> Coord {
let out = affine * NPoint2::new(xy.x, xy.y);
coord!(x: out.x, y: out.y)
}
fn help_render_geo(
txgeo: &Geometry<f64>,
pen_width: f64,
hatch_angle: f64,
hatch_scale: Option<f64>,
hatch_pattern: Arc<Box<dyn HatchPattern>>, ) -> (MultiLineString<f64>, MultiLineString<f64>) {
match txgeo {
Geometry::MultiLineString(mls) => (mls.clone(), MultiLineString::new(vec![])),
Geometry::LineString(ls) => (
MultiLineString::new(vec![ls.clone()]),
MultiLineString::new(vec![]),
),
Geometry::Polygon(poly) => Self::poly2lines(
&poly,
pen_width,
hatch_angle,
hatch_scale,
hatch_pattern.clone(),
),
Geometry::MultiPolygon(polys) => {
Self::mpoly2lines(
&polys,
pen_width,
hatch_angle,
hatch_scale,
hatch_pattern.clone(),
)
}
Geometry::GeometryCollection(collection) => {
let mut strokes = MultiLineString::new(vec![]);
let mut fills = MultiLineString::new(vec![]);
for item in collection {
let (mut tmpstrokes, mut tmpfills) = Operation::help_render_geo(
item,
pen_width,
hatch_angle,
hatch_scale,
hatch_pattern.clone(),
);
strokes.0.append(tmpstrokes.0.borrow_mut());
fills.0.append(tmpfills.0.borrow_mut());
}
(strokes, fills)
}
_ => (MultiLineString::new(vec![]), MultiLineString::new(vec![])),
}
}
fn vectorize_flat_geo(geo: &Geometry<f64>) -> Vec<Geometry<f64>> {
let mut out: Vec<Geometry<f64>> = vec![];
out.append(&mut match geo {
Geometry::Point(p) => vec![Geometry::Point(p.clone())],
Geometry::Line(l) => vec![Geometry::Line(l.clone())],
Geometry::LineString(ls) => vec![Geometry::LineString(ls.clone())],
Geometry::Polygon(p) => vec![Geometry::Polygon(p.clone())],
Geometry::MultiPoint(mp) => vec![Geometry::MultiPoint(mp.clone())],
Geometry::MultiLineString(mls) => vec![Geometry::MultiLineString(mls.clone())],
Geometry::MultiPolygon(mp) => vec![Geometry::MultiPolygon(mp.clone())],
Geometry::GeometryCollection(coll) => coll.iter().map(|gc| gc.to_owned()).collect(),
Geometry::Rect(r) => vec![Geometry::Rect(r.clone())],
Geometry::Triangle(t) => vec![Geometry::Triangle(t.clone())],
});
out
}
pub fn render_to_lines(&self) -> (MultiLineString<f64>, MultiLineString<f64>) {
let flat_geo = Self::vectorize_flat_geo(&self.content);
let ofvec: Vec<(MultiLineString<f64>, MultiLineString<f64>)> = flat_geo
.iter()
.map(|g| {
Self::help_render_geo(
&g,
self.pen_width,
self.hatch_angle,
self.hatch_scale,
self.hatch_pattern.clone(),
)
})
.collect();
let (mut outlines, mut fills) =
(MultiLineString::new(vec![]), MultiLineString::new(vec![]));
for (mut outline, mut fill) in ofvec {
outlines.0.append(&mut outline.0);
fills.0.append(&mut fill.0);
}
if self.stroke_color.is_none() {
outlines.0 = Vec::new();
}
if self.fill_color.is_none() {
fills.0 = Vec::new();
}
if let Some(filter) = &self.stroke_filter {
outlines = filter.apply(&outlines);
}
if let Some(filter) = &self.hatch_filter {
fills = filter.apply(&fills);
}
let outlines = match self.outline_stroke {
Some(stroke) => outlines
.outline_fill_stroke_with_hatch(
stroke,
self.pen_width,
Arc::new(Box::new(LineHatch {})),
self.hatch_angle,
)
.unwrap_or(outlines),
None => outlines,
};
(
outlines.simplify(&self.accuracy),
fills.simplify(&self.accuracy),
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OPLayer {
pub(crate) stroke_lines: MultiLineString<f64>,
pub(crate) fill_lines: MultiLineString<f64>,
pub(crate) stroke: Option<String>,
pub(crate) fill: Option<String>,
pub(crate) stroke_width: f64,
pub(crate) stroke_linejoin: String,
pub(crate) stroke_linecap: String,
}
impl OPLayer {
pub fn to_lines(&self) -> (MultiLineString<f64>, MultiLineString<f64>) {
(self.stroke_lines.clone(), self.fill_lines.clone())
}
pub fn stroke(&self) -> Option<String> {
self.stroke.clone()
}
pub fn fill(&self) -> Option<String> {
self.fill.clone()
}
pub fn stroke_width(&self) -> f64 {
self.stroke_width.clone()
}
}
#[cfg(test)]
pub mod test {
use geo::LineString;
use geo_types::Coord;
use crate::context::line_filter::SketchyLineFilter;
use super::*;
#[test]
pub fn test_stroke_filter() {
let foo = SketchyLineFilter::new(0.1, 3.);
let mls = MultiLineString(vec![LineString::new(vec![
Coord { x: 0.0, y: 0.0 },
Coord { x: 50.0, y: 50.0 },
])]);
let _new_mls = foo.apply(&mls);
}
}