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() == 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