From 22218e90978b4db33745bcabb7acd96c067b302d Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 31 Oct 2026 19:37:26 -0700 Subject: [PATCH] Place: a Word .doc paragraph's own indent holds over its style's A paragraph style can set a logical start or end indent and the paragraph itself only a physical left or right one, or the other way round. The indent applied last now holds, after the sides are mapped for the paragraph's direction; before, the logical indent always held, so a paragraph's own indent was lost under its style's. --- crates/engine/src/cmd/place/doc.rs | 32 ++++++++++++++++++++++----- crates/engine/src/tests/textimport.rs | 27 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/crates/engine/src/cmd/place/doc.rs b/crates/engine/src/cmd/place/doc.rs index f46634b07..c1d40a34d 100644 --- a/crates/engine/src/cmd/place/doc.rs +++ b/crates/engine/src/cmd/place/doc.rs @@ -890,6 +890,11 @@ struct Para { /// The logical start and end indents (`sprmPDxaLeft`, `sprmPDxaRight`). start: Option, end: Option, + /// When `left`, `right`, `start` and `end` were last set, counted in [`Para::applied`]: of a + /// physical and a logical indent on the same side, the one set later holds. + set_at: [u32; 4], + /// The indent properties applied so far. + applied: u32, first: i16, before: u16, after: u16, @@ -929,10 +934,19 @@ impl Para { _ => self.align, } } - 0x840F => self.left = word.map(|v| v as i16), - 0x840E => self.right = word.map(|v| v as i16), - 0x845E => self.start = word.map(|v| v as i16), - 0x845D => self.end = word.map(|v| v as i16), + 0x840F | 0x840E | 0x845E | 0x845D => { + let (field, slot) = match sprm { + 0x840F => (&mut self.left, 0), + 0x840E => (&mut self.right, 1), + 0x845E => (&mut self.start, 2), + _ => (&mut self.end, 3), + }; + *field = word.map(|v| v as i16); + self.applied = self.applied.saturating_add(1); + if let Some(at) = self.set_at.get_mut(slot) { + *at = self.applied; + } + } 0x8411 | 0x8460 => self.first = word.map_or(self.first, |v| v as i16), 0xA413 => self.before = word.unwrap_or(self.before), 0xA414 => self.after = word.unwrap_or(self.after), @@ -947,8 +961,14 @@ impl Para { /// The paragraph attributes of this formatting. fn style(&self) -> ParaStyle { let rtl = self.rtl == Some(true); - let (physical_start, physical_end) = if rtl { (self.right, self.left) } else { (self.left, self.right) }; - let (start, end) = (self.start.or(physical_start).unwrap_or_default(), self.end.or(physical_end).unwrap_or_default()); + let [left_at, right_at, start_at, end_at] = self.set_at; + let (physical_start, physical_end) = + if rtl { ((self.right, right_at), (self.left, left_at)) } else { ((self.left, left_at), (self.right, right_at)) }; + let side = |logical: (Option, u32), physical: (Option, u32)| { + let (first, second) = if physical.1 > logical.1 { (physical.0, logical.0) } else { (logical.0, physical.0) }; + first.or(second).unwrap_or_default() + }; + let (start, end) = (side((self.start, start_at), physical_start), side((self.end, end_at), physical_end)); let (left, right) = if rtl { (end, start) } else { (start, end) }; let points = |v: f64| (v / 20.0).clamp(-1296.0, 1296.0); ParaStyle { diff --git a/crates/engine/src/tests/textimport.rs b/crates/engine/src/tests/textimport.rs index 74eda3f6b..78b7c8977 100644 --- a/crates/engine/src/tests/textimport.rs +++ b/crates/engine/src/tests/textimport.rs @@ -746,6 +746,33 @@ fn doc_places_as_area_type_with_its_styles_and_formatting() { assert_eq!((q["count"].as_u64(), q["files"][0]["format"].as_str()), (Some(1), Some("doc"))); } +#[test] +fn doc_indents_set_later_hold_over_the_styles_physical_or_logical_ones() { + let twips = |v: i16| v.to_le_bytes(); + // Normal sets a logical start indent of 36 pt. The first paragraph sets only a physical left + // indent of 18 pt; the second sets a logical start indent of 9 pt over a style with a physical + // left indent; the third sets a physical right indent of 27 pt and then right-to-left. + let styles = vec![Some((1, 0x0FFF, prls(&[(0x845E, &twips(720))]), vec![])), Some((1, 0, prls(&[(0x840F, &twips(540))]), vec![]))]; + let doc = Word97 { + pieces: vec![("one\rtwo\rthree\r", true)], + papx: vec![ + (3, 0, prls(&[(0x840F, &twips(360))])), + (7, 1, prls(&[(0x845E, &twips(180))])), + (13, 0, prls(&[(0x840E, &twips(540)), (0x2441, &[1])])), + ], + styles, + fonts: vec!["Arial"], + ..Word97::default() + }; + let mut s = session(); + let file = json!({"name": "indents.doc", "dataBase64": vectorcraft_format::base64_encode(&doc.build())}); + let r = s.execute("file.place", &file).unwrap(); + let n = s.doc().unwrap().doc.node(NodeId(r["ids"][0].as_u64().unwrap())).unwrap().clone(); + let NodeKind::Text(t) = &n.kind else { panic!("not type") }; + let summary: Vec<_> = t.paragraph_styles().iter().map(|p| (p.left_indent, p.right_indent)).collect(); + assert_eq!(summary, [(18.0, 0.0), (9.0, 0.0), (0.0, 27.0)]); +} + #[test] fn doc_tables_breaks_and_other_stories_place_as_paragraphs_and_tabs_with_warnings() { let in_table = prls(&[(0x2416, &[1])]);