diff --git a/crates/pptx/src/read/chart.rs b/crates/pptx/src/read/chart.rs index 358f88ff5..3f63709b1 100644 --- a/crates/pptx/src/read/chart.rs +++ b/crates/pptx/src/read/chart.rs @@ -300,8 +300,11 @@ fn read_series(s: &El, g: &El, group: (ChartType, bool, Option), categorie secondary_axis: secondary, ..Default::default() }; + // `` hides every label of the series, whatever the group says. + if s.path(&["dLbls", "delete"]).map(|v| v.bool("val").unwrap_or(true)) == Some(true) { + ser.data_labels = Some(false); // `` without a val attribute means true, as in the schema's default. - if let Some(d) = s.child("dLbls").and_then(|d| d.child("showVal")).map(|v| v.bool("val").unwrap_or(true)) { + } else if let Some(d) = s.child("dLbls").and_then(|d| d.child("showVal")).map(|v| v.bool("val").unwrap_or(true)) { ser.data_labels = Some(d); } else if let Some(false) = group_labels { // The group asks for no labels and the series does not say otherwise. diff --git a/crates/pptx/tests/autocraft_regression_1977.rs b/crates/pptx/tests/autocraft_regression_1977.rs new file mode 100644 index 000000000..6bd2a1dac --- /dev/null +++ b/crates/pptx/tests/autocraft_regression_1977.rs @@ -0,0 +1,73 @@ +//! A series-level `c:dLbls/c:delete val="1"` hides that series' labels although the +//! chart-wide `showVal` is on (#1977). + +use std::io::{Cursor, Read, Write}; +use std::sync::Arc; + +use deckcraft_geom::Xfrm; +use deckcraft_model::chart::{Chart, ChartType}; +use deckcraft_model::{LayoutType, Presentation, Shape, ShapeId, ShapeKind, defaults}; + +fn bar_chart_pptx() -> Vec { + let mut p = defaults::new_presentation(None); + p.slides.clear(); + let layout = defaults::layout_of_kind(&p, LayoutType::Blank).unwrap_or_default(); + let mut slide = defaults::new_slide(&mut p, layout).expect("slide"); + let mut shape = + Shape { id: ShapeId(p.alloc_id().expect("id")), name: "Bars".into(), xfrm: Some(Xfrm::new(40.0, 40.0, 400.0, 300.0)), ..Default::default() }; + let mut chart = Chart::sample(ChartType::Column); + chart.data_labels = false; + for s in &mut chart.series { + s.data_labels = None; + } + shape.kind = ShapeKind::Chart(Box::new(chart)); + slide.shapes.push(shape); + p.slides.push(Arc::new(slide)); + deckcraft_pptx::export(&p).expect("export") +} + +fn reimport(bytes: &[u8], f: impl Fn(&str) -> String) -> Presentation { + let mut zip = zip::ZipArchive::new(Cursor::new(bytes)).expect("zip"); + let mut out = zip::ZipWriter::new(Cursor::new(Vec::new())); + let opts = zip::write::SimpleFileOptions::default(); + for i in 0..zip.len() { + let mut entry = zip.by_index(i).expect("entry"); + let name = entry.name().to_string(); + let mut data = vec![]; + entry.read_to_end(&mut data).expect("read"); + if name.starts_with("ppt/charts/chart") && name.ends_with(".xml") && !name.contains("_rels") { + let xml = String::from_utf8(data).expect("chart xml"); + data = f(&xml).into_bytes(); + } + out.start_file(&name, opts).expect("start"); + out.write_all(&data).expect("write"); + } + let patched = out.finish().expect("finish").into_inner(); + deckcraft_pptx::import(&patched).expect("import") +} + +fn chart_of(p: &Presentation) -> &Chart { + let s = p.slides.first().expect("slide"); + for sh in &s.shapes { + if let ShapeKind::Chart(c) = &sh.kind { + return c; + } + } + panic!("no chart in the deck"); +} + +#[test] +fn series_dlbls_delete_hides_that_series_labels() { + let p = reimport(&bar_chart_pptx(), |xml| { + xml.replacen("", r#""#, 1).replacen( + "