rust-office-charts 0.1.7

Render Office Open XML charts to SVG from native Rust or WebAssembly
Documentation
use crate::{chart::cx_child, RenderOptions};
use roxmltree::{Document, Node};

pub(super) struct Fonts {
    base: String,
    minor: String,
    major: Option<String>,
}

fn drawing(node: Node, name: &str) -> bool {
    node.tag_name().name() == name
        && matches!(
            node.tag_name().namespace(),
            Some(
                "http://schemas.openxmlformats.org/drawingml/2006/main"
                    | "http://purl.oclc.org/ooxml/drawingml/main"
            )
        )
}

fn theme_face(xml: Option<&str>, kind: &str) -> Option<String> {
    let document = Document::parse(xml?).ok()?;
    let font = document
        .descendants()
        .find(|n| drawing(*n, "fontScheme"))?
        .children()
        .find(|n| drawing(*n, kind))?
        .children()
        .find(|n| drawing(*n, "latin"))?
        .attribute("typeface")?;
    (!font.is_empty() && !font.starts_with('+')).then(|| font.to_owned())
}

impl Fonts {
    pub(super) fn new(root: Node, options: &RenderOptions) -> Option<Self> {
        if !options.spreadsheet_defaults {
            return None;
        }
        let theme = |kind| {
            theme_face(options.theme_override_xml, kind)
                .or_else(|| theme_face(options.theme_xml, kind))
        };
        let minor = theme_face(options.theme_override_xml, "minorFont")
            .or_else(|| {
                options
                    .font_family
                    .filter(|s| !s.is_empty() && !s.starts_with('+'))
                    .map(str::to_owned)
            })
            .or_else(|| theme_face(options.theme_xml, "minorFont"))
            .unwrap_or_else(|| "Calibri".to_owned());
        let mut fonts = Self {
            base: minor.clone(),
            minor,
            major: theme("majorFont"),
        };
        let base = cx_child(root, "txPr").and_then(|properties| {
            properties
                .descendants()
                .find(|n| drawing(*n, "latin"))?
                .attribute("typeface")
        });
        fonts.base = fonts.resolve(base).to_owned();
        Some(fonts)
    }

    pub(super) fn resolve<'a>(&'a self, typeface: Option<&'a str>) -> &'a str {
        match typeface {
            Some("+mn-lt") => &self.minor,
            Some("+mj-lt") => self.major.as_deref().unwrap_or(&self.base),
            Some(face) if !face.is_empty() && !face.starts_with('+') => face,
            _ => &self.base,
        }
    }
}