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::{Angle, Axes, Em, Length, Ratio, 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 <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.fill-rule[curve documentation] for an example.
35 #[default]
36 pub fill_rule: FillRule,
37
38 /// How to @stroke[stroke] the polygon.
39 ///
40 /// Can be set to `{none}` to disable the stroke or to `{auto}` for a stroke
41 /// 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[relative lengths].
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.fill[polygon's documentation] for more details.
69 #[named]
70 fill: Option<Option<Paint>>,
71
72 /// How to stroke the polygon. See the general
73 /// @polygon.stroke[polygon's documentation] for more details.
74 #[named]
75 stroke: Option<Smart<Option<Stroke>>>,
76
77 /// The diameter of the
78 /// #link("https://en.wikipedia.org/wiki/Circumcircle")[circumcircle] of
79 /// the regular polygon.
80 #[named]
81 #[default(Em::one().into())]
82 size: Length,
83
84 /// The number of vertices in the polygon.
85 #[named]
86 #[default(3)]
87 vertices: u64,
88 ) -> Content {
89 let radius = size / 2.0;
90 let angle = |i: f64| {
91 let offset = Angle::rad(PI * (1.0 / 2.0 - 1.0 / vertices as f64));
92 let rotation = Angle::from_ratio(Ratio::new(i / vertices as f64));
93 offset + rotation
94 };
95 let (horizontal_offset, vertical_offset) = (0..=vertices)
96 .map(|v| {
97 (
98 (radius * angle(v as f64).cos()) + radius,
99 (radius * angle(v as f64).sin()) + radius,
100 )
101 })
102 .fold((radius, radius), |(min_x, min_y), (v_x, v_y)| {
103 (
104 if min_x < v_x { min_x } else { v_x },
105 if min_y < v_y { min_y } else { v_y },
106 )
107 });
108 let vertices = (0..=vertices)
109 .map(|v| {
110 let x = (radius * angle(v as f64).cos()) + radius - horizontal_offset;
111 let y = (radius * angle(v as f64).sin()) + radius - vertical_offset;
112 Axes::new(x, y).map(Rel::from)
113 })
114 .collect();
115
116 let mut elem = PolygonElem::new(vertices);
117 if let Some(fill) = fill {
118 elem.fill.set(fill);
119 }
120 if let Some(stroke) = stroke {
121 elem.stroke.set(stroke);
122 }
123 elem.pack().spanned(span)
124 }
125}