summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/visualize/polygon.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/visualize/polygon.rs')
-rw-r--r--crates/typst-library/src/visualize/polygon.rs135
1 files changed, 135 insertions, 0 deletions
diff --git a/crates/typst-library/src/visualize/polygon.rs b/crates/typst-library/src/visualize/polygon.rs
new file mode 100644
index 00000000..33e4fd32
--- /dev/null
+++ b/crates/typst-library/src/visualize/polygon.rs
@@ -0,0 +1,135 @@
+use std::f64::consts::PI;
+
+use typst_syntax::Span;
+
+use crate::diag::SourceResult;
+use crate::engine::Engine;
+use crate::foundations::{
+ elem, func, scope, Content, NativeElement, Packed, Show, Smart, StyleChain,
+};
+use crate::layout::{Axes, BlockElem, Em, Length, Rel};
+use crate::visualize::{FillRule, Paint, Stroke};
+
+/// A closed polygon.
+///
+/// The polygon is defined by its corner points and is closed automatically.
+///
+/// # Example
+/// ```example
+/// #polygon(
+/// fill: blue.lighten(80%),
+/// stroke: blue,
+/// (20%, 0pt),
+/// (60%, 0pt),
+/// (80%, 2cm),
+/// (0%, 2cm),
+/// )
+/// ```
+#[elem(scope, Show)]
+pub struct PolygonElem {
+ /// How to fill the polygon.
+ ///
+ /// When setting a fill, the default stroke disappears. To create a
+ /// rectangle with both fill and stroke, you have to configure both.
+ pub fill: Option<Paint>,
+
+ /// The drawing rule used to fill the polygon.
+ ///
+ /// See the [path documentation]($path.fill-rule) for an example.
+ #[default]
+ pub fill_rule: FillRule,
+
+ /// How to [stroke] the polygon. This can be:
+ ///
+ /// Can be set to `{none}` to disable the stroke or to `{auto}` for a
+ /// stroke of `{1pt}` black if and if only if no fill is given.
+ #[resolve]
+ #[fold]
+ pub stroke: Smart<Option<Stroke>>,
+
+ /// The vertices of the polygon. Each point is specified as an array of two
+ /// [relative lengths]($relative).
+ #[variadic]
+ pub vertices: Vec<Axes<Rel<Length>>>,
+}
+
+#[scope]
+impl PolygonElem {
+ /// A regular polygon, defined by its size and number of vertices.
+ ///
+ /// ```example
+ /// #polygon.regular(
+ /// fill: blue.lighten(80%),
+ /// stroke: blue,
+ /// size: 30pt,
+ /// vertices: 3,
+ /// )
+ /// ```
+ #[func(title = "Regular Polygon")]
+ pub fn regular(
+ /// The call span of this function.
+ span: Span,
+ /// How to fill the polygon. See the general
+ /// [polygon's documentation]($polygon.fill) for more details.
+ #[named]
+ fill: Option<Option<Paint>>,
+
+ /// How to stroke the polygon. See the general
+ /// [polygon's documentation]($polygon.stroke) for more details.
+ #[named]
+ stroke: Option<Smart<Option<Stroke>>>,
+
+ /// The diameter of the [circumcircle](https://en.wikipedia.org/wiki/Circumcircle)
+ /// of the regular polygon.
+ #[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().spanned(span)
+ }
+}
+
+impl Show for Packed<PolygonElem> {
+ fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
+ Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_polygon)
+ .pack()
+ .spanned(self.span()))
+ }
+}