ttf_parser/tables/
loca.rs

1//! An [Index to Location Table](https://docs.microsoft.com/en-us/typography/opentype/spec/loca)
2//! implementation.
3
4use core::convert::TryFrom;
5use core::num::NonZeroU16;
6use core::ops::Range;
7
8use crate::parser::{LazyArray16, NumFrom, Stream};
9use crate::{GlyphId, IndexToLocationFormat};
10
11/// An [Index to Location Table](https://docs.microsoft.com/en-us/typography/opentype/spec/loca).
12#[derive(Clone, Copy, Debug)]
13pub enum Table<'a> {
14    /// Short offsets.
15    Short(LazyArray16<'a, u16>),
16    /// Long offsets.
17    Long(LazyArray16<'a, u32>),
18}
19
20impl<'a> Table<'a> {
21    /// Parses a table from raw data.
22    ///
23    /// - `number_of_glyphs` is from the `maxp` table.
24    /// - `format` is from the `head` table.
25    pub fn parse(
26        number_of_glyphs: NonZeroU16,
27        format: IndexToLocationFormat,
28        data: &'a [u8],
29    ) -> Option<Self> {
30        // The number of ranges is `maxp.numGlyphs + 1`.
31        //
32        // Check for overflow first.
33        let mut total = if number_of_glyphs.get() == u16::MAX {
34            number_of_glyphs.get()
35        } else {
36            number_of_glyphs.get() + 1
37        };
38
39        // By the spec, the number of `loca` offsets is `maxp.numGlyphs + 1`.
40        // But some malformed fonts can have less glyphs than that.
41        // In which case we try to parse only the available offsets
42        // and do not return an error, since the expected data length
43        // would go beyond table's length.
44        //
45        // In case when `loca` has more data than needed we simply ignore the rest.
46        let actual_total = match format {
47            IndexToLocationFormat::Short => data.len() / 2,
48            IndexToLocationFormat::Long => data.len() / 4,
49        };
50        let actual_total = u16::try_from(actual_total).ok()?;
51        total = total.min(actual_total);
52
53        let mut s = Stream::new(data);
54        match format {
55            IndexToLocationFormat::Short => Some(Table::Short(s.read_array16::<u16>(total)?)),
56            IndexToLocationFormat::Long => Some(Table::Long(s.read_array16::<u32>(total)?)),
57        }
58    }
59
60    /// Returns the number of offsets.
61    #[inline]
62    pub fn len(&self) -> u16 {
63        match self {
64            Table::Short(ref array) => array.len(),
65            Table::Long(ref array) => array.len(),
66        }
67    }
68
69    /// Checks if there are any offsets.
70    pub fn is_empty(&self) -> bool {
71        self.len() == 0
72    }
73
74    /// Returns glyph's range in the `glyf` table.
75    #[inline]
76    pub fn glyph_range(&self, glyph_id: GlyphId) -> Option<Range<usize>> {
77        let glyph_id = glyph_id.0;
78        if glyph_id == u16::MAX {
79            return None;
80        }
81
82        // Glyph ID must be smaller than total number of values in a `loca` array.
83        if glyph_id + 1 >= self.len() {
84            return None;
85        }
86
87        let range = match self {
88            Table::Short(ref array) => {
89                // 'The actual local offset divided by 2 is stored.'
90                usize::from(array.get(glyph_id)?) * 2..usize::from(array.get(glyph_id + 1)?) * 2
91            }
92            Table::Long(ref array) => {
93                usize::num_from(array.get(glyph_id)?)..usize::num_from(array.get(glyph_id + 1)?)
94            }
95        };
96
97        if range.start >= range.end {
98            // 'The offsets must be in ascending order.'
99            // And range cannot be empty.
100            None
101        } else {
102            Some(range)
103        }
104    }
105}