diff --git a/crates/idml/src/import.rs b/crates/idml/src/import.rs
index 4570a0694..fb35d90c6 100644
--- a/crates/idml/src/import.rs
+++ b/crates/idml/src/import.rs
@@ -1823,6 +1823,15 @@ impl<'r> Importer<'r> {
t
}
+ /// The format of the text in a `CharacterStyleRange`: its style, and its local attributes over
+ /// the enclosing paragraph range's.
+ fn range_format(&mut self, c: &El, pchars: &CharAttrs) -> CharFormat {
+ let style = c.get("AppliedCharacterStyle").map(|r| self.char_style_ref(r)).unwrap_or_else(|| st::NO_CHAR_STYLE.into());
+ let mut over = pchars.clone();
+ over.merge(&self.char_attrs(c));
+ CharFormat { style, over }
+ }
+
fn walk_story(&mut self, e: &El, b: &mut StoryBuilder, pf: &ParaFormat, pchars: &CharAttrs, cf: &CharFormat, brk: Option<&str>) {
for n in &e.children {
match n {
@@ -1837,13 +1846,13 @@ impl<'r> Importer<'r> {
{
*last = npf.clone();
}
- self.walk_story(c, b, &npf, &chars, cf, None);
+ // Text and markers outside any character range (a destination or a
+ // cross-reference source at paragraph level) take the paragraph's format.
+ let pcf = CharFormat { style: st::NO_CHAR_STYLE.into(), over: chars.clone() };
+ self.walk_story(c, b, &npf, &chars, &pcf, None);
}
"CharacterStyleRange" => {
- let style = c.get("AppliedCharacterStyle").map(|r| self.char_style_ref(r)).unwrap_or_else(|| st::NO_CHAR_STYLE.into());
- let mut over = pchars.clone();
- over.merge(&self.char_attrs(c));
- let ncf = CharFormat { style, over };
+ let ncf = self.range_format(c, pchars);
let bt = c.get("ParagraphBreakType").filter(|t| *t != "Anywhere");
b.last = ncf.clone();
self.walk_story(c, b, pf, pchars, &ncf, bt);
@@ -1907,11 +1916,9 @@ impl<'r> Importer<'r> {
"CrossReferenceSource" => {
// The generated text is regenerated from the format.
b.xrefs.push((c.get("Self").unwrap_or("").to_string(), c.get("AppliedFormat").unwrap_or("").to_string()));
- let inner = c.find("CharacterStyleRange");
- let f = match inner.and_then(|r| r.get("AppliedCharacterStyle")) {
- Some(r) => CharFormat { style: self.char_style_ref(r), over: pchars.clone() },
- None => cf.clone(),
- };
+ // A source at paragraph level wraps a range; inside a range it holds the
+ // text directly and takes that range's format.
+ let f = c.find("CharacterStyleRange").map_or_else(|| cf.clone(), |r| self.range_format(r, pchars));
b.push(&designcraft_doc::XREF_MARK.to_string(), &f);
}
"Footnote" => {
diff --git a/crates/idml/src/tests.rs b/crates/idml/src/tests.rs
index 3cdb6d823..e22dd54fd 100644
--- a/crates/idml/src/tests.rs
+++ b/crates/idml/src/tests.rs
@@ -1039,6 +1039,44 @@ fn round_trips_cross_references() {
assert_eq!(back.xref_formats.len(), d.xref_formats.len());
}
+/// A cross-reference source sits either inside a character range (holding the text directly) or at
+/// paragraph level around a range. Both give the generated text the paragraph's and the range's
+/// attributes, so both set the same text at the same width.
+#[test]
+fn cross_reference_source_nesting_keeps_formatting() {
+ let story = |body: &str| {
+ format!(
+ r#"
+
+{body}"#
+ )
+ };
+ const CSR: &str = r#""#;
+ let inside = story(&format!(
+ r#"{CSR}See section 11., then a few more words to wrap."#
+ ));
+ let around = story(&format!(
+ r#"{CSR}See section {CSR}11.{CSR}, then a few more words to wrap."#
+ ));
+ let compose = |xml: &str| {
+ let d = import_idml(&fixture_with_story(xml)).unwrap();
+ d.check().unwrap();
+ let s = d.stories.values().find(|s| !s.xrefs.is_empty()).expect("story with a cross-reference").clone();
+ let cs = designcraft_compose::compose_story(&d, s.id, &Default::default());
+ let glyphs: Vec<_> = cs.frames.iter().flat_map(|f| &f.lines).flat_map(|l| l.glyphs.iter().map(move |g| (g.byte, g.x, l.baseline))).collect();
+ (s, glyphs)
+ };
+ let (a, ga) = compose(&inside);
+ let (b, gb) = compose(&around);
+ assert!(ga.len() > a.text.chars().count() / 2, "the story composes");
+ assert_eq!(a.text, b.text);
+ assert_eq!(a.chars, b.chars);
+ assert_eq!(a.chars.len(), 1, "the generated text shares its neighbours' format");
+ assert_eq!(a.chars[0].format.over.tracking, Some(200.0));
+ assert_eq!(a.chars[0].format.over.size, Some(11.0));
+ assert_eq!(ga, gb);
+}
+
#[test]
fn round_trips_index_references() {
use designcraft_doc::index::IndexRange;