markdown_ppp/html_printer/
mod.rs

1//! HTML renderer for converting Markdown AST to HTML
2//!
3//! This module provides functionality to render a Markdown Abstract Syntax Tree (AST)
4//! into clean, semantic HTML. The renderer supports all CommonMark + GitHub Flavored
5//! Markdown features and produces standards-compliant HTML output.
6//!
7//! # Features
8//!
9//! - **Full AST coverage**: All CommonMark + GFM elements are supported
10//! - **Semantic HTML**: Produces clean, accessible HTML with proper structure
11//! - **GitHub extensions**: Tables, task lists, alerts, footnotes, strikethrough
12//! - **Link resolution**: Automatic resolution of reference links and footnotes
13//! - **Configurable output**: Control HTML formatting and features
14//! - **Security**: Proper escaping of HTML entities and attributes
15//!
16//! # Basic Usage
17//!
18//! ```rust
19//! use markdown_ppp::ast::*;
20//! use markdown_ppp::html_printer::{render_html, config::Config};
21//!
22//! let doc = Document {
23//!     blocks: vec![
24//!         Block::Heading(Heading {
25//!             kind: HeadingKind::Atx(1),
26//!             content: vec![Inline::Text("Hello World".to_string())],
27//!         }),
28//!         Block::Paragraph(vec![
29//!             Inline::Text("This is ".to_string()),
30//!             Inline::Strong(vec![Inline::Text("bold".to_string())]),
31//!             Inline::Text(" text.".to_string()),
32//!         ]),
33//!     ],
34//! };
35//!
36//! let config = Config::default();
37//! let html = render_html(&doc, config);
38//! assert!(html.contains("<h1>Hello World</h1>"));
39//! assert!(html.contains("<b>bold</b>"));
40//! ```
41//!
42//! # Configuration
43//!
44//! Customize the HTML output using configuration:
45//!
46//! ```rust
47//! use markdown_ppp::html_printer::{render_html, config::Config};
48//! use markdown_ppp::ast::{Document, Block, Inline};
49//!
50//! let config = Config::default();
51//! let doc = Document { blocks: vec![Block::Paragraph(vec![Inline::Text("Hello".to_string())])] };
52//! let html = render_html(&doc, config);
53//! ```
54
55mod block;
56
57/// Configuration options for HTML rendering.
58pub mod config;
59mod github_alert;
60mod index;
61mod inline;
62mod tests;
63mod util;
64
65use crate::ast::*;
66use pretty::{Arena, DocBuilder};
67use std::{collections::HashMap, rc::Rc};
68
69/// Internal rendering state for HTML generation
70///
71/// This structure holds the shared state needed during HTML rendering,
72/// including configuration, footnote indexing, and link definition resolution.
73/// It's used internally by the rendering process and is not part of the public API.
74pub(crate) struct State<'a> {
75    /// Pretty-printing arena for efficient document building
76    arena: Arena<'a>,
77    /// HTML rendering configuration
78    config: crate::html_printer::config::Config,
79    /// Mapping of footnote labels to their indices in the footnote list
80    footnote_index: HashMap<String, usize>,
81    /// Mapping of link labels to their definitions for reference link resolution
82    link_definitions: HashMap<Vec<Inline>, LinkDefinition>,
83}
84
85impl State<'_> {
86    pub fn new(config: crate::html_printer::config::Config, ast: &Document) -> Self {
87        let (footnote_index, link_definitions) = crate::html_printer::index::get_indicies(ast);
88        let arena = Arena::new();
89        Self {
90            arena,
91            config,
92            footnote_index,
93            link_definitions,
94        }
95    }
96
97    pub fn get_footnote_index(&self, label: &str) -> Option<&usize> {
98        self.footnote_index.get(label)
99    }
100
101    pub fn get_link_definition(&self, label: &Vec<Inline>) -> Option<&LinkDefinition> {
102        self.link_definitions.get(label)
103    }
104}
105
106/// Render a Markdown AST to semantic HTML
107///
108/// This function takes a parsed Markdown document (AST) and converts it to
109/// clean, standards-compliant HTML. The output includes proper semantic
110/// markup, accessibility features, and support for all CommonMark + GFM
111/// elements.
112///
113/// # Arguments
114///
115/// * `ast` - The Markdown document AST to render
116/// * `config` - Configuration options controlling the HTML output
117///
118/// # Returns
119///
120/// A `String` containing the generated HTML
121///
122/// # Examples
123///
124/// Basic HTML rendering:
125/// ```rust
126/// use markdown_ppp::ast::*;
127/// use markdown_ppp::html_printer::{render_html, config::Config};
128///
129/// let doc = Document {
130///     blocks: vec![
131///         Block::Heading(Heading {
132///             kind: HeadingKind::Atx(1),
133///             content: vec![Inline::Text("Title".to_string())],
134///         }),
135///         Block::Paragraph(vec![
136///             Inline::Text("Text with ".to_string()),
137///             Inline::Strong(vec![Inline::Text("emphasis".to_string())]),
138///         ]),
139///     ],
140/// };
141///
142/// let config = Config::default();
143/// let html = render_html(&doc, config);
144///
145/// assert!(html.contains("<h1>Title</h1>"));
146/// assert!(html.contains("emphasis"));
147/// ```
148///
149/// # HTML Features
150///
151/// The renderer produces:
152/// - Semantic HTML5 elements (`<article>`, `<section>`, `<aside>`, etc.)
153/// - Proper heading hierarchy (`<h1>` through `<h6>`)
154/// - Accessible tables with `<thead>`, `<tbody>`, and proper scoping
155/// - Code syntax highlighting preparation with language classes
156/// - Task list checkboxes with proper `disabled` attributes
157/// - Footnote links with proper `aria-describedby` attributes
158/// - GitHub Alert styling with appropriate CSS classes
159///
160/// # Security
161///
162/// All user content is properly escaped to prevent XSS attacks.
163/// HTML content in the AST is preserved as-is (assumed to be trusted).
164pub fn render_html(ast: &Document, config: crate::html_printer::config::Config) -> String {
165    let state = Rc::new(State::new(config, ast));
166    let doc = ast.to_doc(&state);
167
168    let mut buf = Vec::new();
169    doc.render(state.config.width, &mut buf).unwrap();
170    String::from_utf8(buf).unwrap()
171}
172
173trait ToDoc<'a> {
174    fn to_doc(&self, state: &'a State<'a>) -> DocBuilder<'a, Arena<'a>, ()>;
175}
176
177impl<'a> ToDoc<'a> for Document {
178    fn to_doc(&self, state: &'a State<'a>) -> DocBuilder<'a, Arena<'a>, ()> {
179        self.blocks.to_doc(state)
180    }
181}