#![doc = include_str!("../images/plotters-histogram.html") ]
#![doc = include_str!("../images/square-random-pixels.html") ]
use data_encoding::BASE64_MIME;
use std::{
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,
alt: String,
title: String,
id: String,
class: String,
style: String,
wrapper: String,
wrapper_alt: String,
wrapper_title: String,
wrapper_id: String,
wrapper_class: String,
wrapper_style: String,
wrapper_href: String,
wrapper_target: String,
overwrite: bool,
}
impl Default for ImageFile {
fn default() -> Self {
Self {
width: 0,
height: 0,
path: String::default(),
alt: String::default(),
title: String::default(),
id: String::default(),
class: String::default(),
style: String::default(),
wrapper: String::default(),
wrapper_alt: String::default(),
wrapper_title: String::default(),
wrapper_id: String::default(),
wrapper_class: String::default(),
wrapper_style: String::default(),
wrapper_href: String::default(),
wrapper_target: String::default(),
overwrite: false,
}
}
}
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()));
}
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];
if !self.id.is_empty() {
content += &format!["id=\"{}\" ", self.id];
}
if !self.class.is_empty() {
content += &format!["class=\"{}\" ", self.class];
}
if !self.alt.is_empty() {
content += &format!["alt=\"{}\" ", self.alt];
}
if !self.title.is_empty() {
content += &format!["title=\"{}\" ", self.title];
}
if !self.style.is_empty() {
content += &format!["style=\"{}\" ", self.style];
}
content += "/>";
if !self.wrapper.is_empty() {
let mut wrapper_open = format!["<{0} ", self.wrapper];
if !self.wrapper_id.is_empty() {
wrapper_open += &format!["class=\"{}\" ", self.wrapper_id];
}
if !self.wrapper_class.is_empty() {
wrapper_open += &format!["class=\"{}\" ", self.wrapper_class];
}
if !self.wrapper_alt.is_empty() {
wrapper_open += &format!["alt=\"{}\" ", self.wrapper_alt];
}
if !self.wrapper_title.is_empty() {
wrapper_open += &format!["title=\"{}\" ", self.wrapper_title];
}
if !self.wrapper_style.is_empty() {
wrapper_open += &format!["style=\"{}\" ", self.wrapper_style];
}
if self.wrapper == "a" {
if !self.wrapper_href.is_empty() {
wrapper_open += &format!["href=\"{}\" ", self.wrapper_href];
}
if !self.wrapper_target.is_empty() {
wrapper_open += &format!["target=\"{}\" ", self.wrapper_target];
}
}
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 alt(mut self, alt: &str) -> Self {
self.alt = alt.into();
self
}
pub fn title(mut self, title: &str) -> Self {
self.title = title.into();
self
}
pub fn id(mut self, id: &str) -> Self {
self.id = id.into();
self
}
pub fn class(mut self, class: &str) -> Self {
self.class = class.into();
self
}
pub fn style(mut self, style: &str) -> Self {
self.style = style.into();
self
}
pub fn wrapper(mut self, wrapper: &str) -> Self {
self.wrapper = wrapper.into();
self
}
pub fn wrapper_alt(mut self, alt: &str) -> Self {
self.wrapper_alt = alt.into();
self
}
pub fn wrapper_title(mut self, title: &str) -> Self {
self.wrapper_title = title.into();
self
}
pub fn wrapper_id(mut self, id: &str) -> Self {
self.wrapper_id = id.into();
self
}
pub fn wrapper_class(mut self, class: &str) -> Self {
self.wrapper_class = class.into();
self
}
pub fn wrapper_style(mut self, style: &str) -> Self {
self.wrapper_style = style.into();
self
}
pub fn wrapper_href(mut self, href: &str) -> Self {
self.wrapper_href = href.into();
self
}
pub fn wrapper_target(mut self, target: &str) -> Self {
self.wrapper_target = target.into();
self
}
pub fn overwrite(mut self, overwrite: bool) -> Self {
self.overwrite = overwrite;
self
}
}