markdown_ppp/latex_printer/
util.rs

1//! Utility functions for LaTeX rendering
2//!
3//! This module provides helper functions for LaTeX generation including
4//! character escaping and LaTeX environment/command generation.
5
6use pretty::{Arena, DocAllocator, DocBuilder};
7
8/// Escape LaTeX special characters in text
9///
10/// This function converts plain text to LaTeX-safe text by escaping all
11/// special characters that have meaning in LaTeX. This is essential for
12/// preventing LaTeX compilation errors and ensuring text displays correctly.
13///
14/// # LaTeX Special Characters
15///
16/// The following characters are escaped:
17/// - `\` → `\textbackslash{}`
18/// - `{` → `\{`
19/// - `}` → `\}`
20/// - `$` → `\$`
21/// - `&` → `\&`
22/// - `%` → `\%`
23/// - `#` → `\#`
24/// - `^` → `\textasciicircum{}`
25/// - `_` → `\_`
26/// - `~` → `\textasciitilde{}`
27///
28/// # Examples
29///
30/// ```rust
31/// # use markdown_ppp::latex_printer::util::escape_latex;
32/// assert_eq!(escape_latex("Hello & goodbye"), "Hello \\& goodbye");
33/// assert_eq!(escape_latex("Price: $100"), "Price: \\$100");
34/// assert_eq!(escape_latex("50% off"), "50\\% off");
35/// ```
36pub fn escape_latex(text: &str) -> String {
37    text.chars()
38        .map(|c| match c {
39            '\\' => r"\textbackslash{}".to_string(),
40            '{' => r"\{".to_string(),
41            '}' => r"\}".to_string(),
42            '$' => r"\$".to_string(),
43            '&' => r"\&".to_string(),
44            '%' => r"\%".to_string(),
45            '#' => r"\#".to_string(),
46            '^' => r"\textasciicircum{}".to_string(),
47            '_' => r"\_".to_string(),
48            '~' => r"\textasciitilde{}".to_string(),
49            _ => c.to_string(),
50        })
51        .collect()
52}
53
54/// Create a LaTeX environment with begin/end blocks
55///
56/// This function generates a complete LaTeX environment with proper
57/// `\begin{env}` and `\end{env}` markers, optional parameters, and content.
58///
59/// # Arguments
60///
61/// * `arena` - The pretty-printer arena for document generation
62/// * `name` - The environment name (e.g., "itemize", "verbatim")
63/// * `options` - Optional environment parameters (e.g., "\[htbp\]")
64/// * `content` - The content to place inside the environment
65///
66/// # Examples
67///
68/// ```rust
69/// # use pretty::{Arena, DocAllocator};
70/// # use markdown_ppp::latex_printer::util::environment;
71/// let arena = Arena::new();
72/// let content = arena.text("Hello world");
73/// let env = environment(&arena, "quote", None, content);
74/// // Generates: \begin{quote}\nHello world\n\end{quote}
75/// ```
76///
77/// With options:
78/// ```rust
79/// # use pretty::{Arena, DocAllocator};
80/// # use markdown_ppp::latex_printer::util::environment;
81/// let arena = Arena::new();
82/// let content = arena.text("Column 1 & Column 2");
83/// let env = environment(&arena, "tabular", Some("lc"), content);
84/// // Generates: \begin{tabular}[lc]\nColumn 1 & Column 2\n\end{tabular}
85/// ```
86pub fn environment<'a>(
87    arena: &'a Arena<'a>,
88    name: &str,
89    options: Option<&str>,
90    content: DocBuilder<'a, Arena<'a>, ()>,
91) -> DocBuilder<'a, Arena<'a>, ()> {
92    let begin = if let Some(opts) = options {
93        arena.text(format!(r"\begin{{{name}}}[{opts}]"))
94    } else {
95        arena.text(format!(r"\begin{{{name}}}"))
96    };
97
98    let end = arena.text(format!(r"\end{{{name}}}"));
99
100    begin
101        .append(arena.hardline())
102        .append(content)
103        .append(arena.hardline())
104        .append(end)
105}
106
107/// Create a LaTeX command with optional arguments and content
108///
109/// This function generates a LaTeX command with optional square-bracket
110/// arguments and curly-brace content.
111///
112/// # Arguments
113///
114/// * `arena` - The pretty-printer arena for document generation
115/// * `name` - The command name (without backslash)
116/// * `args` - Optional square-bracket arguments
117/// * `content` - The content to place in curly braces
118///
119/// # Examples
120///
121/// Basic command:
122/// ```rust
123/// # use pretty::{Arena, DocAllocator};
124/// # use markdown_ppp::latex_printer::util::command;
125/// let arena = Arena::new();
126/// let content = arena.text("bold text");
127/// let cmd = command(&arena, "textbf", &[], content);
128/// // Generates: \textbf{bold text}
129/// ```
130///
131/// Command with arguments:
132/// ```rust
133/// # use pretty::{Arena, DocAllocator};
134/// # use markdown_ppp::latex_printer::util::command;
135/// let arena = Arena::new();
136/// let content = arena.text("https://example.com");
137/// let cmd = command(&arena, "href", &["target=_blank"], content);
138/// // Generates: \href[target=_blank]{https://example.com}
139/// ```
140pub fn command<'a>(
141    arena: &'a Arena<'a>,
142    name: &str,
143    args: &[&str],
144    content: DocBuilder<'a, Arena<'a>, ()>,
145) -> DocBuilder<'a, Arena<'a>, ()> {
146    let mut cmd = arena.text(format!(r"\{name}"));
147
148    for arg in args {
149        cmd = cmd.append(arena.text(format!("[{arg}]")));
150    }
151
152    cmd.append(arena.text("{"))
153        .append(content)
154        .append(arena.text("}"))
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_escape_latex() {
163        assert_eq!(escape_latex("hello"), "hello");
164        assert_eq!(escape_latex("$100"), r"\$100");
165        assert_eq!(escape_latex("A & B"), r"A \& B");
166        assert_eq!(escape_latex("50%"), r"50\%");
167        assert_eq!(escape_latex("#hashtag"), r"\#hashtag");
168        assert_eq!(escape_latex("x^2"), r"x\textasciicircum{}2");
169        assert_eq!(escape_latex("file_name"), r"file\_name");
170        assert_eq!(escape_latex("~home"), r"\textasciitilde{}home");
171        assert_eq!(escape_latex("{code}"), r"\{code\}");
172        assert_eq!(escape_latex(r"\command"), r"\textbackslash{}command");
173    }
174}