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}