mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 13:17:19 -06:00
Merge pull request #4755 from mockydevs/fix/eps-group-opacity-after-look
EPS import: keep a layer's opacity when its state block has the stroke style
This commit is contained in:
@@ -72,13 +72,7 @@ impl<'a> Reader<'a> {
|
||||
"X+" => return Ok(()),
|
||||
_ => {}
|
||||
}
|
||||
// What follows an object (its name, transparency and style) ends with anything else. The
|
||||
// state block of a layer whose art ended (`Xy`, `Xd`, `XW`, then `LB`) is read at the `LB`.
|
||||
if !matches!(w, "Xy" | "Xd" | "XW" | "BB" | "LB") {
|
||||
self.after_object = false;
|
||||
self.layer_state = None;
|
||||
self.mesh_undo = None;
|
||||
}
|
||||
self.state_block_continues(w);
|
||||
let group_ended = std::mem::take(&mut self.group_ended);
|
||||
let mesh_ended = std::mem::take(&mut self.mesh_ended);
|
||||
let vals = self.operands();
|
||||
@@ -396,6 +390,22 @@ impl<'a> Reader<'a> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Operator `w` ends what follows an object (its name, transparency and style) unless it is part
|
||||
/// 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`).
|
||||
fn state_block_continues(&mut self, w: &str) {
|
||||
if matches!(w, "Xy" | "Xd" | "XW" | "BB" | "LB") {
|
||||
return;
|
||||
}
|
||||
self.after_object = false;
|
||||
if !matches!(w, "J" | "j" | "w" | "M" | "d" | "XR") {
|
||||
self.layer_state = None;
|
||||
self.mesh_undo = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// `mode opacity isolate knockout shape Xy`; after a group's end, the group's too, 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`).
|
||||
@@ -443,6 +453,10 @@ impl<'a> Reader<'a> {
|
||||
let code = Self::nums(vals).last().copied().unwrap_or(0.0);
|
||||
let style = vals.iter().rev().find_map(V::text).unwrap_or_default();
|
||||
let after = std::mem::take(&mut self.after_object);
|
||||
// An object's own state block ends `6 () XW` (a layer's `7 () XW`): an `Xy` before it isn't the layer's.
|
||||
if code == 6.0 {
|
||||
self.layer_state = None;
|
||||
}
|
||||
if code == 9.0
|
||||
&& let Some(look) = self
|
||||
.frames
|
||||
|
||||
@@ -407,6 +407,8 @@ fn a_layer_keeps_its_own_transparency() {
|
||||
("1 0.35 0 0 0 Xy\n", (0.35, BlendMode::Multiply), (0.8, BlendMode::Normal)),
|
||||
("1 0.35 0 0 0 Xy\n0 0 Xd\n7 () XW\n", (0.35, BlendMode::Multiply), (0.8, BlendMode::Normal)),
|
||||
("1 0.35 0 0 0 Xy\n0 0 Xd\n6 () XW\n", (1.0, BlendMode::Normal), (0.35, BlendMode::Multiply)),
|
||||
// The stroke style in the layer's state block.
|
||||
("1 0.35 0 0 0 Xy\n0 J 0 j 1 w 10 M []0 d\n0 XR\n0 0 Xd\n7 () XW\n", (0.35, BlendMode::Multiply), (0.8, BlendMode::Normal)),
|
||||
] {
|
||||
let doc = read_ok(&layer(&format!("{mesh}{after}"))).doc;
|
||||
let l = &doc.layers[0];
|
||||
@@ -416,6 +418,28 @@ fn a_layer_keeps_its_own_transparency() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_layers_state_keeps_through_the_stroke_style_written_in_it() {
|
||||
// GUARDS: a layer's state block can have the stroke style between its `Xy` and `Xd`; read as
|
||||
// the art moving on, it lost the layer's opacity and the layer drew opaque.
|
||||
let state = "0 0.25 0 2 0 Xy\n0 J 0 j 1 w 10 M []0 d\n0 XR\n0 0 Xd\n7 () XW\n";
|
||||
let clip_group = format!(
|
||||
"q\n{}h\nW\nn\n0 Ae\nu\n{RED}{}f\nU\n9 () XW\nQ\n9 () XW\n{state}",
|
||||
rect_path(10.0, 10.0, 90.0, 90.0),
|
||||
rect_path(0.0, 0.0, 100.0, 100.0)
|
||||
);
|
||||
let path = format!("{RED}{}f\n{state}", rect_path(0.0, 0.0, 10.0, 10.0));
|
||||
for body in [clip_group, path] {
|
||||
let doc = read_ok(&layer(&body)).doc;
|
||||
let l = &doc.layers[0];
|
||||
assert_eq!((l.opacity, l.knockout), (0.25, Knockout::Neutral), "{body}");
|
||||
assert_eq!(art(&doc)[0].opacity, 1.0, "{body}");
|
||||
}
|
||||
// An object's own state block (`6 () XW`) isn't the layer's.
|
||||
let note = format!("{RED}{}f\n{}", rect_path(0.0, 0.0, 10.0, 10.0), state.replace("7 () XW", "6 () XW"));
|
||||
assert_eq!(read_ok(&layer(¬e)).doc.layers[0].opacity, 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transparency_of_objects_and_groups() {
|
||||
let art_ = format!(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# File format parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (EPS and `.ai` import: layers whose type is in the legacy format, beside a page with text) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (`.ai`/EPS editing data: a layer's state block with the stroke style in it keeps the layer's opacity) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
Every format Illustrator opens, places, saves or exports, against VectorCraft's format table (`FORMATS`,
|
||||
`OPEN_EXTS`, `PLACE_EXTS` in `crates/engine/src/cmd/fileio/formats.rs`). Illustrator's list comes from its public
|
||||
@@ -132,6 +132,7 @@ the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPri
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-30 | minor | `.ai`/EPS editing data: a layer's own state block (`Xy`, `Xd`, `7 () XW` before `LB`) can have the stroke style (`J`, `j`, `w`, `M`, `d`, `XR`) between its `Xy` and `Xd`; read as the art moving on, it lost the layer's opacity, so a layer at 17% drew opaque over the art under it and the file came in as its page. When the layer's last object is a gradient mesh, the mesh keeps its own state through the stroke style too (#4748). An object's own state block (`6 () XW`) still isn't the layer's. Of 408 users' files whose editing data was checked, 3 have such a block: an EPS that now opens from its layers 0.5% from its page (it was 18.8%), one whose layer now draws at 85% as its page does, and one whose layers are all at 100%; the others draw as before. Test `import::ai::tests::a_layers_state_keeps_through_the_stroke_style_written_in_it`; scores unchanged |
|
||||
| 2026-10-30 | minor | EPS and `.ai` import: editing data whose type is in the legacy format (`To` … `TO`, as an EPS saved for Illustrator 10 keeps it, with no `/AI11Text` for the page's type to go in) is read through its layers when the page has text: its text objects are the layers' own, and the comparison with the page says whether the layers draw it. Before, such a file came in as its page ("its layers have no text objects to put it in"). Type the editing data keeps only as outlines (what the legacy format can't hold) stays outlines, with a note counting the page's text objects that are; a file whose layers have no text object at all still comes in as its page. Test `import::tests_native::type_in_the_legacy_format_is_the_layers_own_and_the_page_has_text`; checked locally against a user's files (of 6 such EPS that came in as their page, 3 now come in through their layers, within the comparison's limits; 1 is still refused by the comparison for art that isn't type, and 2 have their type only as outlines; no change in the other 284 EPS with editing data and 117 `.ai` files checked); scores unchanged |
|
||||
| 2026-10-30 | minor | `.ai`/EPS editing data: an `Xy` right after a gradient mesh's `/End X!` is the mesh's only when its own `6 () XW` follows; when the layer's `LB` follows (directly or after the layer's `Xd` and `7 () XW`) it is the layer's, as before #4744, and the mesh keeps the state it had. Test `import::ai::tests::a_layer_keeps_its_own_transparency`; scores unchanged |
|
||||
| 2026-10-30 | minor | `.ai`/EPS editing data: type on a path is spaced round curves as the file's own layout spaces it, closer round the outside and further apart round the inside, though its frame keeps no spacing: the Type on a Path Options › Spacing that does that is fitted from the layout's glyph positions, where it came in at the font's advances and ran on past where the page ends it. In 8 files of a user's set with type round curves (19 stories), the fitted spacing is a fifth of the line's ascent outside a curve (17 stories) and half of it inside (2), and the 4 files whose pages were measured space their glyphs as their layouts do. Own-fixture tests in `import::tests_native` and `import::ate`; scores unchanged |
|
||||
|
||||
Reference in New Issue
Block a user