typst_library/visualize/
path.rs

1use self::PathVertex::{AllControlPoints, MirroredControlPoint, Vertex};
2use crate::diag::{bail, SourceResult};
3use crate::engine::Engine;
4use crate::foundations::{
5    array, cast, elem, Array, Content, NativeElement, Packed, Reflect, Show, Smart,
6    StyleChain,
7};
8use crate::layout::{Axes, BlockElem, Length, Rel};
9use crate::visualize::{FillRule, Paint, Stroke};
10
11/// A path through a list of points, connected by Bézier curves.
12///
13/// # Example
14/// ```example
15/// #path(
16///   fill: blue.lighten(80%),
17///   stroke: blue,
18///   closed: true,
19///   (0pt, 50pt),
20///   (100%, 50pt),
21///   ((50%, 0pt), (40pt, 0pt)),
22/// )
23/// ```
24#[elem(Show)]
25pub struct PathElem {
26    /// How to fill the path.
27    ///
28    /// When setting a fill, the default stroke disappears. To create a
29    /// rectangle with both fill and stroke, you have to configure both.
30    pub fill: Option<Paint>,
31
32    /// The drawing rule used to fill the path.
33    ///
34    /// ```example
35    /// // We use `.with` to get a new
36    /// // function that has the common
37    /// // arguments pre-applied.
38    /// #let star = path.with(
39    ///   fill: red,
40    ///   closed: true,
41    ///   (25pt, 0pt),
42    ///   (10pt, 50pt),
43    ///   (50pt, 20pt),
44    ///   (0pt, 20pt),
45    ///   (40pt, 50pt),
46    /// )
47    ///
48    /// #star(fill-rule: "non-zero")
49    /// #star(fill-rule: "even-odd")
50    /// ```
51    #[default]
52    pub fill_rule: FillRule,
53
54    /// How to [stroke] the path. This can be:
55    ///
56    /// Can be set to  `{none}` to disable the stroke or to `{auto}` for a
57    /// stroke of `{1pt}` black if and if only if no fill is given.
58    #[resolve]
59    #[fold]
60    pub stroke: Smart<Option<Stroke>>,
61
62    /// Whether to close this path with one last Bézier curve. This curve will
63    /// take into account the adjacent control points. If you want to close
64    /// with a straight line, simply add one last point that's the same as the
65    /// start point.
66    #[default(false)]
67    pub closed: bool,
68
69    /// The vertices of the path.
70    ///
71    /// Each vertex can be defined in 3 ways:
72    ///
73    /// - A regular point, as given to the [`line`] or [`polygon`] function.
74    /// - An array of two points, the first being the vertex and the second
75    ///   being the control point. The control point is expressed relative to
76    ///   the vertex and is mirrored to get the second control point. The given
77    ///   control point is the one that affects the curve coming _into_ this
78    ///   vertex (even for the first point). The mirrored control point affects
79    ///   the curve going out of this vertex.
80    /// - An array of three points, the first being the vertex and the next
81    ///   being the control points (control point for curves coming in and out,
82    ///   respectively).
83    #[variadic]
84    pub vertices: Vec<PathVertex>,
85}
86
87impl Show for Packed<PathElem> {
88    fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
89        Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_path)
90            .pack()
91            .spanned(self.span()))
92    }
93}
94
95/// A component used for path creation.
96#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
97pub enum PathVertex {
98    Vertex(Axes<Rel<Length>>),
99    MirroredControlPoint(Axes<Rel<Length>>, Axes<Rel<Length>>),
100    AllControlPoints(Axes<Rel<Length>>, Axes<Rel<Length>>, Axes<Rel<Length>>),
101}
102
103impl PathVertex {
104    pub fn vertex(&self) -> Axes<Rel<Length>> {
105        match self {
106            Vertex(x) => *x,
107            MirroredControlPoint(x, _) => *x,
108            AllControlPoints(x, _, _) => *x,
109        }
110    }
111
112    pub fn control_point_from(&self) -> Axes<Rel<Length>> {
113        match self {
114            Vertex(_) => Axes::new(Rel::zero(), Rel::zero()),
115            MirroredControlPoint(_, a) => a.map(|x| -x),
116            AllControlPoints(_, _, b) => *b,
117        }
118    }
119
120    pub fn control_point_to(&self) -> Axes<Rel<Length>> {
121        match self {
122            Vertex(_) => Axes::new(Rel::zero(), Rel::zero()),
123            MirroredControlPoint(_, a) => *a,
124            AllControlPoints(_, a, _) => *a,
125        }
126    }
127}
128
129cast! {
130    PathVertex,
131    self => match self {
132        Vertex(x) => x.into_value(),
133        MirroredControlPoint(x, c) => array![x, c].into_value(),
134        AllControlPoints(x, c1, c2) => array![x, c1, c2].into_value(),
135    },
136    array: Array => {
137        let mut iter = array.into_iter();
138        match (iter.next(), iter.next(), iter.next(), iter.next()) {
139            (Some(a), None, None, None) => {
140                Vertex(a.cast()?)
141            },
142            (Some(a), Some(b), None, None) => {
143                if Axes::<Rel<Length>>::castable(&a) {
144                    MirroredControlPoint(a.cast()?, b.cast()?)
145                } else {
146                    Vertex(Axes::new(a.cast()?, b.cast()?))
147                }
148            },
149            (Some(a), Some(b), Some(c), None) => {
150                AllControlPoints(a.cast()?, b.cast()?, c.cast()?)
151            },
152            _ => bail!("path vertex must have 1, 2, or 3 points"),
153        }
154    },
155}