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.rs93
1 files changed, 93 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..d2d3132a
--- /dev/null
+++ b/crates/typst-library/src/visualize/polygon.rs
@@ -0,0 +1,93 @@
+use crate::prelude::*;
+
+/// A closed polygon.
+///
+/// The polygon is defined by its corner points and is closed automatically.
+///
+/// ## Example { #example }
+/// ```example
+/// #polygon(
+/// fill: blue.lighten(80%),
+/// stroke: blue,
+/// (20%, 0pt),
+/// (60%, 0pt),
+/// (80%, 2cm),
+/// (0%, 2cm),
+/// )
+/// ```
+///
+/// Display: Polygon
+/// Category: visualize
+#[element(Layout)]
+pub struct PolygonElem {
+ /// How to fill the polygon. See the
+ /// [rectangle's documentation]($func/rect.fill) for more details.
+ ///
+ /// Currently all polygons are filled according to the
+ /// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule).
+ pub fill: Option<Paint>,
+
+ /// How to stroke the polygon. This can be:
+ ///
+ /// See the [line's documentation]($func/line.stroke) for more details. 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<PartialStroke>>,
+
+ /// The vertices of the polygon. Each point is specified as an array of two
+ /// [relative lengths]($type/relative-length).
+ #[variadic]
+ pub vertices: Vec<Axes<Rel<Length>>>,
+}
+
+impl Layout for PolygonElem {
+ #[tracing::instrument(name = "PolygonElem::layout", skip_all)]
+ fn layout(
+ &self,
+ _: &mut Vt,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment> {
+ let points: Vec<Point> = self
+ .vertices()
+ .iter()
+ .map(|c| {
+ c.resolve(styles)
+ .zip(regions.base())
+ .map(|(l, b)| l.relative_to(b))
+ .to_point()
+ })
+ .collect();
+
+ let size = points.iter().fold(Point::zero(), |max, c| c.max(max)).to_size();
+ let mut frame = Frame::new(size);
+
+ // Only create a path if there are more than zero points.
+ if points.is_empty() {
+ return Ok(Fragment::frame(frame));
+ }
+
+ // Prepare fill and stroke.
+ let fill = self.fill(styles);
+ let stroke = match self.stroke(styles) {
+ Smart::Auto if fill.is_none() => Some(Stroke::default()),
+ Smart::Auto => None,
+ Smart::Custom(stroke) => stroke.map(PartialStroke::unwrap_or_default),
+ };
+
+ // Construct a closed path given all points.
+ let mut path = Path::new();
+ path.move_to(points[0]);
+ for &point in &points[1..] {
+ path.line_to(point);
+ }
+ path.close_path();
+
+ let shape = Shape { geometry: Geometry::Path(path), stroke, fill };
+ frame.push(Point::zero(), FrameItem::Shape(shape, self.span()));
+
+ Ok(Fragment::frame(frame))
+ }
+}