typst_library/math/
accent.rs

1use crate::diag::bail;
2use crate::foundations::{cast, elem, func, Content, NativeElement, SymbolElem};
3use crate::layout::{Length, Rel};
4use crate::math::Mathy;
5
6/// Attaches an accent to a base.
7///
8/// # Example
9/// ```example
10/// $grave(a) = accent(a, `)$ \
11/// $arrow(a) = accent(a, arrow)$ \
12/// $tilde(a) = accent(a, \u{0303})$
13/// ```
14#[elem(Mathy)]
15pub struct AccentElem {
16    /// The base to which the accent is applied.
17    /// May consist of multiple letters.
18    ///
19    /// ```example
20    /// $arrow(A B C)$
21    /// ```
22    #[required]
23    pub base: Content,
24
25    /// The accent to apply to the base.
26    ///
27    /// Supported accents include:
28    ///
29    /// | Accent        | Name            | Codepoint |
30    /// | ------------- | --------------- | --------- |
31    /// | Grave         | `grave`         | <code>&DiacriticalGrave;</code> |
32    /// | Acute         | `acute`         | `´`       |
33    /// | Circumflex    | `hat`           | `^`       |
34    /// | Tilde         | `tilde`         | `~`       |
35    /// | Macron        | `macron`        | `¯`       |
36    /// | Dash          | `dash`          | `‾`       |
37    /// | Breve         | `breve`         | `˘`       |
38    /// | Dot           | `dot`           | `.`       |
39    /// | Double dot, Diaeresis | `dot.double`, `diaer` | `¨` |
40    /// | Triple dot    | `dot.triple`    | <code>&tdot;</code> |
41    /// | Quadruple dot | `dot.quad`      | <code>&DotDot;</code> |
42    /// | Circle        | `circle`        | `∘`       |
43    /// | Double acute  | `acute.double`  | `˝`       |
44    /// | Caron         | `caron`         | `ˇ`       |
45    /// | Right arrow   | `arrow`, `->`   | `→`       |
46    /// | Left arrow    | `arrow.l`, `<-` | `←`       |
47    /// | Left/Right arrow | `arrow.l.r`  | `↔`       |
48    /// | Right harpoon | `harpoon`       | `⇀`       |
49    /// | Left harpoon  | `harpoon.lt`    | `↼`       |
50    #[required]
51    pub accent: Accent,
52
53    /// The size of the accent, relative to the width of the base.
54    #[resolve]
55    #[default(Rel::one())]
56    pub size: Rel<Length>,
57}
58
59/// An accent character.
60#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
61pub struct Accent(pub char);
62
63impl Accent {
64    /// Normalize a character into an accent.
65    pub fn new(c: char) -> Self {
66        Self(Self::combine(c).unwrap_or(c))
67    }
68}
69
70/// This macro generates accent-related functions.
71///
72/// ```ignore
73/// accents! {
74///     '\u{0300}' | '`' => grave,
75/// //  ^^^^^^^^^    ^^^    ^^^^^
76/// //  |            |      |
77/// //  |            |      +-- The name of the function.
78/// //  |            +--------- The alternative characters that represent the accent.
79/// //  +---------------------- The primary character that represents the accent.
80/// }
81/// ```
82///
83/// When combined with the `Accent::combine` function, accent characters can be normalized
84/// to the primary character.
85macro_rules! accents {
86    ($($primary:literal $(| $alt:literal)* => $name:ident),* $(,)?) => {
87        impl Accent {
88            /// Normalize an accent to a combining one.
89            pub fn combine(c: char) -> Option<char> {
90                Some(match c {
91                    $($primary $(| $alt)* => $primary,)*
92                    _ => return None,
93                })
94            }
95        }
96
97        $(
98            /// The accent function for callable symbol definitions.
99            #[func]
100            pub fn $name(
101                /// The base to which the accent is applied.
102                base: Content,
103                /// The size of the accent, relative to the width of the base.
104                #[named]
105                size: Option<Rel<Length>>,
106            ) -> Content {
107                let mut accent = AccentElem::new(base, Accent::new($primary));
108                if let Some(size) = size {
109                    accent = accent.with_size(size);
110                }
111                accent.pack()
112            }
113        )+
114    };
115}
116
117// Keep it synced with the documenting table above.
118accents! {
119    '\u{0300}' | '`' => grave,
120    '\u{0301}' | '´' => acute,
121    '\u{0302}' | '^' | 'ˆ' => hat,
122    '\u{0303}' | '~' | '∼' | '˜' => tilde,
123    '\u{0304}' | '¯' => macron,
124    '\u{0305}' | '-' | '‾' | '−' => dash,
125    '\u{0306}' | '˘' => breve,
126    '\u{0307}' | '.' | '˙' | '⋅' => dot,
127    '\u{0308}' | '¨' => dot_double,
128    '\u{20db}' => dot_triple,
129    '\u{20dc}' => dot_quad,
130    '\u{030a}' | '∘' | '○' => circle,
131    '\u{030b}' | '˝' => acute_double,
132    '\u{030c}' | 'ˇ' => caron,
133    '\u{20d6}' | '←' => arrow_l,
134    '\u{20d7}' | '→' | '⟶' => arrow,
135    '\u{20e1}' | '↔' | '⟷' => arrow_l_r,
136    '\u{20d0}' | '↼' => harpoon_lt,
137    '\u{20d1}' | '⇀' => harpoon,
138}
139
140cast! {
141    Accent,
142    self => self.0.into_value(),
143    v: char => Self::new(v),
144    v: Content => match v.to_packed::<SymbolElem>() {
145        Some(elem) => Self::new(elem.text),
146        None => bail!("expected a symbol"),
147    },
148}