1//! An [Index to Location Table](https://docs.microsoft.com/en-us/typography/opentype/spec/loca)
2//! implementation.
34use core::convert::TryFrom;
5use core::num::NonZeroU16;
6use core::ops::Range;
78use crate::parser::{LazyArray16, NumFrom, Stream};
9use crate::{GlyphId, IndexToLocationFormat};
1011/// 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.
15Short(LazyArray16<'a, u16>),
16/// Long offsets.
17Long(LazyArray16<'a, u32>),
18}
1920impl<'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.
25pub 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.
33let mut total = if number_of_glyphs.get() == u16::MAX {
34 number_of_glyphs.get()
35 } else {
36 number_of_glyphs.get() + 1
37};
3839// 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.
46let actual_total = match format {
47 IndexToLocationFormat::Short => data.len() / 2,
48 IndexToLocationFormat::Long => data.len() / 4,
49 };
50let actual_total = u16::try_from(actual_total).ok()?;
51 total = total.min(actual_total);
5253let mut s = Stream::new(data);
54match 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 }
5960/// Returns the number of offsets.
61#[inline]
62pub fn len(&self) -> u16 {
63match self {
64 Table::Short(ref array) => array.len(),
65 Table::Long(ref array) => array.len(),
66 }
67 }
6869/// Checks if there are any offsets.
70pub fn is_empty(&self) -> bool {
71self.len() == 0
72}
7374/// Returns glyph's range in the `glyf` table.
75#[inline]
76pub fn glyph_range(&self, glyph_id: GlyphId) -> Option<Range<usize>> {
77let glyph_id = glyph_id.0;
78if glyph_id == u16::MAX {
79return None;
80 }
8182// Glyph ID must be smaller than total number of values in a `loca` array.
83if glyph_id + 1 >= self.len() {
84return None;
85 }
8687let range = match self {
88 Table::Short(ref array) => {
89// 'The actual local offset divided by 2 is stored.'
90usize::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 };
9697if range.start >= range.end {
98// 'The offsets must be in ascending order.'
99 // And range cannot be empty.
100None
101} else {
102Some(range)
103 }
104 }
105}