mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
#2405: an envelope's mesh in an .ai isn't read as art
An envelope is written on `%_` lines as a group holding the mesh that shapes it, with the distortion (`AIDeformStyle`, `DeformValue`) in the group's dictionary; the distorted art the PDF part draws follows on plain lines. The reader kept that group, so the mesh drew as a grey gradient mesh behind the art. A group on `%_` lines that holds only meshes and carries a distortion is now dropped; a mesh with no distortion is still art. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6znVFRqGqvB9f7njwZ46r
This commit is contained in:
committed by
bflatastic
co-authored by
Claude Opus 5.5
parent
3f81c70ade
commit
1946ffca9c
@@ -1434,6 +1434,29 @@ impl<'a> Reader<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop the object just read when it is an envelope's own mesh: a group on `%_` lines holding
|
||||
/// only gradient meshes. Whether it was dropped.
|
||||
fn drop_envelope_mesh(&mut self) -> bool {
|
||||
fn only_meshes(n: &Node) -> bool {
|
||||
match &n.kind {
|
||||
NodeKind::Mesh(_) => true,
|
||||
NodeKind::Group { children, .. } => !children.is_empty() && children.iter().all(|c| only_meshes(c)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
let Some(n) = self.last_object() else { return false };
|
||||
let id = n.id;
|
||||
if !matches!(n.kind, NodeKind::Group { .. }) || !only_meshes(n) || !self.hidden_ids.contains(&id) {
|
||||
return false;
|
||||
}
|
||||
if let Some(f) = self.frames.last_mut() {
|
||||
f.children.pop();
|
||||
}
|
||||
self.hidden_ids.remove(&id);
|
||||
self.after_object = false;
|
||||
true
|
||||
}
|
||||
|
||||
/// Do the layers being read show?
|
||||
fn shown(&self) -> bool {
|
||||
self.frames.iter().all(|f| !matches!(&f.kind, Kind::Layer(a) if !a.visible))
|
||||
@@ -1442,6 +1465,12 @@ impl<'a> Reader<'a> {
|
||||
/// An object's dictionary: its name (`AIArtName`, or the XML id `AI10_ArtUID`), and the
|
||||
/// opacity mask it may carry.
|
||||
fn art_dictionary(&mut self, o: &Obj) {
|
||||
// An envelope (a distortion, `AIDeformStyle` / `DeformValue`) is written on `%_` lines as
|
||||
// a group holding the mesh that shapes it; the distorted art the file's PDF part draws
|
||||
// follows on plain lines. That mesh isn't art, so it goes (#2405).
|
||||
if (o.get("AIDeformStyle").is_some() || o.get("DeformValue").is_some()) && self.drop_envelope_mesh() {
|
||||
return;
|
||||
}
|
||||
if let Some(mask) = o.obj("AI9_Mask").or_else(|| o.obj("Mask")) {
|
||||
if let Some(art) = &mask.art {
|
||||
let on = |key: &str| mask.nums(key).first().copied() == Some(1.0);
|
||||
|
||||
@@ -1226,6 +1226,46 @@ fn a_styled_object_keeps_the_transparency_written_on_its_look() {
|
||||
assert_eq!((a[0].opacity, a[0].blend), (0.6, BlendMode::Multiply));
|
||||
}
|
||||
|
||||
/// An envelope is written on `%_` lines as a group holding the mesh that shapes it, with its
|
||||
/// distortion in the group's dictionary; the distorted art follows on plain lines. Only that art
|
||||
/// is drawn, as in the file's PDF part, not the envelope's mesh (#2405).
|
||||
#[test]
|
||||
fn an_envelope_mesh_is_not_read_as_art() {
|
||||
let corner = |x: i32, y: i32, next: (i32, i32), prev: (i32, i32)| {
|
||||
format!("%_[0.82 0.83 0.83 1 {x} {y} {} {} 1 {} {} 1] /N X#\n", next.0, next.1, prev.0, prev.1)
|
||||
};
|
||||
let mesh = format!(
|
||||
"%_/Mesh X!\n%_(DeviceRGB) /CS X#\n%_[0 0] /P X#\n{}{}{}{}%_0 /E X#\n%_/End X!\n",
|
||||
corner(0, 0, (0, 10), (10, 0)),
|
||||
corner(0, 40, (10, 40), (0, 30)),
|
||||
corner(40, 40, (40, 30), (30, 40)),
|
||||
corner(40, 0, (30, 0), (40, 10)),
|
||||
);
|
||||
let envelope = |dictionary: &str| {
|
||||
format!(
|
||||
"%_0 Ae\n%_u\n{mesh}%_0 0 Xd\n%_6 () XW\n%_U\n%_/ArtDictionary :\n{dictionary}%_;\n%_\n%_0 0 Xd\n%_6 () XW\n{RED}{}f\n",
|
||||
rect_path(0.0, 0.0, 40.0, 40.0)
|
||||
)
|
||||
};
|
||||
let has_mesh = |n: &Node| -> bool {
|
||||
fn walk(n: &Node) -> bool {
|
||||
matches!(n.kind, NodeKind::Mesh(_)) || n.children().is_some_and(|c| c.iter().any(|k| walk(k)))
|
||||
}
|
||||
walk(n)
|
||||
};
|
||||
|
||||
let warped = envelope("%_(Warp: Arch) /UnicodeString (DisplayString) ,\n%_0.27 /Real (DeformValue) ,\n%_1 /Int (AIDeformStyle) ,\n");
|
||||
let doc = read_ok(&layer(&warped)).doc;
|
||||
let a = art(&doc);
|
||||
assert!(!a.iter().any(|n| has_mesh(n)), "{a:#?}");
|
||||
assert_eq!(a.len(), 1, "{a:#?}");
|
||||
assert_eq!(fill(&a[0]).color(), Some(Color::rgb(1.0, 0.0, 0.0)));
|
||||
|
||||
// A mesh in a group with no distortion is still art.
|
||||
let doc = read_ok(&layer(&envelope(""))).doc;
|
||||
assert!(art(&doc).iter().any(|n| has_mesh(n)), "{:#?}", art(&doc));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_gradient_mesh_reads_as_a_patch() {
|
||||
let corner = |rgb: &str, x: i32, y: i32, next: (i32, i32), prev: (i32, i32)| {
|
||||
|
||||
Reference in New Issue
Block a user