use quick_xml::events::Event;
use quick_xml::Reader;
use crate::dml::fill::{FillFormat, GradientFill, GradientStop, SolidFill};
use crate::error::{PptxError, PptxResult};
use crate::xml_util::{attr_value, local_name_str, read_inner_xml};
use super::parse_color_from_xml;
pub fn parse_fill_from_xml(sp_pr_bytes: &[u8]) -> PptxResult<Option<FillFormat>> {
let mut reader = Reader::from_reader(sp_pr_bytes);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
loop {
buf.clear();
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e)) => {
let qn = e.name();
let local = local_name_str(qn.as_ref());
match local {
"solidFill" => {
let inner = read_inner_xml(&mut reader, "solidFill")
.map_err(|e| PptxError::InvalidXml(format!("solidFill: {e}")))?;
let color = match parse_color_from_xml(&inner)? {
Some(c) => c,
None => return Ok(None),
};
return Ok(Some(FillFormat::Solid(SolidFill { color })));
}
"gradFill" => {
let inner = read_inner_xml(&mut reader, "gradFill")
.map_err(|e| PptxError::InvalidXml(format!("gradFill: {e}")))?;
return parse_gradient_from_inner(&inner);
}
_ => {}
}
}
Ok(Event::Empty(ref e)) => {
let qn = e.name();
let local = local_name_str(qn.as_ref());
match local {
"noFill" => return Ok(Some(FillFormat::NoFill)),
"grpFill" => return Ok(Some(FillFormat::Background)),
_ => {}
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(PptxError::InvalidXml(format!("fill XML error: {e}"))),
_ => {}
}
}
Ok(None)
}
pub(super) fn parse_gradient_from_inner(xml: &[u8]) -> PptxResult<Option<FillFormat>> {
let mut reader = Reader::from_reader(xml);
reader.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut stops = Vec::new();
let mut angle: Option<f64> = None;
loop {
buf.clear();
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e)) => {
let qn = e.name();
let local = local_name_str(qn.as_ref());
if local == "gs" {
#[allow(clippy::cast_precision_loss)]
let pos = attr_value(e, b"pos")?
.and_then(|s| s.parse::<i64>().ok())
.map_or(0.0, |v| v as f64 / 100_000.0);
let inner = read_inner_xml(&mut reader, "gs")
.map_err(|e| PptxError::InvalidXml(format!("gs: {e}")))?;
if let Some(color) = parse_color_from_xml(&inner)? {
if let Ok(stop) = GradientStop::new(pos, color) {
stops.push(stop);
}
}
}
}
Ok(Event::Empty(ref e)) => {
let qn = e.name();
let local = local_name_str(qn.as_ref());
if local == "lin" {
let raw_angle = attr_value(e, b"ang")?
.and_then(|s| s.parse::<i64>().ok())
.unwrap_or(0);
#[allow(clippy::cast_precision_loss)]
let cw_degrees = raw_angle as f64 / 60000.0;
let api_angle = if cw_degrees == 0.0 {
0.0
} else {
360.0 - cw_degrees
};
angle = Some(api_angle);
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(PptxError::InvalidXml(format!("gradient XML error: {e}"))),
_ => {}
}
}
if stops.is_empty() {
return Ok(None);
}
Ok(Some(FillFormat::Gradient(GradientFill { stops, angle })))
}