mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 13:17:19 -06:00
.ai/EPS import: a blend takes the transparency written after its record
Squashed from #4757 onto main.
This commit is contained in:
@@ -403,6 +403,8 @@ impl<'a> Reader<'a> {
|
||||
self.warn(crate::import::ai::blend::DRAWN);
|
||||
}
|
||||
}
|
||||
// The blend's own state block follows its record, as a group's follows its end.
|
||||
self.after_object = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -406,9 +406,10 @@ impl<'a> Reader<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// `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`).
|
||||
/// `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`).
|
||||
fn transparency(&mut self, vals: &[V], mesh_ended: bool) {
|
||||
let n = Self::nums(vals);
|
||||
let [mode, opacity, rest @ ..] = n.as_slice() else { return };
|
||||
@@ -430,7 +431,7 @@ impl<'a> Reader<'a> {
|
||||
(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 { .. })) {
|
||||
} else if self.last_object().is_some_and(|n| matches!(n.kind, NodeKind::Group { .. } | NodeKind::Compound { .. } | NodeKind::Blend { .. })) {
|
||||
if let Some(n) = self.last_object() {
|
||||
n.opacity = gs.opacity;
|
||||
n.blend = gs.blend;
|
||||
|
||||
@@ -1969,6 +1969,25 @@ fn a_blend_it_cant_read_keeps_its_drawn_steps() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A blend's transparency (here knockout on, as blends are written) is the state block after its
|
||||
/// record, as a group's follows its end: the live blend's, or its drawn steps' where they stay. It
|
||||
/// is the blend's own, not the next object's.
|
||||
#[test]
|
||||
fn a_blends_transparency_follows_its_record() {
|
||||
let state = "0 0.6 0 1 0 Xy\n0 0 Xd\n6 () XW\n";
|
||||
let next = format!("0 1 0 0 0 Xy\n{RED}{}f\n", rect_path(150.0, 40.0, 170.0, 60.0));
|
||||
let straight = "%_10 50 m\n%_110 50 L\n";
|
||||
let garbled = "(Adobe Path Blends) 1 0 16 XP\n%00112233445566778899AABBCCDDEEFF\n".to_string();
|
||||
for (record, live) in [(blend_record(&['A', 'B']), true), (garbled, false)] {
|
||||
let body = format!("{}{state}{next}", blend_file(straight, [Some("A"), Some("B")], &record));
|
||||
let a = art(&read_ok(&layer(&body)).doc);
|
||||
assert_eq!(a.len(), 2, "{a:?}");
|
||||
assert_eq!(matches!(a[0].kind, NodeKind::Blend { .. }), live, "{:?}", a[0].kind);
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/// The blend is live: moving a key moves the steps between it and the other key with it.
|
||||
#[test]
|
||||
fn moving_a_blends_key_moves_its_steps() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# File format parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (`.ai`/EPS editing data: a gradient mesh with an opacity mask takes the transparency written after the mask) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (`.ai`/EPS editing data: a blend takes the transparency written after its record) · **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 blend takes the transparency (opacity, blend mode, knockout; blends are written with knockout on) in the state block after its record, as a group takes the one after its end; it was left for the next object, so a blend's steps of falling opacity drew over each other instead of knocking each other out, and every such blend came in too dark (a user's file has 143, its bubbles came in as dark rings). Both a live blend and drawn steps kept as a group take it. Test `import::ai::tests::a_blends_transparency_follows_its_record`; checked locally against that file: it comes 1.3% → 0.6% nearer its page, though a mesh seen through a mesh-shaped opacity mask still keeps it just over the limit; scores unchanged |
|
||||
| 2026-10-30 | minor | `.ai`/EPS editing data: a gradient mesh with an opacity mask takes the transparency (`Xy`) written after the mask's dictionary, as one without a mask takes the one right after its `/End X!`; the operators of the mask's art (on `%_` lines) hid it, 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 (a Multiply shading drawn Normal, an Overlay tint drawn Multiply over the art below). Test `import::ai::tests::a_masked_gradient_mesh_takes_the_transparency_written_after_its_mask`; checked locally against a user's files: one opens from its layers 0.1% from its page (it was 0.6%, just inside the limit), another comes 1.6% → 1.3% nearer; scores unchanged |
|
||||
| 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 |
|
||||
|
||||
Reference in New Issue
Block a user