| //! Types for collecting the output when drawing a glyph outline. |
| |
| use alloc::{string::String, vec::Vec}; |
| use core::fmt::{self, Write}; |
| |
| /// Interface for accepting a sequence of path commands. |
| pub trait OutlinePen { |
| /// Emit a command to begin a new subpath at (x, y). |
| fn move_to(&mut self, x: f32, y: f32); |
| |
| /// Emit a line segment from the current point to (x, y). |
| fn line_to(&mut self, x: f32, y: f32); |
| |
| /// Emit a quadratic bezier segment from the current point with a control |
| /// point at (cx0, cy0) and ending at (x, y). |
| fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32); |
| |
| /// Emit a cubic bezier segment from the current point with control |
| /// points at (cx0, cy0) and (cx1, cy1) and ending at (x, y). |
| fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32); |
| |
| /// Emit a command to close the current subpath. |
| fn close(&mut self); |
| } |
| |
| /// Single element of a path. |
| #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] |
| pub enum PathElement { |
| /// Begin a new subpath at (x, y). |
| MoveTo { x: f32, y: f32 }, |
| /// Draw a line from the current point to (x, y). |
| LineTo { x: f32, y: f32 }, |
| /// Draw a quadratic bezier from the current point with a control point at |
| /// (cx0, cy0) and ending at (x, y). |
| QuadTo { cx0: f32, cy0: f32, x: f32, y: f32 }, |
| /// Draw a cubic bezier from the current point with control points at |
| /// (cx0, cy0) and (cx1, cy1) and ending at (x, y). |
| CurveTo { |
| cx0: f32, |
| cy0: f32, |
| cx1: f32, |
| cy1: f32, |
| x: f32, |
| y: f32, |
| }, |
| /// Close the current subpath. |
| Close, |
| } |
| |
| /// Style for path conversion. |
| /// |
| /// The order to process points in a glyf point stream is ambiguous when the |
| /// first point is off-curve. Major implementations differ. Which one would |
| /// you like to match? |
| /// |
| /// **If you add a new one make sure to update the fuzzer.** |
| #[derive(Debug, Default, Copy, Clone)] |
| pub enum PathStyle { |
| /// If the first point is off-curve, check if the last is on-curve |
| /// If it is, start there. If it isn't, start at the implied midpoint |
| /// between first and last. |
| #[default] |
| FreeType, |
| /// If the first point is off-curve, check if the second is on-curve. |
| /// If it is, start there. If it isn't, start at the implied midpoint |
| /// between first and second. |
| /// |
| /// Matches hb-draw's interpretation of a point stream. |
| HarfBuzz, |
| } |
| |
| impl OutlinePen for Vec<PathElement> { |
| fn move_to(&mut self, x: f32, y: f32) { |
| self.push(PathElement::MoveTo { x, y }) |
| } |
| |
| fn line_to(&mut self, x: f32, y: f32) { |
| self.push(PathElement::LineTo { x, y }) |
| } |
| |
| fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) { |
| self.push(PathElement::QuadTo { cx0, cy0, x, y }) |
| } |
| |
| fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) { |
| self.push(PathElement::CurveTo { |
| cx0, |
| cy0, |
| cx1, |
| cy1, |
| x, |
| y, |
| }) |
| } |
| |
| fn close(&mut self) { |
| self.push(PathElement::Close) |
| } |
| } |
| |
| /// Pen that drops all drawing output into the ether. |
| pub struct NullPen; |
| |
| impl OutlinePen for NullPen { |
| fn move_to(&mut self, _x: f32, _y: f32) {} |
| fn line_to(&mut self, _x: f32, _y: f32) {} |
| fn quad_to(&mut self, _cx0: f32, _cy0: f32, _x: f32, _y: f32) {} |
| fn curve_to(&mut self, _cx0: f32, _cy0: f32, _cx1: f32, _cy1: f32, _x: f32, _y: f32) {} |
| fn close(&mut self) {} |
| } |
| |
| /// Pen that generates SVG style path data. |
| #[derive(Clone, Default, Debug)] |
| pub struct SvgPen(String, Option<usize>); |
| |
| impl SvgPen { |
| /// Creates a new SVG pen that formats floating point values with the |
| /// standard behavior. |
| pub fn new() -> Self { |
| Self::default() |
| } |
| |
| /// Creates a new SVG pen with the given precision (the number of digits |
| /// that will be printed after the decimal). |
| pub fn with_precision(precision: usize) -> Self { |
| Self(String::default(), Some(precision)) |
| } |
| |
| /// Clears the content of the internal string. |
| pub fn clear(&mut self) { |
| self.0.clear(); |
| } |
| |
| fn maybe_push_space(&mut self) { |
| if !self.0.is_empty() { |
| self.0.push(' '); |
| } |
| } |
| } |
| |
| impl core::ops::Deref for SvgPen { |
| type Target = str; |
| |
| fn deref(&self) -> &Self::Target { |
| self.0.as_str() |
| } |
| } |
| |
| impl OutlinePen for SvgPen { |
| fn move_to(&mut self, x: f32, y: f32) { |
| self.maybe_push_space(); |
| let _ = if let Some(prec) = self.1 { |
| write!(self.0, "M{x:.0$},{y:.0$}", prec) |
| } else { |
| write!(self.0, "M{x},{y}") |
| }; |
| } |
| |
| fn line_to(&mut self, x: f32, y: f32) { |
| self.maybe_push_space(); |
| let _ = if let Some(prec) = self.1 { |
| write!(self.0, "L{x:.0$},{y:.0$}", prec) |
| } else { |
| write!(self.0, "L{x},{y}") |
| }; |
| } |
| |
| fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) { |
| self.maybe_push_space(); |
| let _ = if let Some(prec) = self.1 { |
| write!(self.0, "Q{cx0:.0$},{cy0:.0$} {x:.0$},{y:.0$}", prec) |
| } else { |
| write!(self.0, "Q{cx0},{cy0} {x},{y}") |
| }; |
| } |
| |
| fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) { |
| self.maybe_push_space(); |
| let _ = if let Some(prec) = self.1 { |
| write!( |
| self.0, |
| "C{cx0:.0$},{cy0:.0$} {cx1:.0$},{cy1:.0$} {x:.0$},{y:.0$}", |
| prec |
| ) |
| } else { |
| write!(self.0, "C{cx0},{cy0} {cx1},{cy1} {x},{y}") |
| }; |
| } |
| |
| fn close(&mut self) { |
| self.maybe_push_space(); |
| self.0.push('Z'); |
| } |
| } |
| |
| impl AsRef<str> for SvgPen { |
| fn as_ref(&self) -> &str { |
| self.0.as_ref() |
| } |
| } |
| |
| impl From<String> for SvgPen { |
| fn from(value: String) -> Self { |
| Self(value, None) |
| } |
| } |
| |
| impl From<SvgPen> for String { |
| fn from(value: SvgPen) -> Self { |
| value.0 |
| } |
| } |
| |
| impl fmt::Display for SvgPen { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "{}", self.0) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn svg_pen_precision() { |
| let svg_data = [None, Some(1), Some(4)].map(|prec| { |
| let mut pen = match prec { |
| None => SvgPen::new(), |
| Some(prec) => SvgPen::with_precision(prec), |
| }; |
| pen.move_to(1.0, 2.45556); |
| pen.line_to(1.2, 4.0); |
| pen.quad_to(2.0345, 3.56789, -0.157, -425.07); |
| pen.curve_to(-37.0010, 4.5, 2.0, 1.0, -0.5, -0.25); |
| pen.close(); |
| pen.to_string() |
| }); |
| let expected = [ |
| "M1,2.45556 L1.2,4 Q2.0345,3.56789 -0.157,-425.07 C-37.001,4.5 2,1 -0.5,-0.25 Z", |
| "M1.0,2.5 L1.2,4.0 Q2.0,3.6 -0.2,-425.1 C-37.0,4.5 2.0,1.0 -0.5,-0.2 Z", |
| "M1.0000,2.4556 L1.2000,4.0000 Q2.0345,3.5679 -0.1570,-425.0700 C-37.0010,4.5000 2.0000,1.0000 -0.5000,-0.2500 Z" |
| ]; |
| for (result, expected) in svg_data.iter().zip(&expected) { |
| assert_eq!(result, expected); |
| } |
| } |
| } |