typst_library/visualize/
polygon.rs

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