Skip to main content

docgen_core/
mathpass.rs

1//! AST pass that replaces comrak math nodes with build-time KaTeX HTML.
2//!
3//! Runs in `render_docs` after the wikilink pass and before `format_ast`.
4//! Each `NodeValue::Math` becomes a `NodeValue::HtmlInline` holding the rendered
5//! KaTeX markup (raw HTML is allowed through because `comrak_options().render
6//! .unsafe = true`). Display math keeps KaTeX's own block-level
7//! `<span class="katex-display">` wrapper, so layout stays correct even though
8//! the carrier node is inline.
9
10use comrak::nodes::{AstNode, NodeValue};
11
12use crate::math::render_math;
13
14/// Replace every math node in the tree with its build-time KaTeX HTML.
15///
16/// Returns the count of math nodes rendered, letting callers record whether a
17/// page used math (drives the conditional KaTeX `<head>` link).
18pub fn transform_math<'a>(root: &'a AstNode<'a>) -> usize {
19    let mut count = 0;
20    transform(root, &mut count);
21    count
22}
23
24fn transform<'a>(node: &'a AstNode<'a>, count: &mut usize) {
25    // Read the math literal/flags first, then drop the borrow before mutating.
26    let replacement = {
27        let data = node.data.borrow();
28        if let NodeValue::Math(m) = &data.value {
29            Some(render_math(&m.literal, m.display_math))
30        } else {
31            None
32        }
33    };
34    if let Some(html) = replacement {
35        node.data.borrow_mut().value = NodeValue::HtmlInline(html);
36        *count += 1;
37    }
38    for child in node.children() {
39        transform(child, count);
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::markdown::{comrak_options, format_ast};
47    use comrak::{parse_document, Arena};
48
49    fn render(md: &str) -> String {
50        let arena = Arena::new();
51        let opts = comrak_options();
52        let root = parse_document(&arena, md, &opts);
53        let n = transform_math(root);
54        assert!(n >= 1, "expected at least one math node");
55        format_ast(root, &opts)
56    }
57
58    #[test]
59    fn inline_dollar_math_becomes_katex_html() {
60        let html = render("Euler: $e^{i\\pi}+1=0$ done\n");
61        assert!(html.contains("katex"));
62        assert!(!html.contains("$e^")); // raw delimiters gone
63    }
64
65    #[test]
66    fn display_math_becomes_katex_display() {
67        let html = render("$$\\sum_{i=1}^n i$$\n");
68        assert!(html.contains("katex-display"));
69    }
70
71    #[test]
72    fn no_math_leaves_document_untouched() {
73        let arena = Arena::new();
74        let opts = comrak_options();
75        let root = parse_document(&arena, "plain text only\n", &opts);
76        assert_eq!(transform_math(root), 0);
77        assert!(format_ast(root, &opts).contains("plain text only"));
78    }
79}