From 0e0d7f8cb2e4959bd3c905d0784f1363df7f4bd3 Mon Sep 17 00:00:00 2001 From: Hamza Date: Thu, 12 Nov 2026 14:48:11 +0500 Subject: [PATCH] Charts: a legend entry's paragraph text wins over its list style Review fixes: an entry's size, weight and colour are read from its paragraph (a:p) first and from a:lstStyle only where the paragraph sets none, as DrawingML inherits them; the writer keeps sizes to the reader's 1-400 pt, so a size reads back as written; and the legend draws an entry at its own size up to 400 pt rather than up to 8 times the legend's. --- crates/pptx/src/read/chart.rs | 9 ++++++--- crates/pptx/src/write/chart.rs | 20 +++++++++++++++++++- crates/render/src/chart.rs | 2 +- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/crates/pptx/src/read/chart.rs b/crates/pptx/src/read/chart.rs index 0608171b0..d2f38ab41 100644 --- a/crates/pptx/src/read/chart.rs +++ b/crates/pptx/src/read/chart.rs @@ -197,12 +197,15 @@ fn chart_within(root: &El, mut budget: usize) -> Option { .flat_map(|l| l.children_named("legendEntry")) .take(MAX_LEGEND_ENTRIES) .filter_map(|e| { + // The paragraph's own run properties first, then the list style's defaults + // (`a:lstStyle` comes before `a:p`, and is the less specific of the two). let tx = e.child("txPr"); + let para = tx.and_then(|t| t.child("p")); let entry = deckcraft_model::chart::LegendEntry { idx: e.child("idx").and_then(|i| i.u32("val"))?, - size: text_size(tx), - bold: text_bold(tx), - color: text_color(tx), + size: text_size(para).or_else(|| text_size(tx)), + bold: text_bold(para).or_else(|| text_bold(tx)), + color: text_color(para).or_else(|| text_color(tx)), }; (entry.size.is_some() || entry.bold.is_some() || entry.color.is_some()).then_some(entry) }) diff --git a/crates/pptx/src/write/chart.rs b/crates/pptx/src/write/chart.rs index 7976ec50e..b0e5a6228 100644 --- a/crates/pptx/src/write/chart.rs +++ b/crates/pptx/src/write/chart.rs @@ -109,7 +109,8 @@ fn legend_entry(w: &mut W, e: &deckcraft_model::chart::LegendEntry) { w.open0("a:pPr"); let mut a = A::new(); if let Some(sz) = e.size.filter(|v| v.is_finite() && *v > 0.0) { - a = a.a("sz", ((sz * 100.0).round().clamp(100.0, 400_000.0) as u32).to_string()); + // 1 to 400 pt, as the reader takes it, so a size reads back as written. + a = a.a("sz", ((sz * 100.0).round().clamp(100.0, 40_000.0) as u32).to_string()); } if let Some(b) = e.bold { a = a.a("b", if b { "1" } else { "0" }); @@ -1243,6 +1244,23 @@ mod tests { // None: nothing written. e.legend_entries.clear(); assert!(!String::from_utf8(super::chart_xml(&e).0).unwrap().contains("legendEntry")); + // The paragraph's own properties win over the list style's, which come first in the XML. + let listed = xml.replacen( + r#""#, + r#""#, + 1, + ); + assert_ne!(listed, xml, "the list style was put in"); + let c = crate::read::chart::read_chart_xml(listed.as_bytes()).unwrap(); + assert_eq!(c.legend_entries[0], want(0, (0xC0, 0, 0)), "the paragraph's 20 pt, bold, C00000"); + // A size reads back as it was written, within the reader's 1-400 pt. + for (size, back) in [(20.5, 20.5), (400.0, 400.0), (500.0, 400.0)] { + let mut e = c.clone(); + e.raw = None; + e.legend_entries = vec![deckcraft_model::chart::LegendEntry { idx: 0, size: Some(size), bold: None, color: None }]; + let back_size = crate::read::chart::read_chart_xml(&super::chart_xml(&e).0).unwrap().legend_entries[0].size; + assert_eq!(back_size, Some(back), "{size} pt"); + } } #[test] diff --git a/crates/render/src/chart.rs b/crates/render/src/chart.rs index cfb68ab95..9eaf637cc 100644 --- a/crates/render/src/chart.rs +++ b/crates/render/src/chart.rs @@ -542,7 +542,7 @@ fn draw_legend(ctx: &mut RenderContext, draw: &LegendDraw<'_, '_>, mut area: Rec let styles: Vec<(f64, bool, Rgba)> = (0..legend_items.len()) .map(|i| { let e = own(i); - let size = e.and_then(|e| e.size).filter(|v| v.is_finite() && *v > 0.0).map_or(base, |v| v.min(base * 8.0)); + let size = e.and_then(|e| e.size).filter(|v| v.is_finite() && *v > 0.0).map_or(base, |v| v.min(400.0)); let col = e.and_then(|e| e.color.as_ref()).map_or(text_col, |cr| rctx.color(cr, None)); (size, e.and_then(|e| e.bold).unwrap_or(false), col) })