diff --git a/crates/eps/src/import/ai/envelope.rs b/crates/eps/src/import/ai/envelope.rs index 3a45bf3a1..461be66fc 100644 --- a/crates/eps/src/import/ai/envelope.rs +++ b/crates/eps/src/import/ai/envelope.rs @@ -131,6 +131,25 @@ fn saved_margin(o: &Obj, content: &[Arc]) -> Option<[f32; 4]> { envelope_stroke_margin(stroked, plain) } +/// Envelope `kind` mapping from its contents' bounds with their strokes, for an envelope with +/// Distort Appearance whose contents have strokes it doesn't map from already; `None` otherwise. +fn with_strokes(kind: &NodeKind) -> Option { + let NodeKind::Envelope { content, options, .. } = kind else { return None }; + if !options.distort_appearance { + return None; + } + let (stroked, plain) = envelope_content_boxes(content, Affine::IDENTITY)?; + let margin = envelope_stroke_margin(stroked, plain)?; + if margin == options.bounds_margin || margin.iter().any(|m| *m < 0.0) || margin.iter().all(|m| *m == 0.0) { + return None; + } + let mut kind = kind.clone(); + if let NodeKind::Envelope { options, .. } = &mut kind { + options.bounds_margin = margin; + } + Some(kind) +} + /// A warp: its style, bend, distortions and orientation. fn warp(o: &Obj) -> Option { let style = whole(o, "DeformStyle")??; @@ -479,10 +498,11 @@ impl WarpedType { /// type's). The live envelope takes its drawn art's place and the hidden type goes. A warp that /// doesn't draw as its drawn art is checked again as a mesh envelope on the grid the file saves /// for it ([`live_on_saved_grid`]), which opens in its place where it does: its type stays - /// editable in that shape, and the warp's style and bend are lost. Each check uses one of the - /// import's checks and, for an envelope that shows, adds to what the page check allows - /// ([`drawn_alike`]). Elsewhere, and where the type is set in a font that isn't available, the - /// drawn art and the hidden type stay. + /// editable in that shape, and the warp's style and bend are lost. With Distort Appearance, each + /// is also checked mapping from its contents' bounds with their strokes ([`with_strokes`]). Each + /// check uses one of the import's checks and, for an envelope that shows, adds to what the page + /// check allows ([`drawn_alike`]). Elsewhere, and where the type is set in a font that isn't + /// available, the drawn art and the hidden type stay. pub(crate) fn settle_envelopes(&mut self, doc: &mut Document, type_of: impl Fn(&Document, NodeId) -> Option<(NodeId, Node)>) -> TypedSettled { let mut settled = TypedSettled::default(); if self.envelopes.is_empty() { @@ -537,7 +557,17 @@ impl WarpedType { // A warp that VectorCraft's own warp doesn't draw as its drawn art may still draw as the // grid the file saves for it: then it opens as a mesh envelope on that grid. let on_grid = if is_warp(&obj) { live_on_saved_grid(&obj, before.clone(), inner.clone()) } else { None }; - let kinds: Vec = live(&obj, before, inner).into_iter().chain(on_grid).collect(); + // With Distort Appearance, a file's envelope may map its contents from their bounds with + // their strokes or without them, and for type the size the file saves for that box is often + // out of date (not the size of the type there now): each is checked. + let kinds: Vec = live(&obj, before, inner) + .into_iter() + .chain(on_grid) + .flat_map(|k| { + let stroked = with_strokes(&k); + std::iter::once(k).chain(stroked) + }) + .collect(); if kinds.is_empty() { return Err(TypeKept::Read); } diff --git a/crates/eps/src/import/tests_native.rs b/crates/eps/src/import/tests_native.rs index e1b4f68e8..e8ad5a3f5 100644 --- a/crates/eps/src/import/tests_native.rs +++ b/crates/eps/src/import/tests_native.rs @@ -5763,6 +5763,45 @@ fn a_warp_of_type_opens_as_a_mesh_on_its_saved_grid_and_keeps_its_shape_when_its } } +/// A warp of stroked type with Distort Appearance whose drawn art maps the type's bounds with its +/// strokes onto the grid the file saves (not its glyphs' bounds alone) opens live as a mesh envelope +/// mapping from that box (#3179). +#[test] +fn a_warp_of_stroked_type_maps_from_its_bounds_with_strokes_where_its_drawn_art_does() { + use vectorcraft_doc::live::{envelope_content_boxes, envelope_stroke_margin}; + let Some(family) = installed_family() else { return }; + // "FLUX" stroked 4 points wide in black. + let stroked = "/53 << /99 /CAITextPaint /0 << /0 2 /1 [ 1.0 0.0 1.0 0.0 0.0 ] >> >> /54 << /99 /CAITextPaint /0 << /0 2 /1 [ 1.0 0.0 0.0 0.0 1.0 ] >> >> /57 true /63 4.0"; + let doc = type_envelope_text(&family, None).replace("/53 << /99 /CAITextPaint /0 << /0 2 /1 [ 1.0 0.0 1.0 0.0 0.0 ] >> >>", stroked); + let mut text = made_envelope_type(&doc); + text.visible = true; + assert!(matches!(&text.kind, NodeKind::Text(t) if t.runs[0].style.stroke_width == 4.0), "{text:?}"); + let content = vec![std::sync::Arc::new(text)]; + let (with, without) = envelope_content_boxes(&content, kurbo::Affine::IDENTITY).unwrap(); + let mut options = vectorcraft_doc::live::EnvelopeOptions::NEW; + options.bounds_margin = envelope_stroke_margin(with, without).unwrap(); + assert!(options.bounds_margin.iter().all(|m| *m > 0.0), "{options:?}"); + let shaped = Node::new( + vectorcraft_doc::NodeId(1_000_000), + NodeKind::Envelope { content, kind: Box::new(rising_kind()), fidelity: 50.0, editing: false, options, frame: Box::default() }, + ); + let (drawn, page) = drawn_envelope(&shaped); + let art = + native_with_text(&layer("Art", true, &format!("{}\n{}", square(10, 10), type_envelope_on(&arc_keys(0.3), &rising_grid(), &drawn, ""))), &doc); + let r = import(&eps(&format!("{}\n{page}", page_square(10, 10)), &art)).unwrap(); + assert_eq!(names(&r.document), ["Art"], "{:?}", r.warnings); + let (envelopes, loose) = envelopes_and_type(&r.document); + assert_eq!(envelopes, [vec!["FLUX".to_string()]], "{:?}", r.warnings); + assert!(loose.is_empty(), "{loose:?}"); + let mut margin = None; + r.document.walk(|n| { + if let NodeKind::Envelope { options, .. } = &n.kind { + margin = Some(options.bounds_margin); + } + }); + assert_eq!(margin, Some(options.bounds_margin)); +} + /// A warp of type whose editing data saves no grid it can be shaped by keeps its drawn art and its /// type, hidden, with the note, when its own warp draws apart from that art (#3179). #[test]