use std::fmt;
use crate::dml::fill::FillFormat;
use crate::enums::dml::MsoColorType;
use crate::enums::text::MsoTextUnderlineType;
use crate::error::PptxError;
use crate::shapes::action::Hyperlink;
use crate::xml_util::{xml_escape, WriteXml};
#[must_use]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RgbColor {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl RgbColor {
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
}
#[inline]
#[must_use]
pub fn to_hex(&self) -> String {
format!("{:02X}{:02X}{:02X}", self.r, self.g, self.b)
}
pub fn from_hex(hex: &str) -> Result<Self, PptxError> {
if hex.len() != 6 {
return Err(PptxError::InvalidValue {
field: "RgbColor",
value: hex.to_string(),
expected: "6-digit hex string (e.g. \"FF0000\")",
});
}
let r = u8::from_str_radix(&hex[0..2], 16).map_err(|_| PptxError::InvalidValue {
field: "RgbColor",
value: hex.to_string(),
expected: "6-digit hex string (e.g. \"FF0000\")",
})?;
let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| PptxError::InvalidValue {
field: "RgbColor",
value: hex.to_string(),
expected: "6-digit hex string (e.g. \"FF0000\")",
})?;
let b = u8::from_str_radix(&hex[4..6], 16).map_err(|_| PptxError::InvalidValue {
field: "RgbColor",
value: hex.to_string(),
expected: "6-digit hex string (e.g. \"FF0000\")",
})?;
Ok(Self { r, g, b })
}
}
impl fmt::Display for RgbColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Font {
pub name: Option<String>,
pub size: Option<f64>,
pub bold: Option<bool>,
pub italic: Option<bool>,
pub underline: Option<MsoTextUnderlineType>,
pub color: Option<RgbColor>,
pub strikethrough: Option<bool>,
pub subscript: Option<bool>,
pub superscript: Option<bool>,
pub language_id: Option<String>,
pub fill: Option<FillFormat>,
pub hyperlink: Option<Hyperlink>,
}
impl Font {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn set_size(&mut self, size: f64) -> Result<(), PptxError> {
if size <= 0.0 {
return Err(PptxError::InvalidValue {
field: "Font.size",
value: size.to_string(),
expected: "greater than 0.0",
});
}
self.size = Some(size);
Ok(())
}
#[must_use]
pub fn color_type(&self) -> Option<MsoColorType> {
self.fill.as_ref().map_or_else(
|| {
if self.color.is_some() {
Some(MsoColorType::Rgb)
} else {
None
}
},
|fill| match fill {
FillFormat::Solid(solid) => Some(solid.color.color_type()),
_ => None,
},
)
}
pub fn write_xml<W: std::fmt::Write>(&self, w: &mut W) -> std::fmt::Result {
w.write_str("<a:rPr")?;
if let Some(ref lang) = self.language_id {
write!(w, r#" lang="{}""#, xml_escape(lang))?;
} else {
w.write_str(r#" lang="en-US""#)?;
}
if let Some(size) = self.size {
#[allow(clippy::cast_possible_truncation)] let sz = (size * 100.0) as i64;
write!(w, r#" sz="{sz}""#)?;
}
if let Some(bold) = self.bold {
write!(w, r#" b="{}""#, if bold { "1" } else { "0" })?;
}
if let Some(italic) = self.italic {
write!(w, r#" i="{}""#, if italic { "1" } else { "0" })?;
}
if let Some(underline) = self.underline {
write!(w, r#" u="{}""#, underline.to_xml_str())?;
}
if let Some(strike) = self.strikethrough {
write!(
w,
r#" strike="{}""#,
if strike { "sngStrike" } else { "noStrike" }
)?;
}
if self.superscript == Some(true) {
w.write_str(r#" baseline="30000""#)?;
} else if self.subscript == Some(true) {
w.write_str(r#" baseline="-25000""#)?;
}
w.write_str(r#" dirty="0""#)?;
let has_children = self.fill.is_some()
|| self.color.is_some()
|| self.name.is_some()
|| self.hyperlink.is_some();
if has_children {
w.write_char('>')?;
if let Some(ref fill) = self.fill {
fill.write_xml(w)?;
} else if let Some(ref color) = self.color {
write!(
w,
r#"<a:solidFill><a:srgbClr val="{}"/></a:solidFill>"#,
color.to_hex()
)?;
}
if let Some(ref name) = self.name {
write!(w, r#"<a:latin typeface="{}"/>"#, xml_escape(name))?;
}
if let Some(ref hlink) = self.hyperlink {
w.write_str("<a:hlinkClick")?;
if let Some(ref rid) = hlink.r_id {
write!(w, r#" r:id="{}""#, xml_escape(rid.as_str()))?;
}
if let Some(ref tooltip) = hlink.tooltip {
write!(w, r#" tooltip="{}""#, xml_escape(tooltip))?;
}
w.write_str("/>")?;
}
w.write_str("</a:rPr>")
} else {
w.write_str("/>")
}
}
#[must_use]
pub fn to_xml_string(&self) -> String {
let mut s = String::new();
self.write_xml(&mut s)
.unwrap_or_else(|_| unreachable!("write to String should not fail"));
s
}
}
#[cfg(test)]
#[path = "font_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "font_color_tests.rs"]
mod color_tests;