typst_library/visualize/polygon.rs
1use std::f64::consts::PI;
2
3use typst_syntax::Span;
4
5use crate::foundations::{Content, NativeElement, Smart, elem, func, scope};
6use crate::layout::{Axes, Em, Length, Rel};
7use crate::visualize::{FillRule, Paint, Stroke};
8
9/// A closed polygon.
10///
11/// The polygon is defined by its corner points and is closed automatically.
12///
13/// # Example
14/// ```example
15/// #polygon(
16/// fill: blue.lighten(80%),
17/// stroke: blue,
18/// (20%, 0pt),
19/// (60%, 0pt),
20/// (80%, 2cm),
21/// (0%, 2cm),
22/// )
23/// ```
24#[elem(scope)]
25pub struct PolygonElem {
26 /// How to fill the polygon.
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 polygon.
33 ///
34 /// See the [curve documentation]($curve.fill-rule) for an example.
35 #[default]
36 pub fill_rule: FillRule,
37
38 /// How to [stroke] the polygon.
39 ///
40 /// Can be set to `{none}` to disable the stroke or to `{auto}` for a
41 /// stroke of `{1pt}` black if and only if no fill is given.
42 #[fold]
43 pub stroke: Smart<Option<Stroke>>,
44
45 /// The vertices of the polygon. Each point is specified as an array of two
46 /// [relative lengths]($relative).
47 #[variadic]
48 pub vertices: Vec<Axes<Rel<Length>>>,
49}
50
51#[scope]
52impl PolygonElem {
53 /// A regular polygon, defined by its size and number of vertices.
54 ///
55 /// ```example
56 /// #polygon.regular(
57 /// fill: blue.lighten(80%),
58 /// stroke: blue,
59 /// size: 30pt,
60 /// vertices: 3,
61 /// )
62 /// ```
63 #[func(title = "Regular Polygon")]
64 pub fn regular(
65 span: Span,
66
67 /// How to fill the polygon. See the general
68 /// [polygon's documentation]($polygon.fill) for more details.
69 #[named]
70 fill: Option<Option<Paint>>,
71
72 /// How to stroke the polygon. See the general
73 /// [polygon's documentation]($polygon.stroke) for more details.
74 #[named]
75 stroke: Option<Smart<Option<Stroke>>>,
76
77 /// The diameter of the [circumcircle](https://en.wikipedia.org/wiki/Circumcircle)
78 /// of the regular polygon.
79 #[named]
80 #[default(Em::one().into())]
81 size: Length,
82
83 /// The number of vertices in the polygon.
84 #[named]
85 #[default(3)]
86 vertices: u64,
87 ) -> Content {
88 let radius = size / 2.0;
89 let angle = |i: f64| {
90 2.0 * PI * i / (vertices as f64) + PI * (1.0 / 2.0 - 1.0 / vertices as f64)
91 };
92 let (horizontal_offset, vertical_offset) = (0..=vertices)
93 .map(|v| {
94 (
95 (radius * angle(v as f64).cos()) + radius,
96 (radius * angle(v as f64).sin()) + radius,
97 )
98 })
99 .fold((radius, radius), |(min_x, min_y), (v_x, v_y)| {
100 (
101 if min_x < v_x { min_x } else { v_x },
102 if min_y < v_y { min_y } else { v_y },
103 )
104 });
105 let vertices = (0..=vertices)
106 .map(|v| {
107 let x = (radius * angle(v as f64).cos()) + radius - horizontal_offset;
108 let y = (radius * angle(v as f64).sin()) + radius - vertical_offset;
109 Axes::new(x, y).map(Rel::from)
110 })
111 .collect();
112
113 let mut elem = PolygonElem::new(vertices);
114 if let Some(fill) = fill {
115 elem.fill.set(fill);
116 }
117 if let Some(stroke) = stroke {
118 elem.stroke.set(stroke);
119 }
120 elem.pack().spanned(span)
121 }
122}