diff options
| author | Emmanuel Lesueur <48604057+Emm54321@users.noreply.github.com> | 2024-12-18 16:58:57 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-12-18 15:58:57 +0000 |
| commit | 257764181e52332a00079b9e3af03823fde1a15d (patch) | |
| tree | 203378b303f1969e684b26dbefb77f7a5979ec9f /crates/typst-library/src/visualize | |
| parent | 24c08a7ec0aaf87d6dc2e1a2c47b7beb6d5ad2a4 (diff) | |
New `curve` element that supersedes `path` (#5323)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
Diffstat (limited to 'crates/typst-library/src/visualize')
| -rw-r--r-- | crates/typst-library/src/visualize/curve.rs | 532 | ||||
| -rw-r--r-- | crates/typst-library/src/visualize/image/mod.rs | 6 | ||||
| -rw-r--r-- | crates/typst-library/src/visualize/line.rs | 2 | ||||
| -rw-r--r-- | crates/typst-library/src/visualize/mod.rs | 3 | ||||
| -rw-r--r-- | crates/typst-library/src/visualize/path.rs | 146 | ||||
| -rw-r--r-- | crates/typst-library/src/visualize/polygon.rs | 2 | ||||
| -rw-r--r-- | crates/typst-library/src/visualize/shape.rs | 12 | ||||
| -rw-r--r-- | crates/typst-library/src/visualize/stroke.rs | 20 |
8 files changed, 563 insertions, 160 deletions
diff --git a/crates/typst-library/src/visualize/curve.rs b/crates/typst-library/src/visualize/curve.rs new file mode 100644 index 00000000..607d92ab --- /dev/null +++ b/crates/typst-library/src/visualize/curve.rs @@ -0,0 +1,532 @@ +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}; + +/// A curve consisting of movements, lines, and Beziér segments. +/// +/// At any point in time, there is a conceptual pen or cursor. +/// - Move elements move the cursor without drawing. +/// - Line/Quadratic/Cubic elements draw a segment from the cursor to a new +/// position, potentially with control point for a Beziér curve. +/// - Close elements draw a straight or smooth line back to the start of the +/// curve or the latest preceding move segment. +/// +/// For layout purposes, the bounding box of the curve is a tight rectangle +/// containing all segments as well as the point `{(0pt, 0pt)}`. +/// +/// Positions may be specified absolutely (i.e. relatively to `{(0pt, 0pt)}`), +/// or relative to the current pen/cursor position, that is, the position where +/// the previous segment ended. +/// +/// Beziér curve control points can be skipped by passing `{none}` or +/// automatically mirrored from the preceding segment by passing `{auto}`. +/// +/// # Example +/// ```example +/// #curve( +/// fill: blue.lighten(80%), +/// stroke: blue, +/// curve.move((0pt, 50pt)), +/// curve.line((100pt, 50pt)), +/// curve.cubic(none, (90pt, 0pt), (50pt, 0pt)), +/// curve.close(), +/// ) +/// ``` +#[elem(scope, Show)] +pub struct CurveElem { + /// How to fill the curve. + /// + /// 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 curve. + /// + /// ```example + /// // We use `.with` to get a new + /// // function that has the common + /// // arguments pre-applied. + /// #let star = curve.with( + /// fill: red, + /// curve.move((25pt, 0pt)), + /// curve.line((10pt, 50pt)), + /// curve.line((50pt, 20pt)), + /// curve.line((0pt, 20pt)), + /// curve.line((40pt, 50pt)), + /// curve.close(), + /// ) + /// + /// #star(fill-rule: "non-zero") + /// #star(fill-rule: "even-odd") + /// ``` + #[default] + pub fill_rule: FillRule, + + /// How to [stroke] the curve. 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. + /// + /// ```example + /// #let down = curve.line((40pt, 40pt), relative: true) + /// #let up = curve.line((40pt, -40pt), relative: true) + /// + /// #curve( + /// stroke: 4pt + gradient.linear(red, blue), + /// down, up, down, up, down, + /// ) + /// ``` + #[resolve] + #[fold] + pub stroke: Smart<Option<Stroke>>, + + /// The components of the curve, in the form of moves, line and Beziér + /// segment, and closes. + #[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; +} + +/// A component used for curve creation. +#[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")) + } +} + +/// Starts a new curve component. +/// +/// If no `curve.move` element is passed, the curve will start at +/// `{(0pt, 0pt)}`. +/// +/// ```example +/// #curve( +/// fill: blue.lighten(80%), +/// fill-rule: "even-odd", +/// stroke: blue, +/// curve.line((50pt, 0pt)), +/// curve.line((50pt, 50pt)), +/// curve.line((0pt, 50pt)), +/// curve.close(), +/// curve.move((10pt, 10pt)), +/// curve.line((40pt, 10pt)), +/// curve.line((40pt, 40pt)), +/// curve.line((10pt, 40pt)), +/// curve.close(), +/// ) +/// ``` +#[elem(name = "move", title = "Curve Move")] +pub struct CurveMove { + /// The starting point for the new component. + #[required] + pub start: Axes<Rel<Length>>, + + /// Whether the coordinates are relative to the previous point. + #[default(false)] + pub relative: bool, +} + +/// Adds a straight line from the current point to a following one. +/// +/// ```example +/// #curve( +/// stroke: blue, +/// curve.line((50pt, 0pt)), +/// curve.line((50pt, 50pt)), +/// curve.line((100pt, 50pt)), +/// curve.line((100pt, 0pt)), +/// curve.line((150pt, 0pt)), +/// ) +/// ``` +#[elem(name = "line", title = "Curve Line")] +pub struct CurveLine { + /// The point at which the line shall end. + #[required] + pub end: Axes<Rel<Length>>, + + /// Whether the coordinates are relative to the previous point. + /// + /// ```example + /// #curve( + /// stroke: blue, + /// curve.line((50pt, 0pt), relative: true), + /// curve.line((0pt, 50pt), relative: true), + /// curve.line((50pt, 0pt), relative: true), + /// curve.line((0pt, -50pt), relative: true), + /// curve.line((50pt, 0pt), relative: true), + /// ) + /// ``` + #[default(false)] + pub relative: bool, +} + +/// Adds a quadratic Beziér curve segment from the last point to `end`, using +/// `control` as the control point. +/// +/// ```example +/// // Function to illustrate where the control point is. +/// #let mark((x, y)) = place( +/// dx: x - 1pt, dy: y - 1pt, +/// circle(fill: aqua, radius: 2pt), +/// ) +/// +/// #mark((20pt, 20pt)) +/// +/// #curve( +/// stroke: blue, +/// curve.move((0pt, 100pt)), +/// curve.quad((20pt, 20pt), (100pt, 0pt)), +/// ) +/// ``` +#[elem(name = "quad", title = "Curve Quadratic Segment")] +pub struct CurveQuad { + /// The control point of the quadratic Beziér curve. + /// + /// - If `{auto}` and this segment follows another quadratic Beziér curve, + /// the previous control point will be mirrored. + /// - If `{none}`, the control point defaults to `end`, and the curve will + /// be a straight line. + /// + /// ```example + /// #curve( + /// stroke: 2pt, + /// curve.quad((20pt, 40pt), (40pt, 40pt), relative: true), + /// curve.quad(auto, (40pt, -40pt), relative: true), + /// ) + /// ``` + #[required] + pub control: Smart<Option<Axes<Rel<Length>>>>, + + /// The point at which the segment shall end. + #[required] + pub end: Axes<Rel<Length>>, + + /// Whether the `control` and `end` coordinates are relative to the previous + /// point. + #[default(false)] + pub relative: bool, +} + +/// Adds a cubic Beziér curve segment from the last point to `end`, using +/// `control-start` and `control-end` as the control points. +/// +/// ```example +/// // Function to illustrate where the control points are. +/// #let handle(start, end) = place( +/// line(stroke: red, start: start, end: end) +/// ) +/// +/// #handle((0pt, 80pt), (10pt, 20pt)) +/// #handle((90pt, 60pt), (100pt, 0pt)) +/// +/// #curve( +/// stroke: blue, +/// curve.move((0pt, 80pt)), +/// curve.cubic((10pt, 20pt), (90pt, 60pt), (100pt, 0pt)), +/// ) +/// ``` +#[elem(name = "cubic", title = "Curve Cubic Segment")] +pub struct CurveCubic { + /// The control point going out from the start of the curve segment. + /// + /// - If `{auto}` and this element follows another `curve.cubic` element, + /// the last control point will be mirrored. In SVG terms, this makes + /// `curve.cubic` behave like the `S` operator instead of the `C` operator. + /// + /// - If `{none}`, the curve has no first control point, or equivalently, + /// the control point defaults to the curve's starting point. + /// + /// ```example + /// #curve( + /// stroke: blue, + /// curve.move((0pt, 50pt)), + /// // - No start control point + /// // - End control point at `(20pt, 0pt)` + /// // - End point at `(50pt, 0pt)` + /// curve.cubic(none, (20pt, 0pt), (50pt, 0pt)), + /// // - No start control point + /// // - No end control point + /// // - End point at `(50pt, 0pt)` + /// curve.cubic(none, none, (100pt, 50pt)), + /// ) + /// + /// #curve( + /// stroke: blue, + /// curve.move((0pt, 50pt)), + /// curve.cubic(none, (20pt, 0pt), (50pt, 0pt)), + /// // Passing `auto` instead of `none` means the start control point + /// // mirrors the end control point of the previous curve. Mirror of + /// // `(20pt, 0pt)` w.r.t `(50pt, 0pt)` is `(80pt, 0pt)`. + /// curve.cubic(auto, none, (100pt, 50pt)), + /// ) + /// + /// #curve( + /// stroke: blue, + /// curve.move((0pt, 50pt)), + /// curve.cubic(none, (20pt, 0pt), (50pt, 0pt)), + /// // `(80pt, 0pt)` is the same as `auto` in this case. + /// curve.cubic((80pt, 0pt), none, (100pt, 50pt)), + /// ) + /// ``` + #[required] + pub control_start: Option<Smart<Axes<Rel<Length>>>>, + + /// The control point going into the end point of the curve segment. + /// + /// If set to `{none}`, the curve has no end control point, or equivalently, + /// the control point defaults to the curve's end point. + #[required] + pub control_end: Option<Axes<Rel<Length>>>, + + /// The point at which the curve segment shall end. + #[required] + pub end: Axes<Rel<Length>>, + + /// Whether the `control-start`, `control-end`, and `end` coordinates are + /// relative to the previous point. + #[default(false)] + pub relative: bool, +} + +/// Closes the curve by adding a segment from the last point to the start of the +/// curve (or the last preceding `curve.move` point). +/// +/// ```example +/// // We define a function to show the same shape with +/// // both closing modes. +/// #let shape(mode: "smooth") = curve( +/// fill: blue.lighten(80%), +/// stroke: blue, +/// curve.move((0pt, 50pt)), +/// curve.line((100pt, 50pt)), +/// curve.cubic(auto, (90pt, 0pt), (50pt, 0pt)), +/// curve.close(mode: mode), +/// ) +/// +/// #shape(mode: "smooth") +/// #shape(mode: "straight") +/// ``` +#[elem(name = "close", title = "Curve Close")] +pub struct CurveClose { + /// How to close the curve. + pub mode: CloseMode, +} + +/// How to close a curve. +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Cast)] +pub enum CloseMode { + /// Closes the curve with a smooth segment that takes into account the + /// control point opposite the start point. + #[default] + Smooth, + /// Closes the curve with a straight line. + Straight, +} + +/// A curve consisting of movements, lines, and Beziér segments. +#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] +pub struct Curve(pub Vec<CurveItem>); + +/// An item in a curve. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum CurveItem { + Move(Point), + Line(Point), + Cubic(Point, Point, Point), + Close, +} + +impl Curve { + /// Creates an empty curve. + pub const fn new() -> Self { + Self(vec![]) + } + + /// Creates a curve that describes a rectangle. + 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 + } + + /// Creates a curve that describes an axis-aligned ellipse. + pub fn ellipse(size: Size) -> Self { + // https://stackoverflow.com/a/2007782 + 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 + } + + /// Push a [`Move`](CurveItem::Move) item. + pub fn move_(&mut self, p: Point) { + self.0.push(CurveItem::Move(p)); + } + + /// Push a [`Line`](CurveItem::Line) item. + pub fn line(&mut self, p: Point) { + self.0.push(CurveItem::Line(p)); + } + + /// Push a [`Cubic`](CurveItem::Cubic) item. + pub fn cubic(&mut self, p1: Point, p2: Point, p3: Point) { + self.0.push(CurveItem::Cubic(p1, p2, p3)); + } + + /// Push a [`Close`](CurveItem::Close) item. + pub fn close(&mut self) { + self.0.push(CurveItem::Close); + } + + /// Check if the curve is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Translate all points in this curve by the given offset. + 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 => (), + } + } + } + + /// Computes the size of the bounding box of this curve. + 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) + } +} diff --git a/crates/typst-library/src/visualize/image/mod.rs b/crates/typst-library/src/visualize/image/mod.rs index fddb4acb..452bb65c 100644 --- a/crates/typst-library/src/visualize/image/mod.rs +++ b/crates/typst-library/src/visualize/image/mod.rs @@ -95,9 +95,9 @@ pub struct ImageElem { #[default(ImageFit::Cover)] pub fit: ImageFit, - /// Whether text in SVG images should be converted into paths before - /// embedding. This will result in the text becoming unselectable in - /// the output. + /// Whether text in SVG images should be converted into curves before + /// embedding. This will result in the text becoming unselectable in the + /// output. #[default(false)] pub flatten_text: bool, } diff --git a/crates/typst-library/src/visualize/line.rs b/crates/typst-library/src/visualize/line.rs index d9ddab84..689321f1 100644 --- a/crates/typst-library/src/visualize/line.rs +++ b/crates/typst-library/src/visualize/line.rs @@ -25,7 +25,7 @@ pub struct LineElem { #[resolve] pub start: Axes<Rel<Length>>, - /// The offset from `start` where the line ends. + /// The point where the line ends. #[resolve] pub end: Option<Axes<Rel<Length>>>, diff --git a/crates/typst-library/src/visualize/mod.rs b/crates/typst-library/src/visualize/mod.rs index 61c56a61..43119149 100644 --- a/crates/typst-library/src/visualize/mod.rs +++ b/crates/typst-library/src/visualize/mod.rs @@ -1,6 +1,7 @@ //! Drawing and visualization. mod color; +mod curve; mod gradient; mod image; mod line; @@ -12,6 +13,7 @@ mod stroke; mod tiling; pub use self::color::*; +pub use self::curve::*; pub use self::gradient::*; pub use self::image::*; pub use self::line::*; @@ -46,6 +48,7 @@ pub(super) fn define(global: &mut Scope) { global.define_elem::<EllipseElem>(); global.define_elem::<CircleElem>(); global.define_elem::<PolygonElem>(); + global.define_elem::<CurveElem>(); global.define_elem::<PathElem>(); // Compatibility. diff --git a/crates/typst-library/src/visualize/path.rs b/crates/typst-library/src/visualize/path.rs index f592a712..6aacb319 100644 --- a/crates/typst-library/src/visualize/path.rs +++ b/crates/typst-library/src/visualize/path.rs @@ -1,6 +1,3 @@ -use kurbo::ParamCurveExtrema; -use typst_utils::Numeric; - use self::PathVertex::{AllControlPoints, MirroredControlPoint, Vertex}; use crate::diag::{bail, SourceResult}; use crate::engine::Engine; @@ -8,7 +5,7 @@ use crate::foundations::{ array, cast, elem, Array, Content, NativeElement, Packed, Reflect, Show, Smart, StyleChain, }; -use crate::layout::{Abs, Axes, BlockElem, Length, Point, Rel, Size}; +use crate::layout::{Axes, BlockElem, Length, Rel}; use crate::visualize::{FillRule, Paint, Stroke}; /// A path through a list of points, connected by Bezier curves. @@ -24,6 +21,9 @@ use crate::visualize::{FillRule, Paint, Stroke}; /// ((50%, 0pt), (40pt, 0pt)), /// ) /// ``` +/// +/// # Deprecation +/// This element is deprecated. The [`curve`] element should be used instead. #[elem(Show)] pub struct PathElem { /// How to fill the path. @@ -156,141 +156,3 @@ cast! { } }, } - -/// A bezier path. -#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] -pub struct Path(pub Vec<PathItem>); - -/// An item in a bezier path. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum PathItem { - MoveTo(Point), - LineTo(Point), - CubicTo(Point, Point, Point), - ClosePath, -} - -impl Path { - /// Create an empty path. - pub const fn new() -> Self { - Self(vec![]) - } - - /// Create a path that describes a rectangle. - pub fn rect(size: Size) -> Self { - let z = Abs::zero(); - let point = Point::new; - let mut path = Self::new(); - path.move_to(point(z, z)); - path.line_to(point(size.x, z)); - path.line_to(point(size.x, size.y)); - path.line_to(point(z, size.y)); - path.close_path(); - path - } - - /// Create a path that describes an axis-aligned ellipse. - pub fn ellipse(size: Size) -> Self { - // https://stackoverflow.com/a/2007782 - 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 path = Path::new(); - path.move_to(point(-rx, z)); - path.cubic_to(point(-rx, -my), point(-mx, -ry), point(z, -ry)); - path.cubic_to(point(mx, -ry), point(rx, -my), point(rx, z)); - path.cubic_to(point(rx, my), point(mx, ry), point(z, ry)); - path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z)); - path - } - - /// Push a [`MoveTo`](PathItem::MoveTo) item. - pub fn move_to(&mut self, p: Point) { - self.0.push(PathItem::MoveTo(p)); - } - - /// Push a [`LineTo`](PathItem::LineTo) item. - pub fn line_to(&mut self, p: Point) { - self.0.push(PathItem::LineTo(p)); - } - - /// Push a [`CubicTo`](PathItem::CubicTo) item. - pub fn cubic_to(&mut self, p1: Point, p2: Point, p3: Point) { - self.0.push(PathItem::CubicTo(p1, p2, p3)); - } - - /// Push a [`ClosePath`](PathItem::ClosePath) item. - pub fn close_path(&mut self) { - self.0.push(PathItem::ClosePath); - } - - /// Translate all points in this path by the given offset. - pub fn translate(&mut self, offset: Point) { - if offset.is_zero() { - return; - } - for item in self.0.iter_mut() { - match item { - PathItem::MoveTo(p) => *p += offset, - PathItem::LineTo(p) => *p += offset, - PathItem::CubicTo(p1, p2, p3) => { - *p1 += offset; - *p2 += offset; - *p3 += offset; - } - PathItem::ClosePath => (), - } - } - } - - /// Computes the size of bounding box of this path. - 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 { - PathItem::MoveTo(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; - } - PathItem::LineTo(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; - } - PathItem::CubicTo(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; - } - PathItem::ClosePath => (), - } - } - - Size::new(max_x - min_x, max_y - min_y) - } -} diff --git a/crates/typst-library/src/visualize/polygon.rs b/crates/typst-library/src/visualize/polygon.rs index 33e4fd32..465f2c1a 100644 --- a/crates/typst-library/src/visualize/polygon.rs +++ b/crates/typst-library/src/visualize/polygon.rs @@ -35,7 +35,7 @@ pub struct PolygonElem { /// The drawing rule used to fill the polygon. /// - /// See the [path documentation]($path.fill-rule) for an example. + /// See the [curve documentation]($curve.fill-rule) for an example. #[default] pub fill_rule: FillRule, diff --git a/crates/typst-library/src/visualize/shape.rs b/crates/typst-library/src/visualize/shape.rs index 01e316a8..3c62b210 100644 --- a/crates/typst-library/src/visualize/shape.rs +++ b/crates/typst-library/src/visualize/shape.rs @@ -4,7 +4,7 @@ use crate::foundations::{ elem, Cast, Content, NativeElement, Packed, Show, Smart, StyleChain, }; use crate::layout::{Abs, BlockElem, Corners, Length, Point, Rel, Sides, Size, Sizing}; -use crate::visualize::{FixedStroke, Paint, Path, Stroke}; +use crate::visualize::{Curve, FixedStroke, Paint, Stroke}; /// A rectangle with optional content. /// @@ -395,7 +395,7 @@ pub struct Shape { pub stroke: Option<FixedStroke>, } -/// A path filling rule. +/// A fill rule for curve drawing. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum FillRule { /// Specifies that "inside" is computed by a non-zero sum of signed edge crossings. @@ -412,8 +412,8 @@ pub enum Geometry { Line(Point), /// A rectangle with its origin in the topleft corner. Rect(Size), - /// A bezier path. - Path(Path), + /// A curve consisting of movements, lines, and Bezier segments. + Curve(Curve), } impl Geometry { @@ -441,8 +441,8 @@ impl Geometry { pub fn bbox_size(&self) -> Size { match self { Self::Line(line) => Size::new(line.x, line.y), - Self::Rect(s) => *s, - Self::Path(p) => p.bbox_size(), + Self::Rect(rect) => *rect, + Self::Curve(curve) => curve.bbox_size(), } } } diff --git a/crates/typst-library/src/visualize/stroke.rs b/crates/typst-library/src/visualize/stroke.rs index 2ab493a5..97a1535d 100644 --- a/crates/typst-library/src/visualize/stroke.rs +++ b/crates/typst-library/src/visualize/stroke.rs @@ -170,14 +170,20 @@ impl Stroke { /// If set to `{auto}`, the value is inherited, defaulting to `{4.0}`. /// /// ```example - /// #let points = ((15pt, 0pt), (0pt, 30pt), (30pt, 30pt), (10pt, 20pt)) - /// #set path(stroke: 6pt + blue) + /// #let items = ( + /// curve.move((15pt, 0pt)), + /// curve.line((0pt, 30pt)), + /// curve.line((30pt, 30pt)), + /// curve.line((10pt, 20pt)), + /// ) + /// + /// #set curve(stroke: 6pt + blue) /// #stack( - /// dir: ltr, - /// spacing: 1cm, - /// path(stroke: (miter-limit: 1), ..points), - /// path(stroke: (miter-limit: 4), ..points), - /// path(stroke: (miter-limit: 5), ..points), + /// dir: ltr, + /// spacing: 1cm, + /// curve(stroke: (miter-limit: 1), ..items), + /// curve(stroke: (miter-limit: 4), ..items), + /// curve(stroke: (miter-limit: 5), ..items), /// ) /// ``` #[external] |
