Skip to main content

math_core_renderer_internal/
attribute.rs

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