mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 13:47:21 -06:00
fix(eps): a layer's state after its last blend stays the layer's
#4757 gives a blend the Xy written after its record. When the blend is a layer's last object, the layer's own state block (Xy, Xd, 7 () XW, LB) went to the blend instead, and the layer stayed opaque. The blend's state is now tentative like a mesh's (#4748): its own 6 () XW confirms it, the layer's LB gives it to the layer and the blend keeps what it had. Also records the .ai/EPS audit fixes under G9, the File formats row and M4.
This commit is contained in:
@@ -188,8 +188,8 @@ struct Reader<'a> {
|
||||
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)>,
|
||||
/// The state a mesh had before an `Xy` right after its end, which is the layer's instead when
|
||||
/// the layer's `LB` follows rather than the mesh's own `6 () XW`.
|
||||
/// The state a mesh or blend had before an `Xy` right after its end, which is the layer's
|
||||
/// instead when the layer's `LB` follows rather than their own `6 () XW`.
|
||||
mesh_undo: Option<(f32, BlendMode, bool, Knockout)>,
|
||||
/// Painted copies of clipping paths that have a fill (drawn under what they clip).
|
||||
clip_fills: HashSet<NodeId>,
|
||||
|
||||
@@ -174,9 +174,9 @@ impl<'a> Reader<'a> {
|
||||
pub(super) fn end_layer(&mut self) -> Result<(), String> {
|
||||
let mut f = self.close("LB", |k| matches!(k, Kind::Layer(_)))?;
|
||||
let state = self.layer_state.take();
|
||||
// An `Xy` right after the layer's last mesh, then `LB`: the layer's, not the mesh's.
|
||||
// An `Xy` right after the layer's last mesh or blend, then `LB`: the layer's, not theirs.
|
||||
if let (Some(_), Some(before)) = (state, self.mesh_undo.take())
|
||||
&& let Some(mesh) = f.children.last_mut().filter(|n| matches!(n.kind, NodeKind::Mesh(_)))
|
||||
&& let Some(mesh) = f.children.last_mut().filter(|n| matches!(n.kind, NodeKind::Mesh(_) | NodeKind::Blend { .. }))
|
||||
{
|
||||
(mesh.opacity, mesh.blend, mesh.isolate, mesh.knockout) = before;
|
||||
}
|
||||
|
||||
@@ -394,7 +394,7 @@ impl<'a> Reader<'a> {
|
||||
/// of a state block.
|
||||
/// The state block of a layer whose art ended (`Xy`, `Xd`, `XW`, then `LB`) is read at the
|
||||
/// `LB`, and can have the stroke style between its `Xy` and `Xd`: that keeps the layer's state,
|
||||
/// and what a mesh before it had (`mesh_undo`).
|
||||
/// and what a mesh or blend before it had (`mesh_undo`).
|
||||
fn state_block_continues(&mut self, w: &str) {
|
||||
if matches!(w, "Xy" | "Xd" | "XW" | "BB" | "LB") {
|
||||
return;
|
||||
@@ -406,10 +406,9 @@ impl<'a> Reader<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// `mode opacity isolate knockout shape Xy`; after a group's end, the group's too, after a
|
||||
/// blend's record the blend's, and right after a gradient mesh's `/End X!` (before its `Xd` and
|
||||
/// `6 () XW`), the mesh's, unless the layer's `LB` follows: then it is the layer's (see
|
||||
/// `end_layer`).
|
||||
/// `mode opacity isolate knockout shape Xy`; after a group's end, the group's too, and right
|
||||
/// after a gradient mesh's `/End X!` or a blend's record (before their `Xd` and `6 () XW`),
|
||||
/// theirs, unless the layer's `LB` follows: then it is the layer's (see `end_layer`).
|
||||
fn transparency(&mut self, vals: &[V], mesh_ended: bool) {
|
||||
let n = Self::nums(vals);
|
||||
let [mode, opacity, rest @ ..] = n.as_slice() else { return };
|
||||
@@ -425,13 +424,15 @@ impl<'a> Reader<'a> {
|
||||
gs.knockout_shape = rest.get(2).is_some_and(|v| *v != 0.0);
|
||||
let gs = self.gs.clone();
|
||||
let in_layer = matches!(self.frames.last(), Some(Frame { kind: Kind::Layer(_), .. }));
|
||||
if let Some(n) = self.last_object().filter(|n| mesh_ended && matches!(n.kind, NodeKind::Mesh(_))) {
|
||||
// The mesh's, or the layer's when it is the layer's last object: kept for either.
|
||||
if let Some(n) =
|
||||
self.last_object().filter(|n| (mesh_ended && matches!(n.kind, NodeKind::Mesh(_))) || matches!(n.kind, NodeKind::Blend { .. }))
|
||||
{
|
||||
// The mesh's or blend's, or the layer's when it is the layer's last object: kept for either.
|
||||
let before = (n.opacity, n.blend, n.isolate, n.knockout);
|
||||
(n.opacity, n.blend, n.isolate, n.knockout) = (gs.opacity, gs.blend, gs.isolate, gs.knockout);
|
||||
self.mesh_undo = Some(before);
|
||||
self.layer_state = in_layer.then_some((gs.opacity, gs.blend, gs.isolate, gs.knockout));
|
||||
} else if self.last_object().is_some_and(|n| matches!(n.kind, NodeKind::Group { .. } | NodeKind::Compound { .. } | NodeKind::Blend { .. })) {
|
||||
} else if self.last_object().is_some_and(|n| matches!(n.kind, NodeKind::Group { .. } | NodeKind::Compound { .. })) {
|
||||
if let Some(n) = self.last_object() {
|
||||
n.opacity = gs.opacity;
|
||||
n.blend = gs.blend;
|
||||
|
||||
@@ -1986,6 +1986,14 @@ fn a_blends_transparency_follows_its_record() {
|
||||
assert_eq!((a[0].opacity, a[0].knockout), (0.6, Knockout::On), "live: {live}");
|
||||
assert_eq!(a[1].opacity, 1.0, "the next object's own state");
|
||||
}
|
||||
// A layer whose last object is a blend: a state block that ends `7 () XW` before `LB` is the
|
||||
// layer's, and the blend keeps the state it had.
|
||||
let body = format!("{}1 0.35 0 0 0 Xy\n0 0 Xd\n7 () XW\n", blend_file(straight, [Some("A"), Some("B")], &blend_record(&['A', 'B'])));
|
||||
let doc = read_ok(&layer(&body)).doc;
|
||||
assert_eq!((doc.layers[0].opacity, doc.layers[0].blend), (0.35, BlendMode::Multiply), "the layer's state");
|
||||
let a = art(&doc);
|
||||
assert!(matches!(a[0].kind, NodeKind::Blend { .. }), "{:?}", a[0].kind);
|
||||
assert_eq!((a[0].opacity, a[0].blend), (1.0, BlendMode::Normal), "the blend's own state");
|
||||
}
|
||||
|
||||
/// The blend is live: moving a key moves the steps between it and the other key with it.
|
||||
|
||||
Reference in New Issue
Block a user