Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
richard-uk1 committed Apr 9, 2023
1 parent b4b33b7 commit 1bd729e
Show file tree
Hide file tree
Showing 2 changed files with 245 additions and 12 deletions.
8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ mod rounded_rect_radii;
mod shape;
pub mod simplify;
mod size;
#[cfg(feature = "std")]
mod svg;
//#[cfg(feature = "std")]
//mod svg;
mod translate_scale;
mod vec2;

Expand All @@ -131,7 +131,7 @@ pub use crate::rounded_rect::*;
pub use crate::rounded_rect_radii::*;
pub use crate::shape::*;
pub use crate::size::*;
#[cfg(feature = "std")]
pub use crate::svg::*;
//#[cfg(feature = "std")]
//pub use crate::svg::*;
pub use crate::translate_scale::*;
pub use crate::vec2::*;
249 changes: 241 additions & 8 deletions src/paths/svg.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! This module provides type [`Path`] representing SVG path data, and associated types.
use crate::{PathEl as KurboPathEl, Point, Shape, Vec2};
use crate::{Affine, PathEl as KurboPathEl, Point, Shape, Vec2};
use anyhow::{anyhow, Result};
use std::{fmt, ops::Deref, vec};
use std::{fmt, io, iter, mem, ops::Deref, slice, vec};

pub use self::one_vec::*;

type OneIter<'a, T> = iter::Chain<iter::Once<&'a T>, slice::Iter<'a, T>>;

/// An SVG path
///
/// A path *MUST* begin with a `MoveTo` element. For this and other invalid inputs, we return
Expand Down Expand Up @@ -250,7 +252,7 @@ impl Path {
}

/// Write out a text representation of the string.
pub fn write(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut iter = self.elements.iter();
if let Some(el) = iter.next() {
el.write(f)?;
Expand All @@ -261,6 +263,18 @@ impl Path {
}
Ok(())
}

/// Write out the text representation of this path to anything implementing `io::Write`.
///
/// `Path` also implements [`Display`][std::fmt::Display], which can be used when you need an
/// in-memory string representation (so you can e.g. `path.to_string()`).
///
/// Note that this call will produce a lot of write calls under the hood, so it is recommended
/// to use a buffer (e.g. [`BufWriter`][std::io::BufWriter]) if your writer's
/// [`write`][io::Write::write] calls are expensive.
pub fn write_to(&self, mut w: impl io::Write) -> io::Result<()> {
write!(w, "{}", self)
}
}

impl Deref for Path {
Expand All @@ -271,6 +285,12 @@ impl Deref for Path {
}
}

impl fmt::Display for Path {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.fmt(f)
}
}

impl Shape for Path {
type PathElementsIter<'iter> = PathElementsIter<'iter>;

Expand All @@ -281,6 +301,8 @@ impl Shape for Path {
path_start_point: Point::ZERO,
current_point: Point::ZERO,
current_bearing: 0.,
state: IterState::None,
seen_moveto: false,
}
}

Expand Down Expand Up @@ -353,11 +375,11 @@ impl PathEl {
}
PathEl::SmoothCubicTo(cubic_tos) => {
write!(f, "S")?;
cubic_tos.write_spaced(CubicTo::write_vals, f)?;
cubic_tos.write_spaced(SmoothCubicTo::write_vals, f)?;
}
PathEl::SmoothCubicToRel(cubic_tos) => {
write!(f, "s")?;
cubic_tos.write_spaced(CubicTo::write_vals, f)?;
cubic_tos.write_spaced(SmoothCubicTo::write_vals, f)?;
}
PathEl::QuadTo(quad_tos) => {
write!(f, "Q")?;
Expand All @@ -375,8 +397,14 @@ impl PathEl {
write!(f, "t")?;
quad_tos.write_spaced(write_point, f)?;
}
PathEl::EllipticArc(_) => todo!(),
PathEl::EllipticArcRel(_) => todo!(),
PathEl::EllipticArc(arcs) => {
write!(f, "A")?;
arcs.write_spaced(Arc::write_vals, f)?;
}
PathEl::EllipticArcRel(arcs) => {
write!(f, "a")?;
arcs.write_spaced(Arc::write_vals, f)?;
}
PathEl::Bearing(bearing) => {
write!(f, "B{bearing}",)?;
}
Expand Down Expand Up @@ -418,6 +446,22 @@ impl QuadTo {
}
}

impl Arc {
fn write_vals(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{},{} {} {},{} {},{}",
self.radii.x,
self.radii.y,
self.x_rotation,
self.large_arc,
self.sweep,
self.to.x,
self.to.y
)
}
}

/// An iterator over the path elements of an SVG path.
///
/// This structure could be `Copy`, but we don't implement it to avoid hidden clones.
Expand All @@ -433,13 +477,156 @@ pub struct PathElementsIter<'iter> {
current_point: Point,
/// The current bearing.
current_bearing: f64,
/// This flag tracks whether we have seen a moveto command yet. It affects the behavior of
/// `MoveToRel`.
seen_moveto: bool,
/// Iterator state machine
state: IterState<'iter>,
}

#[derive(Clone)]
enum IterState<'iter> {
/// We aren't part-way through any data type.
None,
/// We're in the middle of a lineto or moveto.
///
/// These values are actually for drawing lines to. See the spec for details of why this is the
/// state for a `MoveTo` as well.
LineTo {
transform: Affine,
rest: &'iter [Point],
},
}

impl<'iter> PathElementsIter<'iter> {
/// Handle the next element
///
/// Only call this if we finished handling the previous one (i.e. `state = IterState::None`).
fn next_el(&mut self, el: &'iter PathEl) -> Option<KurboPathEl> {
match el {
PathEl::MoveTo(points) => {
self.seen_moveto = true;
let (first, rest) = points.split();
self.current_point = *first;
self.path_start_point = *first;
self.state = IterState::LineTo {
transform: Affine::IDENTITY,
rest,
};
Some(KurboPathEl::MoveTo(*first))
}
PathEl::MoveToRel(points) => {
let (&first, rest) = points.split();
if self.seen_moveto {
let first = self.transform() * first;
self.current_point = first;
self.path_start_point = first;
self.state = IterState::LineTo {
transform: self.transform(),
rest,
};
Some(KurboPathEl::MoveTo(first))
} else {
self.seen_moveto = true;
self.current_point = first;
self.path_start_point = first;
// Even though we treat the first element as absolute, we still treat
// subsequent points as `LineToRel`s. This might make a difference if the
// user added a `Bearing` before the first `MoveTo`.
self.state = IterState::LineTo {
transform: self.transform(),
rest,
};
Some(KurboPathEl::MoveTo(first))
}
}
PathEl::ClosePath => {
self.current_point = self.path_start_point;
Some(KurboPathEl::ClosePath)
}
PathEl::LineTo(points) => {
let (&first, rest) = points.split();
self.current_point = first;
self.state = IterState::LineTo {
transform: Affine::IDENTITY,
rest,
};
Some(KurboPathEl::LineTo(first))
}
PathEl::LineToRel(points) => {
let (&first, rest) = points.split();
self.current_point = self.transform() * first;
self.state = IterState::LineTo {
// TODO we can't store the transform here as it changes for each element.
// Replace it with a flag here (and in the other Rel variants) and get it
// directly where it's needed.
transform: self.transform(),
rest,
};
Some(KurboPathEl::LineTo(first))
}
PathEl::Horiz(_) => todo!(),
PathEl::HorizRel(_) => todo!(),
PathEl::Vert(_) => todo!(),
PathEl::VertRel(_) => todo!(),
PathEl::CubicTo(_) => todo!(),
PathEl::CubicToRel(_) => todo!(),
PathEl::SmoothCubicTo(_) => todo!(),
PathEl::SmoothCubicToRel(_) => todo!(),
PathEl::QuadTo(_) => todo!(),
PathEl::QuadToRel(_) => todo!(),
PathEl::SmoothQuadTo(_) => todo!(),
PathEl::SmoothQuadToRel(_) => todo!(),
PathEl::EllipticArc(_) => todo!(),
PathEl::EllipticArcRel(_) => todo!(),
PathEl::Bearing(_) => todo!(),
PathEl::BearingRel(_) => todo!(),
}
}

fn handle_state(&mut self, state: IterState<'iter>) -> Option<KurboPathEl> {
match state {
IterState::None => None,
IterState::LineTo { transform, rest } => {
// Interpret these points as `LineTo`s.
let Some((&first, rest)) = rest.split_first() else {
return None;
};
// Get the point in absolute coords.
let first = transform * first;
self.state = IterState::LineTo { transform, rest };
self.current_point = first;
Some(KurboPathEl::LineTo(first))
}
}
}

/// Get the transform for the current start position and heading.
fn transform(&self) -> Affine {
// XXX I think this is correct, but not yet 100%
Affine::translate(self.current_point.to_vec2()) * Affine::rotate(self.current_bearing)
}
}

impl<'iter> Iterator for PathElementsIter<'iter> {
type Item = KurboPathEl;

fn next(&mut self) -> Option<Self::Item> {
todo!()
loop {
// Remember to but the state back if necessary.
let existing_state = mem::replace(&mut self.state, IterState::None);
if let Some(el) = self.handle_state(existing_state) {
return Some(el);
}
let Some((first, rest)) = self.path.split_first() else {
break;
};
self.path = rest;
if let Some(kurbo_path) = self.next_el(first) {
return Some(kurbo_path);
}
}
None
}
}

Expand All @@ -454,24 +641,70 @@ mod one_vec {
/// A vector that has at least 1 element.
///
/// It stores the first element on the stack, and the rest on the heap.
///
/// You can create a new `OneVec` either from the first element ([`single`][OneVec::single]) or
/// from the `TryFrom<Vec<T>>` implementation.
#[derive(Debug, Clone)]
pub struct OneVec<T> {
/// The first, required element in the `OneVec`.
pub first: T,
/// The second and subsequent elements in this `OneVec` (all optional).
pub rest: Vec<T>,
}

impl<T> OneVec<T> {
/// Create a `OneVec` from a single element.
pub fn single(val: T) -> Self {
Self {
first: val,
rest: vec![],
}
}

/// Iterate over the values in this `OneVec`.
///
/// The iterator is statically guaranteed to produce at least one element.
pub fn iter(&self) -> iter::Chain<iter::Once<&T>, slice::Iter<'_, T>> {
self.into_iter()
}

/// Get the element at the given index.
///
/// If the index is `0`, then this is guaranteed to return `Some`.
pub fn get(&self, idx: usize) -> Option<&T> {
if idx == 0 {
Some(&self.first)
} else {
self.rest.get(idx - 1)
}
}

/// Get the element at the given index.
///
/// If the index is `0`, then this is guaranteed to return `Some`.
pub fn get_mut(&mut self, idx: usize) -> Option<&mut T> {
if idx == 0 {
Some(&mut self.first)
} else {
self.rest.get_mut(idx - 1)
}
}

/// Get the first element.
pub fn first(&self) -> &T {
&self.first
}

/// Get the first element.
pub fn first_mut(&mut self) -> &mut T {
&mut self.first
}

/// Splits the `OneVec` into the first element and the rest.
pub fn split(&self) -> (&T, &[T]) {
(&self.first, &self.rest)
}

/// Write out the vector with spaces between each element
pub(crate) fn write_spaced(
&self,
Expand Down

0 comments on commit 1bd729e

Please sign in to comment.