use std::fs::File;
use std::path::Path;
use epub_builder::{EpubBuilder, EpubContent, ZipLibrary};
use crate::error::GenResult;
static DEJAVU_SANS: &[u8] = include_bytes!("../assets/DejaVuSans.ttf");
#[derive(Debug, Default, Clone)]
pub struct RenderReport {
pub epub_written: bool,
pub pdf_written: bool,
pub warnings: Vec<String>,
}
pub fn render_handbook_formats(handbook_md: &str, human_dir: &Path) -> GenResult<RenderReport> {
let mut report = RenderReport::default();
match render_pdf(handbook_md, &human_dir.join("handbook.pdf")) {
Ok(()) => report.pdf_written = true,
Err(e) => report.warnings.push(format!("handbook.pdf: {e}")),
}
match render_epub(handbook_md, &human_dir.join("handbook.epub")) {
Ok(()) => report.epub_written = true,
Err(e) => report.warnings.push(format!("handbook.epub: {e}")),
}
Ok(report)
}
fn render_pdf(markdown: &str, out: &Path) -> Result<(), String> {
let mut font_config = markdown2pdf::fonts::FontConfig::new();
font_config.default_font_source = Some(markdown2pdf::fonts::FontSource::bytes(DEJAVU_SANS));
markdown2pdf::parse_into_file(
markdown.to_string(),
out,
markdown2pdf::config::ConfigSource::Default,
Some(&font_config),
)
.map_err(|e| e.to_string())
}
fn render_epub(markdown: &str, out: &Path) -> Result<(), String> {
let mut html_body = String::new();
pulldown_cmark::html::push_html(&mut html_body, pulldown_cmark::Parser::new(markdown));
let xhtml = format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<!DOCTYPE html>\n\
<html xmlns=\"http://www.w3.org/1999/xhtml\">\n\
<head><title>Handbook</title></head>\n\
<body>\n{html_body}\n</body>\n</html>"
);
let zip = ZipLibrary::new().map_err(|e| e.to_string())?;
let mut builder = EpubBuilder::new(zip).map_err(|e| e.to_string())?;
builder
.metadata("title", "Handbook")
.map_err(|e| e.to_string())?;
builder
.add_content(EpubContent::new("handbook.xhtml", xhtml.as_bytes()).title("Handbook"))
.map_err(|e| e.to_string())?;
let mut file = File::create(out).map_err(|e| e.to_string())?;
builder.generate(&mut file).map_err(|e| e.to_string())?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn renders_both_formats_for_simple_markdown() {
let tmp = TempDir::new().unwrap();
let markdown = "# Handbook\n\nSome **bold** text and a list:\n\n- one\n- two\n";
let report = render_handbook_formats(markdown, tmp.path()).unwrap();
assert!(
report.warnings.is_empty(),
"warnings: {:?}",
report.warnings
);
assert!(report.pdf_written);
assert!(report.epub_written);
let pdf = tmp.path().join("handbook.pdf");
let epub = tmp.path().join("handbook.epub");
assert!(pdf.exists());
assert!(epub.exists());
assert!(std::fs::metadata(&pdf).unwrap().len() > 0);
assert!(std::fs::metadata(&epub).unwrap().len() > 0);
}
#[test]
fn renders_empty_markdown_without_error() {
let tmp = TempDir::new().unwrap();
let report = render_handbook_formats("", tmp.path()).unwrap();
assert!(
report.warnings.is_empty(),
"warnings: {:?}",
report.warnings
);
assert!(report.pdf_written);
assert!(report.epub_written);
}
}