#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SymbolFont {
Wingdings,
Wingdings2,
Wingdings3,
Webdings,
}
const UNKNOWN_SLOT: char = '\u{2022}';
impl SymbolFont {
pub fn named(typeface: &str) -> Option<Self> {
match typeface.trim().to_ascii_lowercase().as_str() {
"wingdings" => Some(Self::Wingdings),
"wingdings 2" => Some(Self::Wingdings2),
"wingdings 3" => Some(Self::Wingdings3),
"webdings" => Some(Self::Webdings),
_ => None,
}
}
fn slots(self) -> &'static [(u8, char)] {
match self {
Self::Wingdings => &WINGDINGS,
Self::Wingdings2 => &WINGDINGS_2,
Self::Wingdings3 => &WINGDINGS_3,
Self::Webdings => &WEBDINGS,
}
}
pub fn substitute(self, character: char) -> Option<char> {
symbol_slot(character)?;
Some(self.shape(character).unwrap_or(UNKNOWN_SLOT))
}
pub fn shape(self, character: char) -> Option<char> {
let slot = symbol_slot(character)?;
self.slots()
.iter()
.find(|(position, _)| *position == slot)
.map(|(_, replacement)| *replacement)
}
}
fn symbol_slot(character: char) -> Option<u8> {
match character as u32 {
slot @ 0x21..=0xFF => Some(slot as u8),
slot @ 0xF021..=0xF0FF => Some((slot - 0xF000) as u8),
_ => None,
}
}
const WINGDINGS: [(u8, char); 20] = [
(0x6C, '●'), (0x6D, '○'), (0x6E, '■'), (0x6F, '□'), (0x70, '□'), (0x71, '□'), (0x72, '□'), (0x73, '♦'), (0x74, '♦'), (0x75, '♦'), (0x77, '♦'), (0x9E, '•'), (0x9F, '•'), (0xA0, '▪'), (0xA6, '○'), (0xA7, '▪'), (0xA8, '□'), (0xD7, '◄'), (0xD8, '►'), (0xD9, '▲'), ];
const WINGDINGS_2: [(u8, char); 21] = [
(0x95, '•'), (0x96, '•'), (0x97, '●'), (0x98, '●'), (0x9F, '▪'), (0xA0, '▪'), (0xA1, '■'), (0xA2, '■'), (0xA3, '□'), (0xA4, '□'), (0xA5, '□'), (0xA6, '□'), (0xAB, '♦'), (0xAC, '♦'), (0xAD, '♦'), (0xAE, '♦'), (0xB4, '♦'), (0xB5, '♦'), (0xB6, '♦'), (0xB7, '♦'), (0xB8, '◊'), ];
const WINGDINGS_3: [(u8, char); 19] = [
(0x70, '▲'), (0x71, '▼'), (0x72, '▲'), (0x73, '▼'), (0x74, '◄'), (0x75, '►'), (0x76, '◄'), (0x77, '►'), (0x7C, '◄'), (0x7D, '►'), (0x7E, '▲'), (0x80, '▼'), (0x81, '▲'), (0x82, '▼'), (0x83, '◄'), (0x84, '►'), (0x85, '◄'), (0x86, '►'), (0x87, '▲'), ];
const WEBDINGS: [(u8, char); 3] = [
(0x63, '□'), (0x67, '■'), (0x6E, '●'), ];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_symbol_face_is_matched_by_name_only() {
assert_eq!(SymbolFont::named("Wingdings"), Some(SymbolFont::Wingdings));
assert_eq!(
SymbolFont::named(" wingdings 3 "),
Some(SymbolFont::Wingdings3)
);
assert_eq!(SymbolFont::named("Webdings"), Some(SymbolFont::Webdings));
assert_eq!(SymbolFont::named("Arial"), None);
assert_eq!(SymbolFont::named("Wingdings Pro"), None);
}
#[test]
fn a_slot_resolves_from_either_its_byte_or_its_private_use_form() {
assert_eq!(SymbolFont::Wingdings.substitute('§'), Some('▪'));
assert_eq!(SymbolFont::Wingdings.substitute('\u{f0a7}'), Some('▪'));
assert_eq!(SymbolFont::Wingdings.substitute('\u{f0a0}'), Some('▪'));
assert_eq!(SymbolFont::Wingdings.substitute('l'), Some('●'));
assert_eq!(SymbolFont::Wingdings3.substitute('\u{f075}'), Some('►'));
}
#[test]
fn an_unmapped_slot_still_reads_as_a_bullet_and_a_non_slot_is_left_alone() {
assert_eq!(SymbolFont::Wingdings.substitute('\u{f0fc}'), Some('•'));
assert_eq!(SymbolFont::Wingdings.substitute('\u{f020}'), None);
assert_eq!(SymbolFont::Wingdings.substitute(' '), None);
assert_eq!(SymbolFont::Wingdings.substitute('•'), None);
assert_eq!(SymbolFont::Wingdings.substitute('\u{2013}'), None);
}
}