use std::io::Read;
use anyhow::bail;
use quick_xml::events::{BytesStart, Event};
use regex::Regex;
use crate::{excel::XmlReader, raw::drawing::st_types::st_percentage_to_float};
pub(crate) fn string_to_bool(str: &str) -> Option<bool> {
return match str {
"0" | "false" => Some(false),
"1" | "true" => Some(true),
_ => None,
};
}
pub(crate) fn string_to_unsignedint(str: &str) -> Option<u64> {
return match str.parse::<u64>() {
Ok(int) => Some(int),
Err(_) => None,
};
}
pub(crate) fn string_to_int(str: &str) -> Option<i64> {
if str.ends_with("%") {
return percentage_string_to_int(str);
}
return match str.parse::<i64>() {
Ok(int) => Some(int),
Err(_) => None,
};
}
fn percentage_string_to_int(str: &str) -> Option<i64> {
let mut chars = str.chars();
chars.next_back();
let str = chars.as_str();
return match str.parse::<f64>() {
Ok(f) => Some((f * 1000.0) as i64),
Err(_) => None,
};
}
pub(crate) fn string_to_float(str: &str) -> Option<f64> {
return match str.parse::<f64>() {
Ok(float) => Some(float),
Err(_) => None,
};
}
pub(crate) fn extract_val_attribute(e: &BytesStart) -> anyhow::Result<Option<String>> {
let attributes = e.attributes();
for a in attributes {
match a {
Ok(a) => {
let string_value = String::from_utf8(a.value.to_vec())?;
match a.key.local_name().as_ref() {
b"val" => return Ok(Some(string_value)),
_ => {}
}
}
Err(error) => {
bail!(error.to_string())
}
}
}
return Ok(None);
}
pub(crate) fn extract_text_contents(
reader: &mut XmlReader<impl Read>,
tag: &[u8],
) -> anyhow::Result<String> {
let mut text = String::new();
let mut buf: Vec<u8> = Vec::new();
loop {
buf.clear();
match reader.read_event_into(&mut buf) {
Ok(Event::Text(t)) => text.push_str(&t.unescape()?),
Ok(Event::End(ref e)) if e.local_name().as_ref() == tag => break,
Ok(Event::Eof) => bail!(
"unexpected end of file at `{}`.",
String::from_utf8(tag.to_vec())?
),
Err(e) => bail!(e.to_string()),
_ => (),
}
}
return Ok(text);
}
pub(crate) fn a1_dimension_to_row_col(
a1_dimension: &[u8],
) -> anyhow::Result<((u64, u64), (u64, u64))> {
let parts: Vec<&[u8]> = a1_dimension.split(|c| *c == b':').collect();
if parts.len() != 2 {
bail!("Invalid reference dimension.")
}
let (Some(start_row), Some(start_col)) = a1_address_to_row_col(parts[0])? else {
bail!("Invalid reference start address.")
};
let (Some(end_row), Some(end_col)) = a1_address_to_row_col(parts[1])? else {
bail!("Invalid reference end address.")
};
Ok(((start_row, start_col), (end_row, end_col)))
}
pub(crate) fn r1c1_dimension_to_row_col(
r1c1_dimension: &str,
) -> anyhow::Result<((u64, u64), (u64, u64))> {
let parts: Vec<&str> = r1c1_dimension.split(|c: char| c == ':').collect();
if parts.len() != 2 {
bail!("Invalid reference dimension.")
}
let Some(start) = r1c1_address_to_row_col(parts[0])? else {
bail!("Invalid reference start address.")
};
let Some(end) = r1c1_address_to_row_col(parts[1])? else {
bail!("Invalid reference end address.")
};
Ok((start, end))
}
pub(crate) fn r1c1_address_to_row_col(r1c1: &str) -> anyhow::Result<Option<(u64, u64)>> {
let r1c1 = r1c1.to_ascii_uppercase();
let re = Regex::new(r"R(?<row>[0-9]+)C(?<col>[0-9]+)$")?;
let Some(caps) = re.captures(&r1c1) else {
return Ok(None);
};
let row = &caps["row"].parse::<u64>()?;
let col = &caps["col"].parse::<u64>()?;
return Ok(Some((*row, *col)));
}
pub(crate) fn a1_address_to_row_col(
a1_address: &[u8],
) -> anyhow::Result<(Option<u64>, Option<u64>)> {
let (mut row, mut col) = (0, 0);
let mut power = 1;
let mut reading_row = true;
for c in a1_address.iter().rev() {
match *c {
c @ b'0'..=b'9' => {
if reading_row {
row += ((c - b'0') as u64) * power;
power *= 10;
} else {
bail!("Cell address contains numeric column.")
}
}
c @ b'A'..=b'Z' => {
if reading_row {
power = 1;
reading_row = false;
}
col += ((c - b'A') as u64 + 1) * power;
power *= 26;
}
c @ b'a'..=b'z' => {
if reading_row {
power = 1;
reading_row = false;
}
col += ((c - b'a') as u64 + 1) * power;
power *= 26;
}
_ => bail!("Cell address is not alphaNumeric."),
}
}
let row = if row.eq(&0) { None } else { Some(row) };
let col = if col.eq(&0) { None } else { Some(col) };
Ok((row, col))
}
pub(crate) fn format_hex_string(hex: &str, alpha_first: Option<bool>) -> anyhow::Result<String> {
let mut s = hex;
if s.starts_with("#") {
s = s.trim_start_matches("#");
}
if s.len() != 6 && s.len() != 8 {
bail!("invalid hex.")
}
let alpha_first = alpha_first.unwrap_or(false);
if !alpha_first && s.len() == 8 {
return Ok(format!("#{}", hex.to_ascii_lowercase()));
}
if s.len() == 6 {
return Ok(format!("#{}{}", hex.to_ascii_lowercase(), "ff"));
}
let alpha_hex = &hex[..=1];
let rgb_hex = &hex[2..];
return Ok(format!("#{}{}", rgb_hex, alpha_hex));
}
pub(crate) fn hex_to_rgba(
hex_str: &str,
alpha_first: Option<bool>,
) -> anyhow::Result<(u32, u32, u32, f64)> {
let mut s = hex_str;
if s.starts_with("#") {
s = s.trim_start_matches("#");
}
if s.len() != 6 && s.len() != 8 {
bail!("invalid hex.")
}
if s.len() == 6 {
let num = u32::from_str_radix(s, 16)?;
let r = (num & 0xFF0000) >> 16;
let g = (num & 0x00FF00) >> 8;
let b = (num & 0x0000FF) >> 0;
return Ok((r, g, b, 1.0));
}
if alpha_first.unwrap_or(false) {
let num = u32::from_str_radix(s, 16)?;
let a = (num & 0xFF000000) >> 24;
let r = (num & 0x00FF0000) >> 16;
let g = (num & 0x0000FF00) >> 8;
let b = (num & 0x000000FF) >> 0;
return Ok((r, g, b, (a as f64) / 255.0));
}
let num = u32::from_str_radix(s, 16)?;
let r = (num & 0xFF000000) >> 24;
let g = (num & 0x00FF0000) >> 16;
let b = (num & 0x0000FF00) >> 8;
let a = (num & 0x000000FF) >> 0;
return Ok((r, g, b, (a as f64) / 255.0));
}
pub(crate) fn rgba_to_hex(
rgba: (u32, u32, u32, f64),
alpha_first: Option<bool>,
) -> anyhow::Result<String> {
let (r, g, b, a) = rgba;
if !check_rgb(&r) || !check_rgb(&g) || !check_rgb(&b) {
bail!("invalid rgb value.")
}
fn num_to_hex(n: u32) -> String {
let s = format!("{:x}", n);
if s.len() == 1 {
String::from("0") + &s
} else {
s
}
}
if !check_alpha(&a) {
bail!("invalid alpha value")
}
let a_u32 = (a * 255.0).round() as u32;
if alpha_first.unwrap_or(false) {
return Ok(format!(
"#{}{}{}{}",
num_to_hex(a_u32),
num_to_hex(r),
num_to_hex(g),
num_to_hex(b)
));
}
return Ok(format!(
"#{}{}{}{}",
num_to_hex(r),
num_to_hex(g),
num_to_hex(b),
num_to_hex(a_u32)
));
}
pub(crate) fn hsla_to_rgba(hsla: (f64, f64, f64, f64)) -> anyhow::Result<(u32, u32, u32, f64)> {
if !check_hue(&hsla.0) || !check_percentage(&hsla.1) || !check_percentage(&hsla.2) {
bail!("invalid hsl value.")
}
let (h, s, l, a) = (hsla.0 / 360.0, hsla.1 / 100.0, hsla.2 / 100.0, hsla.3);
if !check_alpha(&a) {
bail!("invalid alpha value")
}
if s == 0.0 {
return Ok((255, 255, 255, a));
}
let q = if l < 0.5 {
l * (1.0 + s)
} else {
l + s - l * s
};
let p = 2.0 * l - q;
fn calc(p: f64, q: f64, t: f64) -> f64 {
let mut t = t;
if t < 0.0 {
t += 1.0
};
if t > 1.0 {
t -= 1.0
};
if t < 1.0 / 6.0 {
return p + (q - p) * 6.0 * t;
};
if t < 1.0 / 2.0 {
return q;
};
if t < 2.0 / 3.0 {
return p + (q - p) * (2.0 / 3.0 - t) * 6.0;
};
return p;
}
let r = calc(p, q, h + 1.0 / 3.0);
let g = calc(p, q, h);
let b = calc(p, q, h - 1.0 / 3.0);
return Ok((
(r * 255.0).round() as u32,
(g * 255.0).round() as u32,
(b * 255.0).round() as u32,
a,
));
}
pub(crate) fn rgba_to_hsla(rgba: (u32, u32, u32, f64)) -> anyhow::Result<(f64, f64, f64, f64)> {
if !check_rgb(&rgba.0) || !check_rgb(&rgba.1) || !check_rgb(&rgba.2) {
bail!("invalid rgb value.")
}
let (r, g, b, a) = (
(rgba.0 as f64) / 255.0,
(rgba.1 as f64) / 255.0,
(rgba.2 as f64) / 255.0,
rgba.3,
);
if !check_alpha(&a) {
bail!("invalid alpha value")
}
let (max, max_index) = max(vec![r, g, b]).unwrap_or((1.0, 0));
let (min, _) = min(vec![r, g, b]).unwrap_or((0.0, 0));
let lum = (max + min) / 2.0;
if max == min {
return Ok((0.0, 0.0, lum, a));
}
let chroma = max - min;
let sat = chroma / (1.0 - (2.0 * lum - 1.0).abs());
let hue = match max_index {
0 => {
let x = if g < b { 6.0 } else { 0.0 };
(g - b) / chroma + x
}
1 => (b - r) / chroma + 2.0,
2 => (r - g) / chroma + 4.0,
_ => unreachable!(),
};
let mut hue = hue * 60.0;
while hue < 0.0 {
hue = hue + 360.0
}
while hue >= 360.0 {
hue = hue - 360.0
}
return Ok((hue, sat * 100.0, lum * 100.0, a));
}
pub(crate) fn hsva_to_rgba(hsva: (f64, f64, f64, f64)) -> anyhow::Result<(u32, u32, u32, f64)> {
if !check_hue(&hsva.0) || !check_percentage(&hsva.1) || !check_percentage(&hsva.2) {
bail!("invalid hsv value.")
}
let (h, s, v, a) = (hsva.0 / 360.0, hsva.1 / 100.0, hsva.2 / 100.0, hsva.3);
if !check_alpha(&a) {
bail!("invalid alpha value")
}
let i = (h * 6.0).floor();
let f = h * 6.0 - i;
let p = v * (1.0 - s);
let q = v * (1.0 - f * s);
let t = v * (1.0 - (1.0 - f) * s);
if s == 0.0 {
return Ok((255, 255, 255, a));
}
let r: f64;
let g: f64;
let b: f64;
match (i as u32) % 6 {
0 => {
r = v;
g = t;
b = p;
}
1 => {
r = q;
g = v;
b = p;
}
2 => {
r = p;
g = v;
b = t;
}
3 => {
r = p;
g = q;
b = v;
}
4 => {
r = t;
g = p;
b = v;
}
5 => {
r = v;
g = p;
b = q;
}
_ => unreachable!(),
}
return Ok((
(r * 255.0).round() as u32,
(g * 255.0).round() as u32,
(b * 255.0).round() as u32,
a,
));
}
pub(crate) fn rgba_to_hsva(rgba: (u32, u32, u32, f64)) -> anyhow::Result<(f64, f64, f64, f64)> {
if !check_rgb(&rgba.0) || !check_rgb(&rgba.1) || !check_rgb(&rgba.2) {
bail!("invalid rgb value.")
}
let (r, g, b, a) = (
(rgba.0 as f64) / 255.0,
(rgba.1 as f64) / 255.0,
(rgba.2 as f64) / 255.0,
rgba.3,
);
if !check_alpha(&a) {
bail!("invalid alpha value")
}
let (max, max_index) = max(vec![r, g, b]).unwrap_or((1.0, 0));
let (min, _) = min(vec![r, g, b]).unwrap_or((0.0, 0));
let val = max;
if max == min {
return Ok((0.0, 0.0, val, a));
}
let diff = max - min;
let hue: f64;
let sat = diff / val;
match max_index {
0 => hue = (g - b) / diff,
1 => hue = 2.0 + (b - r) / diff,
2 => hue = 4.0 + (r - g) / diff,
_ => unreachable!(),
}
let mut hue = hue * 60.0;
while hue < 0.0 {
hue = hue + 360.0
}
while hue >= 360.0 {
hue = hue - 360.0
}
return Ok((hue, sat * 100.0, val * 100.0, a));
}
pub(crate) fn complementary(rgba: (u32, u32, u32, f64)) -> anyhow::Result<(u32, u32, u32, f64)> {
let (mut h, s, v, a) = rgba_to_hsva(rgba)?;
fn shift_hue(h: f64, s: f64) -> f64 {
let mut h = h + s;
while h < 0.0 {
h = h + 360.0
}
while h >= 360.0 {
h = h - 360.0
}
return h;
}
h = shift_hue(h, 180.0);
return hsva_to_rgba((h, s, v, a));
}
pub(crate) fn inverse(rgba: (u32, u32, u32, f64)) -> anyhow::Result<(u32, u32, u32, f64)> {
if !check_rgb(&rgba.0) || !check_rgb(&rgba.1) || !check_rgb(&rgba.2) {
bail!("invalid rgb value.")
}
if !check_alpha(&rgba.3) {
bail!("invalid alpha value")
}
return Ok((255 - rgba.0, 255 - rgba.0, 255 - rgba.0, rgba.3));
}
pub(crate) fn gamma_shift(rgba: (u32, u32, u32, f64)) -> anyhow::Result<(u32, u32, u32, f64)> {
let (h, s, mut v, a) = rgba_to_hsva(rgba)?;
v = (v / 100.0).powf(2.2) * 100.0;
return hsva_to_rgba((h, s, v, a));
}
pub(crate) fn inverse_gamma_shift(
rgba: (u32, u32, u32, f64),
) -> anyhow::Result<(u32, u32, u32, f64)> {
let (h, s, mut v, a) = rgba_to_hsva(rgba)?;
v = (v / 100.0).powf(1.0 / 2.2) * 100.0;
return hsva_to_rgba((h, s, v, a));
}
pub(crate) fn grayscale(rgba: (u32, u32, u32, f64)) -> anyhow::Result<(u32, u32, u32, f64)> {
if !check_rgb(&rgba.0) || !check_rgb(&rgba.1) || !check_rgb(&rgba.2) {
bail!("invalid rgb value.")
}
let (r, g, b, a) = (
(rgba.0 as f64) * 0.299,
(rgba.1 as f64) * 0.587,
(rgba.2 as f64) * 0.114,
rgba.3,
);
if !check_alpha(&a) {
bail!("invalid alpha value")
}
let gray = (r + g + b).round() as u32;
Ok((gray, gray, gray, a))
}
pub(crate) fn apply_tint(
rgba: (u32, u32, u32, f64),
tint: f64,
) -> anyhow::Result<(u32, u32, u32, f64)> {
if tint == 0.0 {
return Ok((rgba.0, rgba.1, rgba.2, rgba.3));
}
let (h, s, mut l, a) = rgba_to_hsla(rgba)?;
l = l / 100.0;
if tint < 0.0 {
l = l * (1.0 + tint);
} else {
l = l * (1.0 - tint) + (1.0 - (1.0 - tint));
}
if l < 0.0 {
l = 0.0
}
if l > 1.0 {
l = 1.0
}
l = l * 100.0;
return hsla_to_rgba((h, s, l, a));
}
pub(crate) fn apply_modulation(original: f64, modulation: i64) -> f64 {
let modulation = st_percentage_to_float(modulation);
let mut new = original * modulation;
if new < 0.0 {
new = 0.0
}
if new > 1.0 {
new = 1.0
}
return new;
}
pub(crate) fn apply_offset(original: f64, offset: i64) -> f64 {
let mut new = original + st_percentage_to_float(offset);
if new < 0.0 {
new = 0.0
}
if new > 1.0 {
new = 1.0
}
return new;
}
fn check_rgb(num: &u32) -> bool {
return (0..=255).contains(num);
}
fn check_alpha(a: &f64) -> bool {
return (0.0..=1.0).contains(a);
}
fn check_hue(h: &f64) -> bool {
return (0.0..=360.0).contains(h);
}
fn check_percentage(n: &f64) -> bool {
return (0.0..=100.0).contains(n);
}
fn min<T: std::cmp::PartialOrd + Copy>(v: Vec<T>) -> Option<(T, usize)> {
if v.is_empty() {
return None;
}
let mut result = v[0];
let mut index: usize = 0;
if v.len() == 1 {
return Some((result, index));
}
for i in 1..v.len() {
let v = v[i];
if v < result {
result = v;
index = i;
}
}
Some((result, index))
}
fn max<T: std::cmp::PartialOrd + Copy>(v: Vec<T>) -> Option<(T, usize)> {
if v.is_empty() {
return None;
}
let mut result = v[0];
let mut index: usize = 0;
if v.len() == 1 {
return Some((result, index));
}
for i in 1..v.len() {
let v = v[i];
if v > result {
result = v;
index = i;
}
}
Some((result, index))
}