mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 07:42:42 +00:00
Effects: a clipping path's members count toward the member limit of a group's effects
When a group's geometry effects evaluate the members of the group, a clip group's clipping path takes the geometry effects in it first. A clipping path that is a group, layer or compound path had each of its members reshaped at every depth without counting them, so a large clipping path was reshaped past the SYMBOL_ART_MAX_NODES members that members_as_drawn documents. Each member of a clipping path now counts as one visited member. Once the limit is reached, that member and the members after it keep their effects, as the other members past the limit do. The canvas, the export bake and clipping type are unchanged: they do not count members.
This commit is contained in:
@@ -12,7 +12,7 @@ use vectorcraft_geom::{BezPath, FillRule, PathData};
|
||||
/// fills and strokes: the region is then the union of the paths they give, as an export bakes them.
|
||||
/// `None` when there is nothing to clip by (the clipped art is then hidden).
|
||||
pub fn clip_outline(clip: &Node) -> Option<(BezPath, FillRule)> {
|
||||
match clip_effects_applied(clip, true) {
|
||||
match clip_effects_applied(clip, true, || true) {
|
||||
Some(m) => m.clip_outline(Some(&outline_text), &unite),
|
||||
None => clip.clip_outline(Some(&outline_text), &unite),
|
||||
}
|
||||
@@ -31,14 +31,21 @@ pub fn clip_outline(clip: &Node) -> Option<(BezPath, FillRule)> {
|
||||
/// geometry effects of its own fills and strokes live instead. `None` when nothing in it has a
|
||||
/// geometry effect. Effects on the clip group or around it then reshape the result, after its own.
|
||||
pub(crate) fn with_clip_effects_applied(n: &Node) -> Option<Node> {
|
||||
let paint = n.clip_paint();
|
||||
clip_effects_applied(n, paint.fill.is_none() && paint.stroke.is_none())
|
||||
with_clip_effects_applied_while(n, || true)
|
||||
}
|
||||
|
||||
/// [`with_clip_effects_applied`]; with `items` unset (a clipping path that paints, and the paths of
|
||||
/// a compound path, which draw with the compound path's fills and strokes), the geometry effects of
|
||||
/// `n`'s own fills and strokes are left out.
|
||||
fn clip_effects_applied(n: &Node, items: bool) -> Option<Node> {
|
||||
/// [`with_clip_effects_applied`] that calls `take` before it visits each member of a group, layer
|
||||
/// or compound path in `n`, at every depth: once `take` returns `false`, that member and the
|
||||
/// members after it stay as they are, with their effects.
|
||||
pub(crate) fn with_clip_effects_applied_while(n: &Node, take: fn() -> bool) -> Option<Node> {
|
||||
let paint = n.clip_paint();
|
||||
clip_effects_applied(n, paint.fill.is_none() && paint.stroke.is_none(), take)
|
||||
}
|
||||
|
||||
/// [`with_clip_effects_applied_while`]; with `items` unset (a clipping path that paints, and the
|
||||
/// paths of a compound path, which draw with the compound path's fills and strokes), the geometry
|
||||
/// effects of `n`'s own fills and strokes are left out.
|
||||
fn clip_effects_applied(n: &Node, items: bool, take: fn() -> bool) -> Option<Node> {
|
||||
let mut m: Option<Node> = None;
|
||||
let members: &[std::sync::Arc<Node>] = match &n.kind {
|
||||
NodeKind::Group { children, clip: true } => children.get(..1).unwrap_or_default(),
|
||||
@@ -53,7 +60,10 @@ fn clip_effects_applied(n: &Node, items: bool) -> Option<Node> {
|
||||
// The paths of a compound path draw with the compound path's fills and strokes, and stay paths.
|
||||
let compound = matches!(n.kind, NodeKind::Compound { .. });
|
||||
for (i, c) in members.iter().enumerate().skip(skip).filter(|(_, c)| all || c.visible) {
|
||||
if let Some(r) = clip_effects_applied(c, items && !compound)
|
||||
if !take() {
|
||||
break;
|
||||
}
|
||||
if let Some(r) = clip_effects_applied(c, items && !compound, take)
|
||||
&& let Some(slot) = m.get_or_insert_with(|| n.clone()).children_mut().and_then(|ch| ch.get_mut(i))
|
||||
{
|
||||
*slot = std::sync::Arc::new(r);
|
||||
|
||||
@@ -904,7 +904,8 @@ pub fn evaluate_container(doc: Option<&Document>, n: &Node) -> Option<Node> {
|
||||
/// depth of its groups and layers, by the art it draws ([`evaluate_container`],
|
||||
/// [`crate::reshape::own_effects_art`]), so the geometry effects of `n` reshape what its members
|
||||
/// draw. A clip group's clipping path only takes the geometry effects in it into its geometry
|
||||
/// ([`crate::clip::with_clip_effects_applied`]). Images, symbol instances and the other objects
|
||||
/// ([`crate::clip::with_clip_effects_applied_while`]), and the members of a clipping group or
|
||||
/// compound path count toward the limit below. Images, symbol instances and the other objects
|
||||
/// that `own_effects_art` leaves alone stay as they are, and so does a group or layer with a 3D
|
||||
/// effect with everything in it. Calls nested in one another, through the
|
||||
/// groups and layers it walks and the art it evaluates (symbol art and Pathfinder members
|
||||
@@ -917,7 +918,7 @@ pub(crate) fn members_as_drawn(doc: Option<&Document>, n: &mut Node) {
|
||||
// The clipping path's own geometry effects shape it before the effects around it do.
|
||||
if clip == 1
|
||||
&& let Some(first) = ch.first_mut()
|
||||
&& let Some(applied) = crate::clip::with_clip_effects_applied(first)
|
||||
&& let Some(applied) = crate::clip::with_clip_effects_applied_while(first, DrawnFrame::take_member)
|
||||
{
|
||||
*first = Arc::new(applied);
|
||||
}
|
||||
|
||||
@@ -849,6 +849,28 @@ fn nested_groups_with_effects_count_toward_the_depth_limit() {
|
||||
assert!(deepest.is_some_and(|d| crate::reshape::geometry_effects(&d).len() == 1), "the deepest member keeps its effect");
|
||||
}
|
||||
|
||||
/// The members of a clipping path count toward the member limit: with room for two more members,
|
||||
/// the third member of a clipping group keeps its own effect as it is.
|
||||
#[test]
|
||||
fn clipping_path_members_past_the_member_limit_stay_as_they_are() {
|
||||
let member = |id: u64| {
|
||||
let mut m = with_effect(square_at_100(), offset_10());
|
||||
m.id = NodeId(id);
|
||||
Arc::new(m)
|
||||
};
|
||||
let clipping = Node::group(NodeId(20), vec![member(2), member(3), member(4)]);
|
||||
let mut clipped = Node::new(NodeId(5), NodeKind::Group { children: vec![Arc::new(clipping), Arc::new(square_at_100())], clip: true });
|
||||
// An enclosing call that has visited all the members it may but two.
|
||||
let _outer = DrawnFrame::enter().unwrap();
|
||||
DRAWN.with(|d| d.set((1, crate::SYMBOL_ART_MAX_NODES - 2)));
|
||||
members_as_drawn(None, &mut clipped);
|
||||
let clipping = clipped.children().unwrap()[0].clone();
|
||||
let effects: Vec<usize> = clipping.children().unwrap().iter().map(|m| crate::reshape::geometry_effects(m).len()).collect();
|
||||
assert_eq!(effects, [0, 0, 1], "the first two members take their Offset Path, the third keeps it");
|
||||
let widths: Vec<f64> = clipping.children().unwrap().iter().map(|m| m.geometric_bounds().unwrap().width()).collect();
|
||||
assert!((widths[0] - 120.0).abs() < 0.5 && (widths[1] - 120.0).abs() < 0.5 && (widths[2] - 100.0).abs() < 0.5, "{widths:?}");
|
||||
}
|
||||
|
||||
/// An image or a symbol instance that a group's Transform effect moves takes the placed gradients
|
||||
/// of its own fills along, as a path does, and each copy takes them as many times as it takes the
|
||||
/// transform.
|
||||
|
||||
Reference in New Issue
Block a user