Coverage Report

Created: 2026-09-04 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ttf-parser/src/tables/loca.rs
Line
Count
Source
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::{LazyArray32, 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(LazyArray32<'a, u16>),
16
    /// Long offsets.
17
    Long(LazyArray32<'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
7.53k
    pub fn parse(
26
7.53k
        number_of_glyphs: NonZeroU16,
27
7.53k
        format: IndexToLocationFormat,
28
7.53k
        data: &'a [u8],
29
7.53k
    ) -> Option<Self> {
30
        // The number of ranges is `maxp.numGlyphs + 1`.
31
        //
32
        // Check for overflow first.
33
7.53k
        let mut total: u32 = number_of_glyphs.get() as u32 + 1;
34
35
        // By the spec, the number of `loca` offsets is `maxp.numGlyphs + 1`.
36
        // But some malformed fonts can have less glyphs than that.
37
        // In which case we try to parse only the available offsets
38
        // and do not return an error, since the expected data length
39
        // would go beyond table's length.
40
        //
41
        // In case when `loca` has more data than needed we simply ignore the rest.
42
7.53k
        let actual_total = match format {
43
6.32k
            IndexToLocationFormat::Short => data.len() / 2,
44
1.21k
            IndexToLocationFormat::Long => data.len() / 4,
45
        };
46
7.53k
        let actual_total = u32::try_from(actual_total).ok()?;
47
7.53k
        total = total.min(actual_total);
48
49
7.53k
        let mut s = Stream::new(data);
50
7.53k
        match format {
51
6.32k
            IndexToLocationFormat::Short => Some(Table::Short(s.read_array32::<u16>(total)?)),
52
1.21k
            IndexToLocationFormat::Long => Some(Table::Long(s.read_array32::<u32>(total)?)),
53
        }
54
7.53k
    }
55
56
    /// Returns the number of offsets.
57
    #[inline]
58
75.3M
    pub fn len(&self) -> u32 {
59
75.3M
        match self {
60
68.8M
            Table::Short(array) => array.len(),
61
6.50M
            Table::Long(array) => array.len(),
62
        }
63
75.3M
    }
<ttf_parser::tables::loca::Table>::len
Line
Count
Source
58
12.5M
    pub fn len(&self) -> u32 {
59
12.5M
        match self {
60
10.1M
            Table::Short(array) => array.len(),
61
2.42M
            Table::Long(array) => array.len(),
62
        }
63
12.5M
    }
Unexecuted instantiation: <ttf_parser::tables::loca::Table>::len
<ttf_parser::tables::loca::Table>::len
Line
Count
Source
58
13.4M
    pub fn len(&self) -> u32 {
59
13.4M
        match self {
60
10.6M
            Table::Short(array) => array.len(),
61
2.86M
            Table::Long(array) => array.len(),
62
        }
63
13.4M
    }
<ttf_parser::tables::loca::Table>::len
Line
Count
Source
58
4
    pub fn len(&self) -> u32 {
59
4
        match self {
60
4
            Table::Short(array) => array.len(),
61
0
            Table::Long(array) => array.len(),
62
        }
63
4
    }
<ttf_parser::tables::loca::Table>::len
Line
Count
Source
58
89.2k
    pub fn len(&self) -> u32 {
59
89.2k
        match self {
60
82.7k
            Table::Short(array) => array.len(),
61
6.45k
            Table::Long(array) => array.len(),
62
        }
63
89.2k
    }
<ttf_parser::tables::loca::Table>::len
Line
Count
Source
58
49.2M
    pub fn len(&self) -> u32 {
59
49.2M
        match self {
60
48.0M
            Table::Short(array) => array.len(),
61
1.20M
            Table::Long(array) => array.len(),
62
        }
63
49.2M
    }
64
65
    /// Checks if there are any offsets.
66
0
    pub fn is_empty(&self) -> bool {
67
0
        self.len() == 0
68
0
    }
69
70
    /// Returns glyph's range in the `glyf` table.
71
    #[inline]
72
75.3M
    pub fn glyph_range(&self, glyph_id: GlyphId) -> Option<Range<usize>> {
73
75.3M
        let glyph_id = glyph_id.0 as u32;
74
75
        // Glyph ID must be smaller than total number of values in a `loca` array.
76
75.3M
        if glyph_id + 1 >= self.len() {
77
36.3M
            return None;
78
39.0M
        }
79
80
39.0M
        let range = match self {
81
37.9M
            Table::Short(array) => {
82
                // 'The actual local offset divided by 2 is stored.'
83
37.9M
                usize::from(array.get(glyph_id)?) * 2..usize::from(array.get(glyph_id + 1)?) * 2
84
            }
85
1.03M
            Table::Long(array) => {
86
1.03M
                usize::num_from(array.get(glyph_id)?)..usize::num_from(array.get(glyph_id + 1)?)
87
            }
88
        };
89
90
39.0M
        if range.start >= range.end {
91
            // 'The offsets must be in ascending order.'
92
            // And range cannot be empty.
93
8.10M
            None
94
        } else {
95
30.9M
            Some(range)
96
        }
97
75.3M
    }
<ttf_parser::tables::loca::Table>::glyph_range
Line
Count
Source
72
12.5M
    pub fn glyph_range(&self, glyph_id: GlyphId) -> Option<Range<usize>> {
73
12.5M
        let glyph_id = glyph_id.0 as u32;
74
75
        // Glyph ID must be smaller than total number of values in a `loca` array.
76
12.5M
        if glyph_id + 1 >= self.len() {
77
10.4M
            return None;
78
2.05M
        }
79
80
2.05M
        let range = match self {
81
1.63M
            Table::Short(array) => {
82
                // 'The actual local offset divided by 2 is stored.'
83
1.63M
                usize::from(array.get(glyph_id)?) * 2..usize::from(array.get(glyph_id + 1)?) * 2
84
            }
85
423k
            Table::Long(array) => {
86
423k
                usize::num_from(array.get(glyph_id)?)..usize::num_from(array.get(glyph_id + 1)?)
87
            }
88
        };
89
90
2.05M
        if range.start >= range.end {
91
            // 'The offsets must be in ascending order.'
92
            // And range cannot be empty.
93
1.10M
            None
94
        } else {
95
953k
            Some(range)
96
        }
97
12.5M
    }
Unexecuted instantiation: <ttf_parser::tables::loca::Table>::glyph_range
<ttf_parser::tables::loca::Table>::glyph_range
Line
Count
Source
72
13.4M
    pub fn glyph_range(&self, glyph_id: GlyphId) -> Option<Range<usize>> {
73
13.4M
        let glyph_id = glyph_id.0 as u32;
74
75
        // Glyph ID must be smaller than total number of values in a `loca` array.
76
13.4M
        if glyph_id + 1 >= self.len() {
77
12.5M
            return None;
78
918k
        }
79
80
918k
        let range = match self {
81
584k
            Table::Short(array) => {
82
                // 'The actual local offset divided by 2 is stored.'
83
584k
                usize::from(array.get(glyph_id)?) * 2..usize::from(array.get(glyph_id + 1)?) * 2
84
            }
85
334k
            Table::Long(array) => {
86
334k
                usize::num_from(array.get(glyph_id)?)..usize::num_from(array.get(glyph_id + 1)?)
87
            }
88
        };
89
90
918k
        if range.start >= range.end {
91
            // 'The offsets must be in ascending order.'
92
            // And range cannot be empty.
93
499k
            None
94
        } else {
95
419k
            Some(range)
96
        }
97
13.4M
    }
<ttf_parser::tables::loca::Table>::glyph_range
Line
Count
Source
72
4
    pub fn glyph_range(&self, glyph_id: GlyphId) -> Option<Range<usize>> {
73
4
        let glyph_id = glyph_id.0 as u32;
74
75
        // Glyph ID must be smaller than total number of values in a `loca` array.
76
4
        if glyph_id + 1 >= self.len() {
77
4
            return None;
78
0
        }
79
80
0
        let range = match self {
81
0
            Table::Short(array) => {
82
                // 'The actual local offset divided by 2 is stored.'
83
0
                usize::from(array.get(glyph_id)?) * 2..usize::from(array.get(glyph_id + 1)?) * 2
84
            }
85
0
            Table::Long(array) => {
86
0
                usize::num_from(array.get(glyph_id)?)..usize::num_from(array.get(glyph_id + 1)?)
87
            }
88
        };
89
90
0
        if range.start >= range.end {
91
            // 'The offsets must be in ascending order.'
92
            // And range cannot be empty.
93
0
            None
94
        } else {
95
0
            Some(range)
96
        }
97
4
    }
<ttf_parser::tables::loca::Table>::glyph_range
Line
Count
Source
72
89.2k
    pub fn glyph_range(&self, glyph_id: GlyphId) -> Option<Range<usize>> {
73
89.2k
        let glyph_id = glyph_id.0 as u32;
74
75
        // Glyph ID must be smaller than total number of values in a `loca` array.
76
89.2k
        if glyph_id + 1 >= self.len() {
77
53.5k
            return None;
78
35.6k
        }
79
80
35.6k
        let range = match self {
81
33.5k
            Table::Short(array) => {
82
                // 'The actual local offset divided by 2 is stored.'
83
33.5k
                usize::from(array.get(glyph_id)?) * 2..usize::from(array.get(glyph_id + 1)?) * 2
84
            }
85
2.14k
            Table::Long(array) => {
86
2.14k
                usize::num_from(array.get(glyph_id)?)..usize::num_from(array.get(glyph_id + 1)?)
87
            }
88
        };
89
90
35.6k
        if range.start >= range.end {
91
            // 'The offsets must be in ascending order.'
92
            // And range cannot be empty.
93
5.06k
            None
94
        } else {
95
30.5k
            Some(range)
96
        }
97
89.2k
    }
<ttf_parser::tables::loca::Table>::glyph_range
Line
Count
Source
72
49.2M
    pub fn glyph_range(&self, glyph_id: GlyphId) -> Option<Range<usize>> {
73
49.2M
        let glyph_id = glyph_id.0 as u32;
74
75
        // Glyph ID must be smaller than total number of values in a `loca` array.
76
49.2M
        if glyph_id + 1 >= self.len() {
77
13.2M
            return None;
78
36.0M
        }
79
80
36.0M
        let range = match self {
81
35.7M
            Table::Short(array) => {
82
                // 'The actual local offset divided by 2 is stored.'
83
35.7M
                usize::from(array.get(glyph_id)?) * 2..usize::from(array.get(glyph_id + 1)?) * 2
84
            }
85
278k
            Table::Long(array) => {
86
278k
                usize::num_from(array.get(glyph_id)?)..usize::num_from(array.get(glyph_id + 1)?)
87
            }
88
        };
89
90
36.0M
        if range.start >= range.end {
91
            // 'The offsets must be in ascending order.'
92
            // And range cannot be empty.
93
6.50M
            None
94
        } else {
95
29.5M
            Some(range)
96
        }
97
49.2M
    }
98
}