use super::styles::ThemeFonts;
use super::{DML_NS, W14_NS, dml as find_dml, parse_hex_color, resolve_theme_color_key};
#[derive(Clone, Default)]
pub(super) struct ColorTransforms {
pub(super) lum_mod: Option<f32>,
pub(super) lum_off: Option<f32>,
pub(super) sat_mod: Option<f32>,
pub(super) tint: Option<f32>,
pub(super) shade: Option<f32>,
pub(super) hue_off: Option<f32>,
pub(super) sat_off: Option<f32>,
}
pub(super) fn parse_color_transforms(scheme_clr: roxmltree::Node) -> ColorTransforms {
let mut t = ColorTransforms::default();
for child in scheme_clr.children().filter(|n| {
matches!(n.tag_name().namespace(), Some(DML_NS) | Some(W14_NS))
}) {
let val = child
.attribute("val")
.and_then(|v| v.parse::<f32>().ok())
.map(|v| v / 100_000.0);
let raw_val = child.attribute("val").and_then(|v| v.parse::<f32>().ok());
match child.tag_name().name() {
"lumMod" => t.lum_mod = val,
"lumOff" => t.lum_off = val,
"satMod" => t.sat_mod = val,
"tint" => t.tint = val,
"shade" => t.shade = val,
"hueOff" => t.hue_off = raw_val,
"satOff" => t.sat_off = raw_val,
_ => {}
}
}
t
}
pub(super) fn apply_color_transforms(base: [u8; 3], t: &ColorTransforms) -> [u8; 3] {
let mut color = base;
if let Some(tint) = t.tint {
color = [
(255.0 - tint * (255.0 - color[0] as f32)).clamp(0.0, 255.0) as u8,
(255.0 - tint * (255.0 - color[1] as f32)).clamp(0.0, 255.0) as u8,
(255.0 - tint * (255.0 - color[2] as f32)).clamp(0.0, 255.0) as u8,
];
}
if let Some(shade) = t.shade {
color = [
(color[0] as f32 * shade).clamp(0.0, 255.0) as u8,
(color[1] as f32 * shade).clamp(0.0, 255.0) as u8,
(color[2] as f32 * shade).clamp(0.0, 255.0) as u8,
];
}
if let Some(sat_mod) = t.sat_mod {
if (sat_mod - 1.0).abs() > 0.001 {
let (h, s, l) = rgb_to_hsl(color);
color = hsl_to_rgb(h, (s * sat_mod).clamp(0.0, 1.0), l);
}
}
if t.lum_mod.is_some() || t.lum_off.is_some() {
let m = t.lum_mod.unwrap_or(1.0);
let o = t.lum_off.unwrap_or(0.0);
color = [
((color[0] as f32 / 255.0 * m + o) * 255.0).clamp(0.0, 255.0) as u8,
((color[1] as f32 / 255.0 * m + o) * 255.0).clamp(0.0, 255.0) as u8,
((color[2] as f32 / 255.0 * m + o) * 255.0).clamp(0.0, 255.0) as u8,
];
}
if t.hue_off.is_some() || t.sat_off.is_some() {
let (h, s, l) = rgb_to_hsl(color);
let h2 = if let Some(ho) = t.hue_off {
(h + ho / (360.0 * 60_000.0)).rem_euclid(1.0)
} else {
h
};
let s2 = if let Some(so) = t.sat_off {
(s + so / 100_000.0).clamp(0.0, 1.0)
} else {
s
};
color = hsl_to_rgb(h2, s2, l);
}
color
}
pub(super) fn resolve_dml_color(parent: roxmltree::Node, theme: &ThemeFonts) -> Option<[u8; 3]> {
if let Some(srgb) = find_dml(parent, "srgbClr") {
return srgb.attribute("val").and_then(parse_hex_color);
}
if let Some(scheme) = find_dml(parent, "schemeClr") {
let val = scheme.attribute("val")?;
let theme_key = resolve_theme_color_key(val);
let base = *theme.colors.get(theme_key)?;
let transforms = parse_color_transforms(scheme);
return Some(apply_color_transforms(base, &transforms));
}
if let Some(prst) = find_dml(parent, "prstClr") {
let base = preset_color_rgb(prst.attribute("val")?)?;
let transforms = parse_color_transforms(prst);
return Some(apply_color_transforms(base, &transforms));
}
None
}
fn preset_color_rgb(name: &str) -> Option<[u8; 3]> {
Some(match name {
"black" => [0, 0, 0],
"white" => [255, 255, 255],
"red" => [255, 0, 0],
"green" => [0, 128, 0],
"blue" => [0, 0, 255],
"yellow" => [255, 255, 0],
"cyan" | "aqua" => [0, 255, 255],
"magenta" | "fuchsia" => [255, 0, 255],
"gray" | "grey" => [128, 128, 128],
"ltGray" | "lightGray" | "lightGrey" => [211, 211, 211],
"dkGray" | "darkGray" | "darkGrey" => [169, 169, 169],
"silver" => [192, 192, 192],
"orange" => [255, 165, 0],
"purple" => [128, 0, 128],
"brown" => [165, 42, 42],
"pink" => [255, 192, 203],
"gold" => [255, 215, 0],
"navy" => [0, 0, 128],
"teal" => [0, 128, 128],
"lime" => [0, 255, 0],
"maroon" => [128, 0, 0],
"olive" => [128, 128, 0],
"darkRed" => [139, 0, 0],
"darkGreen" => [0, 100, 0],
"darkBlue" => [0, 0, 139],
_ => return None,
})
}
pub(super) fn parse_solid_fill(sp_pr: roxmltree::Node, theme: &ThemeFonts) -> Option<[u8; 3]> {
let fill = find_dml(sp_pr, "solidFill")?;
resolve_dml_color(fill, theme)
}
fn rgb_to_hsl(c: [u8; 3]) -> (f32, f32, f32) {
let r = c[0] as f32 / 255.0;
let g = c[1] as f32 / 255.0;
let b = c[2] as f32 / 255.0;
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let l = (max + min) / 2.0;
if (max - min).abs() < f32::EPSILON {
return (0.0, 0.0, l);
}
let d = max - min;
let s = if l > 0.5 {
d / (2.0 - max - min)
} else {
d / (max + min)
};
let h = if (max - r).abs() < f32::EPSILON {
((g - b) / d + if g < b { 6.0 } else { 0.0 }) / 6.0
} else if (max - g).abs() < f32::EPSILON {
((b - r) / d + 2.0) / 6.0
} else {
((r - g) / d + 4.0) / 6.0
};
(h, s, l)
}
fn hsl_to_rgb(h: f32, s: f32, l: f32) -> [u8; 3] {
if s.abs() < f32::EPSILON {
let v = (l * 255.0).clamp(0.0, 255.0) as u8;
return [v, v, v];
}
let q = if l < 0.5 {
l * (1.0 + s)
} else {
l + s - l * s
};
let p = 2.0 * l - q;
let hue_to_rgb = |t: f32| -> f32 {
let t = ((t % 1.0) + 1.0) % 1.0;
if t < 1.0 / 6.0 {
p + (q - p) * 6.0 * t
} else if t < 1.0 / 2.0 {
q
} else if t < 2.0 / 3.0 {
p + (q - p) * (2.0 / 3.0 - t) * 6.0
} else {
p
}
};
[
(hue_to_rgb(h + 1.0 / 3.0) * 255.0).clamp(0.0, 255.0) as u8,
(hue_to_rgb(h) * 255.0).clamp(0.0, 255.0) as u8,
(hue_to_rgb(h - 1.0 / 3.0) * 255.0).clamp(0.0, 255.0) as u8,
]
}