mod blocks;
mod callout;
mod code_block;
mod incremental;
mod inlines;
mod links;
mod visit;
mod write;
use ox_content_ast::{Document, Node};
use rustc_hash::FxHashMap;
use super::autolink::FirstByteIndex;
use super::escape::{write_escaped_into, write_url_escaped_into};
use super::options::HtmlRendererOptions;
use super::toc::{collect_inline_toc_entries, scan_document_for_render, InlineTocEntry};
use crate::render::{RenderResult, Renderer};
pub struct HtmlRenderer {
options: HtmlRendererOptions,
output: String,
heading_id_counts: FxHashMap<String, usize>,
toc_entries: Vec<InlineTocEntry>,
document_has_toc_marker: bool,
heading_text_scratch: String,
heading_slug_scratch: String,
in_link: bool,
autolink_index: Option<FirstByteIndex>,
}
impl HtmlRenderer {
#[must_use]
pub fn new() -> Self {
Self::with_options(HtmlRendererOptions::new())
}
#[must_use]
pub fn with_options(options: HtmlRendererOptions) -> Self {
Self {
options,
output: String::new(),
heading_id_counts: FxHashMap::default(),
toc_entries: Vec::new(),
document_has_toc_marker: false,
heading_text_scratch: String::with_capacity(64),
heading_slug_scratch: String::with_capacity(64),
in_link: false,
autolink_index: None,
}
}
#[must_use]
pub fn render(&mut self, document: &Document<'_>) -> String {
crate::profile_span!("renderer::render");
self.output.clear();
self.toc_entries.clear();
let document_scan = scan_document_for_render(document);
self.document_has_toc_marker = document_scan.has_toc_marker;
if self.document_has_toc_marker {
collect_inline_toc_entries(document, self.options.toc_max_depth, &mut self.toc_entries);
}
self.heading_id_counts.clear();
self.heading_id_counts.reserve(document_scan.heading_count);
self.autolink_index =
if self.options.autolink_urls && !self.options.autolink_patterns.is_empty() {
Some(FirstByteIndex::from_patterns(&self.options.autolink_patterns))
} else {
None
};
let estimated_len = (document.span.len() as usize).saturating_mul(2);
if self.output.capacity() < estimated_len {
self.output.reserve(estimated_len - self.output.capacity());
}
self.render_document(document);
std::mem::take(&mut self.output)
}
pub(in crate::html::renderer) fn render_document(&mut self, document: &Document<'_>) {
for child in &document.children {
self.render_node(child);
}
}
#[inline]
pub(in crate::html::renderer) fn render_node(&mut self, node: &Node<'_>) {
match node {
Node::Paragraph(node) => self.render_paragraph(node),
Node::Heading(node) => self.render_heading(node),
Node::ThematicBreak(node) => self.render_thematic_break(node),
Node::BlockQuote(node) => self.render_block_quote(node),
Node::List(node) => self.render_list(node),
Node::ListItem(node) => self.render_list_item(node),
Node::CodeBlock(node) => self.render_code_block(node),
Node::Html(node) => self.render_html(node),
Node::Table(node) => self.render_table(node),
Node::Text(node) => self.render_text(node),
Node::Emphasis(node) => self.render_emphasis(node),
Node::Strong(node) => self.render_strong(node),
Node::InlineCode(node) => self.render_inline_code(node),
Node::Break(node) => self.render_break(node),
Node::Link(node) => self.render_link(node),
Node::Image(node) => self.render_image(node),
Node::Delete(node) => self.render_delete(node),
Node::FootnoteReference(node) => self.render_footnote_reference(node),
Node::Definition(_) => {}
Node::FootnoteDefinition(node) => self.render_footnote_definition(node),
}
}
pub(in crate::html::renderer) fn render_inline_toc(&mut self) {
use std::fmt::Write as _;
if self.toc_entries.is_empty() {
return;
}
self.write("<nav class=\"ox-toc\" aria-label=\"Table of contents\">\n<ul>\n");
for entry in &self.toc_entries {
self.output.push_str("<li class=\"ox-toc__item ox-toc__item--depth-");
let _ = write!(self.output, "{}", entry.depth);
self.output.push_str("\"><a href=\"#");
write_url_escaped_into(&mut self.output, &entry.id);
self.output.push_str("\">");
write_escaped_into(&mut self.output, &entry.text);
self.output.push_str("</a></li>\n");
}
self.write("</ul>\n</nav>\n");
}
}
impl Default for HtmlRenderer {
fn default() -> Self {
Self::new()
}
}
impl Renderer for HtmlRenderer {
type Output = String;
fn render(&mut self, document: &Document<'_>) -> RenderResult<Self::Output> {
Ok(self.render(document))
}
}