diff --git a/crates/typst-library/src/visualize/polygon.rs b/crates/typst-library/src/visualize/polygon.rs index d2d3132a5..b244b2e9e 100644 --- a/crates/typst-library/src/visualize/polygon.rs +++ b/crates/typst-library/src/visualize/polygon.rs @@ -1,3 +1,5 @@ +use std::f64::consts::PI; + use crate::prelude::*; /// A closed polygon. @@ -19,6 +21,10 @@ use crate::prelude::*; /// Display: Polygon /// Category: visualize #[element(Layout)] +#[scope( + scope.define("regular", polygon_regular_func()); + scope +)] pub struct PolygonElem { /// How to fill the polygon. See the /// [rectangle's documentation]($func/rect.fill) for more details. @@ -91,3 +97,71 @@ impl Layout for PolygonElem { Ok(Fragment::frame(frame)) } } + +/// A regular polygon, defined by its size and number of vertices. +/// +/// ## Example { #example } +/// ```example +/// #polygon.regular( +/// fill: blue.lighten(80%), +/// stroke: blue, +/// size: 30pt, +/// vertices: 3, +/// ) +/// ``` +/// +/// Display: Regular Polygon +/// Category: visualize +#[func] +pub fn polygon_regular( + /// How to fill the polygon. See the general + /// [polygon's documentation]($func/polygon.fill) for more details. + #[named] + fill: Option>, + + /// How to stroke the polygon. See the general + /// [polygon's documentation]($func/polygon.stroke) for more details. + #[named] + stroke: Option>>, + + /// The diameter of the circumcircle of the regular polygon (https://en.wikipedia.org/wiki/Circumcircle). + #[named] + #[default(Em::one().into())] + size: Length, + + /// The number of vertices in the polygon. + #[named] + #[default(3)] + vertices: u64, +) -> Content { + let radius = size / 2.0; + let angle = |i: f64| { + 2.0 * PI * i / (vertices as f64) + PI * (1.0 / 2.0 - 1.0 / vertices as f64) + }; + let (horizontal_offset, vertical_offset) = (0..=vertices) + .map(|v| { + ( + (radius * angle(v as f64).cos()) + radius, + (radius * angle(v as f64).sin()) + radius, + ) + }) + .fold((radius, radius), |(min_x, min_y), (v_x, v_y)| { + (if min_x < v_x { min_x } else { v_x }, if min_y < v_y { min_y } else { v_y }) + }); + let vertices = (0..=vertices) + .map(|v| { + let x = (radius * angle(v as f64).cos()) + radius - horizontal_offset; + let y = (radius * angle(v as f64).sin()) + radius - vertical_offset; + Axes::new(x, y).map(Rel::from) + }) + .collect(); + + let mut elem = PolygonElem::new(vertices); + if let Some(fill) = fill { + elem.push_fill(fill); + } + if let Some(stroke) = stroke { + elem.push_stroke(stroke); + } + elem.pack() +} diff --git a/tests/ref/visualize/polygon.png b/tests/ref/visualize/polygon.png index 2ffd7f8a1..234aeb148 100644 Binary files a/tests/ref/visualize/polygon.png and b/tests/ref/visualize/polygon.png differ diff --git a/tests/typ/visualize/polygon.typ b/tests/typ/visualize/polygon.typ index 9f40d7fd2..cad624978 100644 --- a/tests/typ/visualize/polygon.typ +++ b/tests/typ/visualize/polygon.typ @@ -8,6 +8,7 @@ #polygon() #polygon((0em, 0pt)) #polygon((0pt, 0pt), (10pt, 0pt)) +#polygon.regular(size: 0pt, vertices: 9) #polygon((5pt, 0pt), (0pt, 10pt), (10pt, 10pt)) #polygon( @@ -27,6 +28,9 @@ // Self-intersections #polygon((0pt, 10pt), (30pt, 20pt), (0pt, 30pt), (20pt, 0pt), (20pt, 35pt)) +// Regular polygon; should have equal side lengths +#for k in range(3, 9) {polygon.regular(size: 30pt, vertices: k,)} + --- // Error: 10-17 point array must contain exactly two entries #polygon((50pt,))