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::{DeprecationSink, SourceResult, StrResult, bail, error};
14use crate::foundations::{
15    Array, Content, Func, NativeElement, NativeFunc, Packed, PlainText, Repr as _, cast,
16    elem, func, 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 [field access notation]($scripting/#fields):
24///
25/// - General symbols are defined in the [`sym` module]($category/symbols/sym)
26///   and are accessible without the `sym.` prefix in math mode.
27/// - Emoji are defined in the [`emoji` module]($category/symbols/emoji)
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(Repr);
52
53/// The internal representation.
54#[derive(Clone, Eq, PartialEq, Hash)]
55enum Repr {
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(Repr::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(Repr::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(Repr::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            Repr::Single(value) => value,
122            Repr::Complex(_) => ModifierSet::<&'static str>::default()
123                .best_match_in(self.variants().map(|(m, v, _)| (m, v)))
124                .unwrap(),
125            Repr::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        match self.get() {
135            "⌈" => Ok(crate::math::ceil::func()),
136            "⌊" => Ok(crate::math::floor::func()),
137            "–" => Ok(crate::math::accent::dash::func()),
138            "⋅" | "\u{0307}" => Ok(crate::math::accent::dot::func()),
139            "¨" => Ok(crate::math::accent::dot_double::func()),
140            "\u{20db}" => Ok(crate::math::accent::dot_triple::func()),
141            "\u{20dc}" => Ok(crate::math::accent::dot_quad::func()),
142            "∼" => Ok(crate::math::accent::tilde::func()),
143            "´" => Ok(crate::math::accent::acute::func()),
144            "˝" => Ok(crate::math::accent::acute_double::func()),
145            "˘" => Ok(crate::math::accent::breve::func()),
146            "ˇ" => Ok(crate::math::accent::caron::func()),
147            "^" => Ok(crate::math::accent::hat::func()),
148            "`" => Ok(crate::math::accent::grave::func()),
149            "¯" => Ok(crate::math::accent::macron::func()),
150            "○" => Ok(crate::math::accent::circle::func()),
151            "→" => Ok(crate::math::accent::arrow::func()),
152            "←" => Ok(crate::math::accent::arrow_l::func()),
153            "↔" => Ok(crate::math::accent::arrow_l_r::func()),
154            "⇀" => Ok(crate::math::accent::harpoon::func()),
155            "↼" => Ok(crate::math::accent::harpoon_lt::func()),
156            _ => bail!("symbol {self} is not callable"),
157        }
158    }
159
160    /// Apply a modifier to the symbol.
161    pub fn modified(
162        mut self,
163        sink: impl DeprecationSink,
164        modifier: &str,
165    ) -> StrResult<Self> {
166        if let Repr::Complex(list) = self.0 {
167            self.0 = Repr::Modified(Arc::new(Modified {
168                list: List::Static(list),
169                modifiers: ModifierSet::default(),
170                deprecated: false,
171            }));
172        }
173
174        if let Repr::Modified(arc) = &mut self.0 {
175            let modified = Arc::make_mut(arc);
176            modified.modifiers.insert_raw(modifier);
177            if let Some(deprecation) = modified
178                .modifiers
179                .best_match_in(modified.list.variants().map(|(m, _, d)| (m, d)))
180            {
181                // If we already emitted a deprecation warning during a previous
182                // modification of the symbol, do not emit another one.
183                if !modified.deprecated
184                    && let Some(message) = deprecation
185                {
186                    modified.deprecated = true;
187                    sink.emit(message, None);
188                }
189                return Ok(self);
190            }
191        }
192
193        bail!("unknown symbol modifier")
194    }
195
196    /// The characters that are covered by this symbol.
197    pub fn variants(&self) -> impl Iterator<Item = Variant<&str>> {
198        match &self.0 {
199            Repr::Single(value) => Variants::Single(std::iter::once(*value)),
200            Repr::Complex(list) => Variants::Static(list.iter()),
201            Repr::Modified(arc) => arc.list.variants(),
202        }
203    }
204
205    /// Possible modifiers.
206    pub fn modifiers(&self) -> impl Iterator<Item = &str> + '_ {
207        let modifiers = match &self.0 {
208            Repr::Modified(arc) => arc.modifiers.as_deref(),
209            _ => ModifierSet::default(),
210        };
211        self.variants()
212            .flat_map(|(m, _, _)| m)
213            .filter(|modifier| !modifier.is_empty() && !modifiers.contains(modifier))
214            .collect::<BTreeSet<_>>()
215            .into_iter()
216    }
217}
218
219#[scope]
220impl Symbol {
221    /// Create a custom symbol with modifiers.
222    ///
223    /// ```example
224    /// #let envelope = symbol(
225    ///   "🖂",
226    ///   ("stamped", "🖃"),
227    ///   ("stamped.pen", "🖆"),
228    ///   ("lightning", "🖄"),
229    ///   ("fly", "🖅"),
230    /// )
231    ///
232    /// #envelope
233    /// #envelope.stamped
234    /// #envelope.stamped.pen
235    /// #envelope.lightning
236    /// #envelope.fly
237    /// ```
238    #[func(constructor)]
239    pub fn construct(
240        span: Span,
241        /// The variants of the symbol.
242        ///
243        /// Can be a just a string consisting of a single character for the
244        /// modifierless variant or an array with two strings specifying the modifiers
245        /// and the symbol. Individual modifiers should be separated by dots. When
246        /// displaying a symbol, Typst selects the first from the variants that have
247        /// all attached modifiers and the minimum number of other modifiers.
248        #[variadic]
249        variants: Vec<Spanned<SymbolVariant>>,
250    ) -> SourceResult<Symbol> {
251        if variants.is_empty() {
252            bail!(span, "expected at least one variant");
253        }
254
255        // Maps from canonicalized 128-bit hashes to indices of variants we've
256        // seen before.
257        let mut seen = FxHashMap::<u128, usize>::default();
258
259        // A list of modifiers, cleared & reused in each iteration.
260        let mut modifiers = Vec::new();
261
262        let mut errors = ecow::eco_vec![];
263
264        // Validate the variants.
265        'variants: for (i, &Spanned { ref v, span }) in variants.iter().enumerate() {
266            modifiers.clear();
267
268            if v.1.is_empty() || v.1.graphemes(true).nth(1).is_some() {
269                errors.push(error!(
270                    span, "invalid variant value: {}", v.1.repr();
271                    hint: "variant value must be exactly one grapheme cluster"
272                ));
273            }
274
275            if !v.0.is_empty() {
276                // Collect all modifiers.
277                for modifier in v.0.split('.') {
278                    if !is_ident(modifier) {
279                        errors.push(error!(
280                            span,
281                            "invalid symbol modifier: {}",
282                            modifier.repr()
283                        ));
284                        continue 'variants;
285                    }
286                    modifiers.push(modifier);
287                }
288            }
289
290            // Canonicalize the modifier order.
291            modifiers.sort();
292
293            // Ensure that there are no duplicate modifiers.
294            if let Some(ms) = modifiers.windows(2).find(|ms| ms[0] == ms[1]) {
295                errors.push(error!(
296                    span, "duplicate modifier within variant: {}", ms[0].repr();
297                    hint: "modifiers are not ordered, so each one may appear only once"
298                ));
299                continue 'variants;
300            }
301
302            // Check whether we had this set of modifiers before.
303            let hash = hash128(&modifiers);
304            if let Some(&i) = seen.get(&hash) {
305                errors.push(if v.0.is_empty() {
306                    error!(span, "duplicate default variant")
307                } else if v.0 == variants[i].v.0 {
308                    error!(span, "duplicate variant: {}", v.0.repr())
309                } else {
310                    error!(
311                        span, "duplicate variant: {}", v.0.repr();
312                        hint: "variants with the same modifiers are identical, regardless of their order"
313                    )
314                });
315                continue 'variants;
316            }
317
318            seen.insert(hash, i);
319        }
320        if !errors.is_empty() {
321            return Err(errors);
322        }
323
324        let list = variants
325            .into_iter()
326            .map(|s| (ModifierSet::from_raw_dotted(s.v.0), s.v.1, None))
327            .collect();
328        Ok(Symbol::runtime(list))
329    }
330}
331
332impl Display for Symbol {
333    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
334        f.write_str(self.get())
335    }
336}
337
338impl Debug for Repr {
339    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
340        match self {
341            Self::Single(value) => Debug::fmt(value, f),
342            Self::Complex(list) => list.fmt(f),
343            Self::Modified(lists) => lists.fmt(f),
344        }
345    }
346}
347
348impl Debug for List {
349    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
350        match self {
351            Self::Static(list) => list.fmt(f),
352            Self::Runtime(list) => list.fmt(f),
353        }
354    }
355}
356
357impl crate::foundations::Repr for Symbol {
358    fn repr(&self) -> EcoString {
359        match &self.0 {
360            Repr::Single(value) => eco_format!("symbol({})", value.repr()),
361            Repr::Complex(variants) => {
362                eco_format!(
363                    "symbol{}",
364                    repr_variants(variants.iter().copied(), ModifierSet::default())
365                )
366            }
367            Repr::Modified(arc) => {
368                let Modified { list, modifiers, .. } = arc.as_ref();
369                if modifiers.is_empty() {
370                    eco_format!(
371                        "symbol{}",
372                        repr_variants(list.variants(), ModifierSet::default())
373                    )
374                } else {
375                    eco_format!(
376                        "symbol{}",
377                        repr_variants(list.variants(), modifiers.as_deref())
378                    )
379                }
380            }
381        }
382    }
383}
384
385fn repr_variants<'a>(
386    variants: impl Iterator<Item = Variant<&'a str>>,
387    applied_modifiers: ModifierSet<&str>,
388) -> String {
389    crate::foundations::repr::pretty_array_like(
390        &variants
391            .filter(|(modifiers, _, _)| {
392                // Only keep variants that can still be accessed, i.e., variants
393                // that contain all applied modifiers.
394                applied_modifiers.iter().all(|am| modifiers.contains(am))
395            })
396            .map(|(modifiers, value, _)| {
397                let trimmed_modifiers =
398                    modifiers.into_iter().filter(|&m| !applied_modifiers.contains(m));
399                if trimmed_modifiers.clone().all(|m| m.is_empty()) {
400                    value.repr()
401                } else {
402                    let trimmed_modifiers =
403                        trimmed_modifiers.collect::<Vec<_>>().join(".");
404                    eco_format!("({}, {})", trimmed_modifiers.repr(), value.repr())
405                }
406            })
407            .collect::<Vec<_>>(),
408        false,
409    )
410}
411
412impl Serialize for Symbol {
413    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
414    where
415        S: Serializer,
416    {
417        serializer.serialize_str(self.get())
418    }
419}
420
421impl List {
422    /// The characters that are covered by this list.
423    fn variants(&self) -> Variants<'_> {
424        match self {
425            List::Static(list) => Variants::Static(list.iter()),
426            List::Runtime(list) => Variants::Runtime(list.iter()),
427        }
428    }
429}
430
431/// A value that can be cast to a symbol.
432pub struct SymbolVariant(EcoString, EcoString);
433
434cast! {
435    SymbolVariant,
436    s: EcoString => Self(EcoString::new(), s),
437    array: Array => {
438        let mut iter = array.into_iter();
439        match (iter.next(), iter.next(), iter.next()) {
440            (Some(a), Some(b), None) => Self(a.cast()?, b.cast()?),
441            _ => Err("variant array must contain exactly two entries")?,
442        }
443    },
444}
445
446/// Iterator over variants.
447enum Variants<'a> {
448    Single(std::iter::Once<&'static str>),
449    Static(std::slice::Iter<'static, Variant<&'static str>>),
450    Runtime(std::slice::Iter<'a, Variant<EcoString>>),
451}
452
453impl<'a> Iterator for Variants<'a> {
454    type Item = Variant<&'a str>;
455
456    fn next(&mut self) -> Option<Self::Item> {
457        match self {
458            Self::Single(iter) => Some((ModifierSet::default(), iter.next()?, None)),
459            Self::Static(list) => list.next().copied(),
460            Self::Runtime(list) => {
461                list.next().map(|(m, s, d)| (m.as_deref(), s.as_str(), d.as_deref()))
462            }
463        }
464    }
465}
466
467/// A single character.
468#[elem(Repr, PlainText)]
469pub struct SymbolElem {
470    /// The symbol's value.
471    #[required]
472    pub text: EcoString, // This is called `text` for consistency with `TextElem`.
473}
474
475impl SymbolElem {
476    /// Create a new packed symbol element.
477    pub fn packed(text: impl Into<EcoString>) -> Content {
478        Self::new(text.into()).pack()
479    }
480}
481
482impl PlainText for Packed<SymbolElem> {
483    fn plain_text(&self, text: &mut EcoString) {
484        text.push_str(&self.text);
485    }
486}
487
488impl crate::foundations::Repr for SymbolElem {
489    /// Use a custom repr that matches normal content.
490    fn repr(&self) -> EcoString {
491        eco_format!("[{}]", self.text)
492    }
493}