1 | //! An [Index to Location Table](https://docs.microsoft.com/en-us/typography/opentype/spec/loca) |
2 | //! implementation. |
3 | |
4 | use core::convert::TryFrom; |
5 | use core::num::NonZeroU16; |
6 | use core::ops::Range; |
7 | |
8 | use crate::parser::{LazyArray16, NumFrom, Stream}; |
9 | use 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)] |
13 | pub enum Table<'a> { |
14 | /// Short offsets. |
15 | Short(LazyArray16<'a, u16>), |
16 | /// Long offsets. |
17 | Long(LazyArray16<'a, u32>), |
18 | } |
19 | |
20 | impl<'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() == core::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 == core::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 | } |
106 | |