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>`</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>⃛</code> |
41 /// | Quadruple dot | `dot.quad` | <code>⃜</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}