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}