diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-01 16:30:58 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-01 16:33:28 +0100 |
| commit | 6ab7760822ccd24b4ef126d4737d41f1be15fe19 (patch) | |
| tree | 49905f91d292ceefe4f9878ead43f117c4b1fec0 /src/eval/symbol.rs | |
| parent | ab841188e3d2687ee8f436336e6fde337985a83e (diff) | |
Split up `model` module
Diffstat (limited to 'src/eval/symbol.rs')
| -rw-r--r-- | src/eval/symbol.rs | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/src/eval/symbol.rs b/src/eval/symbol.rs new file mode 100644 index 00000000..73c41067 --- /dev/null +++ b/src/eval/symbol.rs @@ -0,0 +1,189 @@ +use std::cmp::Reverse; +use std::collections::BTreeSet; +use std::fmt::{self, Debug, Display, Formatter, Write}; + +use ecow::{EcoString, EcoVec}; + +use crate::diag::StrResult; + +#[doc(inline)] +pub use typst_macros::symbols; + +/// A symbol. +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct Symbol { + repr: Repr, + modifiers: EcoString, +} + +/// A collection of symbols. +#[derive(Clone, Eq, PartialEq, Hash)] +enum Repr { + Single(char), + Static(&'static [(&'static str, char)]), + Runtime(EcoVec<(EcoString, char)>), +} + +impl Symbol { + /// Create a new symbol from a single character. + pub const fn new(c: char) -> Self { + Self { repr: Repr::Single(c), modifiers: EcoString::new() } + } + + /// Create a symbol with a static variant list. + #[track_caller] + pub const fn list(list: &'static [(&'static str, char)]) -> Self { + debug_assert!(!list.is_empty()); + Self { + repr: Repr::Static(list), + modifiers: EcoString::new(), + } + } + + /// Create a symbol with a runtime variant list. + #[track_caller] + pub fn runtime(list: EcoVec<(EcoString, char)>) -> Self { + debug_assert!(!list.is_empty()); + Self { + repr: Repr::Runtime(list), + modifiers: EcoString::new(), + } + } + + /// Get the symbol's text. + pub fn get(&self) -> char { + match self.repr { + Repr::Single(c) => c, + _ => find(self.variants(), &self.modifiers).unwrap(), + } + } + + /// Apply a modifier to the symbol. + pub fn modified(mut self, modifier: &str) -> StrResult<Self> { + if !self.modifiers.is_empty() { + self.modifiers.push('.'); + } + self.modifiers.push_str(modifier); + if find(self.variants(), &self.modifiers).is_none() { + Err("unknown modifier")? + } + Ok(self) + } + + /// The characters that are covered by this symbol. + pub fn variants(&self) -> impl Iterator<Item = (&str, char)> { + match &self.repr { + Repr::Single(c) => Variants::Single(Some(*c).into_iter()), + Repr::Static(list) => Variants::Static(list.iter()), + Repr::Runtime(list) => Variants::Runtime(list.iter()), + } + } + + /// Possible modifiers. + pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ { + let mut set = BTreeSet::new(); + for modifier in self.variants().flat_map(|(name, _)| name.split('.')) { + if !modifier.is_empty() && !contained(&self.modifiers, modifier) { + set.insert(modifier); + } + } + set.into_iter() + } +} + +impl Debug for Symbol { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_char(self.get()) + } +} + +impl Display for Symbol { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_char(self.get()) + } +} + +/// Iterator over variants. +enum Variants<'a> { + Single(std::option::IntoIter<char>), + Static(std::slice::Iter<'static, (&'static str, char)>), + Runtime(std::slice::Iter<'a, (EcoString, char)>), +} + +impl<'a> Iterator for Variants<'a> { + type Item = (&'a str, char); + + fn next(&mut self) -> Option<Self::Item> { + match self { + Self::Single(iter) => Some(("", iter.next()?)), + Self::Static(list) => list.next().copied(), + Self::Runtime(list) => list.next().map(|(s, c)| (s.as_str(), *c)), + } + } +} + +/// Find the best symbol from the list. +fn find<'a>( + variants: impl Iterator<Item = (&'a str, char)>, + modifiers: &str, +) -> Option<char> { + let mut best = None; + let mut best_score = None; + + // Find the best table entry with this name. + 'outer: for candidate in variants { + for modifier in parts(modifiers) { + if !contained(candidate.0, modifier) { + continue 'outer; + } + } + + let mut matching = 0; + let mut total = 0; + for modifier in parts(candidate.0) { + if contained(modifiers, modifier) { + matching += 1; + } + total += 1; + } + + let score = (matching, Reverse(total)); + if best_score.map_or(true, |b| score > b) { + best = Some(candidate.1); + best_score = Some(score); + } + } + + best +} + +/// Split a modifier list into its parts. +fn parts(modifiers: &str) -> impl Iterator<Item = &str> { + modifiers.split('.').filter(|s| !s.is_empty()) +} + +/// Whether the modifier string contains the modifier `m`. +fn contained(modifiers: &str, m: &str) -> bool { + parts(modifiers).any(|part| part == m) +} + +/// Normalize an accent to a combining one. +pub fn combining_accent(c: char) -> Option<char> { + Some(match c { + '\u{0300}' | '`' => '\u{0300}', + '\u{0301}' | '´' => '\u{0301}', + '\u{0302}' | '^' | 'ˆ' => '\u{0302}', + '\u{0303}' | '~' | '∼' | '˜' => '\u{0303}', + '\u{0304}' | '¯' => '\u{0304}', + '\u{0305}' | '-' | '‾' | '−' => '\u{0305}', + '\u{0306}' | '˘' => '\u{0306}', + '\u{0307}' | '.' | '˙' | '⋅' => '\u{0307}', + '\u{0308}' | '¨' => '\u{0308}', + '\u{030a}' | '∘' | '○' => '\u{030a}', + '\u{030b}' | '˝' => '\u{030b}', + '\u{030c}' | 'ˇ' => '\u{030c}', + '\u{20d6}' | '←' => '\u{20d6}', + '\u{20d7}' | '→' | '⟶' => '\u{20d7}', + _ => return None, + }) +} |
