Skip to main content

latex_rust/font/
mod.rs

1//! OpenType math font metrics. Integer font units → [`Dim`](crate::Dim).
2
3use ttf_parser::Face;
4
5use crate::dim::Dim;
6use crate::error::{Error, FontError};
7
8/// Embedded STIX Two Math Regular 2.13 (SIL OFL 1.1).
9pub const STIX_TWO_MATH_OTF: &[u8] =
10    include_bytes!("../../fonts/stix-two-math/STIXTwoMath-Regular.otf");
11
12/// SHA-256 (hex) of [`STIX_TWO_MATH_OTF`]. Locked by gold.
13pub const STIX_TWO_MATH_SHA256: &str =
14    "f2076b9f1676438439dd41e23676f5ab99056e83d6b8f8c27841591ef2ccfa72";
15
16/// Face name as shipped.
17pub const STIX_TWO_MATH_NAME: &str = "STIX Two Math";
18
19/// Horizontal glyph metrics in font units and em.
20///
21/// # Examples
22///
23/// ```
24/// use latex_rust::MathFont;
25///
26/// let font = MathFont::stix_two_math().unwrap();
27/// let g = font.glyph('x').unwrap();
28/// assert_eq!(g.ch, 'x');
29/// assert!(!g.advance.is_zero());
30/// ```
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct GlyphMetrics {
33    /// Character requested.
34    pub ch: char,
35    /// OpenType glyph id.
36    pub glyph_id: u16,
37    /// Horizontal advance, font units.
38    pub advance_fu: u16,
39    /// Advance in em.
40    pub advance: Dim,
41    /// Height above baseline in em (`max(y_max, 0)`).
42    pub height: Dim,
43    /// Depth below baseline in em (`max(-y_min, 0)`).
44    pub depth: Dim,
45}
46
47/// Loaded math face.
48///
49/// # Examples
50///
51/// ```
52/// use latex_rust::MathFont;
53///
54/// let font = MathFont::stix_two_math().unwrap();
55/// assert_eq!(font.units_per_em(), 1000);
56/// ```
57pub struct MathFont {
58    raw: &'static [u8],
59    face: Face<'static>,
60    units_per_em: u16,
61    ascender_fu: i16,
62    descender_fu: i16,
63}
64
65impl MathFont {
66    /// Load the embedded STIX Two Math Regular face.
67    ///
68    /// # Errors
69    ///
70    /// [`crate::FontError::InvalidFace`] if the embedded bytes are not a usable OpenType face.
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use latex_rust::MathFont;
76    /// assert!(MathFont::stix_two_math().is_ok());
77    /// ```
78    pub fn stix_two_math() -> Result<Self, Error> {
79        Self::from_bytes(STIX_TWO_MATH_OTF)
80    }
81
82    /// Parse OpenType bytes. Lifetime is `'static` for the embedded font only;
83    /// this constructor requires a static buffer so the face can be rebuilt.
84    pub fn from_bytes(raw: &'static [u8]) -> Result<Self, Error> {
85        let face = Face::parse(raw, 0).map_err(|_| FontError::InvalidFace)?;
86        let units_per_em = face.units_per_em();
87        if units_per_em == 0 {
88            return Err(FontError::InvalidFace.into());
89        }
90        let ascender_fu = face.ascender();
91        let descender_fu = face.descender();
92        Ok(Self {
93            raw,
94            face,
95            units_per_em,
96            ascender_fu,
97            descender_fu,
98        })
99    }
100
101    pub(crate) fn face(&self) -> &Face<'static> {
102        &self.face
103    }
104
105    /// OpenType bytes this face was parsed from.
106    #[must_use]
107    pub fn bytes(&self) -> &'static [u8] {
108        self.raw
109    }
110
111    /// `unitsPerEm` from the `head` table.
112    #[must_use]
113    pub fn units_per_em(&self) -> u16 {
114        self.units_per_em
115    }
116
117    /// `hhea` ascender in font units.
118    #[must_use]
119    pub fn ascender_fu(&self) -> i16 {
120        self.ascender_fu
121    }
122
123    /// `hhea` descender in font units (typically negative).
124    #[must_use]
125    pub fn descender_fu(&self) -> i16 {
126        self.descender_fu
127    }
128
129    /// Ascender in em.
130    #[must_use]
131    pub fn ascender(&self) -> Dim {
132        Dim::from_font_units(i64::from(self.ascender_fu), self.units_per_em)
133    }
134
135    /// Depth below baseline from `hhea` descender, in em (non-negative).
136    #[must_use]
137    pub fn descender(&self) -> Dim {
138        let d = i64::from(self.descender_fu);
139        Dim::from_font_units(-d, self.units_per_em)
140    }
141
142    /// Metrics for `ch`, or [`FontError::MissingGlyph`].
143    pub fn glyph(&self, ch: char) -> Result<GlyphMetrics, Error> {
144        let face = self.face();
145        let gid = face.glyph_index(ch).ok_or(FontError::MissingGlyph { ch })?;
146        let advance_fu = face
147            .glyph_hor_advance(gid)
148            .ok_or(FontError::MissingGlyph { ch })?;
149        let mut height_fu = 0i64;
150        let mut depth_fu = 0i64;
151        if let Some(bbox) = face.glyph_bounding_box(gid) {
152            height_fu = i64::from(bbox.y_max).max(0);
153            depth_fu = i64::from(-bbox.y_min).max(0);
154        }
155        let upem = self.units_per_em;
156        Ok(GlyphMetrics {
157            ch,
158            glyph_id: gid.0,
159            advance_fu,
160            advance: Dim::from_font_units(i64::from(advance_fu), upem),
161            height: Dim::from_font_units(height_fu, upem),
162            depth: Dim::from_font_units(depth_fu, upem),
163        })
164    }
165
166    /// Metrics for OpenType glyph id `gid`, tagged with `ch` for the box payload.
167    pub fn glyph_id(&self, ch: char, gid: u16) -> Result<GlyphMetrics, Error> {
168        let face = self.face();
169        let gid = ttf_parser::GlyphId(gid);
170        let advance_fu = face
171            .glyph_hor_advance(gid)
172            .ok_or(FontError::MissingGlyph { ch })?;
173        let mut height_fu = 0i64;
174        let mut depth_fu = 0i64;
175        if let Some(bbox) = face.glyph_bounding_box(gid) {
176            height_fu = i64::from(bbox.y_max).max(0);
177            depth_fu = i64::from(-bbox.y_min).max(0);
178        }
179        let upem = self.units_per_em;
180        Ok(GlyphMetrics {
181            ch,
182            glyph_id: gid.0,
183            advance_fu,
184            advance: Dim::from_font_units(i64::from(advance_fu), upem),
185            height: Dim::from_font_units(height_fu, upem),
186            depth: Dim::from_font_units(depth_fu, upem),
187        })
188    }
189
190    /// MATH italic correction for `glyph_id`, or zero.
191    pub fn italic_correction(&self, glyph_id: u16) -> Dim {
192        let face = self.face();
193        let Some(math) = face.tables().math else {
194            return Dim::zero();
195        };
196        let Some(info) = math.glyph_info else {
197            return Dim::zero();
198        };
199        let Some(table) = info.italic_corrections else {
200            return Dim::zero();
201        };
202        match table.get(ttf_parser::GlyphId(glyph_id)) {
203            Some(v) => Dim::from_font_units(i64::from(v.value), self.units_per_em),
204            None => Dim::zero(),
205        }
206    }
207
208    /// MATH top-accent attachment (em from glyph left), if present.
209    pub fn top_accent_attachment(&self, glyph_id: u16) -> Option<Dim> {
210        let face = self.face();
211        let math = face.tables().math?;
212        let info = math.glyph_info?;
213        let table = info.top_accent_attachments?;
214        let v = table.get(ttf_parser::GlyphId(glyph_id))?;
215        Some(Dim::from_font_units(i64::from(v.value), self.units_per_em))
216    }
217
218    /// Horizontal glyph-assembly parts: `(gid, start_connector, end_connector, advance, extender)`.
219    /// Lengths are font units.
220    pub fn horizontal_assembly_parts(&self, glyph_id: u16) -> Vec<(u16, u16, u16, u16, bool)> {
221        let mut out = Vec::new();
222        let face = self.face();
223        let Some(math) = face.tables().math else {
224            return out;
225        };
226        let Some(variants) = math.variants else {
227            return out;
228        };
229        let Some(cons) = variants
230            .horizontal_constructions
231            .get(ttf_parser::GlyphId(glyph_id))
232        else {
233            return out;
234        };
235        let Some(assembly) = cons.assembly else {
236            return out;
237        };
238        for i in 0..assembly.parts.len() {
239            if let Some(p) = assembly.parts.get(i) {
240                out.push((
241                    p.glyph_id.0,
242                    p.start_connector_length,
243                    p.end_connector_length,
244                    p.full_advance,
245                    p.part_flags.extender(),
246                ));
247            }
248        }
249        out
250    }
251
252    /// Horizontal MATH variants of `glyph_id`, including the base glyph first.
253    pub fn horizontal_variants(&self, glyph_id: u16) -> Vec<u16> {
254        let mut out = vec![glyph_id];
255        let face = self.face();
256        let Some(math) = face.tables().math else {
257            return out;
258        };
259        let Some(variants) = math.variants else {
260            return out;
261        };
262        let Some(cons) = variants
263            .horizontal_constructions
264            .get(ttf_parser::GlyphId(glyph_id))
265        else {
266            return out;
267        };
268        for i in 0..cons.variants.len() {
269            if let Some(v) = cons.variants.get(i) {
270                out.push(v.variant_glyph.0);
271            }
272        }
273        out
274    }
275
276    /// Vertical MATH variants of `glyph_id`, including the base glyph first.
277    pub fn vertical_variants(&self, glyph_id: u16) -> Vec<u16> {
278        let mut out = vec![glyph_id];
279        let face = self.face();
280        let Some(math) = face.tables().math else {
281            return out;
282        };
283        let Some(variants) = math.variants else {
284            return out;
285        };
286        let Some(cons) = variants
287            .vertical_constructions
288            .get(ttf_parser::GlyphId(glyph_id))
289        else {
290            return out;
291        };
292        for i in 0..cons.variants.len() {
293            if let Some(v) = cons.variants.get(i) {
294                out.push(v.variant_glyph.0);
295            }
296        }
297        out
298    }
299
300    /// SHA-256 hex of the raw face bytes (zenith-float SHA-256).
301    #[must_use]
302    pub fn sha256_hex(bytes: &[u8]) -> String {
303        let d = zenith_float::sha256(bytes);
304        let mut s = String::with_capacity(64);
305        for b in d {
306            s.push_str(&hex_byte(b));
307        }
308        s
309    }
310}
311
312fn hex_byte(b: u8) -> String {
313    const H: &[u8; 16] = b"0123456789abcdef";
314    let hi = H[(b >> 4) as usize];
315    let lo = H[(b & 0xf) as usize];
316    let mut out = String::with_capacity(2);
317    out.push(hi as char);
318    out.push(lo as char);
319    out
320}