use std::fmt::{self, Write};
use std::iter::FusedIterator;
pub const UNICODE_VERSION: (u8, u8, u8) = (16, 0, 0);
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Default)]
pub enum MathStyle {
#[default]
Plain,
Bold,
Italic,
BoldItalic,
Script,
BoldScript,
Fraktur,
BoldFraktur,
SansSerif,
SansSerifBold,
SansSerifItalic,
SansSerifBoldItalic,
Monospace,
Isolated,
Initial,
Tailed,
Stretched,
Looped,
DoubleStruck,
DoubleStruckItalic,
Chancery,
BoldChancery,
Roundhand,
BoldRoundhand,
Hebrew,
}
#[non_exhaustive]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum MathVariant {
Plain,
Fraktur,
SansSerif,
Monospace,
DoubleStruck,
Chancery,
Roundhand,
}
impl MathStyle {
pub fn select(
c: char,
variant: Option<MathVariant>,
bold: bool,
italic: Option<bool>,
) -> MathStyle {
use conversions::*;
use MathVariant::*;
match (variant.unwrap_or(Plain), bold, italic) {
(SansSerif, false, Some(false)) if is_latin(c) => MathStyle::SansSerif,
(SansSerif, false, _) if is_latin(c) => MathStyle::SansSerifItalic,
(SansSerif, true, Some(false)) if is_latin(c) => MathStyle::SansSerifBold,
(SansSerif, true, _) if is_latin(c) => MathStyle::SansSerifBoldItalic,
(SansSerif, false, _) if is_digit(c) => MathStyle::SansSerif,
(SansSerif, true, _) if is_digit(c) => MathStyle::SansSerifBold,
(SansSerif, _, Some(false)) if is_greek(c) => MathStyle::SansSerifBold,
(SansSerif, _, Some(true)) if is_greek(c) => MathStyle::SansSerifBoldItalic,
(SansSerif, _, None) if is_upper_greek(c) => MathStyle::SansSerifBold,
(SansSerif, _, None) if is_lower_greek(c) => MathStyle::SansSerifBoldItalic,
(Fraktur, false, _) if is_latin(c) => MathStyle::Fraktur,
(Fraktur, true, _) if is_latin(c) => MathStyle::BoldFraktur,
(Monospace, _, _) if is_digit(c) | is_latin(c) => MathStyle::Monospace,
(DoubleStruck, _, Some(true)) if matches!(c, 'D' | 'd' | 'e' | 'i' | 'j') => {
MathStyle::DoubleStruckItalic
}
(DoubleStruck, _, _)
if is_digit(c)
| is_latin(c)
| matches!(c, '∑' | 'Γ' | 'Π' | 'γ' | 'π') =>
{
MathStyle::DoubleStruck
}
(Chancery, false, _) if is_latin(c) => MathStyle::Chancery,
(Chancery, true, _) if is_latin(c) => MathStyle::BoldChancery,
(Roundhand, false, _) if is_latin(c) => MathStyle::Roundhand,
(Roundhand, true, _) if is_latin(c) => MathStyle::BoldRoundhand,
(_, false, Some(true)) if is_latin(c) | is_greek(c) => MathStyle::Italic,
(_, false, None) if is_latin(c) | is_lower_greek(c) => MathStyle::Italic,
(_, true, Some(false)) if is_latin(c) | is_greek(c) => MathStyle::Bold,
(_, true, Some(true)) if is_latin(c) | is_greek(c) => MathStyle::BoldItalic,
(_, true, None) if is_latin(c) | is_lower_greek(c) => MathStyle::BoldItalic,
(_, true, None) if is_upper_greek(c) => MathStyle::Bold,
(_, true, _) if is_digit(c) | matches!(c, 'Ϝ' | 'ϝ') => MathStyle::Bold,
(_, _, Some(true) | None) if matches!(c, 'ı' | 'ȷ' | 'ħ') => {
MathStyle::Italic
}
(_, _, Some(true) | None) if is_hebrew(c) => MathStyle::Hebrew,
_ => MathStyle::Plain,
}
}
}
#[derive(Debug, Clone)]
pub struct ToStyle(core::array::IntoIter<char, 2>);
impl ToStyle {
#[inline]
fn new(chars: [char; 2]) -> ToStyle {
let mut iter = chars.into_iter();
if chars[1] == '\0' {
iter.next_back();
}
ToStyle(iter)
}
}
impl Iterator for ToStyle {
type Item = char;
fn next(&mut self) -> Option<char> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
fn fold<Acc, Fold>(self, init: Acc, fold: Fold) -> Acc
where
Fold: FnMut(Acc, Self::Item) -> Acc,
{
self.0.fold(init, fold)
}
fn count(self) -> usize {
self.0.count()
}
fn last(self) -> Option<Self::Item> {
self.0.last()
}
}
impl DoubleEndedIterator for ToStyle {
fn next_back(&mut self) -> Option<char> {
self.0.next_back()
}
fn rfold<Acc, Fold>(self, init: Acc, rfold: Fold) -> Acc
where
Fold: FnMut(Acc, Self::Item) -> Acc,
{
self.0.rfold(init, rfold)
}
}
impl ExactSizeIterator for ToStyle {
fn len(&self) -> usize {
self.0.len()
}
}
impl FusedIterator for ToStyle {}
impl fmt::Display for ToStyle {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for c in self.0.clone() {
f.write_char(c)?;
}
Ok(())
}
}
pub fn to_style(c: char, style: MathStyle) -> ToStyle {
use conversions::*;
use MathStyle::*;
let styled = match style {
Plain => [c, '\0'],
Bold => [to_bold(c), '\0'],
Italic => [to_italic(c), '\0'],
BoldItalic => [to_bold_italic(c), '\0'],
Script => [to_script(c), '\0'],
BoldScript => [to_bold_script(c), '\0'],
Fraktur => [to_fraktur(c), '\0'],
BoldFraktur => [to_bold_fraktur(c), '\0'],
SansSerif => [to_sans_serif(c), '\0'],
SansSerifBold => [to_sans_serif_bold(c), '\0'],
SansSerifItalic => [to_sans_serif_italic(c), '\0'],
SansSerifBoldItalic => [to_sans_serif_bold_italic(c), '\0'],
Monospace => [to_monospace(c), '\0'],
Isolated => [to_isolated(c), '\0'],
Initial => [to_initial(c), '\0'],
Tailed => [to_tailed(c), '\0'],
Stretched => [to_stretched(c), '\0'],
Looped => [to_looped(c), '\0'],
DoubleStruck => [to_double_struck(c), '\0'],
DoubleStruckItalic => [to_double_struck_italic(c), '\0'],
Chancery => to_chancery(c),
BoldChancery => to_bold_chancery(c),
Roundhand => to_roundhand(c),
BoldRoundhand => to_bold_roundhand(c),
Hebrew => [to_hebrew(c), '\0'],
};
ToStyle::new(styled)
}
mod conversions {
const VARIATION_SELECTOR_1: char = '\u{FE00}';
const VARIATION_SELECTOR_2: char = '\u{FE01}';
#[inline]
pub fn is_digit(c: char) -> bool {
c.is_ascii_digit()
}
#[inline]
pub fn is_latin(c: char) -> bool {
c.is_ascii_alphabetic()
}
#[inline]
pub fn is_greek(c: char) -> bool {
is_upper_greek(c) || is_lower_greek(c)
}
#[inline]
pub fn is_upper_greek(c: char) -> bool {
matches!(c, 'Α'..='Ω' | '∇' | 'ϴ')
}
#[inline]
pub fn is_lower_greek(c: char) -> bool {
matches!(c, 'α'..='ω' | '∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ')
}
#[inline]
pub fn is_hebrew(c: char) -> bool {
matches!(c, 'א'..='ד')
}
#[inline]
fn apply_delta(c: char, delta: u32) -> char {
std::char::from_u32((c as u32) + delta).unwrap()
}
pub fn to_bold(c: char) -> char {
let delta = match c {
'A'..='Z' => 0x1D3BF,
'a'..='z' => 0x1D3B9,
'Α'..='Ρ' => 0x1D317,
'ϴ' => 0x1D2C5,
'Σ'..='Ω' => 0x1D317,
'∇' => 0x1B4BA,
'α'..='ω' => 0x1D311,
'∂' => 0x1B4D9,
'ϵ' => 0x1D2E7,
'ϑ' => 0x1D30C,
'ϰ' => 0x1D2EE,
'ϕ' => 0x1D30A,
'ϱ' => 0x1D2EF,
'ϖ' => 0x1D30B,
'Ϝ'..='ϝ' => 0x1D3EE,
'0'..='9' => 0x1D79E,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_italic(c: char) -> char {
let delta = match c {
'h' => 0x20A6,
'ħ' => 0x1FE8,
'A'..='Z' => 0x1D3F3,
'a'..='z' => 0x1D3ED,
'ı' => 0x1D573,
'ȷ' => 0x1D46E,
'Α'..='Ρ' => 0x1D351,
'ϴ' => 0x1D2FF,
'Σ'..='Ω' => 0x1D351,
'∇' => 0x1B4F4,
'α'..='ω' => 0x1D34B,
'∂' => 0x1B513,
'ϵ' => 0x1D321,
'ϑ' => 0x1D346,
'ϰ' => 0x1D328,
'ϕ' => 0x1D344,
'ϱ' => 0x1D329,
'ϖ' => 0x1D345,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_bold_italic(c: char) -> char {
let delta = match c {
'A'..='Z' => 0x1D427,
'a'..='z' => 0x1D421,
'Α'..='Ρ' => 0x1D38B,
'ϴ' => 0x1D339,
'Σ'..='Ω' => 0x1D38B,
'∇' => 0x1B52E,
'α'..='ω' => 0x1D385,
'∂' => 0x1B54D,
'ϵ' => 0x1D35B,
'ϑ' => 0x1D380,
'ϰ' => 0x1D362,
'ϕ' => 0x1D37E,
'ϱ' => 0x1D363,
'ϖ' => 0x1D37F,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_script(c: char) -> char {
let delta = match c {
'g' => 0x20A3,
'H' => 0x20C3,
'I' => 0x20C7,
'L' => 0x20C6,
'R' => 0x20C9,
'B' => 0x20EA,
'e' => 0x20CA,
'E'..='F' => 0x20EB,
'M' => 0x20E6,
'o' => 0x20C5,
'A'..='Z' => 0x1D45B,
'a'..='z' => 0x1D455,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_bold_script(c: char) -> char {
let delta = match c {
'A'..='Z' => 0x1D48F,
'a'..='z' => 0x1D489,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_fraktur(c: char) -> char {
let delta = match c {
'H' => 0x20C4,
'I' => 0x20C8,
'R' => 0x20CA,
'Z' => 0x20CE,
'C' => 0x20EA,
'A'..='Z' => 0x1D4C3,
'a'..='z' => 0x1D4BD,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_bold_fraktur(c: char) -> char {
let delta = match c {
'A'..='Z' => 0x1D52B,
'a'..='z' => 0x1D525,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_sans_serif(c: char) -> char {
let delta = match c {
'A'..='Z' => 0x1D55F,
'a'..='z' => 0x1D559,
'0'..='9' => 0x1D7B2,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_sans_serif_bold(c: char) -> char {
let delta = match c {
'A'..='Z' => 0x1D593,
'a'..='z' => 0x1D58D,
'Α'..='Ρ' => 0x1D3C5,
'ϴ' => 0x1D373,
'Σ'..='Ω' => 0x1D3C5,
'∇' => 0x1B568,
'α'..='ω' => 0x1D3BF,
'∂' => 0x1B587,
'ϵ' => 0x1D395,
'ϑ' => 0x1D3BA,
'ϰ' => 0x1D39C,
'ϕ' => 0x1D3B8,
'ϱ' => 0x1D39D,
'ϖ' => 0x1D3B9,
'0'..='9' => 0x1D7BC,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_sans_serif_italic(c: char) -> char {
let delta = match c {
'A'..='Z' => 0x1D5C7,
'a'..='z' => 0x1D5C1,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_sans_serif_bold_italic(c: char) -> char {
let delta = match c {
'A'..='Z' => 0x1D5FB,
'a'..='z' => 0x1D5F5,
'Α'..='Ρ' => 0x1D3FF,
'ϴ' => 0x1D3AD,
'Σ'..='Ω' => 0x1D3FF,
'∇' => 0x1B5A2,
'α'..='ω' => 0x1D3F9,
'∂' => 0x1B5C1,
'ϵ' => 0x1D3CF,
'ϑ' => 0x1D3F4,
'ϰ' => 0x1D3D6,
'ϕ' => 0x1D3F2,
'ϱ' => 0x1D3D7,
'ϖ' => 0x1D3F3,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_monospace(c: char) -> char {
let delta = match c {
'A'..='Z' => 0x1D62F,
'a'..='z' => 0x1D629,
'0'..='9' => 0x1D7C6,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_isolated(c: char) -> char {
let delta = match c {
'ا'..='ب' => 0x1E7D9,
'ج' => 0x1E7D6,
'د' => 0x1E7D4,
'و' => 0x1E7BD,
'ز' => 0x1E7D4,
'ح' => 0x1E7DA,
'ط' => 0x1E7D1,
'ي' => 0x1E7BF,
'ك'..='ن' => 0x1E7C7,
'س' => 0x1E7DB,
'ع' => 0x1E7D6,
'ف' => 0x1E7CF,
'ص' => 0x1E7DC,
'ق' => 0x1E7D0,
'ر' => 0x1E7E2,
'ش' => 0x1E7E0,
'ت'..='ث' => 0x1E7EB,
'خ' => 0x1E7E9,
'ذ' => 0x1E7E8,
'ض' => 0x1E7E3,
'ظ' => 0x1E7E2,
'غ' => 0x1E7E1,
'ٮ' => 0x1E7AE,
'ں' => 0x1E763,
'ڡ' => 0x1E77D,
'ٯ' => 0x1E7B0,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_initial(c: char) -> char {
let delta = match c {
'ب' => 0x1E7F9,
'ج' => 0x1E7F6,
'ه' => 0x1E7DD,
'ح' => 0x1E7FA,
'ي' => 0x1E7DF,
'ك'..='ن' => 0x1E7E7,
'س' => 0x1E7FB,
'ع' => 0x1E7F6,
'ف' => 0x1E7EF,
'ص' => 0x1E7FC,
'ق' => 0x1E7F0,
'ش' => 0x1E800,
'ت'..='ث' => 0x1E80B,
'خ' => 0x1E809,
'ض' => 0x1E803,
'غ' => 0x1E801,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_tailed(c: char) -> char {
let delta = match c {
'ج' => 0x1E816,
'ح' => 0x1E81A,
'ي' => 0x1E7FF,
'ل' => 0x1E807,
'ن' => 0x1E807,
'س' => 0x1E81B,
'ع' => 0x1E816,
'ص' => 0x1E81C,
'ق' => 0x1E810,
'ش' => 0x1E820,
'خ' => 0x1E829,
'ض' => 0x1E823,
'غ' => 0x1E821,
'ں' => 0x1E7A3,
'ٯ' => 0x1E7F0,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_stretched(c: char) -> char {
let delta = match c {
'ب' => 0x1E839,
'ج' => 0x1E836,
'ه' => 0x1E81D,
'ح' => 0x1E83A,
'ط' => 0x1E831,
'ي' => 0x1E81F,
'ك' => 0x1E827,
'م'..='ن' => 0x1E827,
'س' => 0x1E83B,
'ع' => 0x1E836,
'ف' => 0x1E82F,
'ص' => 0x1E83C,
'ق' => 0x1E830,
'ش' => 0x1E840,
'ت'..='ث' => 0x1E84B,
'خ' => 0x1E849,
'ض' => 0x1E843,
'ظ' => 0x1E842,
'غ' => 0x1E841,
'ٮ' => 0x1E80E,
'ڡ' => 0x1E7DD,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_looped(c: char) -> char {
let delta = match c {
'ا'..='ب' => 0x1E859,
'ج' => 0x1E856,
'د' => 0x1E854,
'ه'..'و' => 0x1E83D,
'ز' => 0x1E854,
'ح' => 0x1E85A,
'ط' => 0x1E851,
'ي' => 0x1E83F,
'ل'..='ن' => 0x1E847,
'س' => 0x1E85B,
'ع' => 0x1E856,
'ف' => 0x1E84F,
'ص' => 0x1E85C,
'ق' => 0x1E850,
'ر' => 0x1E862,
'ش' => 0x1E860,
'ت'..='ث' => 0x1E86B,
'خ' => 0x1E869,
'ذ' => 0x1E868,
'ض' => 0x1E863,
'ظ' => 0x1E862,
'غ' => 0x1E861,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_double_struck(c: char) -> char {
let delta = match c {
'C' => 0x20BF,
'H' => 0x20C5,
'N' => 0x20C7,
'P'..='Q' => 0x20C9,
'R' => 0x20CB,
'Z' => 0x20CA,
'π' => 0x1D7C,
'γ' => 0x1D8A,
'Γ' => 0x1DAB,
'Π' => 0x1D9F,
'∑' => return '⅀',
'A'..='Z' => 0x1D4F7,
'a'..='z' => 0x1D4F1,
'0'..='9' => 0x1D7A8,
'ب' => 0x1E879,
'ج' => 0x1E876,
'د' => 0x1E874,
'و' => 0x1E85D,
'ز' => 0x1E874,
'ح' => 0x1E87A,
'ط' => 0x1E871,
'ي' => 0x1E85F,
'ل'..='ن' => 0x1E867,
'س' => 0x1E87B,
'ع' => 0x1E876,
'ف' => 0x1E86F,
'ص' => 0x1E87C,
'ق' => 0x1E870,
'ر' => 0x1E882,
'ش' => 0x1E880,
'ت'..='ث' => 0x1E88B,
'خ' => 0x1E889,
'ذ' => 0x1E888,
'ض' => 0x1E883,
'ظ' => 0x1E882,
'غ' => 0x1E881,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_double_struck_italic(c: char) -> char {
let delta = match c {
'D' => 0x2101,
'd'..='e' => 0x20E2,
'i'..='j' => 0x20DF,
_ => return c,
};
apply_delta(c, delta)
}
pub fn to_chancery(c: char) -> [char; 2] {
let next = if is_latin(c) { VARIATION_SELECTOR_1 } else { '\0' };
[to_script(c), next]
}
pub fn to_bold_chancery(c: char) -> [char; 2] {
let next = if is_latin(c) { VARIATION_SELECTOR_1 } else { '\0' };
[to_bold_script(c), next]
}
pub fn to_roundhand(c: char) -> [char; 2] {
let next = if is_latin(c) { VARIATION_SELECTOR_2 } else { '\0' };
[to_script(c), next]
}
pub fn to_bold_roundhand(c: char) -> [char; 2] {
let next = if is_latin(c) { VARIATION_SELECTOR_2 } else { '\0' };
[to_bold_script(c), next]
}
pub fn to_hebrew(c: char) -> char {
let delta = match c {
'א'..='ד' => 0x1B65,
_ => return c,
};
apply_delta(c, delta)
}
}