mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 11:55:23 +00:00
Merge pull request #7061 from dotCooCoo/fix/dcs-transparency-clip
Place: DCS transparency interactions look only at what clipping paths show
This commit is contained in:
@@ -2009,15 +2009,21 @@ fn status_rank(status: &str) -> usize {
|
||||
/// holds them) or overlap an object that is, as Show DCS Transparency Interactions marks them.
|
||||
fn dcs_transparency(d: &Document) -> BTreeSet<NodeId> {
|
||||
/// Each visible DCS image's id, bounds and whether it is transparent, and the bounds of each
|
||||
/// other visible object with its own transparency.
|
||||
fn walk(d: &Document, n: &Node, held: bool, images: &mut Vec<(NodeId, Rect, bool)>, see_through: &mut Vec<Rect>) {
|
||||
/// other visible object with its own transparency; bounds are cut to `clip`, the bounds of the
|
||||
/// clipping paths of the clip groups and layers holding the object.
|
||||
fn walk(d: &Document, n: &Node, held: bool, clip: Option<Rect>, images: &mut Vec<(NodeId, Rect, bool)>, see_through: &mut Vec<Rect>) {
|
||||
if !n.visible {
|
||||
return;
|
||||
}
|
||||
let own = n.shows_own_transparency();
|
||||
// The part of the object that shows through `clip`; none when it is clipped away.
|
||||
let shown = n.geometric_bounds().and_then(|b| match clip {
|
||||
Some(c) => Some(c.intersect(b)).filter(|i| i.area() > 0.0),
|
||||
None => Some(b),
|
||||
});
|
||||
match &n.kind {
|
||||
NodeKind::Image(im) if d.images.get(&im.key).is_some_and(ImageBlob::is_separated) => {
|
||||
if let Some(b) = n.geometric_bounds() {
|
||||
if let Some(b) = shown {
|
||||
images.push((n.id, b, held || own));
|
||||
// A transparent DCS image is an object with its own transparency for the
|
||||
// others it overlaps.
|
||||
@@ -2026,16 +2032,25 @@ fn dcs_transparency(d: &Document) -> BTreeSet<NodeId> {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ if own => see_through.extend(n.geometric_bounds()),
|
||||
_ if own => see_through.extend(shown),
|
||||
_ => {}
|
||||
}
|
||||
for c in n.children().into_iter().flatten() {
|
||||
walk(d, c, held || own, images, see_through);
|
||||
let Some(children) = n.children() else { return };
|
||||
// A clip group's or clipping layer's first child clips the others.
|
||||
let clip = match &n.kind {
|
||||
NodeKind::Group { clip: true, .. } | NodeKind::Layer { clip: true, .. } => match children.first().and_then(|c| c.geometric_bounds()) {
|
||||
Some(c) => Some(clip.map_or(c, |o| o.intersect(c))),
|
||||
None => clip,
|
||||
},
|
||||
_ => clip,
|
||||
};
|
||||
for c in children {
|
||||
walk(d, c, held || own, clip, images, see_through);
|
||||
}
|
||||
}
|
||||
let (mut images, mut see_through) = (vec![], vec![]);
|
||||
for l in &d.layers {
|
||||
walk(d, l, false, &mut images, &mut see_through);
|
||||
walk(d, l, false, None, &mut images, &mut see_through);
|
||||
}
|
||||
images
|
||||
.into_iter()
|
||||
|
||||
@@ -773,6 +773,88 @@ fn dcs_transparency_marks_transparent_and_overlapped_dcs_images() {
|
||||
assert!(error(&mut s, "links.list", json!({ "dcsTransparency": "yes" })).contains("dcsTransparency"), "a flag that isn't true or false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dcs_transparency_looks_only_at_the_part_of_an_image_its_clipping_path_shows() {
|
||||
let dir = temp_dir("transparency-clipped");
|
||||
let path = write(&dir, "pic.eps", &two_plates());
|
||||
let mut s = session();
|
||||
let r = s.execute("file.place", &json!({ "path": path, "at": [100, 100] })).unwrap();
|
||||
let group = NodeId(r["ids"][0].as_u64().unwrap());
|
||||
let image = image_of(&s, &r["ids"][0]).id;
|
||||
let (clip, all) = {
|
||||
let doc = &s.doc().unwrap().doc;
|
||||
let NodeKind::Group { children, clip: true } = &doc.node(group).unwrap().kind else { panic!("a clipping group") };
|
||||
(children[0].geometric_bounds().unwrap(), doc.node(image).unwrap().geometric_bounds().unwrap())
|
||||
};
|
||||
assert!(all.x1 - clip.x1 > 2.0, "the file's clipping path shows the left half: {clip:?} of {all:?}");
|
||||
// A half-transparent square over the part of the image the clipping path hides.
|
||||
let x = (clip.x1 + all.x1) / 2.0 - 1.0;
|
||||
let over = s.execute("shape.rectangle", &json!({"x": x, "y": all.y0, "width": 2, "height": 2})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("transparency.set", &json!({"ids": [over], "opacity": 50})).unwrap();
|
||||
let marked = |s: &mut Session| s.execute("links.list", &json!({ "dcsTransparency": true })).unwrap()["links"][0]["dcsTransparency"] == true;
|
||||
assert!(!marked(&mut s), "the square is over the hidden part only");
|
||||
s.execute("transparency.set", &json!({"ids": [over], "opacity": 100})).unwrap();
|
||||
let shown = s.execute("shape.rectangle", &json!({"x": clip.x0, "y": all.y0, "width": 2, "height": 2})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("transparency.set", &json!({"ids": [shown], "opacity": 50})).unwrap();
|
||||
assert!(marked(&mut s), "a square over the part that shows");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dcs_transparency_skips_a_transparent_object_its_own_clipping_paths_hide() {
|
||||
let dir = temp_dir("transparency-clipped-over");
|
||||
let path = write(&dir, "pic.eps", &two_plates());
|
||||
// A session with the file placed, and the top-left corner of the image, inside the part its
|
||||
// clipping path shows.
|
||||
let placed = || {
|
||||
let mut s = session();
|
||||
let r = s.execute("file.place", &json!({ "path": path, "at": [100, 100] })).unwrap();
|
||||
let b = image_of(&s, &r["ids"][0]).geometric_bounds().unwrap();
|
||||
(s, b.x0, b.y0)
|
||||
};
|
||||
let rect = |s: &mut Session, x: f64, y: f64, width: f64| {
|
||||
s.execute("shape.rectangle", &json!({"x": x, "y": y, "width": width, "height": 2})).unwrap()["id"].as_u64().unwrap()
|
||||
};
|
||||
let make = |s: &mut Session, ids: [u64; 2]| {
|
||||
s.execute("select.set", &json!({ "ids": ids })).unwrap();
|
||||
s.execute("object.clippingMask.make", &json!({})).unwrap()["id"].as_u64().unwrap()
|
||||
};
|
||||
let marked = |s: &mut Session| s.execute("links.list", &json!({ "dcsTransparency": true })).unwrap()["links"][0]["dcsTransparency"] == true;
|
||||
// A half-transparent square over the part of the image that shows.
|
||||
let square = |s: &mut Session, x: f64, y: f64| {
|
||||
let id = rect(s, x, y, 2.0);
|
||||
s.execute("transparency.set", &json!({"ids": [id], "opacity": 50})).unwrap();
|
||||
assert!(marked(s), "the square over the image");
|
||||
id
|
||||
};
|
||||
|
||||
// Its own clip group's clipping path is beside it.
|
||||
let (mut s, x, y) = placed();
|
||||
let over = square(&mut s, x, y);
|
||||
let beside = rect(&mut s, x + 5.0, y, 2.0);
|
||||
make(&mut s, [over, beside]);
|
||||
assert!(!marked(&mut s), "a clip group that hides the square");
|
||||
|
||||
// Its layer's clipping path is beside it.
|
||||
let (mut s, x, y) = placed();
|
||||
s.execute("layer.new", &json!({})).unwrap();
|
||||
square(&mut s, x, y);
|
||||
rect(&mut s, x + 5.0, y, 2.0);
|
||||
s.execute("select.none", &json!({})).unwrap();
|
||||
assert!(s.execute("layer.clippingMask.toggle", &json!({})).unwrap()["clip"].as_bool().unwrap());
|
||||
assert!(!marked(&mut s), "a clipping layer that hides the square");
|
||||
|
||||
// Each of two nested clipping paths shows part of the inner group, and only the inner one
|
||||
// covers the square.
|
||||
let (mut s, x, y) = placed();
|
||||
let over = square(&mut s, x, y);
|
||||
let inner = rect(&mut s, x, y, 40.0);
|
||||
let group = make(&mut s, [over, inner]);
|
||||
assert!(marked(&mut s), "the inner clipping path shows the square");
|
||||
let outer = rect(&mut s, x + 10.0, y, 20.0);
|
||||
make(&mut s, [group, outer]);
|
||||
assert!(!marked(&mut s), "the two clipping paths together hide the square");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dcs_transparency_counts_a_transparent_dcs_image_over_another() {
|
||||
let dir = temp_dir("transparency-two");
|
||||
|
||||
+2
-1
@@ -6817,7 +6817,8 @@ DCS Transparency Interactions, off by default) adds `dcsTransparency: true` to t
|
||||
and the panel shows a warning mark on them. VectorCraft's rule for it: the image, or a group or layer holding it, has
|
||||
an opacity below 100%, a blend mode other than Normal, an opacity mask or an effect, or its bounds overlap the
|
||||
bounds of another visible object that has one of those or a gradient with a transparent stop (in a fill, a stroke or
|
||||
a character's fill or stroke); a DCS image with transparency of its own counts as such an object. The check looks at
|
||||
a character's fill or stroke); a DCS image with transparency of its own counts as such an object. Bounds are cut to the
|
||||
bounds of the clipping paths of the clip groups and clipping layers holding an object. The check looks at
|
||||
every object in the layers each time the list is read. A `dcsTransparency` that is not true or false is an error.
|
||||
`links.goTo {id}` selects an image; `links.embed {ids?}` keeps linked files' pixels in the document (an image whose
|
||||
file is missing stays linked: relink it first); `links.unembed {id, path}` writes an embedded image to a file
|
||||
|
||||
Reference in New Issue
Block a user