use crate::english::encode_english;
use crate::unicode::decode_unicode;
const GRAVE: &[u8] = &[decode_unicode('⠘'), decode_unicode('⠡')];
const ACUTE: &[u8] = &[decode_unicode('⠘'), decode_unicode('⠌')];
const CIRCUMFLEX: &[u8] = &[decode_unicode('⠘'), decode_unicode('⠩')];
const DIAERESIS: &[u8] = &[decode_unicode('⠘'), decode_unicode('⠒')];
const CARON: &[u8] = &[decode_unicode('⠘'), decode_unicode('⠬')];
const RING: &[u8] = &[decode_unicode('⠘'), decode_unicode('⠫')];
const CEDILLA: &[u8] = &[decode_unicode('⠘'), decode_unicode('⠯')];
const TILDE: &[u8] = &[decode_unicode('⠘'), decode_unicode('⠻')];
const STROKE: &[u8] = &[decode_unicode('⠈'), decode_unicode('⠡')];
const MACRON: &[u8] = &[decode_unicode('⠈'), decode_unicode('⠤')];
const BREVE: &[u8] = &[decode_unicode('⠈'), decode_unicode('⠬')];
const COMMA_BELOW: &[u8] = &[
decode_unicode('⠘'),
decode_unicode('⠸'),
decode_unicode('⠂'),
];
const H_STROKE: &[u8] = &[decode_unicode('⠈'), decode_unicode('⠒')];
const DOT_ABOVE: &[u8] = &[
decode_unicode('⠘'),
decode_unicode('⠸'),
decode_unicode('⠆'),
];
fn accent_of(c: char) -> Option<(&'static [u8], char)> {
let m = match c.to_lowercase().next()? {
'à' => (GRAVE, 'a'),
'è' => (GRAVE, 'e'),
'ì' => (GRAVE, 'i'),
'ò' => (GRAVE, 'o'),
'ù' => (GRAVE, 'u'),
'á' => (ACUTE, 'a'),
'é' => (ACUTE, 'e'),
'í' => (ACUTE, 'i'),
'ó' => (ACUTE, 'o'),
'ú' => (ACUTE, 'u'),
'ý' => (ACUTE, 'y'),
'ć' => (ACUTE, 'c'),
'ń' => (ACUTE, 'n'),
'â' => (CIRCUMFLEX, 'a'),
'ê' => (CIRCUMFLEX, 'e'),
'î' => (CIRCUMFLEX, 'i'),
'ô' => (CIRCUMFLEX, 'o'),
'û' => (CIRCUMFLEX, 'u'),
'ä' => (DIAERESIS, 'a'),
'ë' => (DIAERESIS, 'e'),
'ü' => (DIAERESIS, 'u'),
'ÿ' => (DIAERESIS, 'y'),
'ï' => (DIAERESIS, 'i'),
'ö' => (DIAERESIS, 'o'),
'č' => (CARON, 'c'),
'š' => (CARON, 's'),
'ž' => (CARON, 'z'),
'ě' => (CARON, 'e'),
'ř' => (CARON, 'r'),
'å' => (RING, 'a'),
'ç' => (CEDILLA, 'c'),
'ã' => (TILDE, 'a'),
'ñ' => (TILDE, 'n'),
'õ' => (TILDE, 'o'),
'ø' => (STROKE, 'o'),
'ā' => (MACRON, 'a'),
'ē' => (MACRON, 'e'),
'ī' => (MACRON, 'i'),
'ō' => (MACRON, 'o'),
'ū' => (MACRON, 'u'),
'ȳ' => (MACRON, 'y'),
'ă' => (BREVE, 'a'),
'ĕ' => (BREVE, 'e'),
'ĭ' => (BREVE, 'i'),
'ŏ' => (BREVE, 'o'),
'ŭ' => (BREVE, 'u'),
'ł' => (STROKE, 'l'),
'ț' => (COMMA_BELOW, 't'),
'ș' => (COMMA_BELOW, 's'),
'ħ' => (H_STROKE, 'h'),
'ġ' => (DOT_ABOVE, 'g'),
_ => return None,
};
Some(m)
}
fn ligature_bases(c: char) -> Option<(char, char)> {
match c {
'æ' | 'Æ' => Some(('a', 'e')),
'œ' | 'Œ' => Some(('o', 'e')),
_ => None,
}
}
fn eszett_cells(c: char) -> Option<Vec<u8>> {
matches!(c, 'ß' | 'ẞ').then(|| {
let mut cells = Vec::with_capacity(3);
if c.is_uppercase() {
cells.push(decode_unicode('⠠'));
}
cells.extend([decode_unicode('⠨'), decode_unicode('⠮')]);
cells
})
}
pub fn is_accented(c: char) -> bool {
accent_of(c).is_some() || ligature_bases(c).is_some() || matches!(c, 'ß' | 'ẞ')
}
pub fn is_modified_letter(c: char) -> bool {
accent_of(c).is_some()
}
pub fn accent_cells(c: char) -> Option<Vec<u8>> {
if let Some(cells) = eszett_cells(c) {
return Some(cells);
}
if let Some((first, second)) = ligature_bases(c) {
let mut cells = Vec::with_capacity(5);
if c.is_uppercase() {
cells.push(decode_unicode('⠠'));
}
cells.push(encode_english(first).ok()?);
if c.is_uppercase() {
cells.push(decode_unicode('⠠'));
}
cells.extend([decode_unicode('⠘'), decode_unicode('⠖')]);
cells.push(encode_english(second).ok()?);
return Some(cells);
}
let (indicator, base) = accent_of(c)?;
let mut cells = Vec::with_capacity(indicator.len() + 2);
if c.is_uppercase() {
cells.push(decode_unicode('⠠'));
}
cells.extend_from_slice(indicator);
cells.push(encode_english(base).ok()?);
Some(cells)
}
#[cfg(test)]
mod tests {
use super::*;
#[rstest::rstest]
#[case::u_grave('ù', "⠘⠡⠥")]
#[case::y_acute('ý', "⠘⠌⠽")]
#[case::c_acute('ć', "⠘⠌⠉")]
#[case::n_acute('ń', "⠘⠌⠝")]
#[case::e_circumflex('ê', "⠘⠩⠑")]
#[case::y_diaeresis('ÿ', "⠘⠒⠽")]
#[case::s_caron('š', "⠘⠬⠎")]
#[case::e_caron('ě', "⠘⠬⠑")]
#[case::r_caron('ř', "⠘⠬⠗")]
#[case::o_tilde('õ', "⠘⠻⠕")]
#[case::i_macron('ī', "⠈⠤⠊")]
#[case::u_breve('ŭ', "⠈⠬⠥")]
#[case::e_grave('è', "⠘⠡⠑")]
#[case::e_acute('é', "⠘⠌⠑")]
#[case::u_circumflex('û', "⠘⠩⠥")]
#[case::e_diaeresis('ë', "⠘⠒⠑")]
#[case::u_diaeresis('ü', "⠘⠒⠥")]
#[case::i_diaeresis('ï', "⠘⠒⠊")]
#[case::a_ring('å', "⠘⠫⠁")]
#[case::c_cedilla('ç', "⠘⠯⠉")]
#[case::a_tilde('ã', "⠘⠻⠁")]
#[case::o_stroke('ø', "⠈⠡⠕")]
#[case::l_stroke('ł', "⠈⠡⠇")]
#[case::e_acute_upper('É', "⠠⠘⠌⠑")]
#[case::o_diaeresis_upper('Ö', "⠠⠘⠒⠕")]
#[case::u_circumflex_upper('Û', "⠠⠘⠩⠥")]
#[case::ae_ligature('æ', "⠁⠘⠖⠑")]
#[case::ae_ligature_upper('Æ', "⠠⠁⠠⠘⠖⠑")]
#[case::oe_ligature('œ', "⠕⠘⠖⠑")]
#[case::oe_ligature_upper('Œ', "⠠⠕⠠⠘⠖⠑")]
#[case::eszett('ß', "⠨⠮")]
#[case::eszett_upper('ẞ', "⠠⠨⠮")]
#[case::t_comma_below('ț', "⠘⠸⠂⠞")]
#[case::s_comma_below('ș', "⠘⠸⠂⠎")]
#[case::h_stroke('ħ', "⠈⠒⠓")]
#[case::h_stroke_upper('Ħ', "⠠⠈⠒⠓")]
#[case::g_dot_above('ġ', "⠘⠸⠆⠛")]
fn accent_cells_match_indicator_plus_base(#[case] c: char, #[case] expected: &str) {
let want: Vec<u8> = expected.chars().map(decode_unicode).collect();
assert_eq!(accent_cells(c), Some(want));
}
#[test]
fn plain_letter_is_not_accented() {
assert!(!is_accented('e'));
assert!(!is_modified_letter('e'));
assert!(accent_cells('e').is_none());
}
#[test]
fn ligature_and_eszett_are_accented_but_not_modified_letters() {
assert!(is_accented('æ'));
assert!(is_accented('ß'));
assert!(!is_modified_letter('æ'));
assert!(!is_modified_letter('ß'));
}
#[test]
fn accent_cells_runtime_ligature_allocates_cells() {
let letter = std::hint::black_box('æ');
assert_eq!(
accent_cells(letter),
Some(vec![
decode_unicode('⠁'),
decode_unicode('⠘'),
decode_unicode('⠖'),
decode_unicode('⠑')
])
);
}
#[test]
fn accent_cells_runtime_eszett_and_upper_ligature_paths() {
assert_eq!(
accent_cells(std::hint::black_box('ß')),
Some(vec![decode_unicode('⠨'), decode_unicode('⠮')])
);
assert_eq!(
accent_cells(std::hint::black_box('Æ')),
Some(vec![
decode_unicode('⠠'),
decode_unicode('⠁'),
decode_unicode('⠠'),
decode_unicode('⠘'),
decode_unicode('⠖'),
decode_unicode('⠑')
])
);
}
}