use kurbo::ParamCurveExtrema;
use typst_macros::{scope, Cast};
use typst_utils::Numeric;
use crate::diag::{bail, HintedStrResult, HintedString, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
cast, elem, Content, NativeElement, Packed, Show, Smart, StyleChain,
};
use crate::layout::{Abs, Axes, BlockElem, Length, Point, Rel, Size};
use crate::visualize::{FillRule, Paint, Stroke};
#[elem(scope, Show)]
pub struct CurveElem {
pub fill: Option<Paint>,
#[default]
pub fill_rule: FillRule,
#[resolve]
#[fold]
pub stroke: Smart<Option<Stroke>>,
#[variadic]
pub components: Vec<CurveComponent>,
}
impl Show for Packed<CurveElem> {
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_curve)
.pack()
.spanned(self.span()))
}
}
#[scope]
impl CurveElem {
#[elem]
type CurveMove;
#[elem]
type CurveLine;
#[elem]
type CurveQuad;
#[elem]
type CurveCubic;
#[elem]
type CurveClose;
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum CurveComponent {
Move(Packed<CurveMove>),
Line(Packed<CurveLine>),
Quad(Packed<CurveQuad>),
Cubic(Packed<CurveCubic>),
Close(Packed<CurveClose>),
}
cast! {
CurveComponent,
self => match self {
Self::Move(element) => element.into_value(),
Self::Line(element) => element.into_value(),
Self::Quad(element) => element.into_value(),
Self::Cubic(element) => element.into_value(),
Self::Close(element) => element.into_value(),
},
v: Content => {
v.try_into()?
}
}
impl TryFrom<Content> for CurveComponent {
type Error = HintedString;
fn try_from(value: Content) -> HintedStrResult<Self> {
value
.into_packed::<CurveMove>()
.map(Self::Move)
.or_else(|value| value.into_packed::<CurveLine>().map(Self::Line))
.or_else(|value| value.into_packed::<CurveQuad>().map(Self::Quad))
.or_else(|value| value.into_packed::<CurveCubic>().map(Self::Cubic))
.or_else(|value| value.into_packed::<CurveClose>().map(Self::Close))
.or_else(|_| bail!("expecting a curve element"))
}
}
#[elem(name = "move", title = "Curve Move")]
pub struct CurveMove {
#[required]
pub start: Axes<Rel<Length>>,
#[default(false)]
pub relative: bool,
}
#[elem(name = "line", title = "Curve Line")]
pub struct CurveLine {
#[required]
pub end: Axes<Rel<Length>>,
#[default(false)]
pub relative: bool,
}
#[elem(name = "quad", title = "Curve Quadratic Segment")]
pub struct CurveQuad {
#[required]
pub control: Smart<Option<Axes<Rel<Length>>>>,
#[required]
pub end: Axes<Rel<Length>>,
#[default(false)]
pub relative: bool,
}
#[elem(name = "cubic", title = "Curve Cubic Segment")]
pub struct CurveCubic {
#[required]
pub control_start: Option<Smart<Axes<Rel<Length>>>>,
#[required]
pub control_end: Option<Axes<Rel<Length>>>,
#[required]
pub end: Axes<Rel<Length>>,
#[default(false)]
pub relative: bool,
}
#[elem(name = "close", title = "Curve Close")]
pub struct CurveClose {
pub mode: CloseMode,
}
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Cast)]
pub enum CloseMode {
#[default]
Smooth,
Straight,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct Curve(pub Vec<CurveItem>);
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum CurveItem {
Move(Point),
Line(Point),
Cubic(Point, Point, Point),
Close,
}
impl Curve {
pub const fn new() -> Self {
Self(vec![])
}
pub fn rect(size: Size) -> Self {
let z = Abs::zero();
let point = Point::new;
let mut curve = Self::new();
curve.move_(point(z, z));
curve.line(point(size.x, z));
curve.line(point(size.x, size.y));
curve.line(point(z, size.y));
curve.close();
curve
}
pub fn ellipse(size: Size) -> Self {
let z = Abs::zero();
let rx = size.x / 2.0;
let ry = size.y / 2.0;
let m = 0.551784;
let mx = m * rx;
let my = m * ry;
let point = |x, y| Point::new(x + rx, y + ry);
let mut curve = Curve::new();
curve.move_(point(-rx, z));
curve.cubic(point(-rx, -my), point(-mx, -ry), point(z, -ry));
curve.cubic(point(mx, -ry), point(rx, -my), point(rx, z));
curve.cubic(point(rx, my), point(mx, ry), point(z, ry));
curve.cubic(point(-mx, ry), point(-rx, my), point(-rx, z));
curve
}
pub fn move_(&mut self, p: Point) {
self.0.push(CurveItem::Move(p));
}
pub fn line(&mut self, p: Point) {
self.0.push(CurveItem::Line(p));
}
pub fn cubic(&mut self, p1: Point, p2: Point, p3: Point) {
self.0.push(CurveItem::Cubic(p1, p2, p3));
}
pub fn close(&mut self) {
self.0.push(CurveItem::Close);
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn translate(&mut self, offset: Point) {
if offset.is_zero() {
return;
}
for item in self.0.iter_mut() {
match item {
CurveItem::Move(p) => *p += offset,
CurveItem::Line(p) => *p += offset,
CurveItem::Cubic(p1, p2, p3) => {
*p1 += offset;
*p2 += offset;
*p3 += offset;
}
CurveItem::Close => (),
}
}
}
pub fn bbox_size(&self) -> Size {
let mut min_x = Abs::inf();
let mut min_y = Abs::inf();
let mut max_x = -Abs::inf();
let mut max_y = -Abs::inf();
let mut cursor = Point::zero();
for item in self.0.iter() {
match item {
CurveItem::Move(to) => {
min_x = min_x.min(cursor.x);
min_y = min_y.min(cursor.y);
max_x = max_x.max(cursor.x);
max_y = max_y.max(cursor.y);
cursor = *to;
}
CurveItem::Line(to) => {
min_x = min_x.min(cursor.x);
min_y = min_y.min(cursor.y);
max_x = max_x.max(cursor.x);
max_y = max_y.max(cursor.y);
cursor = *to;
}
CurveItem::Cubic(c0, c1, end) => {
let cubic = kurbo::CubicBez::new(
kurbo::Point::new(cursor.x.to_pt(), cursor.y.to_pt()),
kurbo::Point::new(c0.x.to_pt(), c0.y.to_pt()),
kurbo::Point::new(c1.x.to_pt(), c1.y.to_pt()),
kurbo::Point::new(end.x.to_pt(), end.y.to_pt()),
);
let bbox = cubic.bounding_box();
min_x = min_x.min(Abs::pt(bbox.x0)).min(Abs::pt(bbox.x1));
min_y = min_y.min(Abs::pt(bbox.y0)).min(Abs::pt(bbox.y1));
max_x = max_x.max(Abs::pt(bbox.x0)).max(Abs::pt(bbox.x1));
max_y = max_y.max(Abs::pt(bbox.y0)).max(Abs::pt(bbox.y1));
cursor = *end;
}
CurveItem::Close => (),
}
}
Size::new(max_x - min_x, max_y - min_y)
}
}