use crate::dml::color::ColorFormat;
use crate::dml::fill::FillFormat;
use crate::enums::dml::MsoLineDashStyle;
use crate::units::Emu;
use crate::xml_util::WriteXml;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineCap {
Flat,
Round,
Square,
}
impl LineCap {
#[must_use]
pub const fn to_xml_str(self) -> &'static str {
match self {
Self::Flat => "flat",
Self::Round => "rnd",
Self::Square => "sq",
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineJoin {
Round,
Bevel,
Miter,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct LineFormat {
pub color: Option<ColorFormat>,
pub width: Option<Emu>,
pub dash_style: Option<MsoLineDashStyle>,
pub fill: Option<FillFormat>,
pub cap: Option<LineCap>,
pub join: Option<LineJoin>,
}
impl LineFormat {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn solid(color: ColorFormat, width: Emu) -> Self {
let fill = FillFormat::solid(color.clone());
Self {
color: Some(color),
width: Some(width),
dash_style: None,
fill: Some(fill),
cap: None,
join: None,
}
}
pub fn write_xml<W: std::fmt::Write>(&self, w: &mut W) -> Result<bool, std::fmt::Error> {
let has_any = self.color.is_some()
|| self.width.is_some()
|| self.dash_style.is_some()
|| self.fill.is_some()
|| self.cap.is_some()
|| self.join.is_some();
if !has_any {
return Ok(false);
}
w.write_str("<a:ln")?;
if let Some(w_val) = self.width {
write!(w, r#" w="{}""#, w_val.0)?;
}
if let Some(cap) = self.cap {
write!(w, r#" cap="{}""#, cap.to_xml_str())?;
}
w.write_char('>')?;
if let Some(ref fill) = self.fill {
fill.write_xml(w)?;
} else if let Some(ref color) = self.color {
w.write_str("<a:solidFill>")?;
color.write_xml(w)?;
w.write_str("</a:solidFill>")?;
}
if let Some(dash) = self.dash_style {
write!(w, r#"<a:prstDash val="{}"/>"#, dash.to_xml_str())?;
}
match self.join {
Some(LineJoin::Round) => w.write_str("<a:round/>")?,
Some(LineJoin::Bevel) => w.write_str("<a:bevel/>")?,
Some(LineJoin::Miter) => w.write_str("<a:miter/>")?,
None => {}
}
w.write_str("</a:ln>")?;
Ok(true)
}
#[must_use]
pub fn to_xml_string(&self) -> Option<String> {
let mut s = String::new();
match self.write_xml(&mut s) {
Ok(true) => Some(s),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_line_no_xml() {
let l = LineFormat::new();
assert!(l.to_xml_string().is_none());
}
#[test]
fn test_solid_line_xml() {
let l = LineFormat::solid(ColorFormat::rgb(0, 0, 0), Emu(12700));
let xml = l.to_xml_string().unwrap();
assert!(xml.starts_with("<a:ln"));
assert!(xml.contains(r#"w="12700""#));
assert!(xml.contains("<a:solidFill>"));
assert!(xml.contains(r#"val="000000""#));
assert!(xml.ends_with("</a:ln>"));
}
#[test]
fn test_line_with_dash_style() {
let l = LineFormat {
color: Some(ColorFormat::rgb(255, 0, 0)),
width: Some(Emu(25400)),
dash_style: Some(MsoLineDashStyle::Dash),
fill: None,
cap: None,
join: None,
};
let xml = l.to_xml_string().unwrap();
assert!(xml.contains(r#"w="25400""#));
assert!(xml.contains(r#"<a:prstDash val="dash"/>"#));
assert!(xml.contains("<a:solidFill>"));
assert!(xml.contains("FF0000"));
}
#[test]
fn test_line_width_only() {
let l = LineFormat {
width: Some(Emu(9525)),
..LineFormat::new()
};
let xml = l.to_xml_string().unwrap();
assert!(xml.contains(r#"w="9525""#));
}
#[test]
fn test_line_no_fill() {
let l = LineFormat {
fill: Some(FillFormat::no_fill()),
..LineFormat::new()
};
let xml = l.to_xml_string().unwrap();
assert!(xml.contains("<a:noFill/>"));
}
}