Skip to main content

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}