1use bitflags::bitflags;
2#[cfg(feature = "serde")]
3use serde::Serialize;
4
5use strum_macros::IntoStaticStr;
6
7#[derive(Debug, Clone, Copy, PartialEq)]
9#[cfg_attr(feature = "serde", derive(Serialize))]
10pub enum MathVariant {
11 Normal,
13 Transform(TextTransform),
15}
16
17impl MathVariant {
18 #[inline]
21 pub fn differs_on_upright_letters(&self) -> bool {
22 matches!(self, MathVariant::Transform(TextTransform::BoldItalic))
23 }
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
27#[cfg_attr(feature = "serde", derive(Serialize))]
28pub enum OpAttr {
29 #[strum(serialize = r#" stretchy="false""#)]
30 StretchyFalse = 1,
31 #[strum(serialize = r#" stretchy="true""#)]
32 StretchyTrue,
33 #[strum(serialize = r#" movablelimits="false""#)]
34 NoMovableLimits,
35 #[strum(serialize = r#" movablelimits="true""#)]
36 ForceMovableLimits,
37 #[strum(serialize = r#" form="prefix""#)]
38 FormPrefix,
39 #[strum(serialize = r#" form="postfix""#)]
40 FormPostfix,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq)]
44#[cfg_attr(feature = "serde", derive(Serialize))]
45pub enum LetterAttr {
46 Default,
47 ForcedUpright,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
51#[cfg_attr(feature = "serde", derive(Serialize))]
52pub enum Size {
53 #[strum(serialize = "1.2em")]
54 Scale1,
55 #[strum(serialize = "1.623em")]
56 Scale2,
57 #[strum(serialize = "2.047em")]
58 Scale3,
59 #[strum(serialize = "2.470em")]
60 Scale4,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64#[cfg_attr(feature = "serde", derive(Serialize))]
65pub enum StretchMode {
66 NoStretch = 1,
68 Fence,
70 Middle,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
76#[cfg_attr(feature = "serde", derive(Serialize))]
77pub enum FracAttr {
78 #[strum(serialize = r#" displaystyle="true""#)]
79 DisplayStyleTrue = 1,
80 #[strum(serialize = r#" displaystyle="false""#)]
81 DisplayStyleFalse,
82 #[strum(serialize = r#" displaystyle="true" scriptlevel="0" style="padding-top: 0.1667em""#)]
83 CFracStyle,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
87#[cfg_attr(feature = "serde", derive(Serialize))]
88pub enum Style {
89 #[strum(serialize = r#" displaystyle="true" scriptlevel="0""#)]
90 Display = 1,
91 #[strum(serialize = r#" displaystyle="false" scriptlevel="0""#)]
92 Text,
93 #[strum(serialize = r#" displaystyle="false" scriptlevel="1""#)]
94 Script,
95 #[strum(serialize = r#" displaystyle="false" scriptlevel="2""#)]
96 ScriptScript,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
100#[cfg_attr(feature = "serde", derive(Serialize))]
101pub enum MathSpacing {
102 #[strum(serialize = "0")]
103 Zero = 1,
104 #[strum(serialize = "0.1667em")]
105 ThreeMu, #[strum(serialize = "0.2222em")]
107 FourMu, #[strum(serialize = "0.2778em")]
109 FiveMu, }
111
112#[derive(Debug, Clone, Copy, PartialEq, IntoStaticStr)]
113#[cfg_attr(feature = "serde", derive(Serialize))]
114pub enum RowAttr {
115 Style(Style),
116 Color(u8, u8, u8),
117}
118
119bitflags! {
120 #[repr(transparent)]
121 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
122 #[cfg_attr(feature = "serde", derive(Serialize))]
123 pub struct Notation: u8 {
124 const HORIZONTAL = 1;
125 const UP_DIAGONAL = 1 << 1;
126 const DOWN_DIAGONAL = 1 << 2;
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq)]
131#[cfg_attr(feature = "serde", derive(Serialize))]
132pub enum HtmlTextStyle {
133 Bold = 1,
134 Italic,
135 Emphasis,
136 Typewriter,
137 SmallCaps,
138}
139
140#[derive(Debug, Clone, Copy, PartialEq)]
142#[cfg_attr(feature = "serde", derive(Serialize))]
143pub enum TextTransform {
144 Bold = 1,
145 BoldFraktur,
146 BoldItalic,
147 BoldSansSerif,
148 BoldScript,
149 DoubleStruck,
150 Fraktur,
151 Italic,
153 Monospace,
155 SansSerif,
156 SansSerifBoldItalic,
157 SansSerifItalic,
158 ScriptChancery,
159 ScriptRoundhand,
160 }
163
164#[inline]
165const fn add_offset(c: char, offset: u32) -> char {
166 debug_assert!(char::from_u32(c as u32 + offset).is_some());
167 unsafe { char::from_u32_unchecked(c as u32 + offset) }
169}
170
171impl TextTransform {
172 #[allow(clippy::manual_is_ascii_check)]
173 pub const fn transform(&self, c: char, is_upright: bool) -> char {
174 let tf = if is_upright && matches!(self, TextTransform::BoldItalic) {
175 &TextTransform::Bold
176 } else {
177 self
178 };
179 match tf {
180 TextTransform::BoldScript => match c {
181 'A'..='Z' => add_offset(c, 0x1D48F),
182 'a'..='z' => add_offset(c, 0x1D489),
183 _ => c,
184 },
185 TextTransform::BoldItalic => match c {
186 'A'..='Z' => add_offset(c, 0x1D427),
187 'a'..='z' => add_offset(c, 0x1D421),
188 'Α'..='Ω' => add_offset(c, 0x1D38B),
189 'α'..='ω' => add_offset(c, 0x1D385),
190 'ϴ' => '𝜭',
191 '∇' => '𝜵',
192 '∂' => '𝝏',
193 'ϵ' => '𝝐',
194 'ϑ' => '𝝑',
195 'ϰ' => '𝝒',
196 'ϕ' => '𝝓',
197 'ϱ' => '𝝔',
198 'ϖ' => '𝝕',
199 _ => c,
200 },
201 TextTransform::Bold => match c {
202 'A'..='Z' => add_offset(c, 0x1D3BF),
203 'a'..='z' => add_offset(c, 0x1D3B9),
204 'Α'..='Ω' => add_offset(c, 0x1D317),
205 'α'..='ω' => add_offset(c, 0x1D311),
206 'Ϝ'..='ϝ' => add_offset(c, 0x1D3EE),
207 '0'..='9' => add_offset(c, 0x1D79E),
208 'ϴ' => '𝚹',
209 '∇' => '𝛁',
210 '∂' => '𝛛',
211 'ϵ' => '𝛜',
212 'ϑ' => '𝛝',
213 'ϰ' => '𝛞',
214 'ϕ' => '𝛟',
215 'ϱ' => '𝛠',
216 'ϖ' => '𝛡',
217 _ => c,
218 },
219 TextTransform::Fraktur => match c {
220 'A'..='B' | 'D'..='G' | 'J'..='Q' | 'S'..='Y' => add_offset(c, 0x1D4C3),
221 'H'..='I' => add_offset(c, 0x20C4),
222 'a'..='z' => add_offset(c, 0x1D4BD),
223 'C' => 'ℭ',
224 'R' => 'ℜ',
225 'Z' => 'ℨ',
226 _ => c,
227 },
228 TextTransform::ScriptChancery | TextTransform::ScriptRoundhand => match c {
229 'A' | 'C'..='D' | 'G' | 'J'..='K' | 'N'..='Q' | 'S'..='Z' => add_offset(c, 0x1D45B),
230 'E'..='F' => add_offset(c, 0x20EB),
231 'a'..='d' | 'f' | 'h'..='n' | 'p'..='z' => add_offset(c, 0x1D455),
232 'B' => 'ℬ',
233 'H' => 'ℋ',
234 'I' => 'ℐ',
235 'L' => 'ℒ',
236 'M' => 'ℳ',
237 'R' => 'ℛ',
238 'e' => 'ℯ',
239 'g' => 'ℊ',
240 'o' => 'ℴ',
241 _ => c,
242 },
243 TextTransform::Monospace => match c {
244 'A'..='Z' => add_offset(c, 0x1D62F),
245 'a'..='z' => add_offset(c, 0x1D629),
246 '0'..='9' => add_offset(c, 0x1D7C6),
247 _ => c,
248 },
249 TextTransform::SansSerif => match c {
250 'A'..='Z' => add_offset(c, 0x1D55F),
251 'a'..='z' => add_offset(c, 0x1D559),
252 '0'..='9' => add_offset(c, 0x1D7B2),
253 _ => c,
254 },
255 TextTransform::BoldFraktur => match c {
256 'A'..='Z' => add_offset(c, 0x1D52B),
257 'a'..='z' => add_offset(c, 0x1D525),
258 _ => c,
259 },
260 TextTransform::SansSerifBoldItalic => match c {
261 'A'..='Z' => add_offset(c, 0x1D5FB),
262 'a'..='z' => add_offset(c, 0x1D5F5),
263 'Α'..='Ω' => add_offset(c, 0x1D3FF),
264 'α'..='ω' => add_offset(c, 0x1D3F9),
265 'ϴ' => '𝞡',
266 '∇' => '𝞩',
267 '∂' => '𝟃',
268 'ϵ' => '𝟄',
269 'ϑ' => '𝟅',
270 'ϰ' => '𝟆',
271 'ϕ' => '𝟇',
272 'ϱ' => '𝟈',
273 'ϖ' => '𝟉',
274 _ => c,
275 },
276 TextTransform::SansSerifItalic => match c {
277 'A'..='Z' => add_offset(c, 0x1D5C7),
278 'a'..='z' => add_offset(c, 0x1D5C1),
279 _ => c,
280 },
281 TextTransform::BoldSansSerif => match c {
282 'A'..='Z' => add_offset(c, 0x1D593),
283 'a'..='z' => add_offset(c, 0x1D58D),
284 'Α'..='Ω' => add_offset(c, 0x1D3C5),
285 'α'..='ω' => add_offset(c, 0x1D3BF),
286 '0'..='9' => add_offset(c, 0x1D7BC),
287 'ϴ' => '𝝧',
288 '∇' => '𝝯',
289 '∂' => '𝞉',
290 'ϵ' => '𝞊',
291 'ϑ' => '𝞋',
292 'ϰ' => '𝞌',
293 'ϕ' => '𝞍',
294 'ϱ' => '𝞎',
295 'ϖ' => '𝞏',
296 _ => c,
297 },
298 TextTransform::DoubleStruck => match c {
299 'A'..='B' | 'D'..='G' | 'I'..='M' | 'O' | 'S'..='Y' => add_offset(c, 0x1D4F7),
300 'P'..='Q' => add_offset(c, 0x20C9),
301 'a'..='z' => add_offset(c, 0x1D4F1),
302 '0'..='9' => add_offset(c, 0x1D7A8),
303 'C' => 'ℂ',
304 'H' => 'ℍ',
305 'N' => 'ℕ',
306 'R' => 'ℝ',
307 'Z' => 'ℤ',
308 _ => c,
309 },
310 TextTransform::Italic => match c {
311 'A'..='Z' => add_offset(c, 0x1D3F3),
312 'a'..='g' | 'i'..='z' => add_offset(c, 0x1D3ED),
313 'Α'..='Ω' => add_offset(c, 0x1D351),
314 'α'..='ω' => add_offset(c, 0x1D34B),
315 'h' => 'ℎ',
316 'ı' => '𝚤',
317 'ȷ' => '𝚥',
318 'ϴ' => '𝛳',
319 '∇' => '𝛻',
320 '∂' => '𝜕',
321 'ϵ' => '𝜖',
322 'ϑ' => '𝜗',
323 'ϰ' => '𝜘',
324 'ϕ' => '𝜙',
325 'ϱ' => '𝜚',
326 'ϖ' => '𝜛',
327 _ => c,
328 },
329 }
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::{MathVariant, TextTransform};
336
337 #[test]
338 fn transform_test() {
339 let problems = [
340 ('G', TextTransform::BoldScript, '𝓖'),
341 ('H', TextTransform::Italic, '𝐻'),
342 ('X', TextTransform::Fraktur, '𝔛'),
343 ('S', TextTransform::ScriptChancery, '𝒮'),
344 ('f', TextTransform::Bold, '𝐟'),
345 ('g', TextTransform::Bold, '𝐠'),
346 ('o', TextTransform::DoubleStruck, '𝕠'),
347 ('D', TextTransform::Monospace, '𝙳'),
348 ('x', TextTransform::Monospace, '𝚡'),
349 ('2', TextTransform::Monospace, '𝟸'),
350 ('U', TextTransform::SansSerif, '𝖴'),
351 ('v', TextTransform::SansSerif, '𝗏'),
352 ('4', TextTransform::SansSerif, '𝟦'),
353 ('A', TextTransform::SansSerifBoldItalic, '𝘼'),
354 ('a', TextTransform::SansSerifBoldItalic, '𝙖'),
355 ('Α', TextTransform::SansSerifBoldItalic, '𝞐'),
356 ('α', TextTransform::SansSerifBoldItalic, '𝞪'),
357 ('A', TextTransform::SansSerifItalic, '𝘈'),
358 ('a', TextTransform::SansSerifItalic, '𝘢'),
359 ('J', TextTransform::BoldSansSerif, '𝗝'),
360 ('r', TextTransform::BoldSansSerif, '𝗿'),
361 ('Ξ', TextTransform::BoldSansSerif, '𝝣'),
362 ('τ', TextTransform::BoldSansSerif, '𝞃'),
363 ];
364 for (source, transform, target) in problems.into_iter() {
365 assert_eq!(
366 target,
367 transform.transform(source, false),
368 "executed: {:?}({})",
369 transform,
370 source
371 );
372 }
373 }
374
375 #[test]
376 fn size_test() {
377 assert_eq!(
378 std::mem::size_of::<MathVariant>(),
379 std::mem::size_of::<TextTransform>()
380 );
381 assert_eq!(
382 std::mem::size_of::<Option<MathVariant>>(),
383 std::mem::size_of::<TextTransform>()
384 );
385 }
386}