codex/
styling.rs

1//! Style mathematical symbols in Unicode.
2
3use std::fmt::{self, Write};
4use std::iter::FusedIterator;
5
6/// The version of [Unicode](https://www.unicode.org/) that this version of the
7/// styling module is based on.
8pub const UNICODE_VERSION: (u8, u8, u8) = (16, 0, 0);
9
10/// A style for mathematical symbols.
11///
12/// Notation in mathematics uses a basic set of characters which can be styled.
13/// The following groupings are used in the documentation:
14/// - digits: The basic Latin digits 0–9 (U+0030..U+0039).
15/// - latin: The basic uppercase and lowercase Latin letters, a–z
16///   (U+0061..U+007A) and A–Z (U+0041..U+005A).
17/// - greek: The uppercase Greek letters Α–Ω (U+0391..U+03A9), plus nabla ∇
18///   (U+2207) and theta ϴ (U+03F4). The lowercase Greek letters α–ω
19///   (U+03B1..U+03C9), plus the partial differential sign ∂ (U+2202), and the
20///   glyph variants ϵ (U+03F5), ϑ (U+03D1), ϰ (U+03F0), ϕ (U+03D5), ϱ
21///   (U+03F1), ϖ (U+03D6).
22/// - arabic: The Arabic letters ا (U+0627), ب (U+0628), ت–غ (U+062A..U+063A),
23///   ف–و (U+0641..U+0648), ي (U+064A).
24/// - arabic-dotless: The dotless Arabic letter variants ٮ (U+066E), ٯ
25///   (U+066F), ڡ (U+06A1), ں (U+06BA)
26/// - digamma: The uppercase and lowercase digamma, Ϝ (U+03DC) and ϝ (U+03DD).
27/// - dotless: The dotless variants of the lowercase Latin letters i and j, ı
28///   (U+0131) and ȷ (U+0237).
29/// - hebrew: The Hebrew letters א–ד (U+05D0..U+05D3).
30///
31/// Note that some styles support only a subset of a group. The characters each
32/// style supports are given in their documentation.
33///
34/// # Script style variants
35///
36/// There are two widely recognized variants of the script style: chancery and
37/// roundhand. They can be distinguished with variation sequences, by using the
38/// variation selectors U+FE00 and U+FE01 for chancery and roundhand
39/// respectively. These are specified in the [StandardizedVariants.txt] file
40/// from the Unicode Character Database.
41///
42/// Only the uppercase Latin letters are standardized variation sequences, but
43/// the [`Chancery`](MathStyle::Chancery) and
44/// [`Roundhand`](MathStyle::Roundhand) styles also support the lowercase Latin
45/// letters. In addition, the bold styles
46/// [`BoldChancery`](MathStyle::BoldChancery) and
47/// [`BoldRoundhand`](MathStyle::BoldRoundhand) are provided, which support
48/// both the uppercase and lowercase Latin letters despite not being specified
49/// as standardized variation sequences by Unicode.
50///
51/// # Shaping
52///
53/// The Arabic styles (including those from the
54/// [`DoubleStruck`](MathStyle::DoubleStruck) style) are not subject to
55/// shaping. However, [`Plain`](MathStyle::Plain) should still be shaped, as
56/// the characters are Arabic letters in the Arabic block (U+0600..U+06FF).
57///
58/// [StandardizedVariants.txt]: <https://www.unicode.org/Public/UNIDATA/StandardizedVariants.txt>
59#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Default)]
60pub enum MathStyle {
61    /// Unstyled default. May be serif or sans-serif depending on the font.
62    #[default]
63    Plain,
64    /// Bold style. May be serif or sans-serif depending on the font.
65    ///
66    /// Supported characters: digits, latin, greek, digamma.
67    Bold,
68    /// Italic style. May be serif or sans-serif depending on the font.
69    ///
70    /// Supported characters: latin, greek, dotless, and the extra ħ (U+0127).
71    Italic,
72    /// Bold italic style. May be serif or sans-serif depending on the font.
73    ///
74    /// Supported characters: latin, greek.
75    BoldItalic,
76    /// Script style. May be chancery or roundhand depending on the font.
77    ///
78    /// Supported characters: latin.
79    Script,
80    /// Bold script style. May be chancery or roundhand depending on the font.
81    ///
82    /// Supported characters: latin.
83    BoldScript,
84    /// Fraktur style. Also known as black-letter style.
85    ///
86    /// Supported characters: latin.
87    Fraktur,
88    /// Bold fraktur style. Also known as bold black-letter style.
89    ///
90    /// Supported characters: latin.
91    BoldFraktur,
92    /// Sans-serif style.
93    ///
94    /// Supported characters: digits, latin.
95    SansSerif,
96    /// Bold sans-serif style.
97    ///
98    /// Supported characters: digits, latin, greek.
99    SansSerifBold,
100    /// Italic sans-serif style.
101    ///
102    /// Supported characters: latin.
103    SansSerifItalic,
104    /// Bold italic sans-serif style.
105    ///
106    /// Supported characters: latin, greek.
107    SansSerifBoldItalic,
108    /// Monospace style.
109    ///
110    /// Supported characters: digits, latin.
111    Monospace,
112    /// Isolated style.
113    ///
114    /// Supported characters: arabic excluding ه (U+0647), arabic-dotless.
115    Isolated,
116    /// Initial style.
117    ///
118    /// Supported characters: arabic excluding ا (U+0627), د–ز
119    /// (U+062F..U+0632), ط (U+0637), ظ (U+0638), و (U+0648).
120    Initial,
121    /// Tailed style.
122    ///
123    /// Supported characters: arabic excluding ا (U+0627), ب (U+0628),
124    /// ت (U+062A), ث (U+062B),  د–ز (U+062F..U+0632), ط (U+0637), ظ (U+0638),
125    /// ف (U+0641), ك (U+0643), م (U+0645), ه (U+0647), و (U+0648), and
126    /// arabic-dotless excluding ٮ (U+066E), ڡ (U+06A1).
127    Tailed,
128    /// Stretched style.
129    ///
130    /// Supported characters: arabic excluding ا (U+0627), د–ز
131    /// (U+062F..U+0632), ل (U+0644), و (U+0648), and arabic-dotless excluding
132    /// ٯ (U+066F), ں (U+06BA).
133    Stretched,
134    /// Looped style.
135    ///
136    /// Supported characters: arabic excluding ك (U+0643).
137    Looped,
138    /// Double-struck style. Also known as open-face style or blackboard-bold
139    /// style.
140    ///
141    /// Supported characters: digits, latin, arabic excluding ا (U+0627),
142    /// ك (U+0643), ه (U+0647), and the extras ∑ (U+2211), Γ (U+0393), Π
143    /// (U+03A0), γ (U+03B3), π (U+03C0).
144    DoubleStruck,
145    /// Italic double-struck style. Also known as italic open-face style or
146    /// italic blackboard-bold style.
147    ///
148    /// This is an exceptional style as only the following Latin letters are
149    /// supported: D (U+0044), d (U+0064), e (U+0065), i (U+0069), j (U+006A).
150    DoubleStruckItalic,
151    /// Chancery variant of script style.
152    ///
153    /// Supported characters: latin.
154    Chancery,
155    /// Chancery variant of bold script style.
156    ///
157    /// Supported characters: latin.
158    BoldChancery,
159    /// Roundhand variant of script style.
160    ///
161    /// Supported characters: latin.
162    Roundhand,
163    /// Roundhand variant of bold script style.
164    ///
165    /// Supported characters: latin.
166    BoldRoundhand,
167    /// Hebrew letterlike math symbols.
168    ///
169    /// Supported characters: hebrew.
170    Hebrew,
171}
172
173/// Base [`MathStyle`]s used in Typst.
174#[non_exhaustive]
175#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
176pub enum MathVariant {
177    Plain,
178    Fraktur,
179    SansSerif,
180    Monospace,
181    DoubleStruck,
182    Chancery,
183    Roundhand,
184}
185
186impl MathStyle {
187    /// Selects an appropriate [`MathStyle`] for the given `char`.
188    ///
189    /// If `variant` is `None`, then [`Plain`](MathVariant::Plain) is used. If
190    /// `italic` is `None`, then the TeX auto-italic rules are followed: only
191    /// Latin and lowercase Greek are italicized.
192    ///
193    /// If the combination of inputs leads to a style which does not support
194    /// the given `char`, then fallback occurs, prioritizing the
195    /// [`MathVariant`] given.
196    ///
197    /// # Examples
198    ///
199    /// In the following example, as Greek letters are not supported by
200    /// [`MathStyle::Fraktur`], the variant falls back to
201    /// [`Plain`](MathVariant::Plain). Since auto-italic was requested and the
202    /// given char is lowercase Greek, the selected style is italicized.
203    ///
204    /// ```
205    /// use codex::styling::{MathStyle, MathVariant};
206    ///
207    /// assert_eq!(
208    ///     MathStyle::BoldItalic,
209    ///     MathStyle::select('α', Some(MathVariant::Fraktur), true, None)
210    /// );
211    /// ```
212    ///
213    /// In this example, the request for bold fell back to `false` as there is
214    /// no bold double-struck style, and the request for italic fell back to
215    /// `Some(false)` as [`MathStyle::DoubleStruckItalic`] does not support
216    /// `'R'`.
217    ///
218    /// ```
219    /// # use codex::styling::{MathStyle, MathVariant};
220    /// assert_eq!(
221    ///     MathStyle::DoubleStruck,
222    ///     MathStyle::select('R', Some(MathVariant::DoubleStruck), true, Some(true))
223    /// );
224    /// ```
225    pub fn select(
226        c: char,
227        variant: Option<MathVariant>,
228        bold: bool,
229        italic: Option<bool>,
230    ) -> MathStyle {
231        use conversions::*;
232        use MathVariant::*;
233        match (variant.unwrap_or(Plain), bold, italic) {
234            (SansSerif, false, Some(false)) if is_latin(c) => MathStyle::SansSerif,
235            (SansSerif, false, _) if is_latin(c) => MathStyle::SansSerifItalic,
236            (SansSerif, true, Some(false)) if is_latin(c) => MathStyle::SansSerifBold,
237            (SansSerif, true, _) if is_latin(c) => MathStyle::SansSerifBoldItalic,
238            (SansSerif, false, _) if is_digit(c) => MathStyle::SansSerif,
239            (SansSerif, true, _) if is_digit(c) => MathStyle::SansSerifBold,
240            (SansSerif, _, Some(false)) if is_greek(c) => MathStyle::SansSerifBold,
241            (SansSerif, _, Some(true)) if is_greek(c) => MathStyle::SansSerifBoldItalic,
242            (SansSerif, _, None) if is_upper_greek(c) => MathStyle::SansSerifBold,
243            (SansSerif, _, None) if is_lower_greek(c) => MathStyle::SansSerifBoldItalic,
244            (Fraktur, false, _) if is_latin(c) => MathStyle::Fraktur,
245            (Fraktur, true, _) if is_latin(c) => MathStyle::BoldFraktur,
246            (Monospace, _, _) if is_digit(c) | is_latin(c) => MathStyle::Monospace,
247            (DoubleStruck, _, Some(true)) if matches!(c, 'D' | 'd' | 'e' | 'i' | 'j') => {
248                MathStyle::DoubleStruckItalic
249            }
250            (DoubleStruck, _, _)
251                if is_digit(c)
252                    | is_latin(c)
253                    | matches!(c, '∑' | 'Γ' | 'Π' | 'γ' | 'π') =>
254            {
255                MathStyle::DoubleStruck
256            }
257            (Chancery, false, _) if is_latin(c) => MathStyle::Chancery,
258            (Chancery, true, _) if is_latin(c) => MathStyle::BoldChancery,
259            (Roundhand, false, _) if is_latin(c) => MathStyle::Roundhand,
260            (Roundhand, true, _) if is_latin(c) => MathStyle::BoldRoundhand,
261            (_, false, Some(true)) if is_latin(c) | is_greek(c) => MathStyle::Italic,
262            (_, false, None) if is_latin(c) | is_lower_greek(c) => MathStyle::Italic,
263            (_, true, Some(false)) if is_latin(c) | is_greek(c) => MathStyle::Bold,
264            (_, true, Some(true)) if is_latin(c) | is_greek(c) => MathStyle::BoldItalic,
265            (_, true, None) if is_latin(c) | is_lower_greek(c) => MathStyle::BoldItalic,
266            (_, true, None) if is_upper_greek(c) => MathStyle::Bold,
267            (_, true, _) if is_digit(c) | matches!(c, 'Ϝ' | 'ϝ') => MathStyle::Bold,
268            (_, _, Some(true) | None) if matches!(c, 'ı' | 'ȷ' | 'ħ') => {
269                MathStyle::Italic
270            }
271            (_, _, Some(true) | None) if is_hebrew(c) => MathStyle::Hebrew,
272            _ => MathStyle::Plain,
273        }
274    }
275}
276
277/// Returns an iterator that yields the styled equivalent of a `char`.
278///
279/// This `struct` is created by the [`to_style`] function. See its
280/// documentation for more.
281#[derive(Debug, Clone)]
282pub struct ToStyle(core::array::IntoIter<char, 2>);
283
284impl ToStyle {
285    #[inline]
286    fn new(chars: [char; 2]) -> ToStyle {
287        let mut iter = chars.into_iter();
288        if chars[1] == '\0' {
289            iter.next_back();
290        }
291        ToStyle(iter)
292    }
293}
294
295impl Iterator for ToStyle {
296    type Item = char;
297
298    fn next(&mut self) -> Option<char> {
299        self.0.next()
300    }
301
302    fn size_hint(&self) -> (usize, Option<usize>) {
303        self.0.size_hint()
304    }
305
306    fn fold<Acc, Fold>(self, init: Acc, fold: Fold) -> Acc
307    where
308        Fold: FnMut(Acc, Self::Item) -> Acc,
309    {
310        self.0.fold(init, fold)
311    }
312
313    fn count(self) -> usize {
314        self.0.count()
315    }
316
317    fn last(self) -> Option<Self::Item> {
318        self.0.last()
319    }
320}
321
322impl DoubleEndedIterator for ToStyle {
323    fn next_back(&mut self) -> Option<char> {
324        self.0.next_back()
325    }
326
327    fn rfold<Acc, Fold>(self, init: Acc, rfold: Fold) -> Acc
328    where
329        Fold: FnMut(Acc, Self::Item) -> Acc,
330    {
331        self.0.rfold(init, rfold)
332    }
333}
334
335impl ExactSizeIterator for ToStyle {
336    fn len(&self) -> usize {
337        self.0.len()
338    }
339}
340
341impl FusedIterator for ToStyle {}
342
343impl fmt::Display for ToStyle {
344    #[inline]
345    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346        for c in self.0.clone() {
347            f.write_char(c)?;
348        }
349        Ok(())
350    }
351}
352
353/// Returns an iterator that yields the styled conversion of a `char`, as
354/// specified by `style`, as one or more `char`s.
355///
356/// # Examples
357///
358/// ```
359/// use codex::styling::{to_style, MathStyle};
360///
361/// assert_eq!("𞺚", to_style('ظ', MathStyle::Looped).to_string());
362/// assert_eq!("𝒬\u{fe00}", to_style('Q', MathStyle::Chancery).to_string());
363///
364/// let s = "xγΩAذح1∑س"
365///     .chars()
366///     .flat_map(|c| to_style(c, MathStyle::DoubleStruck))
367///     .collect::<String>();
368/// assert_eq!("𝕩ℽΩ𝔸𞺸𞺧𝟙⅀𞺮", s);
369/// ```
370pub fn to_style(c: char, style: MathStyle) -> ToStyle {
371    use conversions::*;
372    use MathStyle::*;
373    let styled = match style {
374        Plain => [c, '\0'],
375        Bold => [to_bold(c), '\0'],
376        Italic => [to_italic(c), '\0'],
377        BoldItalic => [to_bold_italic(c), '\0'],
378        Script => [to_script(c), '\0'],
379        BoldScript => [to_bold_script(c), '\0'],
380        Fraktur => [to_fraktur(c), '\0'],
381        BoldFraktur => [to_bold_fraktur(c), '\0'],
382        SansSerif => [to_sans_serif(c), '\0'],
383        SansSerifBold => [to_sans_serif_bold(c), '\0'],
384        SansSerifItalic => [to_sans_serif_italic(c), '\0'],
385        SansSerifBoldItalic => [to_sans_serif_bold_italic(c), '\0'],
386        Monospace => [to_monospace(c), '\0'],
387        Isolated => [to_isolated(c), '\0'],
388        Initial => [to_initial(c), '\0'],
389        Tailed => [to_tailed(c), '\0'],
390        Stretched => [to_stretched(c), '\0'],
391        Looped => [to_looped(c), '\0'],
392        DoubleStruck => [to_double_struck(c), '\0'],
393        DoubleStruckItalic => [to_double_struck_italic(c), '\0'],
394        Chancery => to_chancery(c),
395        BoldChancery => to_bold_chancery(c),
396        Roundhand => to_roundhand(c),
397        BoldRoundhand => to_bold_roundhand(c),
398        Hebrew => [to_hebrew(c), '\0'],
399    };
400    ToStyle::new(styled)
401}
402
403/// Functions which convert a `char` to its specified styled form.
404///
405/// Sourced from:
406/// - [Unicode Core Specification - Section 22.2, Letterlike Symbols]
407/// - [Letterlike Symbols]
408/// - [Mathematical Alphanumeric Symbols]
409/// - [Arabic Mathematical Alphabetic Symbols]
410///
411/// [Unicode Core Specification - Section 22.2, Letterlike Symbols]: <https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-22/#G14143>
412/// [Letterlike Symbols]: <https://unicode.org/charts/PDF/U2100.pdf>
413/// [Mathematical Alphanumeric Symbols]: <https://unicode.org/charts/PDF/U1D400.pdf>
414/// [Arabic Mathematical Alphabetic Symbols]: <https://unicode.org/charts/PDF/U1EE00.pdf>
415mod conversions {
416    const VARIATION_SELECTOR_1: char = '\u{FE00}';
417    const VARIATION_SELECTOR_2: char = '\u{FE01}';
418
419    #[inline]
420    pub fn is_digit(c: char) -> bool {
421        c.is_ascii_digit()
422    }
423
424    #[inline]
425    pub fn is_latin(c: char) -> bool {
426        c.is_ascii_alphabetic()
427    }
428
429    #[inline]
430    pub fn is_greek(c: char) -> bool {
431        is_upper_greek(c) || is_lower_greek(c)
432    }
433
434    #[inline]
435    pub fn is_upper_greek(c: char) -> bool {
436        matches!(c, 'Α'..='Ω' | '∇' | 'ϴ')
437    }
438
439    #[inline]
440    pub fn is_lower_greek(c: char) -> bool {
441        matches!(c, 'α'..='ω' | '∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ')
442    }
443
444    #[inline]
445    pub fn is_hebrew(c: char) -> bool {
446        matches!(c, 'א'..='ד')
447    }
448
449    /// The character given by adding `delta` to the codepoint of `c`.
450    #[inline]
451    fn apply_delta(c: char, delta: u32) -> char {
452        std::char::from_u32((c as u32) + delta).unwrap()
453    }
454
455    pub fn to_bold(c: char) -> char {
456        let delta = match c {
457            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
458            // Bold symbols (U+1D400..U+1D433)
459            'A'..='Z' => 0x1D3BF,
460            'a'..='z' => 0x1D3B9,
461            // Bold Greek symbols (U+1D6A8..U+1D6DA)
462            'Α'..='Ρ' => 0x1D317,
463            'ϴ' => 0x1D2C5,
464            'Σ'..='Ω' => 0x1D317,
465            '∇' => 0x1B4BA,
466            'α'..='ω' => 0x1D311,
467            // Additional bold Greek symbols (U+1D6DB..U+1D6E1)
468            '∂' => 0x1B4D9,
469            'ϵ' => 0x1D2E7,
470            'ϑ' => 0x1D30C,
471            'ϰ' => 0x1D2EE,
472            'ϕ' => 0x1D30A,
473            'ϱ' => 0x1D2EF,
474            'ϖ' => 0x1D30B,
475            // Additional bold Greek symbols (U+1D7CA..U+1D7CB)
476            'Ϝ'..='ϝ' => 0x1D3EE,
477            // Bold digits (U+1D7CE..U+1D7D7)
478            '0'..='9' => 0x1D79E,
479            _ => return c,
480        };
481        apply_delta(c, delta)
482    }
483
484    pub fn to_italic(c: char) -> char {
485        let delta = match c {
486            // Letterlike Symbols Block (U+2100..U+214F)
487            // Letterlike symbols (U+2100..U+2134)
488            'h' => 0x20A6,
489            'ħ' => 0x1FE8,
490
491            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
492            // Italic symbols (U+1D434..U+1D467)
493            'A'..='Z' => 0x1D3F3,
494            'a'..='z' => 0x1D3ED,
495            // Dotless symbols (U+1D6A4..U+1D6A5)
496            'ı' => 0x1D573,
497            'ȷ' => 0x1D46E,
498            // Italic Greek symbols (U+1D6E2..U+1D714)
499            'Α'..='Ρ' => 0x1D351,
500            'ϴ' => 0x1D2FF,
501            'Σ'..='Ω' => 0x1D351,
502            '∇' => 0x1B4F4,
503            'α'..='ω' => 0x1D34B,
504            // Additional italic Greek symbols (U+1D715..U+1D71B)
505            '∂' => 0x1B513,
506            'ϵ' => 0x1D321,
507            'ϑ' => 0x1D346,
508            'ϰ' => 0x1D328,
509            'ϕ' => 0x1D344,
510            'ϱ' => 0x1D329,
511            'ϖ' => 0x1D345,
512            _ => return c,
513        };
514        apply_delta(c, delta)
515    }
516
517    pub fn to_bold_italic(c: char) -> char {
518        let delta = match c {
519            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
520            // Bold italic symbols (U+1D468..U+1D49B)
521            'A'..='Z' => 0x1D427,
522            'a'..='z' => 0x1D421,
523            // Bold italic Greek symbols (U+1D71C..U+1D74E)
524            'Α'..='Ρ' => 0x1D38B,
525            'ϴ' => 0x1D339,
526            'Σ'..='Ω' => 0x1D38B,
527            '∇' => 0x1B52E,
528            'α'..='ω' => 0x1D385,
529            // Additional bold italic Greek symbols (U+1D74F..U+1D755)
530            '∂' => 0x1B54D,
531            'ϵ' => 0x1D35B,
532            'ϑ' => 0x1D380,
533            'ϰ' => 0x1D362,
534            'ϕ' => 0x1D37E,
535            'ϱ' => 0x1D363,
536            'ϖ' => 0x1D37F,
537            _ => return c,
538        };
539        apply_delta(c, delta)
540    }
541
542    pub fn to_script(c: char) -> char {
543        let delta = match c {
544            // Letterlike Symbols Block (U+2100..U+214F)
545            // Letterlike symbols (U+2100..U+2134)
546            'g' => 0x20A3,
547            'H' => 0x20C3,
548            'I' => 0x20C7,
549            'L' => 0x20C6,
550            'R' => 0x20C9,
551            'B' => 0x20EA,
552            'e' => 0x20CA,
553            'E'..='F' => 0x20EB,
554            'M' => 0x20E6,
555            'o' => 0x20C5,
556
557            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
558            // Script symbols (U+1D49C..U+1D4CF)
559            'A'..='Z' => 0x1D45B,
560            'a'..='z' => 0x1D455,
561            _ => return c,
562        };
563        apply_delta(c, delta)
564    }
565
566    pub fn to_bold_script(c: char) -> char {
567        let delta = match c {
568            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
569            // Bold script symbols (U+1D4D0..U+1D503)
570            'A'..='Z' => 0x1D48F,
571            'a'..='z' => 0x1D489,
572            _ => return c,
573        };
574        apply_delta(c, delta)
575    }
576
577    pub fn to_fraktur(c: char) -> char {
578        let delta = match c {
579            // Letterlike Symbols Block (U+2100..U+214F)
580            // Letterlike symbols (U+2100..U+2134)
581            'H' => 0x20C4,
582            'I' => 0x20C8,
583            'R' => 0x20CA,
584            'Z' => 0x20CE,
585            'C' => 0x20EA,
586
587            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
588            // Fraktur symbols (U+1D504..U+1D537)
589            'A'..='Z' => 0x1D4C3,
590            'a'..='z' => 0x1D4BD,
591            _ => return c,
592        };
593        apply_delta(c, delta)
594    }
595
596    pub fn to_bold_fraktur(c: char) -> char {
597        let delta = match c {
598            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
599            // Bold Fraktur symbols (U+1D56C..U+1D59F)
600            'A'..='Z' => 0x1D52B,
601            'a'..='z' => 0x1D525,
602            _ => return c,
603        };
604        apply_delta(c, delta)
605    }
606
607    pub fn to_sans_serif(c: char) -> char {
608        let delta = match c {
609            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
610            // Sans-serif symbols (U+1D5A0..U+1D5D3)
611            'A'..='Z' => 0x1D55F,
612            'a'..='z' => 0x1D559,
613            // Sans-serif digits (U+1D7E2..U+1D7EB)
614            '0'..='9' => 0x1D7B2,
615            _ => return c,
616        };
617        apply_delta(c, delta)
618    }
619
620    pub fn to_sans_serif_bold(c: char) -> char {
621        let delta = match c {
622            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
623            // Sans-serif bold symbols (U+1D5D4..U+1D607)
624            'A'..='Z' => 0x1D593,
625            'a'..='z' => 0x1D58D,
626            // Sans-serif bold Greek symbols (U+1D756..U+1D788)
627            'Α'..='Ρ' => 0x1D3C5,
628            'ϴ' => 0x1D373,
629            'Σ'..='Ω' => 0x1D3C5,
630            '∇' => 0x1B568,
631            'α'..='ω' => 0x1D3BF,
632            // Additional sans-serif bold Greek symbols (U+1D789..U+1D78F)
633            '∂' => 0x1B587,
634            'ϵ' => 0x1D395,
635            'ϑ' => 0x1D3BA,
636            'ϰ' => 0x1D39C,
637            'ϕ' => 0x1D3B8,
638            'ϱ' => 0x1D39D,
639            'ϖ' => 0x1D3B9,
640            // Sans-serif bold digits (U+1D7EC..U+1D7F5)
641            '0'..='9' => 0x1D7BC,
642            _ => return c,
643        };
644        apply_delta(c, delta)
645    }
646
647    pub fn to_sans_serif_italic(c: char) -> char {
648        let delta = match c {
649            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
650            // Sans-serif italic symbols (U+1D608..U+1D63B)
651            'A'..='Z' => 0x1D5C7,
652            'a'..='z' => 0x1D5C1,
653            _ => return c,
654        };
655        apply_delta(c, delta)
656    }
657
658    pub fn to_sans_serif_bold_italic(c: char) -> char {
659        let delta = match c {
660            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
661            // Sans-serif bold italic symbols (U+1D63C..U+1D66F)
662            'A'..='Z' => 0x1D5FB,
663            'a'..='z' => 0x1D5F5,
664            // Sans-serif bold italic Greek symbols (U+1D790..U+1D7C2)
665            'Α'..='Ρ' => 0x1D3FF,
666            'ϴ' => 0x1D3AD,
667            'Σ'..='Ω' => 0x1D3FF,
668            '∇' => 0x1B5A2,
669            'α'..='ω' => 0x1D3F9,
670            // Additional sans-serif bold italic Greek symbols (U+1D7C3..U+1D7C9)
671            '∂' => 0x1B5C1,
672            'ϵ' => 0x1D3CF,
673            'ϑ' => 0x1D3F4,
674            'ϰ' => 0x1D3D6,
675            'ϕ' => 0x1D3F2,
676            'ϱ' => 0x1D3D7,
677            'ϖ' => 0x1D3F3,
678            _ => return c,
679        };
680        apply_delta(c, delta)
681    }
682
683    pub fn to_monospace(c: char) -> char {
684        let delta = match c {
685            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
686            // Monospace symbols (U+1D670..U+1D6A3)
687            'A'..='Z' => 0x1D62F,
688            'a'..='z' => 0x1D629,
689            // Monospace digits (U+1D7F6..U+1D7FF)
690            '0'..='9' => 0x1D7C6,
691            _ => return c,
692        };
693        apply_delta(c, delta)
694    }
695
696    pub fn to_isolated(c: char) -> char {
697        let delta = match c {
698            // Arabic Mathematical Alphabetic Symbols Block (U+1EE00..U+1EEFF)
699            // Isolated symbols (U+1EE00..U+1EE1F)
700            'ا'..='ب' => 0x1E7D9,
701            'ج' => 0x1E7D6,
702            'د' => 0x1E7D4,
703            'و' => 0x1E7BD,
704            'ز' => 0x1E7D4,
705            'ح' => 0x1E7DA,
706            'ط' => 0x1E7D1,
707            'ي' => 0x1E7BF,
708            'ك'..='ن' => 0x1E7C7,
709            'س' => 0x1E7DB,
710            'ع' => 0x1E7D6,
711            'ف' => 0x1E7CF,
712            'ص' => 0x1E7DC,
713            'ق' => 0x1E7D0,
714            'ر' => 0x1E7E2,
715            'ش' => 0x1E7E0,
716            'ت'..='ث' => 0x1E7EB,
717            'خ' => 0x1E7E9,
718            'ذ' => 0x1E7E8,
719            'ض' => 0x1E7E3,
720            'ظ' => 0x1E7E2,
721            'غ' => 0x1E7E1,
722            'ٮ' => 0x1E7AE,
723            'ں' => 0x1E763,
724            'ڡ' => 0x1E77D,
725            'ٯ' => 0x1E7B0,
726            _ => return c,
727        };
728        apply_delta(c, delta)
729    }
730
731    pub fn to_initial(c: char) -> char {
732        let delta = match c {
733            // Arabic Mathematical Alphabetic Symbols Block (U+1EE00..U+1EEFF)
734            // Initial symbols (U+1EE21..U+1EE3B)
735            'ب' => 0x1E7F9,
736            'ج' => 0x1E7F6,
737            'ه' => 0x1E7DD,
738            'ح' => 0x1E7FA,
739            'ي' => 0x1E7DF,
740            'ك'..='ن' => 0x1E7E7,
741            'س' => 0x1E7FB,
742            'ع' => 0x1E7F6,
743            'ف' => 0x1E7EF,
744            'ص' => 0x1E7FC,
745            'ق' => 0x1E7F0,
746            'ش' => 0x1E800,
747            'ت'..='ث' => 0x1E80B,
748            'خ' => 0x1E809,
749            'ض' => 0x1E803,
750            'غ' => 0x1E801,
751            _ => return c,
752        };
753        apply_delta(c, delta)
754    }
755
756    pub fn to_tailed(c: char) -> char {
757        let delta = match c {
758            // Arabic Mathematical Alphabetic Symbols Block (U+1EE00..U+1EEFF)
759            // Tailed symbols (U+1EE42..U+1EE5F)
760            'ج' => 0x1E816,
761            'ح' => 0x1E81A,
762            'ي' => 0x1E7FF,
763            'ل' => 0x1E807,
764            'ن' => 0x1E807,
765            'س' => 0x1E81B,
766            'ع' => 0x1E816,
767            'ص' => 0x1E81C,
768            'ق' => 0x1E810,
769            'ش' => 0x1E820,
770            'خ' => 0x1E829,
771            'ض' => 0x1E823,
772            'غ' => 0x1E821,
773            'ں' => 0x1E7A3,
774            'ٯ' => 0x1E7F0,
775            _ => return c,
776        };
777        apply_delta(c, delta)
778    }
779
780    pub fn to_stretched(c: char) -> char {
781        let delta = match c {
782            // Arabic Mathematical Alphabetic Symbols Block (U+1EE00..U+1EEFF)
783            // Stretched symbols (U+1EE61..U+1EE7E)
784            'ب' => 0x1E839,
785            'ج' => 0x1E836,
786            'ه' => 0x1E81D,
787            'ح' => 0x1E83A,
788            'ط' => 0x1E831,
789            'ي' => 0x1E81F,
790            'ك' => 0x1E827,
791            'م'..='ن' => 0x1E827,
792            'س' => 0x1E83B,
793            'ع' => 0x1E836,
794            'ف' => 0x1E82F,
795            'ص' => 0x1E83C,
796            'ق' => 0x1E830,
797            'ش' => 0x1E840,
798            'ت'..='ث' => 0x1E84B,
799            'خ' => 0x1E849,
800            'ض' => 0x1E843,
801            'ظ' => 0x1E842,
802            'غ' => 0x1E841,
803            'ٮ' => 0x1E80E,
804            'ڡ' => 0x1E7DD,
805            _ => return c,
806        };
807        apply_delta(c, delta)
808    }
809
810    pub fn to_looped(c: char) -> char {
811        let delta = match c {
812            // Arabic Mathematical Alphabetic Symbols Block (U+1EE00..U+1EEFF)
813            // Looped symbols (U+1EE80..U+1EE9B)
814            'ا'..='ب' => 0x1E859,
815            'ج' => 0x1E856,
816            'د' => 0x1E854,
817            'ه'..'و' => 0x1E83D,
818            'ز' => 0x1E854,
819            'ح' => 0x1E85A,
820            'ط' => 0x1E851,
821            'ي' => 0x1E83F,
822            'ل'..='ن' => 0x1E847,
823            'س' => 0x1E85B,
824            'ع' => 0x1E856,
825            'ف' => 0x1E84F,
826            'ص' => 0x1E85C,
827            'ق' => 0x1E850,
828            'ر' => 0x1E862,
829            'ش' => 0x1E860,
830            'ت'..='ث' => 0x1E86B,
831            'خ' => 0x1E869,
832            'ذ' => 0x1E868,
833            'ض' => 0x1E863,
834            'ظ' => 0x1E862,
835            'غ' => 0x1E861,
836            _ => return c,
837        };
838        apply_delta(c, delta)
839    }
840
841    pub fn to_double_struck(c: char) -> char {
842        let delta = match c {
843            // Letterlike Symbols Block (U+2100..U+214F)
844            // Letterlike symbols (U+2100..U+2134)
845            'C' => 0x20BF,
846            'H' => 0x20C5,
847            'N' => 0x20C7,
848            'P'..='Q' => 0x20C9,
849            'R' => 0x20CB,
850            'Z' => 0x20CA,
851            // Additional letterlike symbols (U+2139..U+213F)
852            'π' => 0x1D7C,
853            'γ' => 0x1D8A,
854            'Γ' => 0x1DAB,
855            'Π' => 0x1D9F,
856            // Double-struck large operator (U+2140)
857            '∑' => return '⅀', // delta is negative
858
859            // Mathematical Alphanumeric Symbols Block (U+1D400..U+1D7FF)
860            // Double-struck symbols (U+1D538..U+1D56B)
861            'A'..='Z' => 0x1D4F7,
862            'a'..='z' => 0x1D4F1,
863            // Double-struck digits (U+1D7D8..U+1D7E1)
864            '0'..='9' => 0x1D7A8,
865
866            // Arabic Mathematical Alphabetic Symbols Block (U+1EE00..U+1EEFF)
867            // Double-struck symbols (U+1EEA1..U+1EEBB)
868            'ب' => 0x1E879,
869            'ج' => 0x1E876,
870            'د' => 0x1E874,
871            'و' => 0x1E85D,
872            'ز' => 0x1E874,
873            'ح' => 0x1E87A,
874            'ط' => 0x1E871,
875            'ي' => 0x1E85F,
876            'ل'..='ن' => 0x1E867,
877            'س' => 0x1E87B,
878            'ع' => 0x1E876,
879            'ف' => 0x1E86F,
880            'ص' => 0x1E87C,
881            'ق' => 0x1E870,
882            'ر' => 0x1E882,
883            'ش' => 0x1E880,
884            'ت'..='ث' => 0x1E88B,
885            'خ' => 0x1E889,
886            'ذ' => 0x1E888,
887            'ض' => 0x1E883,
888            'ظ' => 0x1E882,
889            'غ' => 0x1E881,
890            _ => return c,
891        };
892        apply_delta(c, delta)
893    }
894
895    pub fn to_double_struck_italic(c: char) -> char {
896        let delta = match c {
897            // Letterlike Symbols Block (U+2100..U+214F)
898            // Double-struck italic math symbols (U+2145..U+2149)
899            'D' => 0x2101,
900            'd'..='e' => 0x20E2,
901            'i'..='j' => 0x20DF,
902            _ => return c,
903        };
904        apply_delta(c, delta)
905    }
906
907    pub fn to_chancery(c: char) -> [char; 2] {
908        // Standardized Variation Sequences (uppercase Latin script characters)
909        // Variation Sequences (lowercase Latin script characters)
910        let next = if is_latin(c) { VARIATION_SELECTOR_1 } else { '\0' };
911        [to_script(c), next]
912    }
913
914    pub fn to_bold_chancery(c: char) -> [char; 2] {
915        // Variation Sequences (Latin script characters)
916        let next = if is_latin(c) { VARIATION_SELECTOR_1 } else { '\0' };
917        [to_bold_script(c), next]
918    }
919
920    pub fn to_roundhand(c: char) -> [char; 2] {
921        // Standardized Variation Sequences (uppercase Latin script characters)
922        // Variation Sequences (lowercase Latin script characters)
923        let next = if is_latin(c) { VARIATION_SELECTOR_2 } else { '\0' };
924        [to_script(c), next]
925    }
926
927    pub fn to_bold_roundhand(c: char) -> [char; 2] {
928        // Variation Sequences (Latin script characters)
929        let next = if is_latin(c) { VARIATION_SELECTOR_2 } else { '\0' };
930        [to_bold_script(c), next]
931    }
932
933    pub fn to_hebrew(c: char) -> char {
934        let delta = match c {
935            // Letterlike Symbols Block (U+2100..U+214F)
936            // Hebrew letterlike math symbols (U+2135..U+2138)
937            'א'..='ד' => 0x1B65,
938            _ => return c,
939        };
940        apply_delta(c, delta)
941    }
942}