Skip to main content

retex_engine/
mathparams.rs

1//! LuaTeX's math parameters (`\Umath…`) and how font assignments set them.
2//!
3//! LuaTeX takes every font-dependent value of math typesetting from a
4//! parameter of the current style. Assigning a TFM font to family 2 or 3
5//! (`\textfont2=\tensy`, …) sets the parameters of the styles of that
6//! size from the font dimensions (LuaTeX manual, "Font-based math
7//! parameters"); the assignments obey grouping. Observed details:
8//!
9//! * family 2 needs at least 22 font dimensions, family 3 at least 13;
10//!   other fonts leave the parameters alone;
11//! * `\Umathsubshiftdrop` and `\Umathsupshiftdrop` of a style come from
12//!   the font one size smaller (TeX's `sub_drop(t)`), so `\scriptfont2`
13//!   sets them for the display and text styles and `\scriptscriptfont2`
14//!   for the script and scriptscript styles;
15//! * the skewed fraction gaps are set to zero, the radical degree
16//!   placement to 5/18 and −10/18 quad and 60%;
17//! * `\Umathradicalvgap` is set when family 3 is assigned (with the
18//!   x-height of the family-2 font of the same size in display styles);
19//! * `\Umathspaceafterscript` is set from `\scriptspace` (globally, for all
20//!   styles still undefined) when the first formula is typeset, and never
21//!   again.
22//!
23//! A font with OpenType `MathConstants` sets the parameters of its size
24//! from them instead, whatever the family (manual table; `\Umathquad` is
25//! the font size and the limit kerns are zero).
26
27use crate::Engine;
28use crate::codes::UNDEFINED_MATH_PARAM;
29use crate::eqtb::{Loc, Val};
30use crate::primitives_gen::assign_int as ip;
31
32/// Math parameter numbers (the `chr` codes of the `\Umath…` primitives).
33#[allow(missing_docs)]
34pub mod mp {
35    pub const QUAD: u32 = 0;
36    pub const AXIS: u32 = 1;
37    pub const OPERATOR_SIZE: u32 = 2;
38    pub const OVERBAR_KERN: u32 = 3;
39    pub const OVERBAR_RULE: u32 = 4;
40    pub const OVERBAR_VGAP: u32 = 5;
41    pub const UNDERBAR_KERN: u32 = 6;
42    pub const UNDERBAR_RULE: u32 = 7;
43    pub const UNDERBAR_VGAP: u32 = 8;
44    pub const RADICAL_KERN: u32 = 9;
45    pub const RADICAL_RULE: u32 = 10;
46    pub const RADICAL_VGAP: u32 = 11;
47    pub const RADICAL_DEGREE_BEFORE: u32 = 12;
48    pub const RADICAL_DEGREE_AFTER: u32 = 13;
49    pub const RADICAL_DEGREE_RAISE: u32 = 14;
50    pub const STACK_VGAP: u32 = 15;
51    pub const STACK_NUM_UP: u32 = 16;
52    pub const STACK_DENOM_DOWN: u32 = 17;
53    pub const FRACTION_RULE: u32 = 18;
54    pub const FRACTION_NUM_VGAP: u32 = 19;
55    pub const FRACTION_NUM_UP: u32 = 20;
56    pub const FRACTION_DENOM_VGAP: u32 = 21;
57    pub const FRACTION_DENOM_DOWN: u32 = 22;
58    pub const FRACTION_DEL_SIZE: u32 = 23;
59    pub const SKEWED_FRACTION_HGAP: u32 = 24;
60    pub const SKEWED_FRACTION_VGAP: u32 = 25;
61    pub const LIMIT_ABOVE_VGAP: u32 = 26;
62    pub const LIMIT_ABOVE_BGAP: u32 = 27;
63    pub const LIMIT_ABOVE_KERN: u32 = 28;
64    pub const LIMIT_BELOW_VGAP: u32 = 29;
65    pub const LIMIT_BELOW_BGAP: u32 = 30;
66    pub const LIMIT_BELOW_KERN: u32 = 31;
67    pub const NOLIMIT_SUB_FACTOR: u32 = 32;
68    pub const NOLIMIT_SUP_FACTOR: u32 = 33;
69    pub const UNDER_DELIMITER_VGAP: u32 = 34;
70    pub const UNDER_DELIMITER_BGAP: u32 = 35;
71    pub const OVER_DELIMITER_VGAP: u32 = 36;
72    pub const OVER_DELIMITER_BGAP: u32 = 37;
73    pub const SUB_SHIFT_DROP: u32 = 38;
74    pub const SUP_SHIFT_DROP: u32 = 39;
75    pub const SUB_SHIFT_DOWN: u32 = 40;
76    pub const SUB_SUP_SHIFT_DOWN: u32 = 41;
77    pub const SUB_TOP_MAX: u32 = 42;
78    pub const SUP_SHIFT_UP: u32 = 43;
79    pub const SUP_BOTTOM_MIN: u32 = 44;
80    pub const SUP_SUB_BOTTOM_MAX: u32 = 45;
81    pub const SUB_SUP_VGAP: u32 = 46;
82    pub const SPACE_AFTER_SCRIPT: u32 = 47;
83    pub const CONNECTOR_OVERLAP_MIN: u32 = 48;
84}
85
86/// The styles of a size: text size covers display and text styles.
87fn size_styles(size: u32) -> &'static [u32] {
88    match size {
89        0 => &[0, 1, 2, 3],
90        1 => &[4, 5],
91        _ => &[6, 7],
92    }
93}
94
95impl Engine {
96    /// Defines math parameter `p` for style `s`. Assignments are traced
97    /// only when `\tracingassigns` is 2 or more (LuaTeX manual, "Less
98    /// tracing").
99    pub fn define_math_param(&mut self, global: bool, p: u32, s: u32, v: i32) {
100        let ta = self.eqtb.int(ip::TRACINGASSIGNS);
101        let quiet = ta > 0 && ta < 2;
102        if quiet {
103            self.eqtb.int_par[ip::TRACINGASSIGNS as usize].val = 0;
104        }
105        self.define(global, Loc::MathParam(p * 8 + s), Val::Int(i64::from(v)));
106        if quiet {
107            self.eqtb.int_par[ip::TRACINGASSIGNS as usize].val = ta;
108        }
109    }
110
111    /// Sets the math parameters that follow from assigning font `f` to
112    /// family `fam` in size `size` (0 text, 1 script, 2 scriptscript).
113    pub fn set_font_math_params(&mut self, global: bool, size: u32, fam: u32, f: u32) {
114        if self.set_opentype_math_params(global, size, f) {
115            return;
116        }
117        let params = self.font(f).params.clone();
118        let par = |n: usize| params.get(n).copied().unwrap_or(0);
119        let np = params.len().saturating_sub(1);
120        let styles = size_styles(size);
121        if fam == 2 && np >= 22 {
122            let xh = par(5);
123            let quad = par(6);
124            let deg = |n: i32| retex_arith::xn_over_d(quad, n, 18).map_or(0, |r| r.quotient);
125            for &s in styles {
126                let display = s < 2;
127                let cramped = s % 2 == 1;
128                let set = |e: &mut Engine, p: u32, v: i32| e.define_math_param(global, p, s, v);
129                set(self, mp::QUAD, quad);
130                set(self, mp::AXIS, par(22));
131                set(
132                    self,
133                    mp::FRACTION_DEL_SIZE,
134                    if display { par(20) } else { par(21) },
135                );
136                set(
137                    self,
138                    mp::FRACTION_DENOM_DOWN,
139                    if display { par(11) } else { par(12) },
140                );
141                set(
142                    self,
143                    mp::FRACTION_NUM_UP,
144                    if display { par(8) } else { par(9) },
145                );
146                set(
147                    self,
148                    mp::STACK_NUM_UP,
149                    if display { par(8) } else { par(10) },
150                );
151                set(
152                    self,
153                    mp::STACK_DENOM_DOWN,
154                    if display { par(11) } else { par(12) },
155                );
156                set(self, mp::SKEWED_FRACTION_HGAP, 0);
157                set(self, mp::SKEWED_FRACTION_VGAP, 0);
158                set(self, mp::SUB_SHIFT_DOWN, par(16));
159                set(self, mp::SUB_SUP_SHIFT_DOWN, par(17));
160                set(self, mp::SUB_TOP_MAX, (xh * 4).abs() / 5);
161                set(self, mp::SUP_BOTTOM_MIN, (xh / 4).abs());
162                set(self, mp::SUP_SUB_BOTTOM_MAX, (xh * 4).abs() / 5);
163                let sup = if cramped {
164                    par(15)
165                } else if display {
166                    par(13)
167                } else {
168                    par(14)
169                };
170                set(self, mp::SUP_SHIFT_UP, sup);
171                set(self, mp::RADICAL_DEGREE_BEFORE, deg(5));
172                set(self, mp::RADICAL_DEGREE_AFTER, -deg(10));
173                set(self, mp::RADICAL_DEGREE_RAISE, 60);
174            }
175            // The drops of the styles one size larger.
176            let drop_styles: &[u32] = match size {
177                1 => &[0, 1, 2, 3],
178                2 => &[4, 5, 6, 7],
179                _ => &[],
180            };
181            for &s in drop_styles {
182                self.define_math_param(global, mp::SUB_SHIFT_DROP, s, par(19));
183                self.define_math_param(global, mp::SUP_SHIFT_DROP, s, par(18));
184            }
185        } else if fam == 3 && np >= 13 {
186            let drt = par(8);
187            let f2 = self.fam_fnt(2, size);
188            let xh2 = self.font(f2).params.get(5).copied().unwrap_or(0);
189            for &s in styles {
190                let display = s < 2;
191                let set = |e: &mut Engine, p: u32, v: i32| e.define_math_param(global, p, s, v);
192                set(self, mp::OVERBAR_KERN, drt);
193                set(self, mp::OVERBAR_RULE, drt);
194                set(self, mp::OVERBAR_VGAP, 3 * drt);
195                set(self, mp::UNDERBAR_KERN, drt);
196                set(self, mp::UNDERBAR_RULE, drt);
197                set(self, mp::UNDERBAR_VGAP, 3 * drt);
198                set(self, mp::RADICAL_KERN, drt);
199                set(
200                    self,
201                    mp::RADICAL_VGAP,
202                    if display {
203                        drt + xh2.abs() / 4
204                    } else {
205                        drt + drt.abs() / 4
206                    },
207                );
208                set(
209                    self,
210                    mp::STACK_VGAP,
211                    if display { 7 * drt } else { 3 * drt },
212                );
213                set(self, mp::FRACTION_RULE, drt);
214                set(
215                    self,
216                    mp::FRACTION_NUM_VGAP,
217                    if display { 3 * drt } else { drt },
218                );
219                set(
220                    self,
221                    mp::FRACTION_DENOM_VGAP,
222                    if display { 3 * drt } else { drt },
223                );
224                set(self, mp::LIMIT_ABOVE_VGAP, par(9));
225                set(self, mp::LIMIT_ABOVE_BGAP, par(11));
226                set(self, mp::LIMIT_ABOVE_KERN, par(13));
227                set(self, mp::LIMIT_BELOW_VGAP, par(10));
228                set(self, mp::LIMIT_BELOW_BGAP, par(12));
229                set(self, mp::LIMIT_BELOW_KERN, par(13));
230                set(self, mp::UNDER_DELIMITER_VGAP, par(10));
231                set(self, mp::UNDER_DELIMITER_BGAP, par(12));
232                set(self, mp::OVER_DELIMITER_VGAP, par(9));
233                set(self, mp::OVER_DELIMITER_BGAP, par(11));
234                set(self, mp::SUB_SUP_VGAP, 4 * drt);
235                set(self, mp::CONNECTOR_OVERLAP_MIN, 0);
236            }
237        }
238    }
239
240    /// Sets the math parameters from an OpenType MATH table; returns false
241    /// if font `f` has none.
242    pub fn set_opentype_math_params(&mut self, global: bool, size: u32, f: u32) -> bool {
243        let Some(mc) = self.font(f).math_constants.clone() else {
244            return false;
245        };
246        let font_size = self.font(f).size;
247        // (parameter, constant for D and D', constant for the other styles)
248        // from the LuaTeX manual's "Font-based math parameters"; a
249        // constant the font lacks leaves the parameter alone.
250        const MAP: &[(u32, &str, &str)] = &[
251            (mp::AXIS, "AxisHeight", "AxisHeight"),
252            (mp::OPERATOR_SIZE, "DisplayOperatorMinHeight", ""),
253            (
254                mp::FRACTION_DEL_SIZE,
255                "FractionDelimiterDisplayStyleSize",
256                "FractionDelimiterSize",
257            ),
258            (
259                mp::FRACTION_DENOM_DOWN,
260                "FractionDenominatorDisplayStyleShiftDown",
261                "FractionDenominatorShiftDown",
262            ),
263            (
264                mp::FRACTION_DENOM_VGAP,
265                "FractionDenominatorDisplayStyleGapMin",
266                "FractionDenominatorGapMin",
267            ),
268            (
269                mp::FRACTION_NUM_UP,
270                "FractionNumeratorDisplayStyleShiftUp",
271                "FractionNumeratorShiftUp",
272            ),
273            (
274                mp::FRACTION_NUM_VGAP,
275                "FractionNumeratorDisplayStyleGapMin",
276                "FractionNumeratorGapMin",
277            ),
278            (
279                mp::FRACTION_RULE,
280                "FractionRuleThickness",
281                "FractionRuleThickness",
282            ),
283            (
284                mp::SKEWED_FRACTION_HGAP,
285                "SkewedFractionHorizontalGap",
286                "SkewedFractionHorizontalGap",
287            ),
288            (
289                mp::SKEWED_FRACTION_VGAP,
290                "SkewedFractionVerticalGap",
291                "SkewedFractionVerticalGap",
292            ),
293            (
294                mp::LIMIT_ABOVE_BGAP,
295                "UpperLimitBaselineRiseMin",
296                "UpperLimitBaselineRiseMin",
297            ),
298            (mp::LIMIT_ABOVE_VGAP, "UpperLimitGapMin", "UpperLimitGapMin"),
299            (
300                mp::LIMIT_BELOW_BGAP,
301                "LowerLimitBaselineDropMin",
302                "LowerLimitBaselineDropMin",
303            ),
304            (mp::LIMIT_BELOW_VGAP, "LowerLimitGapMin", "LowerLimitGapMin"),
305            (
306                mp::OVER_DELIMITER_VGAP,
307                "StretchStackGapBelowMin",
308                "StretchStackGapBelowMin",
309            ),
310            (
311                mp::OVER_DELIMITER_BGAP,
312                "StretchStackTopShiftUp",
313                "StretchStackTopShiftUp",
314            ),
315            (
316                mp::UNDER_DELIMITER_VGAP,
317                "StretchStackGapAboveMin",
318                "StretchStackGapAboveMin",
319            ),
320            (
321                mp::UNDER_DELIMITER_BGAP,
322                "StretchStackBottomShiftDown",
323                "StretchStackBottomShiftDown",
324            ),
325            (
326                mp::OVERBAR_KERN,
327                "OverbarExtraAscender",
328                "OverbarExtraAscender",
329            ),
330            (
331                mp::OVERBAR_RULE,
332                "OverbarRuleThickness",
333                "OverbarRuleThickness",
334            ),
335            (mp::OVERBAR_VGAP, "OverbarVerticalGap", "OverbarVerticalGap"),
336            (
337                mp::RADICAL_KERN,
338                "RadicalExtraAscender",
339                "RadicalExtraAscender",
340            ),
341            (
342                mp::RADICAL_RULE,
343                "RadicalRuleThickness",
344                "RadicalRuleThickness",
345            ),
346            (
347                mp::RADICAL_VGAP,
348                "RadicalDisplayStyleVerticalGap",
349                "RadicalVerticalGap",
350            ),
351            (
352                mp::RADICAL_DEGREE_BEFORE,
353                "RadicalKernBeforeDegree",
354                "RadicalKernBeforeDegree",
355            ),
356            (
357                mp::RADICAL_DEGREE_AFTER,
358                "RadicalKernAfterDegree",
359                "RadicalKernAfterDegree",
360            ),
361            (
362                mp::RADICAL_DEGREE_RAISE,
363                "RadicalDegreeBottomRaisePercent",
364                "RadicalDegreeBottomRaisePercent",
365            ),
366            (
367                mp::SPACE_AFTER_SCRIPT,
368                "SpaceAfterScript",
369                "SpaceAfterScript",
370            ),
371            (
372                mp::STACK_DENOM_DOWN,
373                "StackBottomDisplayStyleShiftDown",
374                "StackBottomShiftDown",
375            ),
376            (
377                mp::STACK_NUM_UP,
378                "StackTopDisplayStyleShiftUp",
379                "StackTopShiftUp",
380            ),
381            (mp::STACK_VGAP, "StackDisplayStyleGapMin", "StackGapMin"),
382            (
383                mp::SUB_SHIFT_DOWN,
384                "SubscriptShiftDown",
385                "SubscriptShiftDown",
386            ),
387            (
388                mp::SUB_SHIFT_DROP,
389                "SubscriptBaselineDropMin",
390                "SubscriptBaselineDropMin",
391            ),
392            (
393                mp::SUB_SUP_SHIFT_DOWN,
394                "SubscriptShiftDownWithSuperscript",
395                "SubscriptShiftDownWithSuperscript",
396            ),
397            (mp::SUB_TOP_MAX, "SubscriptTopMax", "SubscriptTopMax"),
398            (
399                mp::SUB_SUP_VGAP,
400                "SubSuperscriptGapMin",
401                "SubSuperscriptGapMin",
402            ),
403            (
404                mp::SUP_BOTTOM_MIN,
405                "SuperscriptBottomMin",
406                "SuperscriptBottomMin",
407            ),
408            (
409                mp::SUP_SHIFT_DROP,
410                "SuperscriptBaselineDropMax",
411                "SuperscriptBaselineDropMax",
412            ),
413            (
414                mp::SUP_SUB_BOTTOM_MAX,
415                "SuperscriptBottomMaxWithSubscript",
416                "SuperscriptBottomMaxWithSubscript",
417            ),
418            (
419                mp::UNDERBAR_KERN,
420                "UnderbarExtraDescender",
421                "UnderbarExtraDescender",
422            ),
423            (
424                mp::UNDERBAR_RULE,
425                "UnderbarRuleThickness",
426                "UnderbarRuleThickness",
427            ),
428            (
429                mp::UNDERBAR_VGAP,
430                "UnderbarVerticalGap",
431                "UnderbarVerticalGap",
432            ),
433            (
434                mp::CONNECTOR_OVERLAP_MIN,
435                "MinConnectorOverlap",
436                "MinConnectorOverlap",
437            ),
438        ];
439        for &s in size_styles(size) {
440            let display = s < 2;
441            let cramped = s % 2 == 1;
442            for &(p, d, o) in MAP {
443                let key = if display { d } else { o };
444                if let Some(&v) = mc.get(key) {
445                    self.define_math_param(global, p, s, v);
446                }
447            }
448            let sup = if cramped {
449                "SuperscriptShiftUpCramped"
450            } else {
451                "SuperscriptShiftUp"
452            };
453            if let Some(&v) = mc.get(sup) {
454                self.define_math_param(global, mp::SUP_SHIFT_UP, s, v);
455            }
456            // Observed: without SubscriptShiftDownWithSuperscript (as in
457            // Latin Modern Math) SubscriptShiftDown is used.
458            if !mc.contains_key("SubscriptShiftDownWithSuperscript")
459                && let Some(&v) = mc.get("SubscriptShiftDown")
460            {
461                self.define_math_param(global, mp::SUB_SUP_SHIFT_DOWN, s, v);
462            }
463            // Not in the MATH table (manual, note 1).
464            self.define_math_param(global, mp::QUAD, s, font_size);
465            self.define_math_param(global, mp::LIMIT_ABOVE_KERN, s, 0);
466            self.define_math_param(global, mp::LIMIT_BELOW_KERN, s, 0);
467        }
468        true
469    }
470
471    /// Initializes the parameters LuaTeX sets when the first formula is
472    /// typeset (`\Umathspaceafterscript` from `\scriptspace`).
473    pub fn init_math_params_once(&mut self) {
474        if self.math_params_initialized {
475            return;
476        }
477        self.math_params_initialized = true;
478        let ss = self
479            .eqtb
480            .dimen(crate::primitives_gen::assign_dimen::SCRIPTSPACE);
481        for s in 0..8 {
482            if self.math_param_value(mp::SPACE_AFTER_SCRIPT, s) == UNDEFINED_MATH_PARAM {
483                let loc = Loc::MathParam(mp::SPACE_AFTER_SCRIPT * 8 + s);
484                self.eqtb
485                    .set(loc, Val::Int(i64::from(ss)), crate::eqtb::LEVEL_ONE);
486            }
487        }
488    }
489}