Merge pull request #7676 from dotCooCoo/fix/clip-group-paint-bakes-clip-effects

Effects: a clip group with its own paint exports the clip its clipping path's effects shape
This commit is contained in:
BFlat
2026-11-15 06:16:48 +07:00
committed by GitHub
2 changed files with 49 additions and 3 deletions
+7 -3
View File
@@ -476,14 +476,18 @@ fn clip_group_with_type_clip(d: &mut Document, n: &Node) -> Option<Node> {
/// `m`, art evaluated from an object, with its children baked and then given ids of their own (the
/// pieces copied from the source keep the source's ids until then). The pieces are baked with the
/// ids the renderer draws them with: Random in a Transform effect takes its seed from the id.
/// ids the renderer draws them with: Random in a Transform effect takes its seed from the id. When
/// `m` clips, its clipping path, unless it is type, first takes the geometry effects in it as the
/// canvas clips by it ([`crate::clip::with_clip_effects_applied`]).
fn bake_pieces(d: &mut Document, mut m: Node) -> Node {
let mut seen = std::collections::HashSet::from([m.id]);
let clip = usize::from(m.clips() && !m.children().and_then(|ch| ch.first()).is_some_and(|c| matches!(c.kind, NodeKind::Text(_))));
let children = m.children().cloned().unwrap_or_default();
let children = children
.into_iter()
.map(|c| {
let c = Arc::unwrap_or_clone(c);
.enumerate()
.map(|(i, c)| {
let c = (i < clip).then(|| crate::clip::with_clip_effects_applied(&c)).flatten().unwrap_or_else(|| Arc::unwrap_or_clone(c));
let mut c = one_deeper(|| bake_node(d, &c)).unwrap_or(c);
fresh_ids(d, &mut c, &mut seen);
Arc::new(c)
+42
View File
@@ -172,6 +172,48 @@ fn the_clipping_paths_effects_apply_before_the_clip_groups() {
assert!((b.x0 - want.x0).abs() < 0.5 && (b.x1 - want.x1).abs() < 0.5, "exported: {b:?} against {want:?}");
}
#[test]
fn a_clip_group_with_a_fill_or_stroke_of_its_own_clips_as_the_canvas_does() {
// A clip group with a fill or a stroke of its own and no effects: the export turns its paint
// into art and still clips by the outline the canvas clips by, Transform copies included.
let items = [
vectorcraft_doc::AppearanceItem::Fill(vectorcraft_doc::FillLayer::new(Paint::solid(Color::BLACK))),
vectorcraft_doc::AppearanceItem::Stroke(vectorcraft_doc::StrokeLayer::new(Paint::solid(Color::BLACK), 2.0)),
];
let copies = || fx("distort.transform", json!({"moveH": 60, "copies": 1}));
let roughen = fx("distort.roughen", json!({"size": 10, "detail": 10, "relative": false}));
// A ring: a compound path of two squares with effects `own`, its hole with effects `hole_fx`.
let ring = |hole_fx: Vec<Effect>, own: Vec<Effect>| {
let outer = Node::path(NodeId(3), shapes::rectangle(Rect::new(50.0, 50.0, 150.0, 150.0)), Appearance::default());
let mut hole = Node::path(NodeId(4), shapes::rectangle(Rect::new(75.0, 75.0, 125.0, 125.0)), Appearance::default());
hole.appearance.effects = hole_fx;
let mut c =
Node::new(NodeId(2), NodeKind::Compound { children: vec![Arc::new(outer), Arc::new(hole)], rule: vectorcraft_geom::FillRule::EvenOdd });
c.appearance.effects = own;
c
};
let clips = [
("copies", clipping_path(2, vec![copies()])),
("roughen", clipping_path(2, vec![roughen])),
("a compound path's copies", ring(vec![], vec![copies()])),
("a compound path member's copies", ring(vec![copies()], vec![])),
];
for item in &items {
for (what, clip) in &clips {
let mut d = Document::new(400.0, 400.0);
let layer = d.layers[0].id;
let live = clip_outline(clip).unwrap();
let mut group = clip_group(1, clip.clone());
group.appearance.items.push(item.clone());
d.insert(Some(layer), 0, group).unwrap();
let exported = clip_outline(&baked_clip(&d)).unwrap();
assert_eq!(live, exported, "{what} with {item:?}: the canvas clips by the outline the export writes");
}
}
let b = bounds(&clips[0].1);
assert!((b.x0 - 50.0).abs() < 1e-6 && (b.x1 - 210.0).abs() < 1e-6, "the clip covers both copies: {b:?}");
}
#[test]
fn nested_effects_apply_before_the_clip_groups() {
// The clip group grows its clipping path by 10 (Offset Path). Inside the clipping path a