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}