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}