Skip to main content

typst_library/foundations/
symbol.rs

1use std::collections::BTreeSet;
2use std::fmt::{self, Debug, Display, Formatter};
3use std::sync::Arc;
4
5use codex::ModifierSet;
6use ecow::{EcoString, eco_format};
7use rustc_hash::FxHashMap;
8use serde::{Serialize, Serializer};
9use typst_syntax::{Span, Spanned, is_ident};
10use typst_utils::hash128;
11use unicode_segmentation::UnicodeSegmentation;
12
13use crate::diag::{SourceResult, StrResult, WarningSink, bail, error};
14use crate::foundations::{
15    Array, Content, Func, NativeElement, Packed, PlainText, Repr, cast, elem, func,
16    scope, ty,
17};
18
19/// A Unicode symbol.
20///
21/// Typst defines common symbols so that they can easily be written with
22/// standard keyboards. The symbols are defined in modules, from which they can
23/// be accessed using @reference:scripting:fields[field access notation]:
24///
25/// - General symbols are defined in the @sym[`sym` module] and are accessible
26///   without the `sym.` prefix in math mode.
27/// - Emoji are defined in the @emoji[`emoji` module]
28///
29/// Moreover, you can define custom symbols with this type's constructor
30/// function.
31///
32/// ```example
33/// #sym.arrow.r \
34/// #sym.gt.eq.not \
35/// $gt.eq.not$ \
36/// #emoji.face.halo
37/// ```
38///
39/// Many symbols have different variants, which can be selected by appending the
40/// modifiers with dot notation. The order of the modifiers is not relevant.
41/// Visit the documentation pages of the symbol modules and click on a symbol to
42/// see its available variants.
43///
44/// ```example
45/// $arrow.l$ \
46/// $arrow.r$ \
47/// $arrow.t.quad$
48/// ```
49#[ty(scope, cast)]
50#[derive(Debug, Clone, Eq, PartialEq, Hash)]
51pub struct Symbol(SymbolInner);
52
53/// The internal representation of a [`Symbol`].
54#[derive(Clone, Eq, PartialEq, Hash)]
55enum SymbolInner {
56    /// A native symbol that has no named variant.
57    Single(&'static str),
58    /// A native symbol with multiple named variants.
59    Complex(&'static [Variant<&'static str>]),
60    /// A symbol that has modifiers applied.
61    Modified(Arc<Modified>),
62}
63
64/// A symbol with multiple named variants, where some modifiers may have been
65/// applied. Also used for symbols defined at runtime by the user with no
66/// modifier applied.
67#[derive(Debug, Clone, Eq, PartialEq, Hash)]
68struct Modified {
69    /// The full list of variants.
70    list: List,
71    /// The modifiers that are already applied.
72    modifiers: ModifierSet<EcoString>,
73    /// Whether we already emitted a deprecation warning for the currently
74    /// applied modifiers.
75    deprecated: bool,
76}
77
78/// A symbol variant, consisting of a set of modifiers, the variant's value, and an
79/// optional deprecation message.
80type Variant<S> = (ModifierSet<S>, S, Option<S>);
81
82/// A collection of symbols.
83#[derive(Clone, Eq, PartialEq, Hash)]
84enum List {
85    Static(&'static [Variant<&'static str>]),
86    Runtime(Box<[Variant<EcoString>]>),
87}
88
89impl Symbol {
90    /// Create a new symbol from a single value.
91    pub const fn single(value: &'static str) -> Self {
92        Self(SymbolInner::Single(value))
93    }
94
95    /// Create a symbol with a static variant list.
96    #[track_caller]
97    pub const fn list(list: &'static [Variant<&'static str>]) -> Self {
98        debug_assert!(!list.is_empty());
99        Self(SymbolInner::Complex(list))
100    }
101
102    /// Create a symbol from a runtime char.
103    pub fn runtime_char(c: char) -> Self {
104        Self::runtime(Box::new([(ModifierSet::default(), c.into(), None)]))
105    }
106
107    /// Create a symbol with a runtime variant list.
108    #[track_caller]
109    pub fn runtime(list: Box<[Variant<EcoString>]>) -> Self {
110        debug_assert!(!list.is_empty());
111        Self(SymbolInner::Modified(Arc::new(Modified {
112            list: List::Runtime(list),
113            modifiers: ModifierSet::default(),
114            deprecated: false,
115        })))
116    }
117
118    /// Get the symbol's value.
119    pub fn get(&self) -> &str {
120        match &self.0 {
121            SymbolInner::Single(value) => value,
122            SymbolInner::Complex(_) => ModifierSet::<&'static str>::default()
123                .best_match_in(self.variants().map(|(m, v, _)| (m, v)))
124                .unwrap(),
125            SymbolInner::Modified(arc) => arc
126                .modifiers
127                .best_match_in(self.variants().map(|(m, v, _)| (m, v)))
128                .unwrap(),
129        }
130    }
131
132    /// Try to get the function associated with the symbol, if any.
133    pub fn func(&self) -> StrResult<Func> {
134        let value = self.get();
135        crate::math::accent::get_accent_func(value)
136            .or_else(|| crate::math::get_lr_wrapper_func(value))
137            .ok_or_else(|| eco_format!("symbol {self} is not callable"))
138    }
139
140    /// Apply a modifier to the symbol.
141    pub fn modified(
142        mut self,
143        mut sink: impl WarningSink,
144        modifier: &str,
145    ) -> StrResult<Self> {
146        if let SymbolInner::Complex(list) = self.0 {
147            self.0 = SymbolInner::Modified(Arc::new(Modified {
148                list: List::Static(list),
149                modifiers: ModifierSet::default(),
150                deprecated: false,
151            }));
152        }
153
154        if let SymbolInner::Modified(arc) = &mut self.0 {
155            let modified = Arc::make_mut(arc);
156            modified.modifiers.insert_raw(modifier);
157            if let Some(deprecation) = modified
158                .modifiers
159                .best_match_in(modified.list.variants().map(|(m, _, d)| (m, d)))
160            {
161                // If we already emitted a deprecation warning during a previous
162                // modification of the symbol, do not emit another one.
163                if !modified.deprecated
164                    && let Some(message) = deprecation
165                {
166                    modified.deprecated = true;
167                    sink.emit(message.into());
168                }
169                return Ok(self);
170            }
171        }
172
173        bail!("unknown symbol modifier")
174    }
175
176    /// The characters that are covered by this symbol.
177    pub fn variants(&self) -> impl Iterator<Item = Variant<&str>> {
178        match &self.0 {
179            SymbolInner::Single(value) => Variants::Single(std::iter::once(*value)),
180            SymbolInner::Complex(list) => Variants::Static(list.iter()),
181            SymbolInner::Modified(arc) => arc.list.variants(),
182        }
183    }
184
185    /// Possible modifiers.
186    pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ {
187        let modifiers = match &self.0 {
188            SymbolInner::Modified(arc) => arc.modifiers.as_deref(),
189            _ => ModifierSet::default(),
190        };
191        self.variants()
192            .flat_map(|(m, _, _)| m)
193            .filter(|modifier| !modifier.is_empty() && !modifiers.contains(modifier))
194            .collect::<BTreeSet<_>>()
195            .into_iter()
196    }
197}
198
199#[scope]
200impl Symbol {
201    /// Create a custom symbol with modifiers.
202    ///
203    /// ```example
204    /// #let envelope = symbol(
205    ///   "🖂",
206    ///   ("stamped", "🖃"),
207    ///   ("stamped.pen", "🖆"),
208    ///   ("lightning", "🖄"),
209    ///   ("fly", "🖅"),
210    /// )
211    ///
212    /// #envelope
213    /// #envelope.stamped
214    /// #envelope.stamped.pen
215    /// #envelope.lightning
216    /// #envelope.fly
217    /// ```
218    #[func(constructor)]
219    pub fn construct(
220        span: Span,
221        /// The variants of the symbol.
222        ///
223        /// Can be a just a string consisting of a single character for the
224        /// modifierless variant or an array with two strings specifying the
225        /// modifiers and the symbol. Individual modifiers should be separated
226        /// by dots. When displaying a symbol, Typst selects the first from the
227        /// variants that have all attached modifiers and the minimum number of
228        /// other modifiers.
229        #[variadic]
230        variants: Vec<Spanned<SymbolVariant>>,
231    ) -> SourceResult<Symbol> {
232        if variants.is_empty() {
233            bail!(span, "expected at least one variant");
234        }
235
236        // Maps from canonicalized 128-bit hashes to indices of variants we've
237        // seen before.
238        let mut seen = FxHashMap::<u128, usize>::default();
239
240        // A list of modifiers, cleared & reused in each iteration.
241        let mut modifiers = Vec::new();
242
243        let mut errors = ecow::eco_vec![];
244
245        // Validate the variants.
246        'variants: for (i, &Spanned { ref v, span }) in variants.iter().enumerate() {
247            modifiers.clear();
248
249            if v.1.is_empty() || v.1.graphemes(true).nth(1).is_some() {
250                errors.push(error!(
251                    span, "invalid variant value: {}", v.1.repr();
252                    hint: "variant value must be exactly one grapheme cluster";
253                ));
254            }
255
256            if !v.0.is_empty() {
257                // Collect all modifiers.
258                for modifier in v.0.split('.') {
259                    if !is_ident(modifier) {
260                        errors.push(error!(
261                            span,
262                            "invalid symbol modifier: {}",
263                            modifier.repr(),
264                        ));
265                        continue 'variants;
266                    }
267                    modifiers.push(modifier);
268                }
269            }
270
271            // Canonicalize the modifier order.
272            modifiers.sort();
273
274            // Ensure that there are no duplicate modifiers.
275            if let Some(ms) = modifiers.windows(2).find(|ms| ms[0] == ms[1]) {
276                errors.push(error!(
277                    span, "duplicate modifier within variant: {}", ms[0].repr();
278                    hint: "modifiers are not ordered, so each one may appear only once";
279                ));
280                continue 'variants;
281            }
282
283            // Check whether we had this set of modifiers before.
284            let hash = hash128(&modifiers);
285            if let Some(&i) = seen.get(&hash) {
286                errors.push(if v.0.is_empty() {
287                    error!(span, "duplicate default variant")
288                } else if v.0 == variants[i].v.0 {
289                    error!(span, "duplicate variant: {}", v.0.repr())
290                } else {
291                    error!(
292                        span, "duplicate variant: {}", v.0.repr();
293                        hint: "variants with the same modifiers are identical, \
294                               regardless of their order";
295                    )
296                });
297                continue 'variants;
298            }
299
300            seen.insert(hash, i);
301        }
302        if !errors.is_empty() {
303            return Err(errors);
304        }
305
306        let list = variants
307            .into_iter()
308            .map(|s| (ModifierSet::from_raw_dotted(s.v.0), s.v.1, None))
309            .collect();
310        Ok(Symbol::runtime(list))
311    }
312}
313
314impl Display for Symbol {
315    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
316        f.write_str(self.get())
317    }
318}
319
320impl Debug for SymbolInner {
321    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
322        match self {
323            Self::Single(value) => Debug::fmt(value, f),
324            Self::Complex(list) => list.fmt(f),
325            Self::Modified(lists) => lists.fmt(f),
326        }
327    }
328}
329
330impl Debug for List {
331    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
332        match self {
333            Self::Static(list) => list.fmt(f),
334            Self::Runtime(list) => list.fmt(f),
335        }
336    }
337}
338
339impl Repr for Symbol {
340    fn repr(&self) -> EcoString {
341        match &self.0 {
342            SymbolInner::Single(value) => eco_format!("symbol({})", value.repr()),
343            SymbolInner::Complex(variants) => {
344                eco_format!(
345                    "symbol{}",
346                    repr_variants(variants.iter().copied(), ModifierSet::default())
347                )
348            }
349            SymbolInner::Modified(arc) => {
350                let Modified { list, modifiers, .. } = arc.as_ref();
351                if modifiers.is_empty() {
352                    eco_format!(
353                        "symbol{}",
354                        repr_variants(list.variants(), ModifierSet::default())
355                    )
356                } else {
357                    eco_format!(
358                        "symbol{}",
359                        repr_variants(list.variants(), modifiers.as_deref())
360                    )
361                }
362            }
363        }
364    }
365}
366
367fn repr_variants<'a>(
368    variants: impl Iterator<Item = Variant<&'a str>>,
369    applied_modifiers: ModifierSet<&str>,
370) -> String {
371    crate::foundations::repr::pretty_array_like(
372        &variants
373            .filter(|(modifiers, _, _)| {
374                // Only keep variants that can still be accessed, i.e., variants
375                // that contain all applied modifiers.
376                applied_modifiers.iter().all(|am| modifiers.contains(am))
377            })
378            .map(|(modifiers, value, _)| {
379                let trimmed_modifiers =
380                    modifiers.into_iter().filter(|&m| !applied_modifiers.contains(m));
381                if trimmed_modifiers.clone().all(|m| m.is_empty()) {
382                    value.repr()
383                } else {
384                    let trimmed_modifiers =
385                        trimmed_modifiers.collect::<Vec<_>>().join(".");
386                    eco_format!("({}, {})", trimmed_modifiers.repr(), value.repr())
387                }
388            })
389            .collect::<Vec<_>>(),
390        false,
391    )
392}
393
394impl Serialize for Symbol {
395    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
396    where
397        S: Serializer,
398    {
399        serializer.serialize_str(self.get())
400    }
401}
402
403impl List {
404    /// The characters that are covered by this list.
405    fn variants(&self) -> Variants<'_> {
406        match self {
407            List::Static(list) => Variants::Static(list.iter()),
408            List::Runtime(list) => Variants::Runtime(list.iter()),
409        }
410    }
411}
412
413/// A value that can be cast to a symbol.
414pub struct SymbolVariant(EcoString, EcoString);
415
416cast! {
417    SymbolVariant,
418    s: EcoString => Self(EcoString::new(), s),
419    array: Array => {
420        let mut iter = array.into_iter();
421        match (iter.next(), iter.next(), iter.next()) {
422            (Some(a), Some(b), None) => Self(a.cast()?, b.cast()?),
423            _ => Err("variant array must contain exactly two entries")?,
424        }
425    },
426}
427
428/// Iterator over variants.
429enum Variants<'a> {
430    Single(std::iter::Once<&'static str>),
431    Static(std::slice::Iter<'static, Variant<&'static str>>),
432    Runtime(std::slice::Iter<'a, Variant<EcoString>>),
433}
434
435impl<'a> Iterator for Variants<'a> {
436    type Item = Variant<&'a str>;
437
438    fn next(&mut self) -> Option<Self::Item> {
439        match self {
440            Self::Single(iter) => Some((ModifierSet::default(), iter.next()?, None)),
441            Self::Static(list) => list.next().copied(),
442            Self::Runtime(list) => {
443                list.next().map(|(m, s, d)| (m.as_deref(), s.as_str(), d.as_deref()))
444            }
445        }
446    }
447}
448
449/// A single character.
450#[elem(Repr, PlainText)]
451pub struct SymbolElem {
452    /// The symbol's value.
453    #[required]
454    pub text: EcoString, // This is called `text` for consistency with `TextElem`.
455}
456
457impl SymbolElem {
458    /// Creates a new symbol element and directly packs it into type-erased
459    /// content.
460    pub fn packed(text: impl Into<EcoString>) -> Content {
461        Self::new(text.into()).pack()
462    }
463}
464
465impl PlainText for Packed<SymbolElem> {
466    fn plain_text(&self, text: &mut EcoString) {
467        text.push_str(&self.text);
468    }
469}
470
471impl Repr for SymbolElem {
472    /// Use a custom repr that matches normal content.
473    fn repr(&self) -> EcoString {
474        eco_format!("[{}]", self.text)
475    }
476}