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}