1use std::borrow::Borrow;
2
3use rust_office_charts::{render_chart, RenderOptions};
4pub use rust_office_charts::{ChartCategory, ChartData, ChartPoint, ChartSeries};
5
6use crate::package::{collect_relationship_targets, relationship_target_from_parts};
7use crate::parse::{svg_data_uri, ParseContext, ParsedStyleSheet};
8use crate::OoxmlPackage;
9
10pub(crate) const CHARTEX_NS: &str = "http://schemas.microsoft.com/office/drawing/2014/chartex";
11
12pub fn extract_chart_data(package: &OoxmlPackage, part_name: &str) -> Option<ChartData> {
15 rust_office_charts::parse_chart_xml(&package.parts.get(part_name)?.content)
16}
17
18pub(crate) fn render_chart_part(
19 part_name: &str,
20 context: &ParseContext<'_, impl Borrow<ParsedStyleSheet>>,
21 width: Option<f64>,
22 height: Option<f64>,
23) -> Option<(String, ChartData)> {
24 let parts = context.parts;
25 let part = parts.get(part_name)?;
26 let chart_targets = collect_relationship_targets(parts, part_name);
27 let related = |kind: &str| {
28 let target = chart_targets.get(kind)?.as_ref()?;
29 Some(parts.get(target)?.content.as_str())
30 };
31
32 let package_targets = collect_relationship_targets(parts, "");
33 let main_part = package_targets
34 .get("officeDocument")
35 .and_then(Option::as_deref)
36 .unwrap_or("word/document.xml");
37 let theme_part = relationship_target_from_parts(parts, main_part, "theme");
38 let theme = theme_part
39 .as_ref()
40 .and_then(|name| parts.get(name))
41 .or_else(|| parts.get("word/theme/theme1.xml"));
42 let properties = package_targets
43 .get("extended-properties")
44 .and_then(Option::as_deref)
45 .unwrap_or("docProps/app.xml");
46 let styles = context.styles();
47 let options = RenderOptions {
48 width,
49 height,
50 theme_colors: Some(&styles.theme_colors),
51 font_family: styles.theme_fonts.minor_latin.as_deref(),
52 theme_xml: theme.map(|part| part.content.as_str()),
53 theme_override_xml: related("themeOverride"),
54 style_xml: related("chartStyle"),
55 color_style_xml: related("chartColorStyle"),
56 legacy_defaults: parts
57 .get(properties)
58 .is_some_and(|part| uses_legacy_chart_defaults(&part.content)),
59 ..Default::default()
60 };
61 let chart = render_chart(&part.content, &options)?;
62 Some((svg_data_uri(&chart.svg), chart.data))
63}
64
65fn uses_legacy_chart_defaults(properties: &str) -> bool {
66 let Ok(xml) = roxmltree::Document::parse(properties) else {
67 return false;
68 };
69 let version = xml
70 .root_element()
71 .children()
72 .find(|node| node.is_element() && node.tag_name().name() == "AppVersion")
73 .and_then(|node| node.text())
74 .and_then(|value| value.trim().split('.').next()?.parse::<u16>().ok());
75 matches!(version, Some(1..=12))
76}