diff --git a/crates/eps/src/ai_editing/tests.rs b/crates/eps/src/ai_editing/tests.rs index 4779afaa27..ce64e56fd5 100644 --- a/crates/eps/src/ai_editing/tests.rs +++ b/crates/eps/src/ai_editing/tests.rs @@ -1187,6 +1187,28 @@ fn a_symbol_definition_is_written_only_with_room_for_its_instance() { } } +/// A symbol definition without room for its group, asked for by an instance in art drawn for an +/// object (a stained instance's art), is left out and leaves the writing of that art as it was. +#[test] +fn a_symbol_definition_without_room_in_drawn_art_is_left_out() { + let mut doc = Document::new(200.0, 100.0); + let layer = doc.layers[0].id; + let dot = square(&mut doc, Rect::new(0.0, 0.0, 10.0, 10.0), Color::gray(0.5)); + doc.symbols.push(Symbol { dynamic: false, name: "Dot".into(), art: Arc::new(dot) }); + let inner = instance(&mut doc, "Dot", Affine::IDENTITY); + doc.symbols.push(Symbol { dynamic: false, name: "Badge".into(), art: Arc::new(inner) }); + let mut stained = instance(&mut doc, "Badge", Affine::translate((50.0, 10.0))); + stained.appearance = Appearance::basic(solid(Color::rgb(1.0, 0.0, 0.0)), Paint::None, 0.0).into(); + doc.insert(Some(layer), usize::MAX, stained).unwrap(); + let read = crate::import::objects_read(&export_ai_editing(&doc, &opts()).unwrap().program).unwrap(); + for max in 0..read { + let out = export_within(&doc, &opts(), max).unwrap(); + assert!(out.warnings.iter().any(|w| w == writer::OBJECT_LIMIT), "room for {max} of {read}"); + let objects = crate::import::objects_read(&out.program).unwrap(); + assert!(objects <= max, "{objects} objects read with room for {max}"); + } +} + /// The writer's limit is the reader's, which its warning names. #[test] fn the_object_limit_is_the_readers() { @@ -10563,14 +10585,15 @@ fn text_wrap_options_of_type_and_envelopes_are_read_back() { } /// A clipping path inside art cut to a clipping object is written as its fills and its strokes, -/// two objects: its text wrap options are written once, whether it has both or only strokes. +/// two objects: its text wrap options are written once, whether it has both or only strokes, and +/// with its strokes when the region outside leaves nothing of its fills. #[test] fn a_clipping_path_written_as_its_paint_keeps_one_text_wrap() { - for fill in [solid(Color::gray(0.5)), Paint::None] { + for (fill, left, width) in [(solid(Color::gray(0.5)), 20.0, 2.0), (Paint::None, 20.0, 2.0), (solid(Color::gray(0.5)), 101.0, 4.0)] { let mut doc = Document::new(200.0, 100.0); let layer = doc.layers[0].id; - let mut cutter = clipping(&mut doc, shapes::rectangle(Rect::new(20.0, 20.0, 80.0, 80.0))); - cutter.appearance = Appearance::basic(fill, solid(Color::BLACK), 2.0).into(); + let mut cutter = clipping(&mut doc, shapes::rectangle(Rect::new(left, 20.0, left + 60.0, 80.0))); + cutter.appearance = Appearance::basic(fill, solid(Color::BLACK), width).into(); let cutter = wrapped(cutter, 5.0, true); let member = square(&mut doc, Rect::new(30.0, 30.0, 120.0, 60.0), Color::rgb(1.0, 0.0, 0.0)); let inner = group(&mut doc, vec![cutter, member], true); diff --git a/crates/eps/src/ai_editing/writer.rs b/crates/eps/src/ai_editing/writer.rs index 64583d3949..6361efe5ff 100644 --- a/crates/eps/src/ai_editing/writer.rs +++ b/crates/eps/src/ai_editing/writer.rs @@ -1102,22 +1102,25 @@ impl<'a> Writer<'a> { /// A container's members, the first one's outline cutting the others: its fills painted /// behind them (cut to the region outside too), its strokes over them, not cut by it. Its text - /// wrap options are written once: with its fills, or without fills with its strokes. + /// wrap options are written once: with its fills, or with its strokes when no fill object is + /// written (it has no fills, or the region outside leaves nothing of them). fn clip_contents(&mut self, children: &[Arc]) { let Some((first, rest)) = children.split_first() else { return }; // Nothing to clip by hides the clipped art. let Some((outline, rule)) = vectorcraft_effects::clip_outline(first) else { return }; let mut paint = first.clip_paint(); - if let (Some(_), Some(stroke)) = (&paint.fill, &mut paint.stroke) - && stroke.extras().wrap.is_some() - { - stroke.extras_mut().wrap = None; - } let outer = self.clip.clone(); let region = self.cut(&PathData::from_bezpath(&outline), rule); self.clip = Some(region); if let Some(fill) = &paint.fill { + let objects = self.objects; self.node(fill); + if self.objects > objects + && let Some(stroke) = &mut paint.stroke + && stroke.extras().wrap.is_some() + { + stroke.extras_mut().wrap = None; + } } for c in rest { self.node(c); diff --git a/crates/eps/src/ai_editing/writer/symbols.rs b/crates/eps/src/ai_editing/writer/symbols.rs index d058c9b770..0c8d2c7d3b 100644 --- a/crates/eps/src/ai_editing/writer/symbols.rs +++ b/crates/eps/src/ai_editing/writer/symbols.rs @@ -233,6 +233,7 @@ impl Writer<'_> { if !self.take_objects(1) { self.defining_symbols.pop(); self.keep_since(before); + (self.in_look, self.drawn) = (in_look, drawn); return false; } // The art is written in the symbol's space, from the state `0 A` and `0 Xw` set, from the