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