use crate::aabb::Aabb;
use crate::path::{LineCommand, Path, ToPaths};
use euclid::point2;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CanvasSpace;
#[derive(Debug)]
pub struct Canvas {
view: Aabb<i64, CanvasSpace>,
paths: Vec<Path<i64, CanvasSpace>>,
stroke_width: i64,
}
impl Canvas {
pub fn new(view: Aabb<i64, CanvasSpace>) -> Canvas {
let stroke_width = view.width() / 500;
Canvas {
view,
paths: Vec::new(),
stroke_width,
}
}
pub fn stroke_width(&self) -> i64 {
self.stroke_width
}
pub fn set_stroke_width(&mut self, stroke_width: i64) {
self.stroke_width = stroke_width;
}
#[inline]
pub fn view(&self) -> &Aabb<i64, CanvasSpace> {
&self.view
}
pub fn set_view(&mut self, view: Aabb<i64, CanvasSpace>) {
self.view = view;
}
pub fn fit_view_to_paths(&mut self) {
if self.paths.is_empty() {
return;
}
let mut min_x = std::i64::MAX;
let mut min_y = std::i64::MAX;
let mut max_x = std::i64::MIN;
let mut max_y = std::i64::MIN;
let mut process_point = |p: &euclid::Point2D<i64, CanvasSpace>| {
min_x = std::cmp::min(min_x, p.x);
min_y = std::cmp::min(min_y, p.y);
max_x = std::cmp::max(max_x, p.x);
max_y = std::cmp::max(max_y, p.y);
};
for path in self.paths.iter() {
for cmd in path.commands.iter() {
match cmd {
LineCommand::MoveTo(p)
| LineCommand::LineTo(p)
| LineCommand::SmoothQuadtraticCurveTo(p) => process_point(p),
LineCommand::CubicBezierTo {
control_1,
control_2,
end,
} => {
process_point(control_1);
process_point(control_2);
process_point(end);
}
LineCommand::SmoothCubicBezierTo { control, end }
| LineCommand::QuadraticBezierTo { control, end } => {
process_point(control);
process_point(end);
}
LineCommand::Close => {}
LineCommand::MoveBy(_)
| LineCommand::LineBy(_)
| LineCommand::HorizontalLineTo(_)
| LineCommand::HorizontalLineBy(_)
| LineCommand::VerticalLineTo(_)
| LineCommand::VerticalLineBy(_)
| LineCommand::CubicBezierBy { .. }
| LineCommand::SmoothCubicBezierBy { .. }
| LineCommand::QuadraticBezierBy { .. }
| LineCommand::SmoothQuadtraticCurveBy(_)
| LineCommand::ArcTo { .. }
| LineCommand::ArcBy { .. } => unimplemented!(),
}
}
}
let view = Aabb::new(point2(min_x, min_y), point2(max_x, max_y));
self.set_view(view);
}
pub fn draw<P>(&mut self, paths: P)
where
P: ToPaths<i64, CanvasSpace>,
{
self.paths.extend(paths.to_paths());
}
pub fn draw_many<I, P>(&mut self, paths: I)
where
I: IntoIterator<Item = P>,
P: ToPaths<i64, CanvasSpace>,
{
for p in paths {
self.draw(p);
}
}
pub fn create_svg<W, H>(&self, width: W, height: H) -> svg::Document
where
W: SvgUnit,
H: SvgUnit,
{
let width = width.into();
let height = height.into();
let mut doc = svg::Document::new()
.set(
"viewBox",
format!(
"{} {} {} {}",
self.view.min().x,
self.view.min().y,
self.view.width(),
self.view.height(),
),
)
.set("width", format!("{}{}", width, W::SUFFIX))
.set("height", format!("{}{}", height, H::SUFFIX));
for path in &self.paths {
let path: svg::node::element::Path = path.into();
doc = doc.add(path.set("stroke-width", self.stroke_width));
}
doc
}
}
impl ToPaths<i64, CanvasSpace> for Canvas {
type Paths = std::vec::IntoIter<Path<i64, CanvasSpace>>;
fn to_paths(&self) -> Self::Paths {
self.paths.clone().into_iter()
}
}
pub trait SvgUnit: Into<f64> {
const SUFFIX: &'static str;
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Inches(pub f64);
impl From<Inches> for f64 {
fn from(i: Inches) -> f64 {
i.0
}
}
impl SvgUnit for Inches {
const SUFFIX: &'static str = "in";
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Millis(pub f64);
impl From<Millis> for f64 {
fn from(i: Millis) -> f64 {
i.0
}
}
impl SvgUnit for Millis {
const SUFFIX: &'static str = "mm";
}