use crate::book::Book;
use crate::book_renderer::BookRenderer;
use crate::error::{Error, Result, Source};
use crate::html::Highlight;
use crate::html::HtmlRenderer;
use crate::parser::Parser;
use crate::renderer::Renderer;
use crate::templates::img;
use crate::token::Token;
use crate::misc;
use std::convert::{AsMut, AsRef};
use std::fmt::Write;
use std::io;
use rust_i18n::t;
pub struct HtmlSingleRenderer<'a> {
html: HtmlRenderer<'a>,
}
impl<'a> HtmlSingleRenderer<'a> {
pub fn new(book: &'a Book) -> Result<HtmlSingleRenderer<'a>> {
let mut html = HtmlRenderer::new(
book,
book.options
.get_str("html.highlight.theme")
.unwrap_or_else(|_| book.options.get_str("rendering.highlight.theme").unwrap()),
)?;
html.handler.set_images_mapping(true);
html.handler.set_base64(true);
Ok(HtmlSingleRenderer { html })
}
#[doc(hidden)]
pub fn static_render_token<T>(this: &mut T, token: &Token) -> Result<String>
where
T: AsMut<HtmlSingleRenderer<'a>>
+ AsRef<HtmlSingleRenderer<'a>>
+ AsMut<HtmlRenderer<'a>>
+ AsRef<HtmlRenderer<'a>>
+ Renderer,
{
HtmlRenderer::static_render_token(this, token)
}
pub fn render_book(&mut self) -> Result<String> {
let menu_svg = misc::u8_to_base64(&img::MENU_SVG);
let menu_svg = format!("data:image/svg+xml;base64,{menu_svg}");
let book_svg = misc::u8_to_base64(&img::BOOK_SVG);
let book_svg = format!("data:image/svg+xml;base64,{book_svg}");
let pages_svg = misc::u8_to_base64(&img::PAGES_SVG);
let pages_svg = format!("data:image/svg+xml;base64,{pages_svg}");
let mut content = String::new();
let mut titles = vec![];
let mut chapters = vec![];
let render_notes_chapter = true;
for (i, chapter) in self.html.book.chapters.iter().enumerate() {
self.html
.handler
.add_link(chapter.filename.as_str(), format!("#chapter-{i}"));
}
for (i, chapter) in self.html.book.chapters.iter().enumerate() {
let n = chapter.number;
let v = &chapter.content;
self.html.chapter_config(i, n, String::new());
self.html.footnote_prefix += 1;
let mut title = String::new();
for token in v {
match *token {
Token::Header(1, ref vec) => {
if self.html.current_hide || self.html.current_numbering == 0 {
title = self.html.render_vec(vec)?;
} else {
title = self
.html
.book
.get_chapter_header(
self.html.current_chapter[1] + 1,
self.html.render_vec(vec)?,
|s| self.render_vec(&Parser::new().parse_inline(s)?),
)?
.text;
}
break;
}
_ => {
continue;
}
}
}
titles.push(title);
chapters.push(format!(
"<div id = \"chapter-{}\" class = \"chapter\">
{}
</div>",
i,
HtmlRenderer::render_html(self, v, render_notes_chapter)?
));
}
self.html.source = Source::empty();
for (i, chapter) in chapters.iter().enumerate() {
if self
.html
.book
.options
.get_bool("html.standalone.one_chapter")
.unwrap()
&& i != 0
{
write!(
content,
"<p onclick = \"javascript:showChapter({})\" class = \
\"chapterControls prev_chapter chapter-{}\">
<a href = \"#chapter-{}\">
« {}
</a>
</p>",
i - 1,
i,
i - 1,
titles[i - 1]
)?;
}
content.push_str(chapter);
if self
.html
.book
.options
.get_bool("html.standalone.one_chapter")
.unwrap()
&& i < titles.len() - 1
{
write!(
content,
"<p onclick = \"javascript:showChapter({})\" class = \
\"chapterControls next_chapter chapter-{}\">
<a href = \"#chapter-{}\">
{} »
</a>
</p>",
i + 1,
i,
i + 1,
titles[i + 1]
)?;
}
}
self.html.render_end_notes(&mut content, "section", "");
let toc = self.html.toc.render(false, false);
if self
.html
.book
.options
.get_bool("rendering.inline_toc")
.unwrap()
{
content = format!(
"<div id = \"toc\">
<h1>{title}</h1>
{toc}
</div>
{content}",
title = self.html.get_toc_name()?,
toc = &toc,
content = content
);
}
let template_css_src = self.html.book.get_template("html.css")?;
let template_css = self.html.book.compile_str(
template_css_src.as_ref(),
&self.html.book.source,
"html.css",
)?;
let mut data = self
.html
.book
.get_metadata(|s| self.render_vec(&Parser::new().parse_inline(s)?))?;
data.insert("colors".into(), self.html.book.get_template("html.css.colors")?.into());
if let Ok(html_css_add) = self.html.book.options.get_str("html.css.add") {
data.insert("additional_code".into(), html_css_add.into());
} else {
data.insert("additional_code".into(), "".into());
}
let css = template_css.render(&self.html.book.registry, &data).to_string()?;
let template_js_src = self.html.book.get_template("html.standalone.js")?;
let template_js = self.html.book.compile_str(
template_js_src.as_ref(),
&self.html.book.source,
"html.standalone.js",
)?;
let mut data = self
.html
.book
.get_metadata(|s| Ok(s.to_owned()))?;
data.insert("book_svg".into(), book_svg.clone().into());
data.insert("pages_svg".into(), pages_svg.clone().into());
data.insert(
"one_chapter".into(),
self.html
.book
.options
.get_bool("html.standalone.one_chapter")
.unwrap()
.into(),
);
data.insert(
"common_script".into(),
self.html.book.get_template("html.js").unwrap().into(),
);
let js = template_js.render(&self.html.book.registry, &data).to_string()?;
let mut data = self
.html
.get_metadata()?;
data.insert("content".into(), content.into());
data.insert(
"one_chapter".into(),
self.html
.book
.options
.get_bool("html.standalone.one_chapter")
.unwrap()
.into(),
);
data.insert("style".into(), css.into());
data.insert("script".into(), js.into()); data.insert(
"print_style".into(),
self.html.book.get_template("html.css.print").unwrap().into(),
);
data.insert("menu_svg".into(), menu_svg.clone().into());
data.insert("book_svg".into(), book_svg.clone().into());
data.insert("pages_svg".into(), pages_svg.clone().into());
if let Ok(favicon) = self.html.book.options.get_path("html.icon") {
let favicon = self
.html
.handler
.map_image(&self.html.book.source, favicon)?;
data.insert(
"favicon".into(),
format!("<link rel = \"icon\" href = \"{favicon}\">").into(),
);
} else {
data.insert("favicon".into(), "".into());
}
if !self.html.toc.is_empty() {
data.insert("has_toc".into(), true.into());
data.insert("toc".into(), toc.into());
} else {
data.insert("has_toc".into(), false.into());
}
if self.html.highlight == Highlight::Js {
let highlight_js = misc::u8_to_base64(&self
.html
.book
.get_template("html.highlight.js")?
.as_bytes());
let highlight_js = format!("data:text/javascript;base64,{highlight_js}");
data.insert("highlight_code".into(), true.into());
data.insert(
"highlight_css".into(),
self.html.book.get_template("html.highlight.css")?.into(),
);
data.insert("highlight_js".into(), highlight_js.into());
}
let template_src = self.html.book.get_template("html.standalone.template")?;
let template = self.html.book.compile_str(
template_src.as_ref(),
&self.html.book.source,
"html.standalone.template",
)?;
Ok(template.render(&self.html.book.registry, &data).to_string()?)
}
}
derive_html! {HtmlSingleRenderer<'a>, HtmlSingleRenderer::static_render_token}
pub struct HtmlSingle {}
impl BookRenderer for HtmlSingle {
fn auto_path(&self, book_name: &str) -> Result<String> {
Ok(format!("{book_name}.html"))
}
fn render(&self, book: &Book, to: &mut dyn io::Write) -> Result<()> {
let mut html = HtmlSingleRenderer::new(book)?;
let result = html.render_book()?;
to.write_all(result.as_bytes()).map_err(|e| {
Error::render(
&book.source,
t!("html.write_error", error = e),
)
})?;
Ok(())
}
}