mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 07:16:52 +00:00
Merge pull request #5132 from dotCooCoo/fix/ai-os23-2422
.ai import: a clipping path keeps its stroke
This commit is contained in:
@@ -185,7 +185,8 @@ impl<'a> Reader<'a> {
|
||||
..FillLayer::new(fill_paint.clone().unwrap_or(gs.fill.clone()))
|
||||
}));
|
||||
}
|
||||
if stroke && !clip {
|
||||
// A clipping path keeps its stroke, which paints over the art it clips.
|
||||
if stroke {
|
||||
let mut st = StrokeLayer::new(stroke_paint.unwrap_or(gs.stroke.clone()), gs.width);
|
||||
st.cap = gs.cap;
|
||||
st.join = gs.join;
|
||||
@@ -297,11 +298,20 @@ impl<'a> Reader<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stroke the path just painted (a gradient-filled one, `1 BB` or `2 BB`) with the stroke's state.
|
||||
/// Stroke the path just painted (a gradient-filled one, `1 BB` or `2 BB`) with the stroke's state. A clipping path
|
||||
/// takes the stroke itself, not the painted copy of its fill.
|
||||
pub(super) fn stroke_last(&mut self) {
|
||||
let gs = self.gs.clone();
|
||||
let Some(n) = self.last_object() else { return };
|
||||
if !matches!(n.kind, NodeKind::Path { clipping: false, .. }) || n.appearance.items.iter().any(|i| matches!(i, AppearanceItem::Stroke(_))) {
|
||||
if !self.after_object {
|
||||
return;
|
||||
}
|
||||
let Some(kids) = self.frames.last_mut().map(|f| &mut f.children) else { return };
|
||||
let Some(mut at) = kids.len().checked_sub(1) else { return };
|
||||
if kids.get(at).is_some_and(|n| self.clip_fills.contains(&n.id)) && at > 0 {
|
||||
at -= 1;
|
||||
}
|
||||
let Some(n) = kids.get_mut(at) else { return };
|
||||
if !matches!(n.kind, NodeKind::Path { .. }) || n.appearance.items.iter().any(|i| matches!(i, AppearanceItem::Stroke(_))) {
|
||||
return;
|
||||
}
|
||||
let mut st = StrokeLayer::new(gs.stroke.clone(), gs.width);
|
||||
|
||||
@@ -195,8 +195,19 @@ impl<'a> Reader<'a> {
|
||||
1 => clip.pop().map(|(_, n)| n),
|
||||
_ => {
|
||||
let rule = clip.first().map_or(FillRule::NonZero, |(_, n)| path_rule(n));
|
||||
// The compound path keeps the clipping paths' stroke, opacity and blend mode when they all have the same
|
||||
// ones: it has one appearance for all its paths.
|
||||
let look = |n: &Node| (n.appearance.clone(), n.opacity, n.blend);
|
||||
let shared =
|
||||
clip.split_first().and_then(|((_, first), others)| others.iter().all(|(_, n)| look(n) == look(first)).then(|| look(first)));
|
||||
let children = clip.into_iter().map(|(_, n)| Arc::new(n)).collect();
|
||||
Some(Node::new(self.doc.alloc_id(), NodeKind::Compound { children, rule }))
|
||||
let mut n = Node::new(self.doc.alloc_id(), NodeKind::Compound { children, rule });
|
||||
if let Some((appearance, opacity, blend)) = shared {
|
||||
n.appearance = appearance;
|
||||
n.opacity = opacity;
|
||||
n.blend = blend;
|
||||
}
|
||||
Some(n)
|
||||
}
|
||||
};
|
||||
let clips = clip_node.is_some();
|
||||
|
||||
@@ -360,6 +360,53 @@ fn compound_paths_and_clipping() {
|
||||
assert!(*clip && matches!(children[0].kind, NodeKind::Path { clipping: true, .. }));
|
||||
}
|
||||
|
||||
/// A stroked clipping path (`W S`, `W B`, or a gradient fill with `1 BB`) keeps its stroke on the clipping path, and a
|
||||
/// fill goes on the painted copy under the clipped art.
|
||||
#[test]
|
||||
fn a_clipping_paths_stroke_stays_on_the_clipping_path() {
|
||||
let blue = Some(Color::rgb(0.0, 0.0, 1.0));
|
||||
let red = Some(Color::rgb(1.0, 0.0, 0.0));
|
||||
// (clipping, fill colour, a gradient fill, stroke colour and width) of each of the clipping group's children.
|
||||
let children = |doc: &Document| {
|
||||
let NodeKind::Group { children, clip: true } = &art(doc)[0].kind else { panic!("{:?}", art(doc)[0].kind) };
|
||||
children
|
||||
.iter()
|
||||
.map(|c| {
|
||||
let clipping = matches!(c.kind, NodeKind::Path { clipping: true, .. });
|
||||
(clipping, fill(c).color(), matches!(fill(c), Paint::Gradient(_)), c.appearance.stroke().map(|s| (s.paint.color(), s.width)))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let under = rect_path(0.0, 0.0, 30.0, 30.0);
|
||||
let clip = rect_path(5.0, 5.0, 25.0, 25.0);
|
||||
let stroked = format!("0 Ae\nq\n{RED}{under}f\n0 0 1 0 0 0 1 XA\n3 w\n{clip}W\nS\nQ\n");
|
||||
assert_eq!(children(&read_ok(&layer(&stroked)).doc), [(true, None, false, Some((blue, 3.0))), (false, red, false, None)]);
|
||||
let both = format!("0 Ae\nq\n{RED}{under}f\n0 1 0 0 0 1 0 Xa\n0 0 1 0 0 0 1 XA\n3 w\n{clip}W\nB\nQ\n");
|
||||
assert_eq!(
|
||||
children(&read_ok(&layer(&both)).doc),
|
||||
[(true, None, false, Some((blue, 3.0))), (false, Some(Color::rgb(0.0, 1.0, 0.0)), false, None), (false, red, false, None)]
|
||||
);
|
||||
let defs = "%AI5_BeginGradient: (G)\n(G) 0 2 Bd\n[\n0 0 0 0 1 0 0 2 0 6 %_BS\n%_0 0 0 0 1 0 0 2 0 6 0 0 Bs\n\
|
||||
%_0 0 0 0 0 0 1 2 1 6 100 50 Bs\nBD\n%AI5_EndGradient\n";
|
||||
let gradient = format!("0 Ae\nq\n{RED}{under}f\n0 0 1 0 0 0 1 XA\n3 w\n{clip}W\nBb\n0 0 0 0 Bh\n1 (G) 5 5 0 20 1 0 0 1 0 0 1 Bg\nf\n1 BB\nQ\n");
|
||||
assert_eq!(
|
||||
children(&read_ok(&format!("{defs}{}", layer(&gradient))).doc),
|
||||
[(true, None, false, Some((blue, 3.0))), (false, None, true, None), (false, red, false, None)]
|
||||
);
|
||||
// Several clipping paths clip as one compound path, which keeps their stroke when they all have the same one.
|
||||
let inner = rect_path(10.0, 10.0, 20.0, 20.0);
|
||||
let compound_stroke = |first: &str, second: &str| {
|
||||
let body = format!("0 Ae\nq\n{RED}{under}f\n0 0 1 0 0 0 1 XA\n3 w\n{first}{clip}W\nS\n{second}{inner}W\nS\nQ\n");
|
||||
let doc = read_ok(&layer(&body)).doc;
|
||||
let NodeKind::Group { children, clip: true } = &art(&doc)[0].kind else { panic!("{:?}", art(&doc)[0].kind) };
|
||||
assert!(matches!(children[0].kind, NodeKind::Compound { .. }), "{:?}", children[0].kind);
|
||||
(children[0].appearance.stroke().map(|s| (s.paint.color(), s.width)), children[0].opacity, children[0].blend)
|
||||
};
|
||||
assert_eq!(compound_stroke("", ""), (Some((blue, 3.0)), 1.0, BlendMode::Normal));
|
||||
assert_eq!(compound_stroke("9 0.5 0 0 0 Xy\n", ""), (Some((blue, 3.0)), 0.5, BlendMode::Lighten));
|
||||
assert_eq!(compound_stroke("", "5 w\n"), (None, 1.0, BlendMode::Normal), "strokes that differ");
|
||||
}
|
||||
|
||||
/// A clipping group's clipping path can sit alone in a group of its own, after the art it clips (`q … u <path> W n U
|
||||
/// … Q`): it still clips the clipping group.
|
||||
#[test]
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user