diff --git a/crates/idml/src/export.rs b/crates/idml/src/export.rs index 717bb5d6c..0c85e8f8a 100644 --- a/crates/idml/src/export.rs +++ b/crates/idml/src/export.rs @@ -106,6 +106,8 @@ struct Ex<'a> { /// Index topic path → first serialized sort key; See / See also cross-references. index_topics: BTreeMap, String>, index_xrefs: Vec<(Vec, &'static str, String)>, + /// Object style name → whether its chain or `[None]` sets corners, found once per style. + style_corners: HashMap, } impl<'a> Ex<'a> { @@ -121,6 +123,7 @@ impl<'a> Ex<'a> { link_text_sources: Vec::new(), index_topics: BTreeMap::new(), index_xrefs: Vec::new(), + style_corners: HashMap::new(), }; ex.assign_swatch_ids(); for s in d.stories.values() { diff --git a/crates/idml/src/export/item.rs b/crates/idml/src/export/item.rs index 469053c2f..3c240deca 100644 --- a/crates/idml/src/export/item.rs +++ b/crates/idml/src/export/item.rs @@ -87,8 +87,10 @@ impl<'a> Ex<'a> { el.set("GradientStrokeAngle", num(a)); } self.stroke_attrs(&mut el, &it.stroke); - // Corner options: map path-order corners to the named corners by geometry. - if !it.corners.is_none() { + // Corner options: map path-order corners to the named corners by geometry. An item without + // corners whose object style chain sets some writes None, so an import does not give it + // the style's corners. + if !it.corners.is_none() || self.style_sets_corners(os) { let names_by_index = corner_names(&it.path); for (i, c) in it.corners.corners.iter().enumerate() { let n = names_by_index[i]; @@ -153,6 +155,36 @@ impl<'a> Ex<'a> { el } + /// Whether object style `name`, a style it is based on, or `[None]` (which an import reads + /// after every item's style chain) sets corners other than None. A built-in style the + /// document lacks counts as its default. + fn style_sets_corners(&mut self, name: &str) -> bool { + if let Some(&known) = self.style_corners.get(name) { + return known; + } + let defaults = designcraft_doc::Styles::default(); + let style = |n: &str| self.d.styles.object_style(n).or_else(|| defaults.object_style(n)); + let rounds = |os: &designcraft_doc::ObjectStyle| os.corners.is_some_and(|c| !c.is_none()); + let mut seen: Vec<&str> = Vec::new(); + let mut cur = Some(name); + let mut found = false; + while let Some(n) = cur.take() { + if seen.contains(&n) { + break; + } + let Some(os) = style(n) else { break }; + if rounds(os) { + found = true; + break; + } + seen.push(n); + cur = os.based_on.as_deref(); + } + let found = found || style(designcraft_doc::NO_OBJECT_STYLE).is_some_and(rounds); + self.style_corners.insert(name.to_string(), found); + found + } + fn append_transparency(&self, el: &mut El, it: &Item, os: &str) { // Transparency. // An item whose style sets effects writes the effects it has turned off too, so that on diff --git a/crates/idml/src/tests/object_styles.rs b/crates/idml/src/tests/object_styles.rs index 4ff0fa6bf..6226d4a74 100644 --- a/crates/idml/src/tests/object_styles.rs +++ b/crates/idml/src/tests/object_styles.rs @@ -232,6 +232,43 @@ fn exported_frames_keep_their_own_values_over_their_object_style() { assert_eq!(got.options, want); } +/// Items without corners keep none after export and import, though their object style, the style +/// it is based on, or `[None]` (read after every style chain) sets rounded corners. +#[test] +fn exported_frames_keep_no_corners_over_their_object_style() { + let mut d = small_doc(); + let lid = d.default_layer(); + let id = designcraft_doc::ItemId(d.alloc()); + let rect = designcraft_doc::Item::new(id, lid, Shape::Rectangle, shapes::rectangle(Rect::new(40.0, 420.0, 200.0, 500.0))); + d.insert_item(SpreadRef::Doc(0), rect, None).unwrap(); + let rounded = Some(designcraft_geom::corners::CornerOptions { + corners: [designcraft_geom::corners::Corner { shape: CornerShape::Rounded, size: 12.0 }; 4], + }); + let styles = std::sync::Arc::make_mut(&mut d.styles); + styles.object.push(designcraft_doc::ObjectStyle { name: "Round".into(), corners: rounded, ..Default::default() }); + styles.object.push(designcraft_doc::ObjectStyle { name: "Child".into(), based_on: Some("Round".into()), ..Default::default() }); + match styles.object.iter_mut().find(|o| o.name == designcraft_doc::NO_OBJECT_STYLE) { + Some(none) => none.corners = rounded, + None => { + styles.object.push(designcraft_doc::ObjectStyle { name: designcraft_doc::NO_OBJECT_STYLE.into(), corners: rounded, ..Default::default() }) + } + } + styles.object.push(designcraft_doc::ObjectStyle { name: "Plain".into(), ..Default::default() }); + let names = ["Round", "Child", designcraft_doc::NO_OBJECT_STYLE, "Plain"]; + let ids: Vec<_> = d.spreads.iter().flat_map(|s| s.items.iter().map(|i| i.id)).take(4).collect(); + assert_eq!(ids.len(), 4); + for (id, style) in ids.iter().zip(names) { + let it = d.item_mut(*id).unwrap(); + it.object_style = style.into(); + assert!(it.corners.is_none()); + } + let back = import_idml(&export_idml(&d)).unwrap(); + for style in names { + let it = back.spreads.iter().flat_map(|s| s.items.iter()).find(|i| i.object_style == style).unwrap(); + assert!(it.corners.is_none(), "{style}: {:?}", it.corners); + } +} + #[test] fn text_wrap_side_and_contour_come_from_the_style_for_frames_and_placed_graphics() { let styles = r#"