mirror of
https://github.com/typst/typst
synced 2025-05-15 17:45:27 +08:00
Fix path and polygon strokes
This commit is contained in:
parent
f9b9be16f9
commit
4f4af02ace
@ -2,51 +2,59 @@ use self::PathVertex::{AllControlPoints, MirroredControlPoint, Vertex};
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use kurbo::{CubicBez, ParamCurveExtrema};
|
use kurbo::{CubicBez, ParamCurveExtrema};
|
||||||
|
|
||||||
/// A path going through a list of points, connected through Bezier curves.
|
/// A path through a list of points, connected by Bezier curves.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set page(height: 100pt)
|
/// #path(
|
||||||
/// #path((10%, 10%), ((20%, 20%), (5%, 5%)))
|
/// fill: blue.lighten(80%),
|
||||||
/// #path((10%, 10%), (10%, 15%))
|
/// stroke: blue,
|
||||||
|
/// closed: true,
|
||||||
|
/// (0pt, 50pt),
|
||||||
|
/// (100%, 50pt),
|
||||||
|
/// ((50%, 0pt), (40pt, 0pt)),
|
||||||
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Display: Path
|
/// Display: Path
|
||||||
/// Category: visualize
|
/// Category: visualize
|
||||||
#[element(Layout)]
|
#[element(Layout)]
|
||||||
pub struct PathElem {
|
pub struct PathElem {
|
||||||
/// Whether to close this path with one last bezier curve. This last curve
|
/// How to fill the path. See the
|
||||||
/// still takes into account the control points.
|
|
||||||
/// If you want to close with a straight line, simply add one last point
|
|
||||||
/// that's the same as the start point.
|
|
||||||
#[default(false)]
|
|
||||||
pub closed: bool,
|
|
||||||
|
|
||||||
/// How to fill the polygon. See the
|
|
||||||
/// [rectangle's documentation]($func/rect.fill) for more details.
|
/// [rectangle's documentation]($func/rect.fill) for more details.
|
||||||
///
|
///
|
||||||
/// Currently all paths are filled according to the
|
/// Currently all paths are filled according to the
|
||||||
/// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule).
|
/// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule).
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// How to stroke the polygon. See the [lines's
|
/// How to stroke the path. See the
|
||||||
/// documentation]($func/line.stroke) for more details.
|
/// [polygon's documentation]($func/polygon.stroke) for more details.
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(Some(PartialStroke::default()))]
|
pub stroke: Smart<Option<PartialStroke>>,
|
||||||
pub stroke: Option<PartialStroke>,
|
|
||||||
|
/// Whether to close this path with one last bezier curve. This curve will
|
||||||
|
/// takes into account the adjacent control points. If you want to close
|
||||||
|
/// with a straight line, simply add one last point that's the same as the
|
||||||
|
/// start point.
|
||||||
|
#[default(false)]
|
||||||
|
pub closed: bool,
|
||||||
|
|
||||||
/// The vertices of the path.
|
/// The vertices of the path.
|
||||||
///
|
///
|
||||||
/// Each vertex can be defined in 3 ways:
|
/// Each vertex can be defined in 3 ways:
|
||||||
///
|
///
|
||||||
/// - A regular point, like [line]($func/line)
|
/// - A regular point, as given to the [`line`]($func/line) or
|
||||||
|
/// [`polygon`]($func/polygon) function.
|
||||||
/// - An array of two points, the first being the vertex and the second
|
/// - An array of two points, the first being the vertex and the second
|
||||||
/// being the control point.
|
/// being the control point. The control point is expressed relative to
|
||||||
/// The control point is expressed relative to the vertex and is mirrored
|
/// the vertex and is mirrored to get the second control point. The given
|
||||||
/// to get the second control point.
|
/// control point is the one that affects the curve coming _into_ this
|
||||||
/// The control point itself refers to the control point that affects the curve coming _into_ this vertex, including for the first point.
|
/// vertex (even for the first point). The mirrored control point affects
|
||||||
/// - An array of three points, the first being the vertex and the next being the control points (control point for curves coming in and out respectively)
|
/// the curve going out of this vertex.
|
||||||
|
/// - An array of three points, the first being the vertex and the next
|
||||||
|
/// being the control points (control point for curves coming in and out,
|
||||||
|
/// respectively)
|
||||||
#[variadic]
|
#[variadic]
|
||||||
pub vertices: Vec<PathVertex>,
|
pub vertices: Vec<PathVertex>,
|
||||||
}
|
}
|
||||||
@ -69,20 +77,19 @@ impl Layout for PathElem {
|
|||||||
let points: Vec<Point> = vertices.iter().map(|c| resolve(c.vertex())).collect();
|
let points: Vec<Point> = vertices.iter().map(|c| resolve(c.vertex())).collect();
|
||||||
|
|
||||||
let mut size = Size::zero();
|
let mut size = Size::zero();
|
||||||
|
if points.is_empty() {
|
||||||
|
return Ok(Fragment::frame(Frame::new(size)));
|
||||||
|
}
|
||||||
|
|
||||||
// Only create a path if there are more than zero points.
|
// Only create a path if there are more than zero points.
|
||||||
let path = if points.len() > 0 {
|
// Construct a closed path given all points.
|
||||||
// Construct a closed path given all points.
|
let mut path = Path::new();
|
||||||
let mut path = Path::new();
|
path.move_to(points[0]);
|
||||||
path.move_to(points[0]);
|
|
||||||
|
|
||||||
let mut add_cubic = |from_point: Point,
|
let mut add_cubic =
|
||||||
to_point: Point,
|
|from_point: Point, to_point: Point, from: PathVertex, to: PathVertex| {
|
||||||
from: PathVertex,
|
|
||||||
to: PathVertex| {
|
|
||||||
let from_control_point = resolve(from.control_point_from()) + from_point;
|
let from_control_point = resolve(from.control_point_from()) + from_point;
|
||||||
let to_control_point = resolve(to.control_point_to()) + to_point;
|
let to_control_point = resolve(to.control_point_to()) + to_point;
|
||||||
|
|
||||||
path.cubic_to(from_control_point, to_control_point, to_point);
|
path.cubic_to(from_control_point, to_control_point, to_point);
|
||||||
|
|
||||||
let p0 = kurbo::Point::new(from_point.x.to_raw(), from_point.y.to_raw());
|
let p0 = kurbo::Point::new(from_point.x.to_raw(), from_point.y.to_raw());
|
||||||
@ -100,38 +107,35 @@ impl Layout for PathElem {
|
|||||||
size.y.set_max(Abs::raw(extrema.y1));
|
size.y.set_max(Abs::raw(extrema.y1));
|
||||||
};
|
};
|
||||||
|
|
||||||
for (vertex_window, point_window) in
|
for (vertex_window, point_window) in vertices.windows(2).zip(points.windows(2)) {
|
||||||
vertices.windows(2).zip(points.windows(2))
|
let from = vertex_window[0];
|
||||||
{
|
let to = vertex_window[1];
|
||||||
let from = vertex_window[0];
|
let from_point = point_window[0];
|
||||||
let to = vertex_window[1];
|
let to_point = point_window[1];
|
||||||
let from_point = point_window[0];
|
|
||||||
let to_point = point_window[1];
|
|
||||||
|
|
||||||
add_cubic(from_point, to_point, from, to);
|
add_cubic(from_point, to_point, from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.closed(styles) {
|
if self.closed(styles) {
|
||||||
let from = *vertices.last().unwrap(); // We checked that we have at least one element.
|
let from = *vertices.last().unwrap(); // We checked that we have at least one element.
|
||||||
let to = vertices[0];
|
let to = vertices[0];
|
||||||
let from_point = *points.last().unwrap();
|
let from_point = *points.last().unwrap();
|
||||||
let to_point = points[0];
|
let to_point = points[0];
|
||||||
|
|
||||||
add_cubic(from_point, to_point, from, to);
|
add_cubic(from_point, to_point, from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(path)
|
// Prepare fill and stroke.
|
||||||
} else {
|
let fill = self.fill(styles);
|
||||||
None
|
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),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut frame = Frame::new(size);
|
let mut frame = Frame::new(size);
|
||||||
if let Some(path) = path {
|
let shape = Shape { geometry: Geometry::Path(path), stroke, fill };
|
||||||
let fill = self.fill(styles);
|
frame.push(Point::zero(), FrameItem::Shape(shape, self.span()));
|
||||||
let stroke = self.stroke(styles).map(PartialStroke::unwrap_or_default);
|
|
||||||
let shape = Shape { geometry: Geometry::Path(path), stroke, fill };
|
|
||||||
frame.push(Point::zero(), FrameItem::Shape(shape, self.span()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Fragment::frame(frame))
|
Ok(Fragment::frame(frame))
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,12 @@ use crate::prelude::*;
|
|||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #polygon(
|
/// #polygon(
|
||||||
/// fill: red,
|
/// fill: blue.lighten(80%),
|
||||||
/// stroke: 2pt + black,
|
/// stroke: blue,
|
||||||
/// (0pt, 0pt),
|
/// (20%, 0pt),
|
||||||
/// (50%, 0pt),
|
/// (60%, 0pt),
|
||||||
/// (50%, 4cm),
|
/// (80%, 2cm),
|
||||||
/// (20%, 4cm),
|
/// (0%, 2cm),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
@ -27,11 +27,20 @@ pub struct PolygonElem {
|
|||||||
/// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule).
|
/// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule).
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// How to stroke the polygon. See the [lines's
|
/// How to stroke the polygon. This can be:
|
||||||
/// documentation]($func/line.stroke) for more details.
|
///
|
||||||
|
/// - `{none}` to disable the stroke.
|
||||||
|
/// - `{auto}` for a stroke of `{1pt}` black if and if only if no fill is
|
||||||
|
/// given.
|
||||||
|
/// - A length specifying the stroke's thickness. The color is inherited,
|
||||||
|
/// defaulting to black.
|
||||||
|
/// - A color to use for the stroke. The thickness is inherited, defaulting
|
||||||
|
/// to `{1pt}`.
|
||||||
|
/// - A stroke combined from color and thickness using the `+` operator as
|
||||||
|
/// in `{2pt + red}`.
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
pub stroke: Option<PartialStroke>,
|
pub stroke: Smart<Option<PartialStroke>>,
|
||||||
|
|
||||||
/// The vertices of the polygon. Each point is specified as an array of two
|
/// The vertices of the polygon. Each point is specified as an array of two
|
||||||
/// [relative lengths]($type/relative-length).
|
/// [relative lengths]($type/relative-length).
|
||||||
@ -61,22 +70,29 @@ impl Layout for PolygonElem {
|
|||||||
let mut frame = Frame::new(size);
|
let mut frame = Frame::new(size);
|
||||||
|
|
||||||
// Only create a path if there are more than zero points.
|
// Only create a path if there are more than zero points.
|
||||||
if !points.is_empty() {
|
if points.is_empty() {
|
||||||
let fill = self.fill(styles);
|
return Ok(Fragment::frame(frame));
|
||||||
let stroke = self.stroke(styles).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()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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))
|
Ok(Fragment::frame(frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@ -11,13 +11,18 @@ Apart
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test box sizing.
|
// Test box sizing.
|
||||||
#box(width: 50pt, height: 50pt, fill: yellow, path(
|
#box(
|
||||||
fill: purple,
|
width: 50pt,
|
||||||
(0pt, 0pt),
|
height: 50pt,
|
||||||
(30pt, 30pt),
|
fill: yellow,
|
||||||
(0pt, 30pt),
|
path(
|
||||||
(30pt, 0pt),
|
fill: purple,
|
||||||
))
|
(0pt, 0pt),
|
||||||
|
(30pt, 30pt),
|
||||||
|
(0pt, 30pt),
|
||||||
|
(30pt, 0pt),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test fr box.
|
// Test fr box.
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
align: center + horizon,
|
align: center + horizon,
|
||||||
path(
|
path(
|
||||||
fill: red,
|
fill: red,
|
||||||
stroke: none,
|
|
||||||
closed: true,
|
closed: true,
|
||||||
((0%, 0%), (4%, -4%)),
|
((0%, 0%), (4%, -4%)),
|
||||||
((50%, 50%), (4%, -4%)),
|
((50%, 50%), (4%, -4%)),
|
||||||
@ -17,6 +16,7 @@
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
fill: purple,
|
fill: purple,
|
||||||
|
stroke: 1pt,
|
||||||
(0pt, 0pt),
|
(0pt, 0pt),
|
||||||
(30pt, 30pt),
|
(30pt, 30pt),
|
||||||
(0pt, 30pt),
|
(0pt, 30pt),
|
||||||
@ -24,6 +24,7 @@
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
fill: blue,
|
fill: blue,
|
||||||
|
stroke: 1pt,
|
||||||
closed: true,
|
closed: true,
|
||||||
((30%, 0%), (35%, 30%), (-20%, 0%)),
|
((30%, 0%), (35%, 30%), (-20%, 0%)),
|
||||||
((30%, 60%), (-20%, 0%), (0%, 0%)),
|
((30%, 60%), (-20%, 0%), (0%, 0%)),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user