karo 0.1.2

Spreadsheet export
Documentation
use crate::{error, AsPaletteColor, Result, XmlWritable, XmlWriter};
use decorum::R64;
use indexmap::{indexmap, IndexMap, IndexSet};
use rgb::RGB8;
use std::cell::RefCell;
use std::rc::Rc;

const SIZE_MIN: f64 = 1.0f64;
const SIZE_MAX: f64 = 409.0f64;
const SIZE_DEFAULT: f64 = 11.0f64;
const NAME_DEFAULT: &str = "Calibri";
const FONT_FAMILY_DEFAULT: u8 = 2;

#[derive(PartialEq, Eq, Clone, Hash, Debug, Default)]
pub struct Font {
    pub name: Option<String>,
    pub size: FontSize,
    pub color: Option<RGB8>,
    pub bold: bool,
    pub italic: bool,
    pub underline: Option<Underline>,
    pub strikeout: bool,
    pub script: Option<Script>,
    pub outline: bool,
    pub shadow: bool,
}

#[derive(PartialEq, Eq, Clone, Hash, Debug)]
pub struct FontSize {
    value: R64,
}

impl Default for FontSize {
    fn default() -> Self {
        FontSize {
            value: R64::from(SIZE_DEFAULT),
        }
    }
}

impl XmlWritable for FontSize {
    fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        w.empty_tag_with_attrs(
            "sz",
            indexmap! { "val" => format!("{}", self.value) },
        )
    }
}

impl FontSize {
    pub fn new(size: f64) -> Result<Self> {
        Ok(FontSize {
            value: Self::build_value(size)?,
        })
    }

    pub fn set(&mut self, size: f64) -> Result<()> {
        self.value = Self::build_value(size)?;
        Ok(())
    }

    pub fn get(&self) -> f64 {
        self.value.into_inner()
    }

    fn build_value(size: f64) -> Result<R64> {
        if size < SIZE_MIN || size > SIZE_MAX {
            error::FontSizeOutOfRange {
                min: SIZE_MIN,
                max: SIZE_MAX,
                size,
            }
            .fail()?
        } else {
            Ok(R64::from(size))
        }
    }
}

#[derive(PartialEq, Eq, Clone, Hash, Debug)]
pub enum Underline {
    Single,
    Double,
    SingleAccounting,
    DoubleAccounting,
}

#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum Script {
    SuperScript,
    SubScript,
}

#[derive(Default, Debug)]
struct Inner {
    fonts: IndexSet<Font>,
}

#[derive(Clone, Debug)]
pub(crate) struct Fonts {
    inner: Rc<RefCell<Inner>>,
}

impl Default for Fonts {
    fn default() -> Self {
        let mut inner = Inner::default();
        inner.fonts.insert(Font::default());
        Fonts {
            inner: Rc::new(RefCell::new(inner)),
        }
    }
}

impl Font {
    pub fn set_name<S: Into<String>>(&mut self, name: S) {
        self.name = Some(name.into());
    }
}

impl Fonts {
    pub fn create_or_get_index(
        &mut self,
        font: Option<&Font>,
    ) -> Option<usize> {
        let mut inner = self.inner.borrow_mut();
        font.map(|f| inner.fonts.insert_full(f.clone()).0)
    }
}

impl XmlWritable for Fonts {
    fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        self.inner.borrow().write_xml(w)
    }
}

impl XmlWritable for Inner {
    fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        if !self.fonts.is_empty() {
            let tag = "fonts";
            let attrs = indexmap! {
                "count" => format!("{}", self.fonts.len()),
            };
            w.start_tag_with_attrs(tag, attrs)?;
            for font in self.fonts.iter() {
                font.write_xml(w)?;
            }
            w.end_tag(tag)?;
        }
        Ok(())
    }
}

impl XmlWritable for Font {
    fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        let tag = "font";
        w.start_tag(tag)?;
        self.write_bold(w)?;
        self.write_italic(w)?;
        self.write_strikeout(w)?;
        self.write_outline(w)?;
        self.write_shadow(w)?;
        self.write_underline(w)?;
        self.write_script(w)?;
        self.write_size(w)?;
        self.write_color(w)?;
        self.write_name(w)?;
        self.write_family(w)?;
        self.write_scheme(w)?;
        w.end_tag(tag)?;
        Ok(())
    }
}

impl Font {
    fn write_bold<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        if self.bold {
            w.empty_tag("b")?;
        }
        Ok(())
    }

    fn write_italic<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        if self.italic {
            w.empty_tag("i")?;
        }
        Ok(())
    }

    fn write_strikeout<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        if self.strikeout {
            w.empty_tag("strike")?;
        }
        Ok(())
    }

    fn write_outline<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        if self.outline {
            w.empty_tag("outline")?;
        }
        Ok(())
    }

    fn write_shadow<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        if self.shadow {
            w.empty_tag("shadow")?;
        }
        Ok(())
    }

    fn write_underline<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        if let Some(ref underline) = self.underline {
            underline.write_xml(w)?;
        }
        Ok(())
    }

    fn write_script<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        if let Some(ref script) = self.script {
            script.write_xml(w)?;
        }
        Ok(())
    }

    fn write_size<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        self.size.write_xml(w)
    }

    fn write_color<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        // TODO: enable writing of theme colors as well.
        if let Some(color) = self.color {
            w.empty_tag_with_attrs(
                "color",
                indexmap! {
                    "rgb" => color.as_palette_color()
                },
            )?;
        }
        Ok(())
    }

    fn write_name<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        let name = self
            .name
            .as_ref()
            .map(|s| s.as_str())
            .unwrap_or(NAME_DEFAULT);
        let attrs = indexmap! {"val" => name };
        // TODO: allow writing of rich string as well
        w.empty_tag_with_attrs("name", attrs)
    }

    fn write_family<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        // TODO: writing the default for now, couldn't find any
        // documentation on whether this is an enum or which values
        // make sense.
        w.empty_tag_with_attrs(
            "family",
            indexmap! {
                "val" => format!("{}", FONT_FAMILY_DEFAULT)
            },
        )
    }

    fn write_scheme<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        // TODO: implement this.
        Ok(())
    }
}

impl Underline {
    fn get_xml_attrs(&self) -> IndexMap<&'static str, &'static str> {
        match self {
            Underline::Single => indexmap! {},
            Underline::Double => indexmap! {"val" => "double"},
            Underline::SingleAccounting => indexmap! {
                "val" => "singleAccounting",
            },
            Underline::DoubleAccounting => indexmap! {
                "val" => "doubleAccounting",
            },
        }
    }
}

impl XmlWritable for Underline {
    fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        w.empty_tag_with_attrs("u", self.get_xml_attrs())
    }
}

impl Script {
    fn get_xml_attrs(&self) -> IndexMap<&'static str, &'static str> {
        match self {
            Script::SuperScript => indexmap! { "val" => "superscript"},
            Script::SubScript => indexmap! { "val" => "subscript"},
        }
    }
}

impl XmlWritable for Script {
    fn write_xml<W: XmlWriter>(&self, w: &mut W) -> Result<()> {
        w.empty_tag_with_attrs("vertAlign", self.get_xml_attrs())
    }
}