1
//! Parsing and writing of the `loca` table.
2
//!
3
//! > The indexToLoc table stores the offsets to the locations of the glyphs in the font, relative
4
//! > to the beginning of the glyphData table.
5
//!
6
//! — <https://docs.microsoft.com/en-us/typography/opentype/spec/loca>
7

            
8
use crate::binary::read::{ReadArray, ReadBinaryDep, ReadCtxt};
9
use crate::binary::write::{WriteBinary, WriteContext};
10
use crate::binary::{U16Be, U32Be};
11
use crate::error::{ParseError, WriteError};
12
use crate::tables::IndexToLocFormat;
13

            
14
/// `loca` table
15
///
16
/// <https://docs.microsoft.com/en-us/typography/opentype/spec/loca>
17
#[derive(Clone, Debug)]
18
pub struct LocaTable<'a> {
19
    pub offsets: LocaOffsets<'a>,
20
}
21

            
22
#[derive(Clone, Debug)]
23
pub enum LocaOffsets<'a> {
24
    Short(ReadArray<'a, U16Be>),
25
    Long(ReadArray<'a, U32Be>),
26
}
27

            
28
impl ReadBinaryDep for LocaTable<'_> {
29
    type Args<'a> = (u16, IndexToLocFormat);
30
    type HostType<'a> = LocaTable<'a>;
31

            
32
    /// Read a `loca` table from `ctxt`
33
    ///
34
    /// * `num_glyphs` is the number of glyphs in the font. The value for `num_glyphs` is found in
35
    ///   the 'maxp' table.
36
    /// * `index_to_loc_format` specifies whether the offsets in the `loca` table are short or
37
    ///   long. This value can be read from the `head` table.
38
13176
    fn read_dep<'a>(
39
13176
        ctxt: &mut ReadCtxt<'a>,
40
13176
        (num_glyphs, index_to_loc_format): (u16, IndexToLocFormat),
41
13176
    ) -> Result<Self::HostType<'a>, ParseError> {
42
13176
        let num_glyphs = usize::from(num_glyphs);
43
13176
        let offsets = match index_to_loc_format {
44
            IndexToLocFormat::Short => {
45
                // The actual local offset divided by 2 is stored. The value of n is numGlyphs + 1.
46
54
                LocaOffsets::Short(ctxt.read_array::<U16Be>(num_glyphs + 1)?)
47
            }
48
            IndexToLocFormat::Long => {
49
                // The actual local offset is stored. The value of n is numGlyphs + 1.
50
13122
                LocaOffsets::Long(ctxt.read_array::<U32Be>(num_glyphs + 1)?)
51
            }
52
        };
53

            
54
13176
        Ok(LocaTable { offsets })
55
13176
    }
56
}
57

            
58
impl<'a> WriteBinary for LocaTable<'a> {
59
    type Output = ();
60

            
61
    fn write<C: WriteContext>(ctxt: &mut C, loca: LocaTable<'a>) -> Result<(), WriteError> {
62
        match loca.offsets {
63
            LocaOffsets::Long(array) => ctxt.write_array(&array),
64
            LocaOffsets::Short(array) => ctxt.write_array(&array),
65
        }?;
66

            
67
        Ok(())
68
    }
69
}
70

            
71
impl LocaTable<'_> {
72
    pub fn empty() -> Self {
73
        LocaTable {
74
            offsets: LocaOffsets::Long(ReadArray::empty()),
75
        }
76
    }
77
}
78

            
79
impl<'a> LocaOffsets<'a> {
80
    /// Iterate the offsets in this table.
81
13176
    pub fn iter(&'a self) -> impl Iterator<Item = u32> + 'a {
82
        // NOTE(unwrap): Safe as iteration is bounded by len
83
19716318
        (0..self.len()).map(move |index| self.get(index).unwrap())
84
13176
    }
85

            
86
    /// Returns the number of offsets in the table.
87
13176
    pub fn len(&self) -> usize {
88
13176
        match self {
89
54
            LocaOffsets::Short(array) => array.len(),
90
13122
            LocaOffsets::Long(array) => array.len(),
91
        }
92
13176
    }
93

            
94
    /// Get a specified offset from the table at `index`.
95
19716318
    pub fn get(&self, index: usize) -> Option<u32> {
96
19716318
        match self {
97
41850
            LocaOffsets::Short(array) => array.get_item(index).map(|offset| u32::from(offset) * 2),
98
19674468
            LocaOffsets::Long(array) => array.get_item(index),
99
        }
100
19716318
    }
101

            
102
    /// Get the last offset in the table.
103
    ///
104
    /// Returns `None` if the table is empty.
105
    pub fn last(&self) -> Option<u32> {
106
        self.len().checked_sub(1).and_then(|index| self.get(index))
107
    }
108
}
109

            
110
pub mod owned {
111
    use super::{IndexToLocFormat, U16Be, U32Be, WriteContext, WriteError};
112
    use crate::binary::write::{WriteBinary, WriteBinaryDep};
113

            
114
    pub struct LocaTable {
115
        pub offsets: Vec<u32>,
116
    }
117

            
118
    impl LocaTable {
119
22464
        pub fn new() -> Self {
120
22464
            LocaTable {
121
22464
                offsets: Vec::new(),
122
22464
            }
123
22464
        }
124
    }
125

            
126
    impl WriteBinaryDep<Self> for LocaTable {
127
        type Output = ();
128
        type Args = IndexToLocFormat;
129

            
130
        fn write_dep<C: WriteContext>(
131
            ctxt: &mut C,
132
            loca: LocaTable,
133
            index_to_loc_format: Self::Args,
134
        ) -> Result<(), WriteError> {
135
            // 0 for short offsets (Offset16), 1 for long (Offset32).
136
            match index_to_loc_format {
137
                IndexToLocFormat::Short => {
138
                    match loca.offsets.last() {
139
                        Some(&last) if (last / 2) > u32::from(u16::MAX) => {
140
                            return Err(WriteError::BadValue)
141
                        }
142
                        _ => {}
143
                    }
144

            
145
                    // The actual loca offset divided by 2 is stored.
146
                    // https://docs.microsoft.com/en-us/typography/opentype/spec/loca#short-version
147
                    for offset in loca.offsets {
148
                        if offset & 1 == 1 {
149
                            // odd offsets can't use this format
150
                            return Err(WriteError::BadValue);
151
                        }
152
                        let short_offset = u16::try_from(offset / 2)?;
153
                        U16Be::write(ctxt, short_offset)?;
154
                    }
155

            
156
                    Ok(())
157
                }
158
                IndexToLocFormat::Long => ctxt.write_vec::<U32Be, _>(loca.offsets),
159
            }
160
        }
161
    }
162

            
163
    impl<'a, 'b: 'a> From<&'b super::LocaTable<'a>> for LocaTable {
164
13176
        fn from(loca: &'b super::LocaTable<'a>) -> Self {
165
13176
            Self {
166
13176
                offsets: loca.offsets.iter().collect(),
167
13176
            }
168
13176
        }
169
    }
170
}