mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 05:37:23 -06:00
fix(eps): a masked gradient mesh takes the transparency after its mask
A gradient mesh with an opacity mask has its own state (`Xy`) after the mask's dictionary, not right after its `/End X!`. The operators of the mask's art, on `%_` lines, cleared the flag that says a mesh just ended, so the state was read as the next object's and each masked mesh in a run drew with the blend mode of the one before it. The flag now goes back to what it was when a dictionary opened once it closes, as `after_object` does.
This commit is contained in:
@@ -183,7 +183,8 @@ struct Reader<'a> {
|
||||
blend_budget: usize,
|
||||
/// A group just ended, its state (`Xy`, `Xd`, `XW`) not yet read.
|
||||
group_ended: bool,
|
||||
/// A gradient mesh just ended (`/End X!`), its own state (`Xy`) not yet read.
|
||||
/// A gradient mesh just ended (`/End X!`), its own state (`Xy`) not yet read: also after its
|
||||
/// dictionary (its name, its opacity mask and the mask's art).
|
||||
mesh_ended: bool,
|
||||
/// A layer's own state (`Xy` after its art, before `LB`), until something says the art moves on.
|
||||
layer_state: Option<(f32, BlendMode, bool, Knockout)>,
|
||||
|
||||
@@ -19,6 +19,7 @@ impl<'a> Reader<'a> {
|
||||
children: Vec::new(),
|
||||
hidden,
|
||||
after: self.after_object,
|
||||
mesh_ended: self.mesh_ended,
|
||||
gs: self.gs.clone(),
|
||||
base,
|
||||
clips: Vec::new(),
|
||||
@@ -243,6 +244,7 @@ impl<'a> Reader<'a> {
|
||||
let art = self.new_node(NodeKind::Group { children, clip }, &f.gs);
|
||||
self.gs = f.gs;
|
||||
self.after_object = f.after;
|
||||
self.mesh_ended = f.mesh_ended;
|
||||
let Some(Frame { kind: Kind::Obj(o), .. }) = self.frames.last_mut() else {
|
||||
return Err("mask art ends outside a mask dictionary".into());
|
||||
};
|
||||
@@ -258,13 +260,15 @@ impl<'a> Reader<'a> {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(f) = self.frames.pop() else { return Ok(()) };
|
||||
let Frame { kind, children, base, hidden, gs, after, .. } = f;
|
||||
let Frame { kind, children, base, hidden, gs, after, mesh_ended, .. } = f;
|
||||
let Kind::Obj(mut o) = kind else { return Ok(()) };
|
||||
obj::close(&mut o, &mut self.stack, base);
|
||||
self.obj_hidden = hidden;
|
||||
// Art made inside a dictionary (type's paths) isn't kept.
|
||||
// Art made inside a dictionary (type's paths, a mask's art) isn't kept, and doesn't end what
|
||||
// follows the object the dictionary is about: a mesh's state comes after its mask.
|
||||
self.gs = gs;
|
||||
self.after_object = after;
|
||||
self.mesh_ended = mesh_ended;
|
||||
if matches!(self.frames.last(), Some(Frame { kind: Kind::Obj(_), .. })) {
|
||||
return self.push(V::Obj(Rc::new(*o)));
|
||||
}
|
||||
|
||||
@@ -142,6 +142,8 @@ pub(super) struct Frame {
|
||||
pub(super) hidden: bool,
|
||||
/// [`Reader::after_object`] when it opened.
|
||||
pub(super) after: bool,
|
||||
/// [`Reader::mesh_ended`] when it opened.
|
||||
pub(super) mesh_ended: bool,
|
||||
/// The state when it opened (a container's lock and visibility; a dictionary's state to go
|
||||
/// back to).
|
||||
pub(super) gs: GState,
|
||||
|
||||
@@ -486,6 +486,28 @@ fn a_gradient_mesh_takes_the_transparency_written_after_it() {
|
||||
assert_eq!((a[2].opacity, a[2].blend), (0.7, BlendMode::Screen), "a state after the mesh's `Xd` and `XW` is the next object's");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_masked_gradient_mesh_takes_the_transparency_written_after_its_mask() {
|
||||
// GUARDS: a mesh with an opacity mask has its state after the mask's dictionary, whose art (on
|
||||
// `%_` lines) has operators of its own; read as the next object's, each masked mesh in a run
|
||||
// drew with the blend mode of the one before it.
|
||||
let corners: String = ["10 20 10 25 1 15 20 1", "10 50 15 50 1 10 45 1", "40 50 40 45 1 35 50 1", "40 20 35 20 1 40 25 1"]
|
||||
.iter()
|
||||
.map(|g| format!("%_[0.5 1 {g}] /N X#\n"))
|
||||
.collect();
|
||||
let mesh = format!("/Mesh X!\n%_/DeviceGray /CS X#\n{corners}%_/E X#\n/End X!\n");
|
||||
let mask = "%_/ArtDictionary :\n%_/Mask :\n%_X=\n%_u\n%_1 g\n%_10 20 m\n%_10 50 L\n%_40 50 L\n%_40 20 L\n%_f\n%_U\n%_X+\n\
|
||||
%_(Art) ,\n%_1 /Bool (Clipping) ,\n%_1 /Bool (Linked) ,\n%_; (AI9_Mask) ,\n%_;\n";
|
||||
let next = format!("0 1 0 0 0 Xy\n{RED}{}f\n", rect_path(60.0, 20.0, 80.0, 40.0));
|
||||
for (state, expected) in [("1 0.5 0 0 0 Xy\n", (0.5, BlendMode::Multiply)), ("", (1.0, BlendMode::Overlay))] {
|
||||
let a = art(&read_ok(&layer(&format!("3 1 0 0 0 Xy\n{mesh}{mask}{state}0 0 Xd\n6 () XW\n{next}"))).doc);
|
||||
assert_eq!(a.len(), 2, "{a:?}");
|
||||
assert!(matches!(a[0].kind, NodeKind::Mesh(_)) && a[0].extras().mask.is_some(), "{:?}", a[0]);
|
||||
assert_eq!((a[0].opacity, a[0].blend), expected, "{state:?}: the state after the mask is the mesh's");
|
||||
assert_eq!((a[1].opacity, a[1].blend), (1.0, BlendMode::Normal), "the next object's own state");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gradients() {
|
||||
let defs = "%AI5_BeginGradient: (G)\n(G) 1 2 Bd\n[\n<00FF>\n0\n0 0 0 0 1 0 0 2 1 6 %_BS\n\
|
||||
|
||||
Reference in New Issue
Block a user