typst_library/visualize/path.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
use self::PathVertex::{AllControlPoints, MirroredControlPoint, Vertex};
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
array, cast, elem, Array, Content, NativeElement, Packed, Reflect, Show, Smart,
StyleChain,
};
use crate::layout::{Axes, BlockElem, Length, Rel};
use crate::visualize::{FillRule, Paint, Stroke};
/// A path through a list of points, connected by Bezier curves.
///
/// # Example
/// ```example
/// #path(
/// fill: blue.lighten(80%),
/// stroke: blue,
/// closed: true,
/// (0pt, 50pt),
/// (100%, 50pt),
/// ((50%, 0pt), (40pt, 0pt)),
/// )
/// ```
#[elem(Show)]
pub struct PathElem {
/// How to fill the path.
///
/// 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 path.
///
/// ```example
/// // We use `.with` to get a new
/// // function that has the common
/// // arguments pre-applied.
/// #let star = path.with(
/// fill: red,
/// closed: true,
/// (25pt, 0pt),
/// (10pt, 50pt),
/// (50pt, 20pt),
/// (0pt, 20pt),
/// (40pt, 50pt),
/// )
///
/// #star(fill-rule: "non-zero")
/// #star(fill-rule: "even-odd")
/// ```
#[default]
pub fill_rule: FillRule,
/// How to [stroke] the path. 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.
#[resolve]
#[fold]
pub stroke: Smart<Option<Stroke>>,
/// 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.
///
/// Each vertex can be defined in 3 ways:
///
/// - A regular point, as given to the [`line`] or [`polygon`] function.
/// - An array of two points, the first being the vertex and the second
/// being the control point. The control point is expressed relative to
/// the vertex and is mirrored to get the second control point. The given
/// control point is the one that affects the curve coming _into_ this
/// vertex (even for the first point). The mirrored control point affects
/// 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]
pub vertices: Vec<PathVertex>,
}
impl Show for Packed<PathElem> {
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_path)
.pack()
.spanned(self.span()))
}
}
/// A component used for path creation.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PathVertex {
Vertex(Axes<Rel<Length>>),
MirroredControlPoint(Axes<Rel<Length>>, Axes<Rel<Length>>),
AllControlPoints(Axes<Rel<Length>>, Axes<Rel<Length>>, Axes<Rel<Length>>),
}
impl PathVertex {
pub fn vertex(&self) -> Axes<Rel<Length>> {
match self {
Vertex(x) => *x,
MirroredControlPoint(x, _) => *x,
AllControlPoints(x, _, _) => *x,
}
}
pub fn control_point_from(&self) -> Axes<Rel<Length>> {
match self {
Vertex(_) => Axes::new(Rel::zero(), Rel::zero()),
MirroredControlPoint(_, a) => a.map(|x| -x),
AllControlPoints(_, _, b) => *b,
}
}
pub fn control_point_to(&self) -> Axes<Rel<Length>> {
match self {
Vertex(_) => Axes::new(Rel::zero(), Rel::zero()),
MirroredControlPoint(_, a) => *a,
AllControlPoints(_, a, _) => *a,
}
}
}
cast! {
PathVertex,
self => match self {
Vertex(x) => x.into_value(),
MirroredControlPoint(x, c) => array![x, c].into_value(),
AllControlPoints(x, c1, c2) => array![x, c1, c2].into_value(),
},
array: Array => {
let mut iter = array.into_iter();
match (iter.next(), iter.next(), iter.next(), iter.next()) {
(Some(a), None, None, None) => {
Vertex(a.cast()?)
},
(Some(a), Some(b), None, None) => {
if Axes::<Rel<Length>>::castable(&a) {
MirroredControlPoint(a.cast()?, b.cast()?)
} else {
Vertex(Axes::new(a.cast()?, b.cast()?))
}
},
(Some(a), Some(b), Some(c), None) => {
AllControlPoints(a.cast()?, b.cast()?, c.cast()?)
},
_ => bail!("path vertex must have 1, 2, or 3 points"),
}
},
}