From 5c1db0c4b8f6422d5dfaaf27b5a0342a78b4a2ce Mon Sep 17 00:00:00 2001 From: Reid Johnson <92353079+arj0019@users.noreply.github.com> Date: Thu, 31 Aug 2023 02:51:00 -0500 Subject: [PATCH] Add regular polygons to visualize (#1215) --- crates/typst-library/src/visualize/polygon.rs | 74 ++++++++++++++++++ tests/ref/visualize/polygon.png | Bin 1957 -> 3865 bytes tests/typ/visualize/polygon.typ | 4 + 3 files changed, 78 insertions(+) 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 2ffd7f8a1ecdbecae011f4e10f37af83f14db5b4..234aeb14880432521d8d3a863fe5ea765a331793 100644 GIT binary patch literal 3865 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 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,))