From 7f2955be1cf76cf3b754a4e6675870a33e610fd8 Mon Sep 17 00:00:00 2001 From: Dominik Strebel Date: Thu, 22 Oct 2026 09:10:13 +0200 Subject: [PATCH] Text: a table in a content placeholder keeps the other-text style A table can sit in a content placeholder (its graphicFrame carries ). PowerPoint for Mac still draws its text in the master's other-text style: in a synthetic deck with a bulleted 36 pt bodyStyle, a 14 pt otherStyle and a 26 pt defaultTextStyle, a table in the placeholder and one outside it both draw 14 pt cells with no bullet or body indent, also when the layout's and master's body placeholders carry 30 and 32 pt list styles. master_list_style now checks for a table before a placeholder, and level_style takes nothing from placeholder parents for a table, so the table shape itself (its formatting state) resolves as its cells do. Co-Authored-By: Claude Opus 5.5 --- crates/model/src/resolve.rs | 17 +++++++++++------ crates/model/src/tests.rs | 8 ++++++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/crates/model/src/resolve.rs b/crates/model/src/resolve.rs index 4457108b..8d87229f 100644 --- a/crates/model/src/resolve.rs +++ b/crates/model/src/resolve.rs @@ -100,11 +100,15 @@ pub fn parents<'a>(ctx: &Ctx<'a>, shape: &Shape) -> (Option<&'a Shape>, Option<& } } -/// The text style group (title / body / other) a shape draws from. Text in a table that isn't a -/// placeholder takes the master's other-text style, as in PowerPoint for Mac (a table cell is 14 pt -/// under an `otherStyle` of 14 pt and a `defaultTextStyle` of 26 pt, while a shape's or a text -/// box's text is 26 pt); other shapes take the presentation's default text style. +/// The text style group (title / body / other) a shape draws from. Text in a table takes the +/// master's other-text style, also when the table sits in a content placeholder, as in PowerPoint +/// for Mac (a table cell is 14 pt under an `otherStyle` of 14 pt, a `defaultTextStyle` of 26 pt and +/// a bulleted 36 pt `bodyStyle`, with or without ``, while a shape's or a text box's +/// text is 26 pt); other shapes take the presentation's default text style. pub fn master_list_style<'a>(ctx: &Ctx<'a>, shape: &Shape) -> &'a ListStyle { + if matches!(shape.kind, crate::ShapeKind::Table(_)) { + return &ctx.master.other_style; + } match shape.ph_type() { Some(PhType::Title | PhType::CtrTitle) => &ctx.master.title_style, Some( @@ -119,7 +123,6 @@ pub fn master_list_style<'a>(ctx: &Ctx<'a>, shape: &Shape) -> &'a ListStyle { | PhType::ClipArt, ) => &ctx.master.body_style, Some(_) => &ctx.master.other_style, - None if matches!(shape.kind, crate::ShapeKind::Table(_)) => &ctx.master.other_style, None => &ctx.pres.default_text_style, } } @@ -252,7 +255,9 @@ pub fn body(ctx: &Ctx, shape: &Shape) -> BodyProps { /// The merged level style (paragraph + run defaults) for paragraphs at `level` in `shape`. pub fn level_style(ctx: &Ctx, shape: &Shape, level: u8) -> LevelStyle { - let (lp, mp) = parents(ctx, shape); + // A table's text inherits nothing from a placeholder the table sits in: PowerPoint draws it in + // the other-text style even under list styles on the layout's and master's placeholders. + let (lp, mp) = if matches!(shape.kind, crate::ShapeKind::Table(_)) { (None, None) } else { parents(ctx, shape) }; let mut out = LevelStyle::default(); let add = |out: &mut LevelStyle, ls: Option<&LevelStyle>| { if let Some(l) = ls { diff --git a/crates/model/src/tests.rs b/crates/model/src/tests.rs index 05915a56..bc3817d9 100644 --- a/crates/model/src/tests.rs +++ b/crates/model/src/tests.rs @@ -273,12 +273,14 @@ fn ink_width_follows_pressure_and_keeps_the_old_placeholder() { #[test] fn table_text_takes_the_masters_other_style_and_shapes_the_default_text_style() { - // PowerPoint for Mac, with an otherStyle of 14 pt and a defaultTextStyle of 26 pt: a table - // cell's text is 14 pt, a shape's and a text box's 26 pt. + // PowerPoint for Mac, with an otherStyle of 14 pt, a defaultTextStyle of 26 pt and a 36 pt + // bodyStyle: a table cell's text is 14 pt, also in a table placed in a content placeholder; a + // shape's and a text box's is 26 pt. let mut p = Presentation::default(); let size = |pt: f64| crate::text::LevelStyle { run: RunProps { size: Some(pt), ..Default::default() }, ..Default::default() }; p.default_text_style.set(0, size(26.0)); std::sync::Arc::make_mut(&mut p.masters[0]).other_style.set(0, size(14.0)); + std::sync::Arc::make_mut(&mut p.masters[0]).body_style.set(0, size(36.0)); let s = p.slides[0].clone(); let ctx = Ctx::for_slide(&p, &s).unwrap(); let para = Paragraph::default(); @@ -287,6 +289,8 @@ fn table_text_takes_the_masters_other_style_and_shapes_the_default_text_style() assert_eq!(pt(&Shape::table_cell(body.clone())), Some(14.0), "a table cell"); let table = Shape { kind: ShapeKind::Table(Table::default()), ..Default::default() }; assert_eq!(pt(&table), Some(14.0), "the table itself (its formatting state)"); + let in_placeholder = Shape { ph: Some(Placeholder { kind: PhType::Obj, idx: 1, ..Default::default() }), ..table }; + assert_eq!(pt(&in_placeholder), Some(14.0), "a table in a content placeholder"); assert_eq!(pt(&Shape { text: Some(body.clone()), ..Default::default() }), Some(26.0), "a shape"); assert_eq!(pt(&Shape { text: Some(body), text_box: true, ..Default::default() }), Some(26.0), "a text box"); }