font_subset/font/
types.rs

1//! Basic types for font parsing.
2
3use core::{fmt, ops};
4
5#[cfg(doc)]
6use crate::Font;
7use crate::{alloc::Vec, write::VecExt, ParseError, ParseErrorKind};
8
9/// 4-byte tag of an OpenType font table.
10#[derive(Clone, Copy, PartialEq, Eq, Hash)]
11pub struct TableTag(pub(crate) [u8; 4]);
12
13impl_tag!(TableTag);
14
15macro_rules! tag_const {
16    ($($val:tt, $int:tt $(as $name:ident)?;)+) => {
17        $(
18        tag_const!(@define $val $(as $name)?);
19        )+
20
21        #[cfg(feature = "woff2")]
22        pub(crate) fn as_u8(self) -> Option<u8> {
23            Some(match &self.0 {
24                $($val => $int,)+
25                _ => return None,
26            })
27        }
28
29        #[cfg(feature = "woff2")]
30        pub(crate) fn from_u8(val: u8) -> Option<Self> {
31            Some(match val & 63 {
32                $($int => Self(*$val),)+
33                _ => return None,
34            })
35        }
36    };
37
38    (@define $val:tt as $name:ident) => {
39        #[doc = concat!("Tag for the `", stringify!($val), "` table.")]
40        pub const $name: Self = Self(*$val);
41    };
42    (@define $val:tt) => {
43        // produce nothing
44    };
45}
46
47impl TableTag {
48    // public tables are ones used in the `Font`
49    tag_const!(
50        b"cmap",  0 as CMAP;
51        b"head",  1 as HEAD;
52        b"hhea",  2 as HHEA;
53        b"hmtx",  3 as HMTX;
54        b"maxp",  4 as MAXP;
55        b"name",  5 as NAME;
56        b"OS/2",  6 as OS2;
57        b"post",  7 as POST;
58        b"cvt ",  8 as CVT;
59        b"fpgm",  9 as FPGM;
60        b"glyf", 10 as GLYF;
61        b"loca", 11 as LOCA;
62        b"prep", 12 as PREP;
63        b"CFF ", 13;
64        b"VORG", 14;
65        b"EBDT", 15;
66        b"EBLC", 16;
67        b"gasp", 17;
68        b"hdmx", 18;
69        b"kern", 19;
70        b"LTSH", 20;
71        b"PCLT", 21;
72        b"VDMX", 22;
73        b"vhea", 23;
74        b"vmtx", 24;
75        b"BASE", 25;
76        b"GDEF", 26;
77        b"GPOS", 27;
78        b"GSUB", 28;
79        b"EBSC", 29;
80        b"JSTF", 30;
81        b"MATH", 31;
82        b"CBDT", 32;
83        b"CBLC", 33;
84        b"COLR", 34;
85        b"CPAL", 35;
86        b"SVG ", 36;
87        b"sbix", 37;
88        b"acnt", 38;
89        b"avar", 39 as AVAR;
90        b"bdat", 40;
91        b"bloc", 41;
92        b"bsln", 42;
93        b"cvar", 43;
94        b"fdsc", 44;
95        b"feat", 45;
96        b"fmtx", 46;
97        b"fvar", 47 as FVAR;
98        b"gvar", 48 as GVAR;
99        b"hsty", 49;
100        b"just", 50;
101        b"lcar", 51;
102        b"mort", 52;
103        b"morx", 53;
104        b"opbd", 54;
105        b"prop", 55;
106        b"trak", 56;
107        b"Zapf", 57;
108        b"Silf", 58;
109        b"Glat", 59;
110        b"Gloc", 60;
111        b"Feat", 61;
112        b"Sill", 62;
113    );
114
115    pub(crate) const STAT: Self = Self(*b"STAT");
116
117    pub(super) fn is_variable(self) -> bool {
118        matches!(self, Self::AVAR | Self::FVAR | Self::GVAR)
119            || matches!(&self.0, b"cvar" | b"HVAR" | b"MVAR" | b"VVAR")
120    }
121}
122
123/// Fixed-point signed 32-bit value. Used in [`VariableAxis`](crate::VariationAxis) params.
124///
125/// This type has the 16.16 shape; i.e., it's mapped from `i32` by dividing by `65_536 == 1 << 16`.
126#[derive(Clone, Copy, PartialEq, Eq, Hash)]
127pub struct Fixed(pub(super) i32);
128
129impl fmt::Debug for Fixed {
130    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
131        fmt::Display::fmt(&f32::from(*self), formatter)
132    }
133}
134
135impl fmt::Display for Fixed {
136    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
137        fmt::Debug::fmt(self, formatter)
138    }
139}
140
141impl From<Fixed> for f32 {
142    #[allow(clippy::cast_precision_loss)] // acceptable
143    fn from(value: Fixed) -> Self {
144        value.0 as f32 / 65_536.0_f32
145    }
146}
147
148impl From<i16> for Fixed {
149    fn from(value: i16) -> Self {
150        Self(i32::from(value) << 16)
151    }
152}
153
154/// Font reading cursor.
155#[derive(Debug, Clone, Copy)]
156pub(crate) struct Cursor<'a> {
157    pub(super) bytes: &'a [u8],
158    pub(super) offset: usize,
159    pub(super) table: Option<TableTag>,
160}
161
162impl<'a> Cursor<'a> {
163    pub(crate) fn new(bytes: &'a [u8]) -> Self {
164        Self {
165            bytes,
166            offset: 0,
167            table: None,
168        }
169    }
170
171    pub(super) fn for_table(bytes: &'a [u8], offset: usize, table: TableTag) -> Self {
172        Self {
173            bytes,
174            offset,
175            table: Some(table),
176        }
177    }
178
179    pub(crate) fn bytes(&self) -> &'a [u8] {
180        self.bytes
181    }
182
183    pub(super) fn offset(&self) -> usize {
184        self.offset
185    }
186
187    #[cfg(feature = "tracing")]
188    pub(crate) fn range(&self) -> ops::Range<usize> {
189        self.offset..self.offset + self.bytes.len()
190    }
191
192    pub(super) fn err(&self, kind: ParseErrorKind) -> ParseError {
193        ParseError {
194            kind,
195            offset: self.offset,
196            table: self.table,
197        }
198    }
199
200    pub(super) fn skip(&mut self, n: usize) -> Result<(), ParseError> {
201        if self.bytes.len() < n {
202            Err(self.err(ParseErrorKind::UnexpectedEof))
203        } else {
204            self.bytes = &self.bytes[n..];
205            self.offset += n;
206            Ok(())
207        }
208    }
209
210    pub(super) fn read_u16(&mut self) -> Result<u16, ParseError> {
211        let [a, b, rest @ ..] = self.bytes else {
212            return Err(self.err(ParseErrorKind::UnexpectedEof));
213        };
214        self.bytes = rest;
215        self.offset += 2;
216        Ok(u16::from_be_bytes([*a, *b]))
217    }
218
219    #[allow(clippy::cast_possible_wrap)] // intentional
220    pub(super) fn read_i16(&mut self) -> Result<i16, ParseError> {
221        Ok(self.read_u16()? as i16)
222    }
223
224    pub(super) fn read_u16_checked<T>(
225        &mut self,
226        check: impl FnOnce(u16) -> Result<T, ParseErrorKind>,
227    ) -> Result<T, ParseError> {
228        check(self.read_u16()?).map_err(|kind| ParseError {
229            kind,
230            table: self.table,
231            offset: self.offset - 2, // use the starting offset for the value
232        })
233    }
234
235    pub(super) fn read_u32(&mut self) -> Result<u32, ParseError> {
236        let [a, b, c, d, rest @ ..] = self.bytes else {
237            return Err(self.err(ParseErrorKind::UnexpectedEof));
238        };
239        self.bytes = rest;
240        self.offset += 4;
241        Ok(u32::from_be_bytes([*a, *b, *c, *d]))
242    }
243
244    #[allow(clippy::cast_possible_wrap)] // intentional
245    pub(super) fn read_i32(&mut self) -> Result<i32, ParseError> {
246        Ok(self.read_u32()? as i32)
247    }
248
249    pub(super) fn read_u32_checked<T>(
250        &mut self,
251        check: impl FnOnce(u32) -> Result<T, ParseErrorKind>,
252    ) -> Result<T, ParseError> {
253        check(self.read_u32()?).map_err(|kind| ParseError {
254            kind,
255            table: self.table,
256            offset: self.offset - 4, // use the starting offset for the value
257        })
258    }
259
260    pub(super) fn read_u64(&mut self) -> Result<u64, ParseError> {
261        let u64_bytes = self
262            .bytes
263            .first_chunk::<8>()
264            .ok_or_else(|| self.err(ParseErrorKind::UnexpectedEof))?;
265
266        self.bytes = &self.bytes[8..];
267        self.offset += 8;
268        Ok(u64::from_be_bytes(*u64_bytes))
269    }
270
271    #[allow(clippy::cast_possible_wrap)] // intentional
272    pub(super) fn read_i64(&mut self) -> Result<i64, ParseError> {
273        Ok(self.read_u64()? as i64)
274    }
275
276    pub(super) fn read_u128(&mut self) -> Result<u128, ParseError> {
277        let u128_bytes = self
278            .bytes
279            .first_chunk::<16>()
280            .ok_or_else(|| self.err(ParseErrorKind::UnexpectedEof))?;
281
282        self.bytes = &self.bytes[16..];
283        self.offset += 16;
284        Ok(u128::from_be_bytes(*u128_bytes))
285    }
286
287    pub(super) fn read_byte_array<const N: usize>(&mut self) -> Result<[u8; N], ParseError> {
288        if self.bytes.len() < N {
289            Err(self.err(ParseErrorKind::UnexpectedEof))
290        } else {
291            let (head, tail) = self.bytes.split_at(N);
292            self.bytes = tail;
293            self.offset += N;
294            Ok(head.try_into().unwrap())
295        }
296    }
297
298    pub(super) fn read_range(&self, range: ops::Range<usize>) -> Result<Self, ParseError> {
299        let bytes = self.bytes.get(range.clone()).ok_or_else(|| {
300            self.err(ParseErrorKind::RangeOutOfBounds {
301                range: range.clone(),
302                len: self.bytes.len(),
303            })
304        })?;
305        Ok(Self {
306            bytes,
307            offset: self.offset + range.start,
308            table: self.table,
309        })
310    }
311
312    pub(super) fn split_at(&mut self, pos: usize) -> Result<Self, ParseError> {
313        let prefix = self.read_range(0..pos)?;
314        self.skip(pos)?;
315        Ok(prefix)
316    }
317}
318
319/// Signed 64-bit timestamps used in some tables, in particular, for [`Font::created_at()`]
320/// and [`Font::modified_at()`].
321#[derive(Debug, Clone, Copy, PartialEq)]
322pub struct LongDateTime(pub(crate) i64);
323
324impl LongDateTime {
325    /// Unix timestamp of the Epoch used by this type (Jan 1, 1904 00:00:00 UTC).
326    const EPOCH_TS: i64 = -2_082_844_800;
327
328    /// Converts this timestamp to a Unix timestamp. Returns `None` if the timestamp would be
329    /// out of `i64` bounds (unlikely).
330    pub fn as_unix_timestamp(self) -> Option<i64> {
331        self.0.checked_add(Self::EPOCH_TS)
332    }
333}
334
335#[derive(Debug, Clone, Copy, PartialEq)]
336pub(crate) struct BoundingBox {
337    pub(crate) x_min: i16,
338    pub(crate) y_min: i16,
339    pub(crate) x_max: i16,
340    pub(crate) y_max: i16,
341}
342
343impl BoundingBox {
344    pub(super) const BYTE_LEN: usize = 8;
345
346    pub(crate) const ZERO: Self = Self {
347        x_min: 0,
348        y_min: 0,
349        x_max: 0,
350        y_max: 0,
351    };
352
353    pub(super) fn parse(cursor: &mut Cursor<'_>) -> Result<Self, ParseError> {
354        let x_min = cursor.read_i16()?;
355        let y_min = cursor.read_i16()?;
356        let x_max = cursor.read_i16()?;
357        let y_max = cursor.read_i16()?;
358        Ok(Self {
359            x_min,
360            y_min,
361            x_max,
362            y_max,
363        })
364    }
365
366    pub(crate) fn write_to_vec(self, buffer: &mut Vec<u8>) {
367        buffer.write_i16(self.x_min);
368        buffer.write_i16(self.y_min);
369        buffer.write_i16(self.x_max);
370        buffer.write_i16(self.y_max);
371    }
372
373    pub(crate) fn union(self, other: Self) -> Self {
374        Self {
375            x_min: self.x_min.min(other.x_min),
376            y_min: self.y_min.min(other.y_min),
377            x_max: self.x_max.max(other.x_max),
378            y_max: self.y_max.max(other.y_max),
379        }
380    }
381}
382
383#[derive(Debug, Clone, Copy)]
384pub(crate) enum OffsetFormat {
385    Short,
386    Long,
387}
388
389impl OffsetFormat {
390    pub(super) const fn bytes_per_offset(self) -> usize {
391        match self {
392            Self::Short => 2,
393            Self::Long => 4,
394        }
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use chrono::{TimeZone, Utc};
401
402    use super::*;
403
404    #[test]
405    fn timestamp_conversion_is_correct() {
406        let ts = Utc
407            .with_ymd_and_hms(1904, 1, 1, 0, 0, 0)
408            .unwrap()
409            .timestamp();
410        assert_eq!(ts, LongDateTime::EPOCH_TS);
411
412        let unix_ts = LongDateTime(-LongDateTime::EPOCH_TS)
413            .as_unix_timestamp()
414            .unwrap();
415        assert_eq!(unix_ts, 0);
416    }
417}