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}