typst_library/visualize/
path.rs

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