typst_library/math/attach.rs
1use typst_utils::default_math_class;
2use unicode_math_class::MathClass;
3
4use crate::foundations::{Content, StyleChain, elem};
5use crate::layout::{Length, Rel};
6use crate::math::{EquationElem, MathSize, Mathy};
7
8/// A base with optional attachments.
9///
10/// ```example
11/// $ attach(
12/// Pi, t: alpha, b: beta,
13/// tl: 1, tr: 2+3, bl: 4+5, br: 6,
14/// ) $
15/// ```
16///
17/// If you want to add accents (hats, tildes, arrows, etc.) instead of scripts
18/// or corner attachments, use the @math.accent[`accent`] function instead.
19#[elem(Mathy)]
20pub struct AttachElem {
21 /// The base to which things are attached.
22 #[required]
23 pub base: Content,
24
25 /// The top attachment, smartly positioned at top-right or above the base.
26 ///
27 /// You can wrap the base in `{limits()}` or `{scripts()}` to override the
28 /// smart positioning.
29 pub t: Option<Content>,
30
31 /// The bottom attachment, smartly positioned at the bottom-right or below
32 /// the base.
33 ///
34 /// You can wrap the base in `{limits()}` or `{scripts()}` to override the
35 /// smart positioning.
36 pub b: Option<Content>,
37
38 /// The top-left attachment (before the base).
39 pub tl: Option<Content>,
40
41 /// The bottom-left attachment (before base).
42 pub bl: Option<Content>,
43
44 /// The top-right attachment (after the base).
45 pub tr: Option<Content>,
46
47 /// The bottom-right attachment (after the base).
48 pub br: Option<Content>,
49}
50
51/// Grouped primes.
52///
53/// ```example
54/// $ a'''_b = a^'''_b $
55/// ```
56///
57/// = Syntax <syntax>
58/// This function has dedicated syntax: use apostrophes instead of primes. They
59/// will automatically attach to the previous element, moving superscripts to
60/// the next level.
61#[elem(Mathy)]
62pub struct PrimesElem {
63 /// The number of grouped primes.
64 #[required]
65 pub count: usize,
66}
67
68/// Forces a base to display attachments as scripts.
69///
70/// ```example
71/// $ scripts(sum)_1^2 != sum_1^2 $
72/// ```
73#[elem(Mathy)]
74pub struct ScriptsElem {
75 /// The base to attach the scripts to.
76 #[required]
77 pub body: Content,
78}
79
80/// Forces a base to display attachments as limits.
81///
82/// ```example
83/// $ limits(A)_1^2 != A_1^2 $
84/// ```
85#[elem(Mathy)]
86pub struct LimitsElem {
87 /// The base to attach the limits to.
88 #[required]
89 pub body: Content,
90
91 /// Whether to also force limits in inline equations.
92 ///
93 /// When applying limits globally (e.g., through a show rule), it is
94 /// typically a good idea to disable this.
95 #[default(true)]
96 pub inline: bool,
97}
98
99/// Stretches a glyph.
100///
101/// This function can also be used to automatically stretch the base of an
102/// attachment, so that it fits the top and bottom attachments.
103///
104/// Note that only some glyphs can be stretched, and which ones can depend on
105/// the math font being used. However, most math fonts are the same in this
106/// regard.
107///
108/// ```example
109/// $ H stretch(=)^"define" U + p V $
110/// $ f : X stretch(->>, size: #150%)_"surjective" Y $
111/// $ x stretch(harpoons.ltrb, size: #3em) y
112/// stretch(\[, size: #150%) z $
113/// ```
114#[elem(Mathy)]
115pub struct StretchElem {
116 /// The glyph to stretch.
117 #[required]
118 pub body: Content,
119
120 /// The size to stretch to, relative to the maximum size of the glyph and
121 /// its attachments.
122 ///
123 /// Note that the resulting glyph may not have the exact desired size. A
124 /// stretched glyph may be either a pre-defined glyph, or a glyph assembled
125 /// from building blocks provided by the font. The possible sizes may not
126 /// cover the entire span. In the example below, when the `size` parameter
127 /// is increased from `{101%}` to `{200%}`, the selected glyph remains the
128 /// same, so the actual size does not change.
129 ///
130 /// #example(
131 /// title: "Size of ∫ growing discontinuously",
132 /// ```
133 /// >>> #set align(center)
134 /// #for size in (
135 /// 100%, // short
136 /// 101%, 200%, // tall
137 /// 201%, 300%, 400%, 500%, 600%, // taller
138 /// ) {
139 /// $stretch(integral, size: #size)$
140 /// }
141 /// ```
142 /// )
143 #[default(Rel::one())]
144 pub size: Rel<Length>,
145}
146
147/// Describes in which situation a frame should use limits for attachments.
148#[derive(Debug, Copy, Clone)]
149pub enum Limits {
150 /// Always scripts.
151 Never,
152 /// Display limits only in `display` math.
153 Display,
154 /// Always limits.
155 Always,
156}
157
158impl Limits {
159 /// The default limit configuration if the given character is the base.
160 pub fn for_char(c: char) -> Self {
161 Self::for_char_with_class(c, default_math_class(c))
162 }
163
164 /// The default limit configuration for a character with a known default class.
165 pub fn for_char_with_class(c: char, class: Option<MathClass>) -> Self {
166 match class {
167 Some(MathClass::Large) => {
168 if is_integral_char(c) {
169 Limits::Never
170 } else {
171 Limits::Display
172 }
173 }
174 Some(MathClass::Relation) => Limits::Always,
175 _ => Limits::Never,
176 }
177 }
178
179 /// The default limit configuration for a math class.
180 pub fn for_class(class: MathClass) -> Self {
181 match class {
182 MathClass::Large => Self::Display,
183 MathClass::Relation => Self::Always,
184 _ => Self::Never,
185 }
186 }
187
188 /// Whether limits should be displayed in this context.
189 pub fn active(&self, styles: StyleChain) -> bool {
190 match self {
191 Self::Always => true,
192 Self::Display => styles.get(EquationElem::size) == MathSize::Display,
193 Self::Never => false,
194 }
195 }
196}
197
198/// Determines if the character is one of a variety of integral signs.
199fn is_integral_char(c: char) -> bool {
200 ('∫'..='∳').contains(&c) || ('⨋'..='⨜').contains(&c)
201}