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