tex2typst_rs/lib.rs
1use crate::command_registry::{parse_custom_macros, CommandRegistry};
2use crate::tex_parser::LatexParser;
3use crate::typst_writer::SymbolShorthand;
4use regex::{Captures, Regex};
5
6pub mod command_registry;
7pub mod converter;
8pub mod definitions;
9pub mod map;
10mod tests;
11pub mod tex_parser;
12pub mod tex_parser_utils;
13pub mod tex_tokenizer;
14pub mod typst_writer;
15
16/// Converts a given TeX string to a Typst string.
17///
18/// This function takes a TeX string as input, parses it into a TeX tree,
19/// converts the TeX tree to a Typst tree, serializes the Typst tree, and
20/// returns the resulting Typst string.
21///
22/// # Arguments
23///
24/// * `tex` - A string slice that holds the TeX input.
25///
26/// # Returns
27///
28/// * `Result<String, String>` - On success, returns the Typst string wrapped in `Ok`.
29/// On failure, returns an error message wrapped in `Err`.
30///
31/// # Errors
32///
33/// This function will return an error if the TeX parsing, conversion, or
34/// serialization fails.
35///
36/// # Example
37///
38/// ```
39/// use tex2typst_rs::tex2typst;
40/// let tex_input = r"\frac{a}{b}";
41/// let typst_output = tex2typst(tex_input).unwrap();
42/// println!("{}", typst_output);
43/// ```
44pub fn tex2typst(tex: &str) -> Result<String, String> {
45 let tex_tree = tex_parser::parse_tex(tex)?;
46 let typst_tree = converter::convert_tree(&tex_tree)?;
47 let mut writer = typst_writer::TypstWriter::new();
48 writer.serialize(&typst_tree)?;
49 let typst = writer.finalize()?;
50 Ok(typst)
51}
52
53/// Converts a given TeX string to a Typst string with custom macro definitions.
54///
55/// This function takes a TeX string and a string containing macro definitions as input,
56/// tokenizes the TeX string, parses the custom macros, registers them, expands the macros,
57/// parses the expanded tokens into a TeX tree, converts the TeX tree to a Typst tree,
58/// serializes the Typst tree, and returns the resulting Typst string.
59///
60/// # Arguments
61///
62/// * `tex` - A string slice that holds the TeX input.
63/// * `macro_definitions` - A string slice that holds the custom macro definitions.
64///
65/// # Returns
66///
67/// * `Result<String, String>` - On success, returns the Typst string wrapped in `Ok`.
68/// On failure, returns an error message wrapped in `Err`.
69///
70/// # Errors
71///
72/// This function will return an error if the tokenization, macro parsing, macro expansion,
73/// TeX parsing, conversion, or serialization fails.
74///
75/// # Example
76///
77/// ```
78/// use tex2typst_rs::tex2typst_with_macros;
79/// let tex_input = r"\foo";
80/// let macro_definitions = r"\newcommand{\foo}{bar}";
81/// let typst_output = tex2typst_with_macros(tex_input, macro_definitions).unwrap();
82/// println!("{}", typst_output);
83/// ```
84pub fn tex2typst_with_macros(tex: &str, macro_definitions: &str) -> Result<String, String> {
85 let tokens = tex_tokenizer::tokenize(tex)?;
86 let custom_macros = parse_custom_macros(macro_definitions)?;
87 let mut registry = CommandRegistry::new();
88 registry.register_custom_macros(custom_macros);
89 let expanded_tokens = registry.expand_macros(&tokens)?;
90
91 let parser = LatexParser::new(false, false);
92 let tex_tree = parser.parse(expanded_tokens)?;
93 let typst_tree = converter::convert_tree(&tex_tree)?;
94
95 let mut writer = typst_writer::TypstWriter::new();
96 writer.serialize(&typst_tree)?;
97 let typst = writer.finalize()?;
98 Ok(typst)
99}
100
101/// Converts a given input string containing TeX math expressions to Typst format.
102///
103/// This function searches for inline and display math expressions within the input string,
104/// converts them to Typst format using the `tex2typst` function, and returns the resulting string.
105///
106/// # Arguments
107///
108/// * `input` - A string slice that holds the input text containing TeX math expressions.
109///
110/// # Returns
111///
112/// * `Result<String, String>` - On success, returns the converted string wrapped in `Ok`.
113/// On failure, returns an error message wrapped in `Err`.
114///
115/// # Errors
116///
117/// This function will return an error if the TeX to Typst conversion fails.
118///
119/// # Example
120///
121/// ```
122/// use tex2typst_rs::text_and_tex2typst;
123/// let input = r"This is inline math: \(a + b\) and this is display math: \[a^2 + b^2 = c^2\]";
124/// let output = text_and_tex2typst(input).unwrap();
125/// println!("{}", output);
126/// ```
127pub fn text_and_tex2typst(input: &str) -> Result<String, String> {
128 let regex = Regex::new(r"\\\((.+?)\\\)|(?s)\\\[(.+?)\\\]").unwrap();
129
130 replace_all(®ex, input, |caps: &Captures| {
131 if let Some(inline_math) = caps.get(1) {
132 let typst_math = tex2typst(inline_math.as_str().trim())?;
133 Ok(format!("${}$", typst_math))
134 } else if let Some(display_math) = caps.get(2) {
135 let typst_math = tex2typst(display_math.as_str().trim()).map_err(|e| e.to_string())?;
136 Ok(format!("$\n{}\n$", typst_math))
137 } else {
138 Ok(caps[0].to_string())
139 }
140 })
141}
142
143/// Converts a given input string containing TeX math expressions to Typst format with custom macro definitions.
144///
145/// This function searches for inline and display math expressions within the input string,
146/// converts them to Typst format using the `tex2typst_with_macros` function, and returns the resulting string.
147///
148/// # Arguments
149///
150/// * `input` - A string slice that holds the input text containing TeX math expressions.
151/// * `macro_definitions` - A string slice that holds the custom macro definitions.
152///
153/// # Returns
154///
155/// * `Result<String, String>` - On success, returns the converted string wrapped in `Ok`.
156/// On failure, returns an error message wrapped in `Err`.
157///
158/// # Errors
159///
160/// This function will return an error if the TeX to Typst conversion fails.
161///
162/// # Example
163///
164/// ```
165/// use tex2typst_rs::text_and_tex2typst_with_macros;
166/// let input = r"This is inline math: \(\foo\) and this is display math: \[\foo\]";
167/// let macro_definitions = r"\newcommand{\foo}{bar}";
168/// let output = text_and_tex2typst_with_macros(input, macro_definitions).unwrap();
169/// println!("{}", output);
170/// ```
171pub fn text_and_tex2typst_with_macros(input: &str, macro_definitions: &str) -> Result<String, String> {
172 let regex = Regex::new(r"\\\((.+?)\\\)|(?s)\\\[(.+?)\\\]").unwrap();
173
174 replace_all(®ex, input, |caps: &Captures| {
175 if let Some(inline_math) = caps.get(1) {
176 let typst_math = tex2typst_with_macros(inline_math.as_str().trim(), macro_definitions)?;
177 Ok(format!("${}$", typst_math))
178 } else if let Some(display_math) = caps.get(2) {
179 let typst_math =
180 tex2typst_with_macros(display_math.as_str().trim(), macro_definitions).map_err(|e| e.to_string())?;
181 Ok(format!("$\n{}\n$", typst_math))
182 } else {
183 Ok(caps[0].to_string())
184 }
185 })
186}
187
188/// Custom implementation of `Regex::replace_all` for error handling.
189pub fn replace_all<E>(
190 re: &Regex,
191 haystack: &str,
192 replacement: impl Fn(&Captures) -> Result<String, E>,
193) -> Result<String, E> {
194 let mut new = String::with_capacity(haystack.len());
195 let mut last_match = 0;
196 for caps in re.captures_iter(haystack) {
197 let m = caps.get(0).unwrap();
198 new.push_str(&haystack[last_match..m.start()]);
199 new.push_str(&replacement(&caps)?);
200 last_match = m.end();
201 }
202 new.push_str(&haystack[last_match..]);
203 Ok(new)
204}
205
206pub fn tex2typst_with_shorthands(tex: &str, shorthands: &Vec<SymbolShorthand>) -> Result<String, String> {
207 let tex_tree = tex_parser::parse_tex(tex)?;
208 let typst_tree = converter::convert_tree(&tex_tree)?;
209 let mut writer = typst_writer::TypstWriter::new();
210 writer.serialize(&typst_tree)?;
211 writer.replace_with_shorthand(shorthands);
212 let typst = writer.finalize()?;
213 Ok(typst)
214}
215
216pub fn text_and_tex2typst_with_shorthands(input: &str, shorthands: &Vec<SymbolShorthand>) -> Result<String, String> {
217 let regex = Regex::new(r"\\\((.+?)\\\)|(?s)\\\[(.+?)\\\]").unwrap();
218
219 replace_all(®ex, input, |caps: &Captures| {
220 if let Some(inline_math) = caps.get(1) {
221 let typst_math = tex2typst_with_shorthands(inline_math.as_str().trim(), shorthands)?;
222 Ok(format!("${}$", typst_math))
223 } else if let Some(display_math) = caps.get(2) {
224 let typst_math =
225 tex2typst_with_shorthands(display_math.as_str().trim(), shorthands).map_err(|e| e.to_string())?;
226 Ok(format!("$\n{}\n$", typst_math))
227 } else {
228 Ok(caps[0].to_string())
229 }
230 })
231}