Skip to main content

fmd_math/
metrics.rs

1//! The math-metrics table: TeX's Appendix-G parameter family, synthesized
2//! for the bundled faces.
3//!
4//! **Provenance.** The values are the published TFM fontdimen families of
5//! Computer Modern at 10 pt — cmsy10's σ₅…σ₂₂ and cmex10's ξ₈…ξ₁₃ (The
6//! TeXbook, Appendix G; the parameters Appendix G consumes by name) —
7//! expressed in ems of the design size. The G0-3 spike ratified this
8//! *synthesis method* (franken_manim `docs/g0/G0-3-fmd-math-ratification.md`,
9//! Verdict 1): compile the published family in as em constants and
10//! **validate** against geometry decoded from the bundled faces by
11//! fmd-font. The bundled CM Unicode faces measure within 0.13 % of the
12//! published x-height and match the axis height exactly — they *are* the
13//! Computer Modern the TFM family describes. The validation tests promoted
14//! from the spike live in this crate's suite; per-face recalibration only
15//! ever happens through the same measure-and-validate seam.
16//!
17//! **Scaling rule.** Every parameter is stored in ems of the text size and
18//! is multiplied by the current style's size factor at use — the analogue
19//! of TeX reading fontdimens from the current size's symbol font.
20
21/// The Appendix-G parameter family, in ems of the text-size em.
22#[derive(Clone, Copy, Debug, PartialEq)]
23pub struct MathConstants {
24    /// σ₅ — x-height.
25    pub x_height: f64,
26    /// σ₆ — quad (1 em).
27    pub quad: f64,
28    /// σ₈ — num1: numerator shift, display style.
29    pub num1: f64,
30    /// σ₉ — num2: numerator shift, non-display, with bar.
31    pub num2: f64,
32    /// σ₁₀ — num3: numerator shift, non-display, barless.
33    pub num3: f64,
34    /// σ₁₁ — denom1: denominator shift, display.
35    pub denom1: f64,
36    /// σ₁₂ — denom2: denominator shift, non-display.
37    pub denom2: f64,
38    /// σ₁₃ — sup1: superscript shift, display uncramped.
39    pub sup1: f64,
40    /// σ₁₄ — sup2: superscript shift, non-display uncramped.
41    pub sup2: f64,
42    /// σ₁₅ — sup3: superscript shift, cramped.
43    pub sup3: f64,
44    /// σ₁₆ — sub1: subscript shift without a superscript.
45    pub sub1: f64,
46    /// σ₁₇ — sub2: subscript shift with a superscript.
47    pub sub2: f64,
48    /// σ₁₈ — sup_drop: superscript baseline drop from the base's top.
49    pub sup_drop: f64,
50    /// σ₁₉ — sub_drop: subscript baseline drop from the base's bottom.
51    pub sub_drop: f64,
52    /// σ₂₀ — delim1: delimiter target for display-style generalized
53    /// fractions.
54    pub delim1: f64,
55    /// σ₂₁ — delim2: delimiter target for non-display generalized
56    /// fractions.
57    pub delim2: f64,
58    /// σ₂₂ — axis height above the baseline.
59    pub axis_height: f64,
60    /// ξ₈ — default rule thickness.
61    pub rule_thickness: f64,
62    /// ξ₉ — big_op_spacing1: minimum gap below an upper limit.
63    pub big_op_spacing1: f64,
64    /// ξ₁₀ — big_op_spacing2: minimum gap above a lower limit.
65    pub big_op_spacing2: f64,
66    /// ξ₁₁ — big_op_spacing3: upper-limit gap floor including its depth.
67    pub big_op_spacing3: f64,
68    /// ξ₁₂ — big_op_spacing4: lower-limit gap floor including its height.
69    pub big_op_spacing4: f64,
70    /// ξ₁₃ — big_op_spacing5: padding above/below limit stacks.
71    pub big_op_spacing5: f64,
72    /// The display-size scale of `\sum`-class big operators. CM has no
73    /// cmex-style size-variant glyphs, so the display variant is the
74    /// authored glyph scaled by the cmex10 display/text height ratio
75    /// (14 pt-class vs 10 pt-class: 1.4) — a calibration constant the Look
76    /// Gallery judges (G0-3 "spike simplifications" work item).
77    pub display_op_scale: f64,
78    /// TeX's `delimiterfactor`/1000: a `\left…\right` delimiter must cover
79    /// at least this fraction of twice the content's axis-distance.
80    pub delimiter_factor: f64,
81    /// TeX's `delimitershortfall` in ems (5 pt at 10 pt): the delimiter
82    /// may fall short of full coverage by at most this much.
83    pub delimiter_shortfall: f64,
84    /// TeX's `nulldelimiterspace` in ems (1.2 pt at 10 pt): the width a
85    /// null delimiter (`\left.`) occupies.
86    pub null_delimiter_space: f64,
87    /// `\baselineskip` in ems (12 pt at 10 pt): minimum baseline-to-
88    /// baseline distance when stacking `\\`-separated lines.
89    pub baseline_skip: f64,
90    /// `\lineskip` in ems (1 pt at 10 pt): the gap used when boxes would
91    /// otherwise touch.
92    pub line_skip: f64,
93    /// The uniform-scaling ceiling of the delimiter mechanism (ADR-0005):
94    /// beyond `1.25×` natural, drawn-path construction takes over (the
95    /// drawn constructions land with the extensions bead; until then the
96    /// engine keeps scaling uniformly and the seam is documented).
97    pub delimiter_scale_ceiling: f64,
98    /// The interword space of text islands, in ems, used when the face
99    /// does not map a space glyph.
100    pub fallback_space: f64,
101}
102
103/// The Computer Modern family at 10 pt, in ems (values as published for
104/// cmsy10/cmex10; see the module docs for provenance and validation).
105pub const CM: MathConstants = MathConstants {
106    x_height: 0.430_555,
107    quad: 1.000_003,
108    num1: 0.676_508,
109    num2: 0.393_732,
110    num3: 0.443_731,
111    denom1: 0.685_951,
112    denom2: 0.344_841,
113    sup1: 0.412_892,
114    sup2: 0.362_892,
115    sup3: 0.288_889,
116    sub1: 0.150_000,
117    sub2: 0.247_217,
118    sup_drop: 0.386_108,
119    sub_drop: 0.050_000,
120    delim1: 2.389_999,
121    delim2: 1.010_000,
122    axis_height: 0.250_000,
123    rule_thickness: 0.039_999,
124    big_op_spacing1: 0.111_112,
125    big_op_spacing2: 0.166_667,
126    big_op_spacing3: 0.200_000,
127    big_op_spacing4: 0.600_000,
128    big_op_spacing5: 0.100_000,
129    display_op_scale: 1.4,
130    delimiter_factor: 0.901,
131    delimiter_shortfall: 0.5,
132    null_delimiter_space: 0.12,
133    baseline_skip: 1.2,
134    line_skip: 0.1,
135    delimiter_scale_ceiling: 1.25,
136    fallback_space: 1.0 / 3.0,
137};
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn the_published_family_is_internally_consistent() {
145        // Sanity relations that hold for the published CM values; a typo in
146        // the table would trip one of these. (Read through black_box so the
147        // relations are checked as runtime assertions.)
148        let c = std::hint::black_box(CM);
149        assert!(c.num1 > c.num2 && c.num2 < c.num3);
150        assert!(c.denom1 > c.denom2);
151        assert!(c.sup1 > c.sup2 && c.sup2 > c.sup3);
152        assert!(c.sub2 > c.sub1);
153        assert!(c.delim1 > c.delim2);
154        assert!((c.axis_height - 0.25).abs() < 1e-9);
155        assert!((c.rule_thickness - 0.04).abs() < 1e-5);
156        assert!(c.x_height < 0.5 && c.x_height > 0.4);
157    }
158}