use std::f64::consts::PI;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
elem, func, scope, Content, NativeElement, Packed, Resolve, Show, Smart, StyleChain,
};
use crate::introspection::Locator;
use crate::layout::{Axes, BlockElem, Em, Frame, FrameItem, Length, Point, Region, Rel};
use crate::syntax::Span;
use crate::utils::Numeric;
use crate::visualize::{FillRule, FixedStroke, Geometry, Paint, Path, Shape, Stroke};
#[elem(scope, Show)]
pub struct PolygonElem {
pub fill: Option<Paint>,
#[default]
pub fill_rule: FillRule,
#[resolve]
#[fold]
pub stroke: Smart<Option<Stroke>>,
#[variadic]
pub vertices: Vec<Axes<Rel<Length>>>,
}
#[scope]
impl PolygonElem {
#[func(title = "Regular Polygon")]
pub fn regular(
span: Span,
#[named]
fill: Option<Option<Paint>>,
#[named]
stroke: Option<Smart<Option<Stroke>>>,
#[named]
#[default(Em::one().into())]
size: Length,
#[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, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
Ok(BlockElem::single_layouter(self.clone(), layout_polygon)
.pack()
.spanned(self.span()))
}
}
#[typst_macros::time(span = elem.span())]
fn layout_polygon(
elem: &Packed<PolygonElem>,
_: &mut Engine,
_: Locator,
styles: StyleChain,
region: Region,
) -> SourceResult<Frame> {
let points: Vec<Point> = elem
.vertices()
.iter()
.map(|c| c.resolve(styles).zip_map(region.size, Rel::relative_to).to_point())
.collect();
let size = points.iter().fold(Point::zero(), |max, c| c.max(max)).to_size();
if !size.is_finite() {
bail!(elem.span(), "cannot create polygon with infinite size");
}
let mut frame = Frame::hard(size);
if points.is_empty() {
return Ok(frame);
}
let fill = elem.fill(styles);
let fill_rule = elem.fill_rule(styles);
let stroke = match elem.stroke(styles) {
Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
Smart::Auto => None,
Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
};
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,
fill_rule,
};
frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
Ok(frame)
}