#![doc = include_str!("../images/plotters-histogram.html") ]
#![doc = include_str!("../images/square-random-pixels.html") ]
use data_encoding::BASE64_MIME;
use std::{
collections::BTreeMap,
fs::{create_dir_all, File},
io::{Cursor, Write},
path::Path,
};
mod error;
mod utils;
pub use error::{DocimaError, DocimaResult, StdResult};
use utils::root_path;
pub struct ImageFile {
width: u32,
height: u32,
path: String,
attributes: BTreeMap<String, String>,
wrapper: String,
wrapper_attributes: BTreeMap<String, String>,
overwrite: bool,
}
impl Default for ImageFile {
fn default() -> Self {
Self {
width: 0,
height: 0,
path: String::default(),
attributes: BTreeMap::new(),
wrapper: String::default(),
wrapper_attributes: BTreeMap::new(),
#[cfg(feature = "not_default_overwrite")]
overwrite: false,
#[cfg(not(feature = "not_default_overwrite"))]
overwrite: true,
}
}
}
impl ImageFile {
pub fn new() -> Self {
Self::default()
}
pub fn generate(
self,
generator: impl Fn(&mut Vec<u8>, u32, u32) -> StdResult<()>,
) -> DocimaResult<()> {
if self.width == 0 {
return Err(DocimaError::MissingField("width".into()));
} else if self.height == 0 {
return Err(DocimaError::MissingField("height".into()));
} else if self.path.is_empty() {
return Err(DocimaError::MissingField("path".into()));
}
if cfg![feature = "build_when_doc"] && cfg![not(feature = "doc")] {
return Ok(());
}
let filepath_str = root_path(&self.path);
let filepath = Path::new(&filepath_str);
let dirpath = filepath.parent().ok_or_else(|| {
DocimaError::Custom(format![
"no parent: `{}`",
filepath.to_str().expect("filepath.to_str()")
])
})?;
if !dirpath.exists() {
create_dir_all(dirpath)?;
}
if filepath.exists() && !self.overwrite {
return Ok(());
}
let mut rgb_buffer = vec![0; self.width as usize * self.height as usize * 3];
generator(&mut rgb_buffer, self.width, self.height)?;
let mut png_buffer = Vec::<u8>::new();
{
let cursor_buffer = Cursor::new(&mut png_buffer);
let mut encoder = png::Encoder::new(cursor_buffer, self.width, self.height);
encoder.set_color(png::ColorType::Rgb);
encoder.set_depth(png::BitDepth::Eight);
encoder.set_compression(png::Compression::Best);
let mut writer = encoder.write_header()?;
writer.write_image_data(&rgb_buffer)?;
}
let base64 = BASE64_MIME.encode(png_buffer.as_slice());
let mut content = format!["<img src=\"data:image/png;base64,\n{}\" ", base64];
for (attr, value) in self.attributes {
content += &format!["{0}=\"{1}\" ", attr, value];
}
content += "/>";
if !self.wrapper.is_empty() {
let mut wrapper_open = format!["<{0} ", self.wrapper];
for (attr, value) in self.wrapper_attributes {
wrapper_open += &format!["{0}=\"{1}\" ", attr, value];
}
wrapper_open += ">";
content = format!["{0}{1}</{2}>", wrapper_open, content, self.wrapper];
}
let mut outfile = File::create(filepath)?;
write!(outfile, "{}", content)?;
Ok(())
}
}
impl ImageFile {
pub fn width(mut self, width: u32) -> Self {
self.width = width;
self
}
pub fn height(mut self, height: u32) -> Self {
self.height = height;
self
}
pub fn path(mut self, path: &str) -> Self {
self.path = path.into();
self
}
}
impl ImageFile {
pub fn attr(mut self, attribute: &str, value: &str) -> Self {
self.attributes.insert(attribute.into(), value.into());
self
}
#[deprecated]
#[doc(hidden)]
pub fn alt(mut self, alt: &str) -> Self {
self.attributes.insert("alt".into(), alt.into());
self
}
#[deprecated]
#[doc(hidden)]
pub fn title(mut self, title: &str) -> Self {
self.attributes.insert("title".into(), title.into());
self
}
#[deprecated]
#[doc(hidden)]
pub fn id(mut self, id: &str) -> Self {
self.attributes.insert("id".into(), id.into());
self
}
#[deprecated]
#[doc(hidden)]
pub fn class(mut self, class: &str) -> Self {
self.attributes.insert("class".into(), class.into());
self
}
#[deprecated]
#[doc(hidden)]
pub fn style(mut self, style: &str) -> Self {
self.attributes.insert("style".into(), style.into());
self
}
pub fn wrapper(mut self, wrapper: &str) -> Self {
self.wrapper = wrapper.into();
self
}
pub fn wrapper_attr(mut self, attribute: &str, value: &str) -> Self {
self.wrapper_attributes
.insert(attribute.into(), value.into());
self
}
#[deprecated]
#[doc(hidden)]
pub fn wrapper_alt(mut self, alt: &str) -> Self {
self.wrapper_attributes.insert("alt".into(), alt.into());
self
}
#[deprecated]
#[doc(hidden)]
pub fn wrapper_title(mut self, title: &str) -> Self {
self.wrapper_attributes.insert("title".into(), title.into());
self
}
#[deprecated]
#[doc(hidden)]
pub fn wrapper_id(mut self, id: &str) -> Self {
self.wrapper_attributes.insert("id".into(), id.into());
self
}
#[deprecated]
#[doc(hidden)]
pub fn wrapper_class(mut self, class: &str) -> Self {
self.wrapper_attributes.insert("class".into(), class.into());
self
}
#[deprecated]
#[doc(hidden)]
pub fn wrapper_style(mut self, style: &str) -> Self {
self.wrapper_attributes.insert("style".into(), style.into());
self
}
#[deprecated]
#[doc(hidden)]
pub fn wrapper_href(mut self, href: &str) -> Self {
self.wrapper_attributes.insert("href".into(), href.into());
self
}
#[deprecated]
#[doc(hidden)]
pub fn wrapper_target(mut self, target: &str) -> Self {
self.wrapper_attributes
.insert("target".into(), target.into());
self
}
pub fn overwrite(mut self, overwrite: bool) -> Self {
self.overwrite = overwrite;
self
}
}