Skip to main content

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}