diff --git a/library/src/lib.rs b/library/src/lib.rs index 1a9987006..178db8a21 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -83,6 +83,7 @@ fn global(math: Module, calc: Module) -> Module { global.define("square", visualize::SquareElem::func()); global.define("ellipse", visualize::EllipseElem::func()); global.define("circle", visualize::CircleElem::func()); + global.define("polygon", visualize::PolygonElem::func()); // Meta. global.define("document", meta::DocumentElem::func()); diff --git a/library/src/visualize/mod.rs b/library/src/visualize/mod.rs index 1c87eeb32..198c707d5 100644 --- a/library/src/visualize/mod.rs +++ b/library/src/visualize/mod.rs @@ -3,7 +3,9 @@ mod image; mod line; mod shape; +mod polygon; pub use self::image::*; pub use self::line::*; pub use self::shape::*; +pub use self::polygon::*; \ No newline at end of file diff --git a/library/src/visualize/polygon.rs b/library/src/visualize/polygon.rs new file mode 100644 index 000000000..40058834a --- /dev/null +++ b/library/src/visualize/polygon.rs @@ -0,0 +1,71 @@ +use crate::prelude::*; + +/// A closed-path polygon. +/// +/// ## Example +/// ```example +/// #polygon(fill: blue, (0pt, 0pt), (10pt, 0pt), (10pt, 10pt)) +/// ``` +/// +/// 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. + pub fill: Option, + + /// How to stroke the polygon. See the [lines's + /// documentation]($func/line.stroke) for more details. + #[resolve] + #[fold] + pub stroke: Option, + + /// The vertices of the polygon. The polygon automatically closes itself. + #[variadic] + pub vertices: Vec>>, +} + +impl Layout for PolygonElem { + fn layout( + &self, + _: &mut Vt, + styles: StyleChain, + regions: Regions, + ) -> SourceResult { + let points: Vec = 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 target = regions.expand.select(regions.size, size); + let mut frame = Frame::new(target); + + // only create a path if there is more than zero points. + if points.len() > 0 { + let stroke = self.stroke(styles).map(|e| e.unwrap_or_default()); + let fill = self.fill(styles); + + // 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)) + } +} diff --git a/tests/ref/visualize/polygon.png b/tests/ref/visualize/polygon.png new file mode 100644 index 000000000..d2a86a53b Binary files /dev/null and b/tests/ref/visualize/polygon.png differ diff --git a/tests/typ/visualize/polygon.typ b/tests/typ/visualize/polygon.typ new file mode 100644 index 000000000..defd89be5 --- /dev/null +++ b/tests/typ/visualize/polygon.typ @@ -0,0 +1,31 @@ +// Test polygons. + +--- +#set page(height: 220pt, width: 50pt) +#box({ + set polygon(stroke: 0.75pt, fill: blue) + polygon((0em, 0pt)) + // this should not give an error + polygon() + polygon((0pt, 0pt), (10pt, 0pt)) + polygon((5pt, 0pt), (0pt, 10pt), (10pt, 10pt)) + polygon( + (0pt, 0pt), (5pt, 5pt), (10pt, 0pt), + (15pt, 5pt), + (5pt, 10pt) + ) + polygon(stroke: none, (5pt, 0pt), (0pt, 10pt), (10pt, 10pt)) + polygon(stroke: 3pt, fill: none, (5pt, 0pt), (0pt, 10pt), (10pt, 10pt)) + // relative size + polygon((0pt, 0pt), (100%, 5pt), (50%, 10pt)) + // antiparallelogram + polygon((0pt, 5pt), (5pt, 0pt), (0pt, 10pt), (5pt, 15pt)) + // self-intersections + polygon((0pt, 10pt), (30pt, 20pt), (0pt, 30pt), (20pt, 0pt), (20pt, 35pt)) +}) + +--- +// Test errors. + +// Error: 10-17 point array must contain exactly two entries +#polygon((50pt,)) \ No newline at end of file