malachite_base/strings/typst.rs
1// Copyright © 2026 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8use crate::chars::typst::fmt_typst_chars;
9use alloc::string::{String, ToString};
10use core::fmt::{Display, Formatter, Result};
11
12/// Converts a value to a Typst math-mode fragment.
13///
14/// The output is a fragment rather than a complete expression: it carries no `$` of its own,
15/// leaving that to the caller. That is what lets one fragment be embedded in another, so that a
16/// value built out of smaller values can write its parts directly.
17///
18/// Every implementation guarantees that its output
19/// - is valid wherever Typst is in math mode, and remains valid when wrapped in parentheses, so
20/// that `(`output`)` is well-formed;
21/// - leaves nothing open behind it: delimiters and string literals are closed, and nothing is
22/// defined or redefined.
23pub trait ToTypst {
24 /// Writes a value as a Typst math-mode fragment.
25 ///
26 /// This is the method implementors define. It takes a [`Formatter`] rather than returning a
27 /// `String` so that a value can write its parts into a caller's buffer, which is what makes a
28 /// fragment embeddable without an allocation per level of nesting.
29 ///
30 /// # Examples
31 /// ```
32 /// use malachite_base::strings::typst::ToTypst;
33 /// use std::fmt::{Display, Formatter, Result};
34 ///
35 /// // A type that embeds another value's fragment inside its own.
36 /// struct Negated(i32);
37 ///
38 /// impl Display for Negated {
39 /// fn fmt(&self, f: &mut Formatter) -> Result {
40 /// f.write_str("-(")?;
41 /// self.0.fmt_typst(f)?;
42 /// f.write_str(")")
43 /// }
44 /// }
45 ///
46 /// assert_eq!(Negated(5).to_string(), "-(5)");
47 /// ```
48 /// That fragment draws the negation of five.
49 fn fmt_typst(&self, f: &mut Formatter) -> Result;
50
51 /// Converts a value to a Typst math-mode fragment.
52 ///
53 /// The returned [`TypstWrapper`] implements [`Display`], so it can be converted to a `String`
54 /// with `to_string`, or written directly with `write!` and friends.
55 ///
56 /// # Worst-case complexity
57 /// Constant time and additional memory.
58 ///
59 /// # Examples
60 /// ```
61 /// use malachite_base::strings::typst::ToTypst;
62 ///
63 /// assert_eq!(123u32.to_typst_string(), "123");
64 /// assert_eq!((-45i16).to_typst_string(), "-45");
65 ///
66 /// // The output is a fragment, so it can be embedded in a larger expression.
67 /// assert_eq!(format!("x^({})", 10u8.to_typst()), "x^(10)");
68 /// ```
69 #[inline]
70 fn to_typst(&self) -> TypstWrapper<'_, Self>
71 where
72 Self: Sized,
73 {
74 TypstWrapper { x: self }
75 }
76
77 /// Converts a value to a Typst math-mode fragment, as a [`String`].
78 ///
79 /// This is `to_typst().to_string()`, which is what a caller who wants the fragment itself,
80 /// rather than something to write into a [`Formatter`], would otherwise have to say.
81 ///
82 /// # Worst-case complexity
83 /// Same as the time and additional memory complexity of `fmt_typst` for `Self`.
84 ///
85 /// # Examples
86 /// ```
87 /// use malachite_base::strings::typst::ToTypst;
88 ///
89 /// assert_eq!(123u32.to_typst_string(), "123");
90 /// assert_eq!((-45i16).to_typst_string(), "-45");
91 /// assert_eq!("100% α".to_typst_string(), r#""100% α""#);
92 /// ```
93 #[inline]
94 fn to_typst_string(&self) -> String
95 where
96 Self: Sized,
97 {
98 self.to_typst().to_string()
99 }
100}
101
102/// A `struct` that can be used to format a value as a Typst math-mode fragment.
103///
104/// It is returned by [`ToTypst::to_typst`].
105pub struct TypstWrapper<'a, T: ToTypst> {
106 pub(crate) x: &'a T,
107}
108
109impl<T: ToTypst> Display for TypstWrapper<'_, T> {
110 #[inline]
111 fn fmt(&self, f: &mut Formatter) -> Result {
112 self.x.fmt_typst(f)
113 }
114}
115
116impl ToTypst for &str {
117 /// Writes a string slice as a Typst math-mode fragment.
118 ///
119 /// The fragment depicts the string: it is one quoted string, which Typst typesets as text, with
120 /// `\` and `"` escaped and control characters spelled rather than written. Typst reads Unicode
121 /// natively, so no character needs a spelling of its own, and `"100% α"` comes out as itself.
122 /// No quotation marks beyond the string literal's own are added; the fragment is the string's
123 /// content and nothing else.
124 ///
125 /// A run of superscript or subscript characters is the exception, and becomes a single script:
126 /// `"2¹⁰"` is two raised to the tenth rather than the two characters, which a text font may
127 /// not have at all. A run of one kind does not run into the next: `"x¹₂"` keeps its one
128 /// beside its two rather than stacking them.
129 ///
130 /// The empty string becomes `""` rather than nothing at all.
131 ///
132 /// # Worst-case complexity
133 /// $T(n) = O(n)$
134 ///
135 /// $M(n) = O(1)$
136 ///
137 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.chars().count()`.
138 ///
139 /// # Examples
140 /// ```
141 /// use malachite_base::strings::typst::ToTypst;
142 ///
143 /// assert_eq!("hello".to_typst_string(), r#""hello""#);
144 /// assert_eq!("100%".to_typst_string(), r#""100%""#);
145 /// assert_eq!("100% α".to_typst_string(), r#""100% α""#);
146 /// assert_eq!("A ≤ B".to_typst_string(), r#""A ≤ B""#);
147 /// assert_eq!("--flag".to_typst_string(), r#""--flag""#);
148 /// assert_eq!("2¹⁰".to_typst_string(), r#""2"^("10")"#);
149 /// assert_eq!("H₂O".to_typst_string(), r#""H"_("2")"O""#);
150 /// ```
151 ///
152 /// | value | fragment |
153 /// |------------|--------------|
154 /// | `"hello"` | `"hello"` |
155 /// | `"100%"` | `"100%"` |
156 /// | `"100% α"` | `"100% α"` |
157 /// | `"A ≤ B"` | `"A ≤ B"` |
158 /// | `"--flag"` | `"--flag"` |
159 /// | `"2¹⁰"` | `"2"^("10")` |
160 /// | `"H₂O"` | `"H"_("2")"O"` |
161 #[inline]
162 fn fmt_typst(&self, f: &mut Formatter) -> Result {
163 fmt_typst_chars(self.chars(), f)
164 }
165}
166
167impl ToTypst for String {
168 /// Writes a [`String`] as a Typst math-mode fragment.
169 ///
170 /// This is the same as the [`&str`] implementation.
171 ///
172 /// The fragment depicts the string: it is one quoted string, which Typst typesets as text, with
173 /// `\` and `"` escaped and control characters spelled rather than written. Typst reads Unicode
174 /// natively, so no character needs a spelling of its own, and `"100% α"` comes out as itself.
175 /// No quotation marks beyond the string literal's own are added; the fragment is the string's
176 /// content and nothing else.
177 ///
178 /// A run of superscript or subscript characters is the exception, and becomes a single script:
179 /// `"2¹⁰"` is two raised to the tenth rather than the two characters, which a text font may
180 /// not have at all. A run of one kind does not run into the next: `"x¹₂"` keeps its one
181 /// beside its two rather than stacking them.
182 ///
183 /// The empty string becomes `""` rather than nothing at all.
184 ///
185 /// # Worst-case complexity
186 /// $T(n) = O(n)$
187 ///
188 /// $M(n) = O(1)$
189 ///
190 /// where $T$ is time, $M$ is additional memory, and $n$ is `self.chars().count()`.
191 ///
192 /// # Examples
193 /// ```
194 /// use malachite_base::strings::typst::ToTypst;
195 ///
196 /// assert_eq!("hello".to_string().to_typst_string(), r#""hello""#);
197 /// assert_eq!("100%".to_string().to_typst_string(), r#""100%""#);
198 /// assert_eq!("100% α".to_string().to_typst_string(), r#""100% α""#);
199 /// assert_eq!("A ≤ B".to_string().to_typst_string(), r#""A ≤ B""#);
200 /// assert_eq!("--flag".to_string().to_typst_string(), r#""--flag""#);
201 /// assert_eq!("2¹⁰".to_string().to_typst_string(), r#""2"^("10")"#);
202 /// assert_eq!("H₂O".to_string().to_typst_string(), r#""H"_("2")"O""#);
203 /// ```
204 ///
205 /// | value | fragment |
206 /// |------------|--------------|
207 /// | `"hello"` | `"hello"` |
208 /// | `"100%"` | `"100%"` |
209 /// | `"100% α"` | `"100% α"` |
210 /// | `"A ≤ B"` | `"A ≤ B"` |
211 /// | `"--flag"` | `"--flag"` |
212 /// | `"2¹⁰"` | `"2"^("10")` |
213 /// | `"H₂O"` | `"H"_("2")"O"` |
214 #[inline]
215 fn fmt_typst(&self, f: &mut Formatter) -> Result {
216 fmt_typst_chars(self.chars(), f)
217 }
218}
219
220impl<T: ToTypst> ToTypst for &T {
221 /// Writes a reference as a Typst math-mode fragment.
222 ///
223 /// The fragment is the referent's own, so a reference is invisible: `&5u8` and `5u8` have the
224 /// same fragment. That is what lets a value be written without being dereferenced first, and a
225 /// collection of references be written at all.
226 ///
227 /// [`&str`] and slices have implementations of their own rather than reaching this one, since
228 /// their referents are unsized. They write the same fragments either way.
229 ///
230 /// # Worst-case complexity
231 /// Same as the time and additional memory complexity of `fmt_typst` for `T`.
232 ///
233 /// # Examples
234 /// ```
235 /// use malachite_base::strings::typst::ToTypst;
236 ///
237 /// // A reference is invisible, which is what lets a collection of references be written.
238 /// assert_eq!(
239 /// vec![&1u8, &2u8].to_typst_string(),
240 /// vec![1u8, 2u8].to_typst_string()
241 /// );
242 ///
243 /// // A method call on a reference resolves to the referent's own implementation, so this is
244 /// // reached through a generic context instead.
245 /// fn fragment<T: ToTypst>(x: T) -> String {
246 /// x.to_typst_string()
247 /// }
248 /// let n = 5u8;
249 /// let n_ref: &u8 = &n;
250 /// assert_eq!(fragment(n_ref), "5");
251 /// ```
252 #[inline]
253 fn fmt_typst(&self, f: &mut Formatter) -> Result {
254 (**self).fmt_typst(f)
255 }
256}