use std::fmt::{Display, Write};
use std::ops::MulAssign;
use euclid::default::Point2D;
use euclid::Trig;
use num_traits::{Float, FromPrimitive};
use points_on_curve::{curve_to_bezier, points_on_bezier_curves};
use svgtypes::PathSegment;
use crate::core::{
Drawable,
FillStyle,
OpSet,
OpSetType,
OpType,
Options,
OptionsBuilder,
PathInfo,
_c,
};
use crate::geometry::{convert_bezier_quadratic_to_cubic, BezierQuadratic};
use crate::points_on_path::{points_on_path, points_on_segments};
use crate::renderer::{
bezier_cubic,
bezier_quadratic,
curve,
ellipse_with_params,
generate_ellipse_params,
line,
linear_path,
pattern_fill_arc,
pattern_fill_polygons,
rectangle,
solid_fill_polygon,
svg_path,
svg_segments,
};
pub struct Generator {
default_options: Options,
}
impl Default for Generator {
fn default() -> Self {
Self {
default_options: OptionsBuilder::default()
.seed(345_u64)
.build()
.expect("failed to build default options"),
}
}
}
impl Generator {
fn new(options: Options) -> Self {
Generator { default_options: options }
}
fn d<T, F>(&self, name: T, op_sets: &[OpSet<F>], options: &Option<Options>) -> Drawable<F>
where
T: Into<String>,
F: Float + Trig + FromPrimitive,
{
Drawable {
shape: name.into(),
options: options
.clone()
.unwrap_or_else(|| self.default_options.clone()),
sets: Vec::from_iter(op_sets.iter().cloned()),
}
}
pub fn line<F>(&self, x1: F, y1: F, x2: F, y2: F, options: &Option<Options>) -> Drawable<F>
where
F: Float + Trig + FromPrimitive,
{
self.d(
"line",
&[line(
x1,
y1,
x2,
y2,
&mut options
.clone()
.unwrap_or_else(|| self.default_options.clone()),
)],
options,
)
}
pub fn rectangle<F>(
&self,
x: F,
y: F,
width: F,
height: F,
options: &Option<Options>,
) -> Drawable<F>
where
F: Float + Trig + FromPrimitive,
{
let mut paths = vec![];
let mut options = options
.clone()
.unwrap_or_else(|| self.default_options.clone());
let outline = rectangle(x, y, width, height, &mut options);
if options.fill.is_some() {
let points = vec![
Point2D::new(x, y),
Point2D::new(x + width, y),
Point2D::new(x + width, y + height),
Point2D::new(x, y + height),
];
if options.fill_style == Some(FillStyle::Solid) {
paths.push(solid_fill_polygon(&vec![points], &mut options));
} else {
paths.push(pattern_fill_polygons(vec![points], &mut options));
}
}
if options.stroke.is_some() {
paths.push(outline);
}
self.d("rectangle", &paths, &Some(options))
}
pub fn ellipse<F>(
&self,
x: F,
y: F,
width: F,
height: F,
options: &Option<Options>,
) -> Drawable<F>
where
F: Float + Trig + FromPrimitive,
{
let mut paths = vec![];
let mut options = options
.clone()
.unwrap_or_else(|| self.default_options.clone());
let ellipse_params = generate_ellipse_params(width, height, &mut options);
let ellipse_response = ellipse_with_params(x, y, &mut options, &ellipse_params);
if options.fill.is_some() {
if options.fill_style == Some(FillStyle::Solid) {
let mut shape = ellipse_with_params(x, y, &mut options, &ellipse_params).opset;
shape.op_set_type = OpSetType::FillPath;
paths.push(shape);
} else {
paths.push(pattern_fill_polygons(
vec![ellipse_response.estimated_points],
&mut options,
));
}
}
if options.stroke.is_some() {
paths.push(ellipse_response.opset);
}
self.d("ellipse", &paths, &Some(options))
}
pub fn circle<F>(&self, x: F, y: F, diameter: F, options: &Option<Options>) -> Drawable<F>
where
F: Float + Trig + FromPrimitive,
{
let mut shape = self.ellipse(x, y, diameter, diameter, options);
shape.shape = "circle".into();
shape
}
pub fn linear_path<F>(
&self,
points: &[Point2D<F>],
close: bool,
options: &Option<Options>,
) -> Drawable<F>
where
F: Float + Trig + FromPrimitive,
{
let mut options = options
.clone()
.unwrap_or_else(|| self.default_options.clone());
self.d(
"linear_path",
&[linear_path(points, close, &mut options)],
&Some(options),
)
}
pub fn arc<F>(
&self,
x: F,
y: F,
width: F,
height: F,
start: F,
stop: F,
closed: bool,
options: &Option<Options>,
) -> Drawable<F>
where
F: Float + Trig + FromPrimitive,
{
let mut options = options
.clone()
.unwrap_or_else(|| self.default_options.clone());
let mut paths = vec![];
let outline =
crate::renderer::arc(x, y, width, height, start, stop, closed, true, &mut options);
if closed && options.fill.is_some() {
if options.fill_style == Some(FillStyle::Solid) {
options.disable_multi_stroke = Some(true);
let mut shape = crate::renderer::arc(
x,
y,
width,
height,
start,
stop,
true,
false,
&mut options,
);
shape.op_set_type = OpSetType::FillPath;
paths.push(shape);
} else {
paths.push(pattern_fill_arc(
x,
y,
width,
height,
start,
stop,
&mut options,
));
}
}
if options.stroke.is_some() {
paths.push(outline);
}
self.d("arc", &paths, &Some(options))
}
pub fn bezier_quadratic<F>(
&self,
start: Point2D<F>,
cp: Point2D<F>,
end: Point2D<F>,
options: &Option<Options>,
) -> Drawable<F>
where
F: Float + Trig + FromPrimitive + MulAssign + Display,
{
let mut paths = vec![];
let mut options = options
.clone()
.unwrap_or_else(|| self.default_options.clone());
let outline = bezier_quadratic(start, cp, end, &mut options);
if options.fill.is_some() {
let cubic = convert_bezier_quadratic_to_cubic(BezierQuadratic { start, cp, end });
let crv = vec![cubic.start, cubic.cp1, cubic.cp2, cubic.end];
let poly_points = points_on_bezier_curves(
&crv,
_c(10.0),
Some(_c::<F>(1.0) + _c::<F>(options.roughness.unwrap_or(0.0)) / _c(2.0)),
);
if options.fill_style == Some(FillStyle::Solid) {
paths.push(solid_fill_polygon(&vec![poly_points], &mut options));
} else {
paths.push(pattern_fill_polygons(&mut vec![poly_points], &mut options));
}
}
if options.stroke.is_some() {
paths.push(outline);
}
self.d("curve", &paths, &Some(options))
}
pub fn bezier_cubic<F>(
&self,
start: Point2D<F>,
cp1: Point2D<F>,
cp2: Point2D<F>,
end: Point2D<F>,
options: &Option<Options>,
) -> Drawable<F>
where
F: Float + Trig + FromPrimitive + MulAssign + Display,
{
let mut paths = vec![];
let mut options = options
.clone()
.unwrap_or_else(|| self.default_options.clone());
let outline = bezier_cubic(start, cp1, cp2, end, &mut options);
if options.fill.is_some() {
let crv = vec![start, cp1, cp2, end];
let poly_points = points_on_bezier_curves(
&crv,
_c(10.0),
Some(_c::<F>(1.0) + _c::<F>(options.roughness.unwrap_or(0.0)) / _c(2.0)),
);
if options.fill_style == Some(FillStyle::Solid) {
paths.push(solid_fill_polygon(&vec![poly_points], &mut options));
} else {
paths.push(pattern_fill_polygons(&mut vec![poly_points], &mut options));
}
}
if options.stroke.is_some() {
paths.push(outline);
}
self.d("curve", &paths, &Some(options))
}
pub fn curve<F>(&self, points: &[Point2D<F>], options: &Option<Options>) -> Drawable<F>
where
F: Float + Trig + FromPrimitive + MulAssign + Display,
{
let mut paths = vec![];
let mut options = options
.clone()
.unwrap_or_else(|| self.default_options.clone());
let outline = curve(points, &mut options);
if options.fill.is_some() && points.len() >= 3 {
let curve = curve_to_bezier(points, _c(0.0));
if let Some(crv) = curve {
let poly_points = points_on_bezier_curves(
&crv,
_c(10.0),
Some(_c::<F>(1.0) + _c::<F>(options.roughness.unwrap_or(0.0)) / _c(2.0)),
);
if options.fill_style == Some(FillStyle::Solid) {
paths.push(solid_fill_polygon(&vec![poly_points], &mut options));
} else {
paths.push(pattern_fill_polygons(&mut vec![poly_points], &mut options));
}
}
}
if options.stroke.is_some() {
paths.push(outline);
}
self.d("curve", &paths, &Some(options))
}
pub fn polygon<F>(&self, points: &[Point2D<F>], options: &Option<Options>) -> Drawable<F>
where
F: Float + Trig + FromPrimitive + MulAssign + Display,
{
let mut options = options
.clone()
.unwrap_or_else(|| self.default_options.clone());
let mut paths = vec![];
let outline = linear_path(points, true, &mut options);
if options.fill.is_some() {
if options.fill_style == Some(FillStyle::Solid) {
paths.push(solid_fill_polygon(&vec![points.to_vec()], &mut options));
} else {
paths.push(pattern_fill_polygons(
&mut vec![points.to_vec()],
&mut options,
));
}
}
if options.stroke.is_some() {
paths.push(outline);
}
self.d("polygon", &paths, &Some(options))
}
pub fn path<F>(&self, d: String, options: &Option<Options>) -> Drawable<F>
where
F: Float + Trig + FromPrimitive + MulAssign + Display,
{
let mut options = options.clone().unwrap_or(self.default_options.clone());
let mut paths = vec![];
if d.is_empty() {
self.d("path", &paths, &Some(options))
} else {
let simplified = options.simplification.map(|a| a < 1.0).unwrap_or(false);
let distance = if simplified {
_c::<F>(4.0) - _c::<F>(4.0) * _c::<F>(options.simplification.unwrap())
} else {
(_c::<F>(1.0) + _c::<F>(options.roughness.unwrap_or(1.0))) / _c::<F>(2.0)
};
let sets = points_on_path(d.clone(), Some(_c(1.0)), Some(distance));
if options.fill.is_some() {
if options.fill_style == Some(FillStyle::Solid) {
paths.push(solid_fill_polygon(&sets, &mut options));
} else {
paths.push(pattern_fill_polygons(sets.clone(), &mut options));
}
}
if options.stroke.is_some() {
if simplified {
sets.iter()
.for_each(|s| paths.push(linear_path(s, false, &mut options)));
} else {
paths.push(svg_path(d, &mut options));
}
}
self.d("path", &paths, &Some(options))
}
}
pub fn path_from_segments<F>(
&self,
segments: Vec<PathSegment>,
options: &Option<Options>,
) -> Drawable<F>
where
F: Float + Trig + FromPrimitive + MulAssign + Display,
{
let mut options = options.clone().unwrap_or(self.default_options.clone());
let mut paths = vec![];
if segments.is_empty() {
self.d("path", &paths, &Some(options))
} else {
let simplified = options.simplification.map(|a| a < 1.0).unwrap_or(false);
let distance = if simplified {
_c::<F>(4.0) - _c::<F>(4.0) * _c::<F>(options.simplification.unwrap())
} else {
(_c::<F>(1.0) + _c::<F>(options.roughness.unwrap_or(1.0))) / _c::<F>(2.0)
};
let sets = points_on_segments(segments.clone(), Some(_c(1.0)), Some(distance));
if options.fill.is_some() {
if options.fill_style == Some(FillStyle::Solid) {
paths.push(solid_fill_polygon(&sets, &mut options));
} else {
paths.push(pattern_fill_polygons(sets.clone(), &mut options));
}
}
if options.stroke.is_some() {
if simplified {
sets.iter()
.for_each(|s| paths.push(linear_path(s, false, &mut options)));
} else {
paths.push(svg_segments(segments, &mut options));
}
}
self.d("path", &paths, &Some(options))
}
}
pub fn ops_to_path<F>(mut drawing: OpSet<F>, fixed_decimals: Option<u32>) -> String
where
F: Float + FromPrimitive + Trig + Display,
{
let mut path = String::new();
for item in drawing.ops.iter_mut() {
if let Some(fd) = fixed_decimals {
let pow: u32 = 10u32.pow(fd);
item.data.iter_mut().for_each(|p| {
*p = (*p * F::from(pow).unwrap()).round() / F::from(pow).unwrap();
});
}
match item.op {
OpType::Move => {
write!(&mut path, "L{} {} ", item.data[0], item.data[1])
.expect("Failed to write path string");
}
OpType::BCurveTo => {
write!(
&mut path,
"C{} {}, {} {}, {} {} ",
item.data[0],
item.data[1],
item.data[2],
item.data[3],
item.data[4],
item.data[5]
)
.expect("Failed to write path string");
}
OpType::LineTo => {
write!(&mut path, "L{} {}, ", item.data[0], item.data[1])
.expect("Failed to write path string");
}
}
}
path
}
pub fn to_paths<F>(drawable: Drawable<F>) -> Vec<PathInfo>
where
F: Float + FromPrimitive + Trig + Display,
{
let sets = drawable.sets;
let o = drawable.options;
let mut path_infos = vec![];
for drawing in sets.iter() {
let path_info = match drawing.op_set_type {
OpSetType::Path => PathInfo {
d: Self::ops_to_path(drawing.clone(), None),
stroke: o.stroke,
stroke_width: o.stroke_width,
fill: None,
},
OpSetType::FillPath => PathInfo {
d: Self::ops_to_path(drawing.clone(), None),
stroke: None,
stroke_width: Some(0.0f32),
fill: o.fill,
},
OpSetType::FillSketch => {
let fill_weight = if o.fill_weight.unwrap_or(0.0) < 0.0 {
o.stroke_width.unwrap_or(0.0) / 2.0
} else {
o.fill_weight.unwrap_or(0.0)
};
PathInfo {
d: Self::ops_to_path(drawing.clone(), None),
stroke: o.fill,
stroke_width: Some(fill_weight),
fill: None,
}
}
};
path_infos.push(path_info);
}
path_infos
}
}