mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 11:17:01 -06:00
A geometry effect applied to one of a type object's own fills or strokes (effect.apply with item) was kept but not drawn or exported unless the type also had a geometry effect or perspective of its own. With a geometry effect, the renderer measured the type for culling without the fill's or stroke's effects, so an outline that reached past the glyphs was not drawn once the glyphs left the view. Type whose own fills or strokes have a visible geometry effect is now drawn, culled and exported through its outlines, the same way as type with an object-level geometry effect. Each effect reshapes the whole outline that its fill or stroke paints. SVG, PDF, EPS and the other vector exports write such type as paths. Type that holds inline graphics stays type, as before.
471 lines
21 KiB
Rust
471 lines
21 KiB
Rust
//! Object-level effects on objects that aren't paths: type, placed images, symbol instances and
|
|
//! live objects (blends, envelopes, meshes, repeats).
|
|
//!
|
|
//! Geometry effects reach them through outlines: [`outline_art`] turns the object into plain paths
|
|
//! (glyph outlines, the evaluated live object, the symbol's art) and [`reshape`] runs each effect
|
|
//! over all of them as one piece, with the whole art's bounds as the effect's reference box, so a
|
|
//! warp bends a line of type as a whole. An image is clipped by its reshaped frame. Raster effects
|
|
//! (shadows, glows, blur, feather) stay on the result for the renderer and exporters. Type without
|
|
//! inline graphics whose own fills or strokes have geometry effects is outlined too: each of those
|
|
//! effects reshapes the whole outline that its fill or stroke paints.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use vectorcraft_doc::{Appearance, AppearanceItem, Effect, Node, NodeKind};
|
|
use vectorcraft_geom::{Affine, FillRule, PathData, Rect, shapes};
|
|
|
|
use crate::{GeomContext, apply_one, is_geometry, merged_params};
|
|
|
|
/// Can object-level effects apply to `n` only through [`outline_art`] / an offscreen layer (it is
|
|
/// neither a path nor a container)?
|
|
pub fn needs_outline(n: &Node) -> bool {
|
|
matches!(
|
|
n.kind,
|
|
NodeKind::Text(_)
|
|
| NodeKind::Image(_)
|
|
| NodeKind::SymbolInstance { .. }
|
|
| NodeKind::Blend { .. }
|
|
| NodeKind::Envelope { .. }
|
|
| NodeKind::Mesh(_)
|
|
| NodeKind::Repeat(_)
|
|
| NodeKind::PlacedDocument(_)
|
|
)
|
|
}
|
|
|
|
/// Is `n` type without inline graphics that has a fill or stroke of its own that paints and has a
|
|
/// visible geometry effect? Such an effect reshapes the whole outline that its fill or stroke
|
|
/// paints ([`outline_text`], which leaves inline graphics out).
|
|
pub fn has_item_geometry(n: &Node) -> bool {
|
|
let NodeKind::Text(t) = &n.kind else { return false };
|
|
t.runs.iter().all(|r| r.inline.is_none()) && n.appearance.items.iter().any(|i| crate::paints(i) && crate::has_geometry(i.effects()))
|
|
}
|
|
|
|
/// The glyph outlines of type `t` in text space with its underline and strikethrough bars: one
|
|
/// path per run, and all of them as one.
|
|
pub(crate) fn glyph_outlines(t: &vectorcraft_doc::TextObject) -> (Vec<kurbo::BezPath>, kurbo::BezPath) {
|
|
let (runs, all, _) = laid_out(t);
|
|
(runs, all)
|
|
}
|
|
|
|
/// [`glyph_outlines`] and the bounds of `t`'s layout in text space.
|
|
fn laid_out(t: &vectorcraft_doc::TextObject) -> (Vec<kurbo::BezPath>, kurbo::BezPath, Rect) {
|
|
let db = vectorcraft_text::FontDb::global();
|
|
let layout = vectorcraft_text::layout(db, t);
|
|
let mut runs = vec![kurbo::BezPath::new(); t.runs.len()];
|
|
let mut all = kurbo::BezPath::new();
|
|
for g in &layout.glyphs {
|
|
if let Some(r) = runs.get_mut(g.run) {
|
|
r.extend(g.outline.iter());
|
|
}
|
|
all.extend(g.outline.iter());
|
|
}
|
|
// Underline and strikethrough bars, painted as their run's type is (#847).
|
|
for (run, bar) in vectorcraft_text::decorations(&layout, db, t) {
|
|
if let Some(r) = runs.get_mut(run) {
|
|
r.extend(bar.iter());
|
|
}
|
|
all.extend(bar.iter());
|
|
}
|
|
(runs, all, layout.bounds)
|
|
}
|
|
|
|
/// Type as glyph outlines in document space: one path per run with the run's fill and stroke,
|
|
/// and (when the object has its own fills/strokes) the whole outline with the object's items below
|
|
/// the characters under them and the others over them, as the renderer paints them. Each run's
|
|
/// path is placed by the type's transform, which also scales its stroke weights and dashes and
|
|
/// transforms its pattern tiles; its unplaced gradients are first fixed to the layout's bounds in
|
|
/// text space. The object's own unplaced gradients are fixed to the bounding box of those bounds
|
|
/// in the document. The group is marked as type's outlines
|
|
/// ([`vectorcraft_doc::NodeExtras::type_outlines`]).
|
|
pub fn outline_text(n: &Node) -> Option<Node> {
|
|
let NodeKind::Text(t) = &n.kind else { return None };
|
|
let (runs, all, bounds) = laid_out(t);
|
|
let path = |bp: &kurbo::BezPath| PathData::from_bezpath(bp).transformed(t.xf);
|
|
let scaling = vectorcraft_doc::Scaling { strokes: true, patterns: true, ..Default::default() };
|
|
let mut children: Vec<Arc<Node>> = runs
|
|
.iter()
|
|
.zip(&t.runs)
|
|
.filter(|(bp, _)| !bp.elements().is_empty())
|
|
.map(|(bp, run)| {
|
|
let mut ap = run.style.appearance();
|
|
ap.pin_gradients(bounds);
|
|
let mut piece = Node::path(n.id, PathData::from_bezpath(bp), ap);
|
|
piece.transform(t.xf, scaling);
|
|
Arc::new(piece)
|
|
})
|
|
.collect();
|
|
if !n.appearance.items.is_empty() && !all.elements().is_empty() {
|
|
let (below, above) = n.appearance.split_contents();
|
|
let tb = t.xf.transform_rect_bbox(bounds);
|
|
let glyphs = |items: &[AppearanceItem]| {
|
|
let mut ap = Appearance { items: items.to_vec(), ..Default::default() };
|
|
ap.pin_gradients(tb);
|
|
Arc::new(Node::path(n.id, path(&all), ap))
|
|
};
|
|
if !below.is_empty() {
|
|
children.insert(0, glyphs(below));
|
|
}
|
|
if !above.is_empty() {
|
|
children.push(glyphs(above));
|
|
}
|
|
}
|
|
let mut g = Node::group(n.id, children);
|
|
g.extras_mut().type_outlines = true;
|
|
Some(g)
|
|
}
|
|
|
|
/// `n` as plain art that geometry effects can reshape, without its transparency and object
|
|
/// effects: type outlined ([`outline_text`]), live objects evaluated (type inside them outlined),
|
|
/// a symbol instance as `symbol_art` (the symbol's art, stained as the caller draws it) placed by
|
|
/// the instance's transform. `None` for other kinds (images, paths, containers).
|
|
pub fn outline_art(n: &Node, symbol_art: Option<&Node>) -> Option<Node> {
|
|
let hook: &dyn Fn(&Node) -> Option<Node> = &outline_text;
|
|
let mut art = match &n.kind {
|
|
NodeKind::Text(_) => outline_text(n)?,
|
|
NodeKind::SymbolInstance { xf, .. } => {
|
|
let mut a = symbol_art?.clone();
|
|
a.transform(*xf, false);
|
|
Node::group(n.id, vec![Arc::new(a)])
|
|
}
|
|
_ if vectorcraft_doc::live::is_live(n) => vectorcraft_doc::live::expand_deep(n, Some(hook)),
|
|
_ => return None,
|
|
};
|
|
outline_nested_text(&mut art, false);
|
|
art.opacity = 1.0;
|
|
art.blend = Default::default();
|
|
art.isolate = false;
|
|
// One element: its pieces never knock each other out.
|
|
art.knockout = vectorcraft_doc::Knockout::Off;
|
|
art.extras_mut().mask = None;
|
|
art.appearance.effects.clear();
|
|
Some(art)
|
|
}
|
|
|
|
/// Replace type anywhere inside `n` by its outlines, as art that keeps the type's look
|
|
/// ([`as_art`]). Type in a clip group's clipping path (all of `n` when `clipping` is set) becomes
|
|
/// its plain outlines ([`outline_text`]) with its visibility, which is what
|
|
/// [`crate::clip_outline`] clips by.
|
|
pub(crate) fn outline_nested_text(n: &mut Node, clipping: bool) {
|
|
let clips = n.clips();
|
|
let Some(ch) = n.children_mut() else { return };
|
|
for (i, c) in ch.iter_mut().enumerate() {
|
|
let clipping = clipping || (clips && i == 0);
|
|
let art = match &c.kind {
|
|
NodeKind::Text(_) if clipping => outline_text(c).map(|o| Node { visible: c.visible, ..o }),
|
|
NodeKind::Text(_) => as_art(c, None),
|
|
_ => None,
|
|
};
|
|
if let Some(o) = art {
|
|
*c = Arc::new(o);
|
|
} else if c.children().is_some() {
|
|
outline_nested_text(Arc::make_mut(c), clipping);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `n` (see [`needs_outline`]) with its object-level geometry effects and its perspective applied:
|
|
/// its [`outline_art`] reshaped as one piece, or for an image the image clipped by its reshaped
|
|
/// frame. Type's own fills and strokes keep their effects on its whole outline. The result is a
|
|
/// group with `n`'s id, name, visibility, transparency, opacity mask and remaining (raster)
|
|
/// effects; `None` when `n` has no visible geometry effect, no perspective and is not type with
|
|
/// a geometry effect on a fill or stroke of its own ([`has_item_geometry`]), or has no art (a
|
|
/// symbol instance without `symbol_art`).
|
|
pub fn reshape(n: &Node, symbol_art: Option<&Node>) -> Option<Node> {
|
|
if (geometry_effects(n).is_empty() && n.projection().is_none() && !has_item_geometry(n)) || !needs_outline(n) {
|
|
return None;
|
|
}
|
|
as_art(n, symbol_art)
|
|
}
|
|
|
|
/// `n` (see [`needs_outline`]) as plain art for Expand Appearance: [`reshape`]d when it has
|
|
/// geometry effects, else type with fills or strokes of its own outlined, in the same form. `None`
|
|
/// when it has neither.
|
|
pub fn expand_outlined(n: &Node, symbol_art: Option<&Node>) -> Option<Node> {
|
|
let own_paint = matches!(n.kind, NodeKind::Text(_)) && !n.appearance.items.is_empty();
|
|
if !needs_outline(n) || (geometry_effects(n).is_empty() && !own_paint) {
|
|
return None;
|
|
}
|
|
as_art(n, symbol_art)
|
|
}
|
|
|
|
/// [`reshape`] without its checks (no geometry effects: just the art).
|
|
pub(crate) fn as_art(n: &Node, symbol_art: Option<&Node>) -> Option<Node> {
|
|
let fx = geometry_effects(n);
|
|
let mut art = match &n.kind {
|
|
NodeKind::Image(im) => {
|
|
let frame = shapes::rectangle(Rect::new(0.0, 0.0, im.width as f64, im.height as f64)).transformed(im.xf);
|
|
let mut clip = Node::path(n.id, frame, Appearance::default());
|
|
if let NodeKind::Path { clipping, .. } = &mut clip.kind {
|
|
*clipping = true;
|
|
}
|
|
let image = Node::new(n.id, n.kind.clone());
|
|
Node::new(n.id, NodeKind::Group { children: vec![Arc::new(clip), Arc::new(image)], clip: true })
|
|
}
|
|
_ => outline_art(n, symbol_art)?,
|
|
};
|
|
reshape_leaves(&mut art, &fx);
|
|
// Type and symbols in perspective: the art projected onto their plane.
|
|
if let Some(h) = n.projection() {
|
|
project_leaves(&mut art, &h);
|
|
}
|
|
// The art is a group with `n`'s id (a clip group for an image).
|
|
let mut out = art;
|
|
out.name = n.name.clone();
|
|
out.visible = n.visible;
|
|
out.opacity = n.opacity;
|
|
out.blend = n.blend;
|
|
out.isolate = n.isolate;
|
|
// One element: its pieces never knock each other out.
|
|
out.knockout = vectorcraft_doc::Knockout::Off;
|
|
out.knockout_shape = n.knockout_shape;
|
|
out.extras_mut().mask = n.extras().mask.clone();
|
|
out.appearance.effects = n.appearance.effects.iter().filter(|e| !is_geometry(&e.id)).cloned().collect();
|
|
Some(out)
|
|
}
|
|
|
|
/// Map every path under `art` (and its gradients) through the projective map `h`, curves split
|
|
/// finely enough to follow it.
|
|
fn project_leaves(art: &mut Node, h: &vectorcraft_geom::Homography) {
|
|
let f = |p: vectorcraft_geom::Point| h.apply(p).unwrap_or(p);
|
|
let piece = art.geometric_bounds().map_or(1.0, |b| (b.width().max(b.height()) / 64.0).max(0.25));
|
|
let project = |path: &mut PathData| *path = vectorcraft_doc::live::map_nonlinear(path, piece, f);
|
|
for_each_leaf(art, &mut |leaf| {
|
|
leaf.pin_gradients();
|
|
leaf.appearance.warp_gradients(&|p| h.affine_at(p).unwrap_or(vectorcraft_geom::Affine::IDENTITY));
|
|
match &mut leaf.kind {
|
|
NodeKind::Path { path, live, .. } => {
|
|
project(path);
|
|
*live = None;
|
|
}
|
|
NodeKind::Compound { children, .. } => {
|
|
for c in children.iter_mut() {
|
|
if let NodeKind::Path { path, live, .. } = &mut Arc::make_mut(c).kind {
|
|
project(path);
|
|
*live = None;
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// The visible geometry effects of `n`'s own effect list, in order.
|
|
pub(crate) fn geometry_effects(n: &Node) -> Vec<&Effect> {
|
|
n.appearance.effects.iter().filter(|e| e.visible && is_geometry(&e.id)).collect()
|
|
}
|
|
|
|
/// Apply `fx` to every path under `art` as one piece: each effect takes the whole art's bounds as
|
|
/// its reference box and the art's seed (a random Transform moves the pieces together). Images and
|
|
/// symbol instances follow a Transform effect the same way ([`transform_placed`]); other geometry
|
|
/// effects leave them as they are (a raster follows a non-affine map only as a mesh warp).
|
|
pub(crate) fn reshape_leaves(art: &mut Node, fx: &[&Effect]) {
|
|
let seed = art.id.0;
|
|
// The placed gradients of type's outlines in `art` (`outline_text`) move and scale with the
|
|
// pieces, from the outlines' bounds before the effects to their bounds after them.
|
|
let mut outlines = vec![];
|
|
type_outline_bounds(art, &mut outlines);
|
|
for e in fx {
|
|
let Some(bounds) = art.geometric_bounds() else { break };
|
|
let params = merged_params(&e.id, &e.params);
|
|
for_each_leaf(art, &mut |leaf| {
|
|
let Some((path, rule)) = leaf_geometry(leaf) else { return };
|
|
let out = apply_one(&e.id, ¶ms, &path, bounds, &GeomContext { seed, ..GeomContext::of(leaf) });
|
|
set_geometry(leaf, out, rule);
|
|
});
|
|
if e.id == "distort.transform" {
|
|
let (m, copies) = crate::distort::transform_map(bounds, ¶ms, seed);
|
|
transform_placed(art, m, copies);
|
|
}
|
|
}
|
|
if !fx.is_empty() && outlines.iter().any(Option::is_some) {
|
|
rebase_type_outlines(art, &mut outlines.into_iter());
|
|
}
|
|
}
|
|
|
|
/// The bounds of each group of type's outlines under `n` ([`outline_text`]), `n` included, in
|
|
/// order. A group inside one of them is part of it.
|
|
fn type_outline_bounds(n: &Node, out: &mut Vec<Option<Rect>>) {
|
|
if n.extras().type_outlines {
|
|
out.push(n.geometric_bounds());
|
|
} else if let NodeKind::Group { children, .. } | NodeKind::Layer { children, .. } = &n.kind {
|
|
for c in children {
|
|
type_outline_bounds(c, out);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Move and scale the placed gradients of each group of type's outlines under `n` from its bounds
|
|
/// in `before` ([`type_outline_bounds`] before its paths changed) to its bounds now.
|
|
fn rebase_type_outlines(n: &mut Node, before: &mut std::vec::IntoIter<Option<Rect>>) {
|
|
if !n.extras().type_outlines {
|
|
if let NodeKind::Group { children, .. } | NodeKind::Layer { children, .. } = &mut n.kind {
|
|
for c in children.iter_mut() {
|
|
rebase_type_outlines(Arc::make_mut(c), before);
|
|
}
|
|
}
|
|
} else if let Some(Some(from)) = before.next()
|
|
&& let Some(to) = n.geometric_bounds()
|
|
&& from != to
|
|
{
|
|
for_each_leaf(n, &mut |leaf| {
|
|
if leaf.appearance.has_placed_gradient() {
|
|
follow_boxes(&mut leaf.appearance, from, to);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Map the placed gradients of `ap` from box `from` to box `to` through the map that takes one box
|
|
/// onto the other (strokes: their stroke-inflated boxes). The start and end go where
|
|
/// [`Appearance::rebase_gradients`] puts them, and a radial's ellipse and a linear's isolines are
|
|
/// mapped with them.
|
|
fn follow_boxes(ap: &mut Appearance, from: Rect, to: Rect) {
|
|
for i in &mut ap.items {
|
|
match i {
|
|
AppearanceItem::Fill(f) => {
|
|
if let vectorcraft_doc::color::Paint::Gradient(g) = &mut f.paint {
|
|
g.transform(box_map(from, to));
|
|
}
|
|
}
|
|
AppearanceItem::Stroke(s) => {
|
|
let m = box_map(s.paint_bounds(from), s.paint_bounds(to));
|
|
if let vectorcraft_doc::color::Paint::Gradient(g) = &mut s.paint {
|
|
g.transform(m);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The map that takes box `from` onto box `to` along each axis (along an axis `from` has no extent
|
|
/// on, a move from its center to `to`'s).
|
|
fn box_map(from: Rect, to: Rect) -> vectorcraft_geom::Affine {
|
|
let axis = |a0: f64, a1: f64, b0: f64, b1: f64| {
|
|
if (a1 - a0).abs() > 1e-12 {
|
|
let s = (b1 - b0) / (a1 - a0);
|
|
(s, b0 - a0 * s)
|
|
} else {
|
|
(1.0, (b0 + b1 - a0 - a1) / 2.0)
|
|
}
|
|
};
|
|
let (sx, tx) = axis(from.x0, from.x1, to.x0, to.x1);
|
|
let (sy, ty) = axis(from.y0, from.y1, to.y0, to.y1);
|
|
vectorcraft_geom::Affine::new([sx, 0.0, 0.0, sy, tx, ty])
|
|
}
|
|
|
|
/// Apply the Transform effect's map `m` to every image and symbol instance under `n`, as
|
|
/// [`for_each_leaf`] does to paths: the map goes into the object's placement, and `copies` more
|
|
/// follow, each mapped once more than the last. Without copies the object itself takes the map,
|
|
/// as a path does.
|
|
fn transform_placed(n: &mut Node, m: Affine, copies: usize) {
|
|
transform_placed_within(n, m, copies, crate::distort::MAX_COPY_OBJECTS)
|
|
}
|
|
|
|
/// [`transform_placed`] whose copies make at most `max` image and symbol instance nodes of the art,
|
|
/// the originals included: each copy adds the object once more, and a Transform after this one
|
|
/// copies the lot again, so copies stop where the result would pass `max` (#2042).
|
|
pub(crate) fn transform_placed_within(n: &mut Node, m: Affine, copies: usize, max: usize) {
|
|
let mut room = max.saturating_sub(placed_count(n));
|
|
copy_placed(n, m, copies, &mut room);
|
|
}
|
|
|
|
/// An object that a Transform effect places through its transform rather than reshapes: an image
|
|
/// (pixel space to document) or a symbol instance (the symbol's art to document).
|
|
fn is_placed(n: &Node) -> bool {
|
|
matches!(n.kind, NodeKind::Image(_) | NodeKind::SymbolInstance { .. })
|
|
}
|
|
|
|
/// The walk of [`transform_placed`]: the map and its copies go to each image and symbol instance in
|
|
/// document order, each copy taking one from `room` (what the art can still grow by), until the
|
|
/// budget is full. As with a path, the copies stop before one reaches past
|
|
/// [`crate::distort::within_reach`].
|
|
fn copy_placed(n: &mut Node, m: Affine, copies: usize, room: &mut usize) {
|
|
let Some(children) = n.children_mut() else { return };
|
|
for c in children.iter_mut() {
|
|
if !is_placed(c) {
|
|
copy_placed(Arc::make_mut(c), m, copies, room);
|
|
continue;
|
|
}
|
|
let src = (**c).clone();
|
|
let placed = |xf: Affine| {
|
|
let mut one = src.clone();
|
|
match &mut one.kind {
|
|
NodeKind::Image(im) => im.xf = xf * im.xf,
|
|
NodeKind::SymbolInstance { xf: at, .. } => *at = xf * *at,
|
|
_ => {}
|
|
}
|
|
one
|
|
};
|
|
if copies == 0 {
|
|
*c = Arc::new(placed(m));
|
|
continue;
|
|
}
|
|
// The copies become nodes of their own, under a group with the object's id, name and place
|
|
// in the art (as the runs an outlined text gets).
|
|
let mut placements: Vec<Arc<Node>> = vec![Arc::new(placed(Affine::IDENTITY))];
|
|
let mut xf = Affine::IDENTITY;
|
|
for _ in 0..copies.min(*room) {
|
|
xf *= m;
|
|
let copy = placed(xf);
|
|
if !copy.geometric_bounds().is_some_and(crate::distort::within_reach) {
|
|
break;
|
|
}
|
|
placements.push(Arc::new(copy));
|
|
}
|
|
*room -= placements.len() - 1;
|
|
let mut g = Node::group(src.id, placements);
|
|
g.name = src.name.clone();
|
|
*c = Arc::new(g);
|
|
}
|
|
}
|
|
|
|
/// How many images and symbol instances `n` holds below itself.
|
|
fn placed_count(n: &Node) -> usize {
|
|
n.children().into_iter().flatten().map(|c| usize::from(is_placed(c)) + placed_count(c)).sum()
|
|
}
|
|
|
|
/// `n` with type anywhere inside it outlined and live objects evaluated, so geometry effects on
|
|
/// it reach every member.
|
|
pub(crate) fn outlined_members(n: &Node) -> Node {
|
|
let hook: &dyn Fn(&Node) -> Option<Node> = &outline_text;
|
|
let mut art = vectorcraft_doc::live::expand_deep(n, Some(hook));
|
|
outline_nested_text(&mut art, false);
|
|
art
|
|
}
|
|
|
|
/// Run `f` on every path and compound path under `n` (guides skipped).
|
|
fn for_each_leaf(n: &mut Node, f: &mut dyn FnMut(&mut Node)) {
|
|
match &mut n.kind {
|
|
NodeKind::Path { guide: false, .. } | NodeKind::Compound { .. } => f(n),
|
|
NodeKind::Group { children, .. } | NodeKind::Layer { children, .. } => {
|
|
for c in children.iter_mut() {
|
|
for_each_leaf(Arc::make_mut(c), f);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn leaf_geometry(n: &Node) -> Option<(PathData, FillRule)> {
|
|
match &n.kind {
|
|
NodeKind::Path { path, rule, .. } => Some((path.clone(), *rule)),
|
|
NodeKind::Compound { children, rule } => {
|
|
Some((PathData::new(children.iter().filter_map(|c| c.path_data()).flat_map(|p| p.subpaths.iter().cloned()).collect()), *rule))
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Replace the geometry of path or compound `n` (a compound when `path` has several subpaths).
|
|
fn set_geometry(n: &mut Node, path: PathData, rule: FillRule) {
|
|
let clipping = matches!(n.kind, NodeKind::Path { clipping: true, .. });
|
|
n.kind = if path.subpaths.len() > 1 {
|
|
let id = n.id;
|
|
let children = path.subpaths.into_iter().map(|sp| Arc::new(Node::path(id, PathData::single(sp), Appearance::default()))).collect();
|
|
NodeKind::Compound { children, rule }
|
|
} else {
|
|
NodeKind::Path { path, rule, live: None, clipping, guide: false }
|
|
};
|
|
}
|