use self::PathVertex::{AllControlPoints, MirroredControlPoint, Vertex}; use crate::diag::{bail, SourceResult}; use crate::engine::Engine; use crate::foundations::{ array, cast, elem, Array, Content, NativeElement, Packed, Reflect, Show, Smart, StyleChain, }; use crate::layout::{Axes, BlockElem, Length, Rel}; use crate::visualize::{FillRule, Paint, Stroke}; /// A path through a list of points, connected by Bézier curves. /// /// # Example /// ```example /// #path( /// fill: blue.lighten(80%), /// stroke: blue, /// closed: true, /// (0pt, 50pt), /// (100%, 50pt), /// ((50%, 0pt), (40pt, 0pt)), /// ) /// ``` #[elem(Show)] pub struct PathElem { /// How to fill the path. /// /// When setting a fill, the default stroke disappears. To create a /// rectangle with both fill and stroke, you have to configure both. pub fill: Option, /// The drawing rule used to fill the path. /// /// ```example /// // We use `.with` to get a new /// // function that has the common /// // arguments pre-applied. /// #let star = path.with( /// fill: red, /// closed: true, /// (25pt, 0pt), /// (10pt, 50pt), /// (50pt, 20pt), /// (0pt, 20pt), /// (40pt, 50pt), /// ) /// /// #star(fill-rule: "non-zero") /// #star(fill-rule: "even-odd") /// ``` #[default] pub fill_rule: FillRule, /// How to [stroke] the path. This can be: /// /// Can be set to `{none}` to disable the stroke or to `{auto}` for a /// stroke of `{1pt}` black if and if only if no fill is given. #[resolve] #[fold] pub stroke: Smart>, /// Whether to close this path with one last Bézier curve. This curve will /// take into account the adjacent control points. If you want to close /// with a straight line, simply add one last point that's the same as the /// start point. #[default(false)] pub closed: bool, /// The vertices of the path. /// /// Each vertex can be defined in 3 ways: /// /// - A regular point, as given to the [`line`] or [`polygon`] function. /// - An array of two points, the first being the vertex and the second /// being the control point. The control point is expressed relative to /// the vertex and is mirrored to get the second control point. The given /// control point is the one that affects the curve coming _into_ this /// vertex (even for the first point). The mirrored control point affects /// the curve going out of this vertex. /// - An array of three points, the first being the vertex and the next /// being the control points (control point for curves coming in and out, /// respectively). #[variadic] pub vertices: Vec, } impl Show for Packed { fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult { Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_path) .pack() .spanned(self.span())) } } /// A component used for path creation. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum PathVertex { Vertex(Axes>), MirroredControlPoint(Axes>, Axes>), AllControlPoints(Axes>, Axes>, Axes>), } impl PathVertex { pub fn vertex(&self) -> Axes> { match self { Vertex(x) => *x, MirroredControlPoint(x, _) => *x, AllControlPoints(x, _, _) => *x, } } pub fn control_point_from(&self) -> Axes> { match self { Vertex(_) => Axes::new(Rel::zero(), Rel::zero()), MirroredControlPoint(_, a) => a.map(|x| -x), AllControlPoints(_, _, b) => *b, } } pub fn control_point_to(&self) -> Axes> { match self { Vertex(_) => Axes::new(Rel::zero(), Rel::zero()), MirroredControlPoint(_, a) => *a, AllControlPoints(_, a, _) => *a, } } } cast! { PathVertex, self => match self { Vertex(x) => x.into_value(), MirroredControlPoint(x, c) => array![x, c].into_value(), AllControlPoints(x, c1, c2) => array![x, c1, c2].into_value(), }, array: Array => { let mut iter = array.into_iter(); match (iter.next(), iter.next(), iter.next(), iter.next()) { (Some(a), None, None, None) => { Vertex(a.cast()?) }, (Some(a), Some(b), None, None) => { if Axes::>::castable(&a) { MirroredControlPoint(a.cast()?, b.cast()?) } else { Vertex(Axes::new(a.cast()?, b.cast()?)) } }, (Some(a), Some(b), Some(c), None) => { AllControlPoints(a.cast()?, b.cast()?, c.cast()?) }, _ => bail!("path vertex must have 1, 2, or 3 points"), } }, }