//! Support for loading user defined rendering options. //! //! User options are given in a toml file, for example //! //! ```toml //! skill_size = 20 //! ``` //! //! The file is automatically deserialized by serde, so all fields defined in //! [`UserOptions`](struct.UserOptions.html) can be used. use image::Rgba; use rusttype::Font; use serde::{Deserialize, Serialize}; use std::{error::Error, fmt, fs, io, path::Path}; use super::render::{Alignment, RenderOptions}; /// Error that can occur during loading or converting user options. #[derive(Debug)] pub enum ConfigError { Io(io::Error), Serialization(toml::de::Error), Font(rusttype::Error), } error_froms! { ConfigError, err: io::Error => ConfigError::Io(err), err: toml::de::Error => ConfigError::Serialization(err), err: rusttype::Error => ConfigError::Font(err), } impl fmt::Display for ConfigError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ConfigError: ")?; match *self { ConfigError::Io(_) => write!(f, "input/output error"), ConfigError::Serialization(_) => write!(f, "serialization error"), ConfigError::Font(_) => write!(f, "could not load the font"), } } } impl Error for ConfigError { fn source(&self) -> Option<&(dyn Error + 'static)> { match *self { ConfigError::Io(ref err) => Some(err), ConfigError::Serialization(ref err) => Some(err), ConfigError::Font(ref err) => Some(err), } } } macro_rules! maybe_take_from { (from: $from:expr, to: $to:ident, $($field:ident,)*) => { $( if let Some(data) = $from.$field { $to.$field = data; } )* } } /// A struct for deserializing user defined options. /// /// This is basically a struct containing the same fields, but optional. If a field is given, it /// should override the corresponding field in /// [`RenderOptions`](../render/struct.RenderOptions.html). /// /// Some fields require some pre-processing, as the proper type cannot be serialized (e.g. /// `image::Rgba` for `background_color`). #[derive(Debug, Serialize, Deserialize)] pub struct UserOptions { pub skill_size: Option, pub traitline_brightness: Option, pub traitline_use_gradient: Option, pub traitline_gradient_size: Option, pub traitline_x_offset: Option, pub trait_size: Option, pub line_color: Option<[u8; 4]>, pub line_height: Option, pub font_path: Option, pub text_color: Option<[u8; 4]>, pub text_size: Option, pub background_color: Option<[u8; 4]>, pub render_specialization_names: Option, pub skill_offset: Option, pub skill_alignment: Option, } impl UserOptions { /// Converts this `UserOptions` to a proper /// [`RenderOptions`](../render/struct.RenderOptions.html). pub fn convert(self) -> Result { let mut result = RenderOptions::default(); if let Some(data) = self.background_color { result.background_color = Rgba(data); } if let Some(data) = self.line_color { result.line_color = Rgba(data); } if let Some(data) = self.text_color { result.text_color = Rgba(data); } if let Some(path) = self.font_path { let data = fs::read(path)?; let font = Font::from_bytes(data)?; result.font = font; } maybe_take_from! { from: self, to: result, skill_size, traitline_brightness, traitline_use_gradient, traitline_gradient_size, traitline_x_offset, trait_size, line_height, text_size, render_specialization_names, skill_offset, skill_alignment, } Ok(result) } } /// Load user given options from the given file path. /// /// This is basically a convenience function around reading the data, deserializing it from toml /// and returning [`UserOptions::convert`](struct.UserOptions.html#method.convert). pub fn load_file>(path: P) -> Result { let data = fs::read(path)?; let opts = toml::from_slice::(&data)?; opts.convert() }