use std::cmp::Reverse; use std::collections::{BTreeSet, HashMap}; use std::fmt::{self, Debug, Display, Formatter, Write}; use std::sync::Arc; use ecow::{eco_format, EcoString}; use serde::{Serialize, Serializer}; use typst_syntax::{is_ident, Span, Spanned}; use typst_utils::hash128; use crate::diag::{bail, SourceResult, StrResult}; use crate::foundations::{ cast, elem, func, scope, ty, Array, Content, Func, NativeElement, NativeFunc, Packed, PlainText, Repr as _, }; /// A Unicode symbol. /// /// Typst defines common symbols so that they can easily be written with /// standard keyboards. The symbols are defined in modules, from which they can /// be accessed using [field access notation]($scripting/#fields): /// /// - General symbols are defined in the [`sym` module]($category/symbols/sym) /// and are accessible without the `sym.` prefix in math mode. /// - Emoji are defined in the [`emoji` module]($category/symbols/emoji) /// /// Moreover, you can define custom symbols with this type's constructor /// function. /// /// ```example /// #sym.arrow.r \ /// #sym.gt.eq.not \ /// $gt.eq.not$ \ /// #emoji.face.halo /// ``` /// /// Many symbols have different variants, which can be selected by appending the /// modifiers with dot notation. The order of the modifiers is not relevant. /// Visit the documentation pages of the symbol modules and click on a symbol to /// see its available variants. /// /// ```example /// $arrow.l$ \ /// $arrow.r$ \ /// $arrow.t.quad$ /// ``` #[ty(scope, cast)] #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Symbol(Repr); /// The internal representation. #[derive(Clone, Eq, PartialEq, Hash)] enum Repr { /// A native symbol that has no named variant. Single(char), /// A native symbol with multiple named variants. Complex(&'static [(&'static str, char)]), /// A symbol with multiple named variants, where some modifiers may have /// been applied. Also used for symbols defined at runtime by the user with /// no modifier applied. Modified(Arc<(List, EcoString)>), } /// A collection of symbols. #[derive(Clone, Eq, PartialEq, Hash)] enum List { Static(&'static [(&'static str, char)]), Runtime(Box<[(EcoString, char)]>), } impl Symbol { /// Create a new symbol from a single character. pub const fn single(c: char) -> Self { Self(Repr::Single(c)) } /// 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::Complex(list)) } /// Create a symbol with a runtime variant list. #[track_caller] pub fn runtime(list: Box<[(EcoString, char)]>) -> Self { debug_assert!(!list.is_empty()); Self(Repr::Modified(Arc::new((List::Runtime(list), EcoString::new())))) } /// Get the symbol's character. pub fn get(&self) -> char { match &self.0 { Repr::Single(c) => *c, Repr::Complex(_) => find(self.variants(), "").unwrap(), Repr::Modified(arc) => find(self.variants(), &arc.1).unwrap(), } } /// Try to get the function associated with the symbol, if any. pub fn func(&self) -> StrResult { match self.get() { '⌈' => Ok(crate::math::ceil::func()), '⌊' => Ok(crate::math::floor::func()), '–' => Ok(crate::math::accent::dash::func()), '⋅' | '\u{0307}' => Ok(crate::math::accent::dot::func()), '¨' => Ok(crate::math::accent::dot_double::func()), '\u{20db}' => Ok(crate::math::accent::dot_triple::func()), '\u{20dc}' => Ok(crate::math::accent::dot_quad::func()), '∼' => Ok(crate::math::accent::tilde::func()), '´' => Ok(crate::math::accent::acute::func()), '˝' => Ok(crate::math::accent::acute_double::func()), '˘' => Ok(crate::math::accent::breve::func()), 'ˇ' => Ok(crate::math::accent::caron::func()), '^' => Ok(crate::math::accent::hat::func()), '`' => Ok(crate::math::accent::grave::func()), '¯' => Ok(crate::math::accent::macron::func()), '○' => Ok(crate::math::accent::circle::func()), '→' => Ok(crate::math::accent::arrow::func()), '←' => Ok(crate::math::accent::arrow_l::func()), '↔' => Ok(crate::math::accent::arrow_l_r::func()), '⇀' => Ok(crate::math::accent::harpoon::func()), '↼' => Ok(crate::math::accent::harpoon_lt::func()), _ => bail!("symbol {self} is not callable"), } } /// Apply a modifier to the symbol. pub fn modified(mut self, modifier: &str) -> StrResult { if let Repr::Complex(list) = self.0 { self.0 = Repr::Modified(Arc::new((List::Static(list), EcoString::new()))); } if let Repr::Modified(arc) = &mut self.0 { let (list, modifiers) = Arc::make_mut(arc); if !modifiers.is_empty() { modifiers.push('.'); } modifiers.push_str(modifier); if find(list.variants(), modifiers).is_some() { return Ok(self); } } bail!("unknown symbol modifier") } /// The characters that are covered by this symbol. pub fn variants(&self) -> impl Iterator { match &self.0 { Repr::Single(c) => Variants::Single(Some(*c).into_iter()), Repr::Complex(list) => Variants::Static(list.iter()), Repr::Modified(arc) => arc.0.variants(), } } /// Possible modifiers. pub fn modifiers(&self) -> impl Iterator + '_ { let mut set = BTreeSet::new(); let modifiers = match &self.0 { Repr::Modified(arc) => arc.1.as_str(), _ => "", }; for modifier in self.variants().flat_map(|(name, _)| name.split('.')) { if !modifier.is_empty() && !contained(modifiers, modifier) { set.insert(modifier); } } set.into_iter() } } #[scope] impl Symbol { /// Create a custom symbol with modifiers. /// /// ```example /// #let envelope = symbol( /// "🖂", /// ("stamped", "🖃"), /// ("stamped.pen", "🖆"), /// ("lightning", "🖄"), /// ("fly", "🖅"), /// ) /// /// #envelope /// #envelope.stamped /// #envelope.stamped.pen /// #envelope.lightning /// #envelope.fly /// ``` #[func(constructor)] pub fn construct( span: Span, /// The variants of the symbol. /// /// Can be a just a string consisting of a single character for the /// modifierless variant or an array with two strings specifying the modifiers /// and the symbol. Individual modifiers should be separated by dots. When /// displaying a symbol, Typst selects the first from the variants that have /// all attached modifiers and the minimum number of other modifiers. #[variadic] variants: Vec>, ) -> SourceResult { if variants.is_empty() { bail!(span, "expected at least one variant"); } // Maps from canonicalized 128-bit hashes to indices of variants we've // seen before. let mut seen = HashMap::::new(); // A list of modifiers, cleared & reused in each iteration. let mut modifiers = Vec::new(); // Validate the variants. for (i, &Spanned { ref v, span }) in variants.iter().enumerate() { modifiers.clear(); if !v.0.is_empty() { // Collect all modifiers. for modifier in v.0.split('.') { if !is_ident(modifier) { bail!(span, "invalid symbol modifier: {}", modifier.repr()); } modifiers.push(modifier); } } // Canonicalize the modifier order. modifiers.sort(); // Ensure that there are no duplicate modifiers. if let Some(ms) = modifiers.windows(2).find(|ms| ms[0] == ms[1]) { bail!( span, "duplicate modifier within variant: {}", ms[0].repr(); hint: "modifiers are not ordered, so each one may appear only once" ) } // Check whether we had this set of modifiers before. let hash = hash128(&modifiers); if let Some(&i) = seen.get(&hash) { if v.0.is_empty() { bail!(span, "duplicate default variant"); } else if v.0 == variants[i].v.0 { bail!(span, "duplicate variant: {}", v.0.repr()); } else { bail!( span, "duplicate variant: {}", v.0.repr(); hint: "variants with the same modifiers are identical, regardless of their order" ) } } seen.insert(hash, i); } let list = variants.into_iter().map(|s| (s.v.0, s.v.1)).collect(); Ok(Symbol::runtime(list)) } } impl Display for Symbol { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_char(self.get()) } } impl Debug for Repr { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Single(c) => Debug::fmt(c, f), Self::Complex(list) => list.fmt(f), Self::Modified(lists) => lists.fmt(f), } } } impl Debug for List { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Self::Static(list) => list.fmt(f), Self::Runtime(list) => list.fmt(f), } } } impl crate::foundations::Repr for Symbol { fn repr(&self) -> EcoString { match &self.0 { Repr::Single(c) => eco_format!("symbol(\"{}\")", *c), Repr::Complex(variants) => { eco_format!("symbol{}", repr_variants(variants.iter().copied(), "")) } Repr::Modified(arc) => { let (list, modifiers) = arc.as_ref(); if modifiers.is_empty() { eco_format!("symbol{}", repr_variants(list.variants(), "")) } else { eco_format!("symbol{}", repr_variants(list.variants(), modifiers)) } } } } } fn repr_variants<'a>( variants: impl Iterator, applied_modifiers: &str, ) -> String { crate::foundations::repr::pretty_array_like( &variants .filter(|(variant, _)| { // Only keep variants that can still be accessed, i.e., variants // that contain all applied modifiers. parts(applied_modifiers).all(|am| variant.split('.').any(|m| m == am)) }) .map(|(variant, c)| { let trimmed_variant = variant .split('.') .filter(|&m| parts(applied_modifiers).all(|am| m != am)); if trimmed_variant.clone().all(|m| m.is_empty()) { eco_format!("\"{c}\"") } else { let trimmed_modifiers = trimmed_variant.collect::>().join("."); eco_format!("(\"{}\", \"{}\")", trimmed_modifiers, c) } }) .collect::>(), false, ) } impl Serialize for Symbol { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_char(self.get()) } } impl List { /// The characters that are covered by this list. fn variants(&self) -> Variants<'_> { match self { List::Static(list) => Variants::Static(list.iter()), List::Runtime(list) => Variants::Runtime(list.iter()), } } } /// A value that can be cast to a symbol. pub struct SymbolVariant(EcoString, char); cast! { SymbolVariant, c: char => Self(EcoString::new(), c), array: Array => { let mut iter = array.into_iter(); match (iter.next(), iter.next(), iter.next()) { (Some(a), Some(b), None) => Self(a.cast()?, b.cast()?), _ => Err("variant array must contain exactly two entries")?, } }, } /// Iterator over variants. enum Variants<'a> { Single(std::option::IntoIter), 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 { 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, modifiers: &str, ) -> Option { 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 { 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) } /// A single character. #[elem(Repr, PlainText)] pub struct SymbolElem { /// The symbol's character. #[required] pub text: char, // This is called `text` for consistency with `TextElem`. } impl SymbolElem { /// Create a new packed symbol element. pub fn packed(text: impl Into) -> Content { Self::new(text.into()).pack() } } impl PlainText for Packed { fn plain_text(&self, text: &mut EcoString) { text.push(self.text); } } impl crate::foundations::Repr for SymbolElem { /// Use a custom repr that matches normal content. fn repr(&self) -> EcoString { eco_format!("[{}]", self.text) } }