use std::fmt::Display;
use std::ops::MulAssign;
use euclid::default::Point2D;
use euclid::Trig;
use num_traits::{Float, FromPrimitive};
use palette::rgb::Rgba;
use palette::Srgba;
use piet::kurbo::{self, BezPath, PathEl, Point};
use piet::{Color, LineJoin, RenderContext, StrokeStyle};
use roughr::core::{Drawable, OpSet, OpSetType, OpType, Options};
use roughr::generator::Generator;
use roughr::PathSegment;
#[derive(Default)]
pub struct KurboGenerator {
gen: Generator,
options: Option<Options>,
}
#[derive(Clone)]
pub struct KurboOpset<F: Float + Trig> {
pub op_set_type: OpSetType,
pub ops: BezPath,
pub size: Option<Point2D<F>>,
pub path: Option<String>,
}
pub trait ToKurboOpset<F: Float + Trig> {
fn to_kurbo_opset(self) -> KurboOpset<F>;
}
impl<F: Float + Trig + FromPrimitive> ToKurboOpset<F> for OpSet<F> {
fn to_kurbo_opset(self) -> KurboOpset<F> {
KurboOpset {
op_set_type: self.op_set_type.clone(),
size: self.size,
path: self.path.clone(),
ops: opset_to_shape(&self),
}
}
}
pub struct KurboDrawable<F: Float + Trig> {
pub shape: String,
pub options: Options,
pub sets: Vec<KurboOpset<F>>,
}
pub trait ToKurboDrawable<F: Float + Trig> {
fn to_kurbo_drawable(self) -> KurboDrawable<F>;
}
impl<F: Float + Trig + FromPrimitive> ToKurboDrawable<F> for Drawable<F> {
fn to_kurbo_drawable(self) -> KurboDrawable<F> {
KurboDrawable {
shape: self.shape,
options: self.options,
sets: self.sets.into_iter().map(|s| s.to_kurbo_opset()).collect(),
}
}
}
impl KurboGenerator {
pub fn new(options: Options) -> Self {
KurboGenerator { gen: Generator::default(), options: Some(options) }
}
}
impl<F: Float + Trig> KurboDrawable<F> {
pub fn draw(&self, ctx: &mut impl RenderContext) {
for set in self.sets.iter() {
match set.op_set_type {
OpSetType::Path => {
ctx.save().expect("Failed to save render context");
if self.options.stroke_line_dash.is_some() {
let stroke_line_dash =
self.options.stroke_line_dash.clone().unwrap_or(Vec::new());
let mut ss = StrokeStyle::new();
ss.set_dash_pattern(stroke_line_dash.as_slice());
ss.set_dash_offset(self.options.stroke_line_dash_offset.unwrap_or(1.0f64));
ss.set_line_cap(convert_line_cap_from_roughr_to_piet(
self.options.line_cap,
));
ss.set_line_join(convert_line_join_from_roughr_to_piet(
self.options.line_join,
));
let stroke_color = self
.options
.stroke
.unwrap_or_else(|| Srgba::from_components((1.0, 1.0, 1.0, 1.0)));
let rgb: (f32, f32, f32, f32) = stroke_color.into_components();
ctx.stroke_styled(
set.ops.clone(),
&Color::rgba(rgb.0 as f64, rgb.1 as f64, rgb.2 as f64, rgb.3 as f64),
self.options.stroke_width.unwrap_or(1.0) as f64,
&ss,
);
ctx.restore().expect("Failed to restore render context");
} else {
let stroke_color = self
.options
.stroke
.unwrap_or_else(|| Srgba::new(1.0, 1.0, 1.0, 1.0));
let rgb: (f32, f32, f32, f32) = stroke_color.into_components();
ctx.stroke(
set.ops.clone(),
&Color::rgba(rgb.0 as f64, rgb.1 as f64, rgb.2 as f64, rgb.3 as f64),
self.options.stroke_width.unwrap_or(1.0) as f64,
);
ctx.restore().expect("Failed to restore render context");
}
}
OpSetType::FillPath => {
ctx.save().expect("Failed to save render context");
match self.shape.as_str() {
"curve" | "polygon" | "path" => {
let fill_color =
self.options.fill.unwrap_or(Rgba::new(1.0, 1.0, 1.0, 1.0));
let rgb: (f32, f32, f32, f32) = fill_color.into_components();
ctx.fill_even_odd(
set.ops.clone(),
&Color::rgba(
rgb.0 as f64,
rgb.1 as f64,
rgb.2 as f64,
rgb.3 as f64,
),
)
}
_ => {
let fill_color =
self.options.fill.unwrap_or(Rgba::new(1.0, 1.0, 1.0, 1.0));
let rgb: (f32, f32, f32, f32) = fill_color.into_components();
ctx.fill(
set.ops.clone(),
&Color::rgba(
rgb.0 as f64,
rgb.1 as f64,
rgb.2 as f64,
rgb.3 as f64,
),
)
}
}
ctx.restore().expect("Failed to restore render context");
}
OpSetType::FillSketch => {
let mut fweight = self.options.fill_weight.unwrap_or_default();
if fweight < 0.0 {
fweight = self.options.stroke_width.unwrap_or(1.0) / 2.0;
}
ctx.save().expect("Failed to save render context");
if self.options.fill_line_dash.is_some() {
let fill_line_dash =
self.options.fill_line_dash.clone().unwrap_or_default();
let mut ss = StrokeStyle::new();
ss.set_dash_pattern(fill_line_dash.as_slice());
ss.set_dash_offset(self.options.fill_line_dash_offset.unwrap_or(0.0f64));
ss.set_line_cap(convert_line_cap_from_roughr_to_piet(
self.options.line_cap,
));
ss.set_line_join(convert_line_join_from_roughr_to_piet(
self.options.line_join,
));
let fill_color = self
.options
.fill
.unwrap_or_else(|| Rgba::new(1.0, 1.0, 1.0, 1.0));
let rgb: (f32, f32, f32, f32) = fill_color.into_components();
ctx.stroke_styled(
set.ops.clone(),
&Color::rgba(rgb.0 as f64, rgb.1 as f64, rgb.2 as f64, rgb.3 as f64),
fweight as f64,
&ss,
);
} else {
let fill_color = self
.options
.fill
.unwrap_or_else(|| Rgba::new(1.0, 1.0, 1.0, 1.0));
let rgb: (f32, f32, f32, f32) = fill_color.into_components();
ctx.stroke(
set.ops.clone(),
&Color::rgba(rgb.0 as f64, rgb.1 as f64, rgb.2 as f64, rgb.3 as f64),
fweight as f64,
);
}
ctx.restore().expect("Failed to restore render context");
}
}
}
}
}
fn opset_to_shape<F: Trig + Float + FromPrimitive>(op_set: &OpSet<F>) -> BezPath {
let mut path: BezPath = BezPath::new();
for item in op_set.ops.iter() {
match item.op {
OpType::Move => path.extend([PathEl::MoveTo(Point::new(
item.data[0].to_f64().unwrap(),
item.data[1].to_f64().unwrap(),
))]),
OpType::BCurveTo => path.extend([PathEl::CurveTo(
Point::new(
item.data[0].to_f64().unwrap(),
item.data[1].to_f64().unwrap(),
),
Point::new(
item.data[2].to_f64().unwrap(),
item.data[3].to_f64().unwrap(),
),
Point::new(
item.data[4].to_f64().unwrap(),
item.data[5].to_f64().unwrap(),
),
)]),
OpType::LineTo => {
path.extend([PathEl::LineTo(Point::new(
item.data[0].to_f64().unwrap(),
item.data[1].to_f64().unwrap(),
))]);
}
}
}
path
}
impl KurboGenerator {
pub fn line<F: Trig + Float + FromPrimitive>(
&self,
x1: F,
y1: F,
x2: F,
y2: F,
) -> KurboDrawable<F> {
let drawable = self.gen.line(x1, y1, x2, y2, &self.options);
drawable.to_kurbo_drawable()
}
pub fn rectangle<F: Trig + Float + FromPrimitive>(
&self,
x: F,
y: F,
width: F,
height: F,
) -> KurboDrawable<F> {
let drawable = self.gen.rectangle(x, y, width, height, &self.options);
drawable.to_kurbo_drawable()
}
pub fn ellipse<F: Trig + Float + FromPrimitive>(
&self,
x: F,
y: F,
width: F,
height: F,
) -> KurboDrawable<F> {
let drawable = self.gen.ellipse(x, y, width, height, &self.options);
drawable.to_kurbo_drawable()
}
pub fn circle<F: Trig + Float + FromPrimitive>(
&self,
x: F,
y: F,
diameter: F,
) -> KurboDrawable<F> {
let drawable = self.gen.circle(x, y, diameter, &self.options);
drawable.to_kurbo_drawable()
}
pub fn linear_path<F: Trig + Float + FromPrimitive>(
&self,
points: &[Point2D<F>],
close: bool,
) -> KurboDrawable<F> {
let drawable = self.gen.linear_path(points, close, &self.options);
drawable.to_kurbo_drawable()
}
pub fn polygon<F: Trig + Float + FromPrimitive + MulAssign + Display>(
&self,
points: &[Point2D<F>],
) -> KurboDrawable<F> {
let drawable = self.gen.polygon(points, &self.options);
drawable.to_kurbo_drawable()
}
pub fn arc<F: Trig + Float + FromPrimitive>(
&self,
x: F,
y: F,
width: F,
height: F,
start: F,
stop: F,
closed: bool,
) -> KurboDrawable<F> {
let drawable = self
.gen
.arc(x, y, width, height, start, stop, closed, &self.options);
drawable.to_kurbo_drawable()
}
pub fn bezier_quadratic<F: Trig + Float + FromPrimitive + MulAssign + Display>(
&self,
start: Point2D<F>,
cp: Point2D<F>,
end: Point2D<F>,
) -> KurboDrawable<F> {
let drawable = self.gen.bezier_quadratic(start, cp, end, &self.options);
drawable.to_kurbo_drawable()
}
pub fn bezier_cubic<F: Trig + Float + FromPrimitive + MulAssign + Display>(
&self,
start: Point2D<F>,
cp1: Point2D<F>,
cp2: Point2D<F>,
end: Point2D<F>,
) -> KurboDrawable<F> {
let drawable = self.gen.bezier_cubic(start, cp1, cp2, end, &self.options);
drawable.to_kurbo_drawable()
}
pub fn curve<F: Trig + Float + FromPrimitive + MulAssign + Display>(
&self,
points: &[Point2D<F>],
) -> KurboDrawable<F> {
let drawable = self.gen.curve(points, &self.options);
drawable.to_kurbo_drawable()
}
pub fn path<F: Trig + Float + FromPrimitive + MulAssign + Display>(
&self,
svg_path: String,
) -> KurboDrawable<F> {
let drawable = self.gen.path(svg_path, &self.options);
drawable.to_kurbo_drawable()
}
pub fn bez_path<F: Trig + Float + FromPrimitive + MulAssign + Display>(
&self,
bezier_path: BezPath,
) -> KurboDrawable<F> {
let segments = bezpath_to_svg_segments(&bezier_path);
self.gen
.path_from_segments(segments, &self.options)
.to_kurbo_drawable()
}
}
fn convert_line_cap_from_roughr_to_piet(
roughr_line_cap: Option<roughr::core::LineCap>,
) -> piet::LineCap {
match roughr_line_cap {
Some(roughr::core::LineCap::Butt) => piet::LineCap::Butt,
Some(roughr::core::LineCap::Round) => piet::LineCap::Round,
Some(roughr::core::LineCap::Square) => piet::LineCap::Square,
None => piet::LineCap::Butt,
}
}
fn convert_line_join_from_roughr_to_piet(
roughr_line_join: Option<roughr::core::LineJoin>,
) -> LineJoin {
match roughr_line_join {
Some(roughr::core::LineJoin::Miter { limit }) => LineJoin::Miter { limit },
Some(roughr::core::LineJoin::Round) => LineJoin::Round,
Some(roughr::core::LineJoin::Bevel) => LineJoin::Bevel,
None => LineJoin::Miter { limit: LineJoin::DEFAULT_MITER_LIMIT },
}
}
pub fn bezpath_to_svg_segments(path: &BezPath) -> Vec<PathSegment> {
let mut segments = Vec::new();
for elem in path.elements() {
match elem {
kurbo::PathEl::MoveTo(p) => {
segments.push(PathSegment::MoveTo { abs: true, x: p.x, y: p.y });
}
kurbo::PathEl::LineTo(p) => {
segments.push(PathSegment::LineTo { abs: true, x: p.x, y: p.y });
}
kurbo::PathEl::QuadTo(p1, p2) => {
segments.push(PathSegment::Quadratic {
abs: true,
x1: p1.x,
y1: p1.y,
x: p2.x,
y: p2.y,
});
}
kurbo::PathEl::CurveTo(p1, p2, p3) => {
segments.push(PathSegment::CurveTo {
abs: true,
x1: p1.x,
y1: p1.y,
x2: p2.x,
y2: p2.y,
x: p3.x,
y: p3.y,
});
}
kurbo::PathEl::ClosePath => {
segments.push(PathSegment::ClosePath { abs: true });
}
}
}
segments
}