read_fonts/tables/postscript/
index.rs

1//! Parsing for PostScript INDEX objects.
2//!
3//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#5-index-data>
4
5use super::{Error, Index1, Index2};
6use crate::codegen_prelude::*;
7
8/// Common type for uniform access to CFF and CFF2 index formats.
9#[derive(Clone)]
10pub enum Index<'a> {
11    Empty,
12    Format1(Index1<'a>),
13    Format2(Index2<'a>),
14}
15
16impl<'a> Index<'a> {
17    /// Creates a new index from the given data.
18    ///
19    /// The caller must specify whether the data comes from a `CFF2` table.
20    pub fn new(data: &'a [u8], is_cff2: bool) -> Result<Self, Error> {
21        let data = FontData::new(data);
22        Ok(if is_cff2 {
23            Index2::read(data).map(|ix| ix.into())?
24        } else {
25            Index1::read(data).map(|ix| ix.into())?
26        })
27    }
28
29    /// Returns the number of objects in the index.
30    pub fn count(&self) -> u32 {
31        match self {
32            Self::Empty => 0,
33            Self::Format1(ix) => ix.count() as u32,
34            Self::Format2(ix) => ix.count(),
35        }
36    }
37
38    /// Computes a bias that is added to a subroutine operator in a
39    /// charstring.
40    ///
41    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>
42    pub fn subr_bias(&self) -> i32 {
43        let count = self.count();
44        if count < 1240 {
45            107
46        } else if count < 33900 {
47            1131
48        } else {
49            32768
50        }
51    }
52
53    /// Returns the total size in bytes of the index table.
54    pub fn size_in_bytes(&self) -> Result<usize, ReadError> {
55        match self {
56            Self::Empty => Ok(0),
57            Self::Format1(ix) => ix.size_in_bytes(),
58            Self::Format2(ix) => ix.size_in_bytes(),
59        }
60    }
61
62    /// Returns the offset at the given index.
63    pub fn get_offset(&self, index: usize) -> Result<usize, Error> {
64        match self {
65            Self::Empty => Err(ReadError::OutOfBounds.into()),
66            Self::Format1(ix) => ix.get_offset(index),
67            Self::Format2(ix) => ix.get_offset(index),
68        }
69    }
70
71    /// Returns the data for the object at the given index.
72    pub fn get(&self, index: usize) -> Result<&'a [u8], Error> {
73        match self {
74            Self::Empty => Err(ReadError::OutOfBounds.into()),
75            Self::Format1(ix) => ix.get(index),
76            Self::Format2(ix) => ix.get(index),
77        }
78    }
79}
80
81impl<'a> From<Index1<'a>> for Index<'a> {
82    fn from(value: Index1<'a>) -> Self {
83        Self::Format1(value)
84    }
85}
86
87impl<'a> From<Index2<'a>> for Index<'a> {
88    fn from(value: Index2<'a>) -> Self {
89        Self::Format2(value)
90    }
91}
92
93impl Default for Index<'_> {
94    fn default() -> Self {
95        Self::Empty
96    }
97}
98
99impl<'a> Index1<'a> {
100    /// Returns the total size in bytes of the index table.
101    pub fn size_in_bytes(&self) -> Result<usize, ReadError> {
102        // 2 byte count + 1 byte off_size
103        const HEADER_SIZE: usize = 3;
104        // An empty CFF index contains only a 2 byte count field
105        const EMPTY_SIZE: usize = 2;
106        let count = self.count() as usize;
107        Ok(match count {
108            0 => EMPTY_SIZE,
109            _ => {
110                HEADER_SIZE
111                    + self.offsets().len()
112                    + self.get_offset(count).map_err(|_| ReadError::OutOfBounds)?
113            }
114        })
115    }
116
117    /// Returns the offset of the object at the given index.
118    pub fn get_offset(&self, index: usize) -> Result<usize, Error> {
119        read_offset(
120            index,
121            self.count() as usize,
122            self.off_size(),
123            self.offsets(),
124        )
125    }
126
127    /// Returns the data for the object at the given index.
128    pub fn get(&self, index: usize) -> Result<&'a [u8], Error> {
129        self.data()
130            .get(self.get_offset(index)?..self.get_offset(index + 1)?)
131            .ok_or(ReadError::OutOfBounds.into())
132    }
133}
134
135impl<'a> Index2<'a> {
136    /// Returns the total size in bytes of the index table.
137    pub fn size_in_bytes(&self) -> Result<usize, ReadError> {
138        // 4 byte count + 1 byte off_size
139        const HEADER_SIZE: usize = 5;
140        // An empty CFF2 index contains only a 4 byte count field
141        const EMPTY_SIZE: usize = 4;
142        let count = self.count() as usize;
143        Ok(match count {
144            0 => EMPTY_SIZE,
145            _ => {
146                HEADER_SIZE
147                    + self.offsets().len()
148                    + self.get_offset(count).map_err(|_| ReadError::OutOfBounds)?
149            }
150        })
151    }
152
153    /// Returns the offset of the object at the given index.
154    pub fn get_offset(&self, index: usize) -> Result<usize, Error> {
155        read_offset(
156            index,
157            self.count() as usize,
158            self.off_size(),
159            self.offsets(),
160        )
161    }
162
163    /// Returns the data for the object at the given index.
164    pub fn get(&self, index: usize) -> Result<&'a [u8], Error> {
165        self.data()
166            .get(self.get_offset(index)?..self.get_offset(index + 1)?)
167            .ok_or(ReadError::OutOfBounds.into())
168    }
169}
170
171/// Reads an offset which is encoded as a variable sized integer.
172fn read_offset(
173    index: usize,
174    count: usize,
175    offset_size: u8,
176    offset_data: &[u8],
177) -> Result<usize, Error> {
178    // There are actually count + 1 entries in the offset array.
179    //
180    // "Offsets in the offset array are relative to the byte that precedes
181    // the object data. Therefore the first element of the offset array is
182    // always 1. (This ensures that every object has a corresponding offset
183    // which is always nonzero and permits the efficient implementation of
184    // dynamic object loading.)"
185    //
186    // See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-7-index-format>
187    if index > count {
188        Err(ReadError::OutOfBounds)?;
189    }
190    let data_offset = index * offset_size as usize;
191    let offset_data = FontData::new(offset_data);
192    match offset_size {
193        1 => offset_data.read_at::<u8>(data_offset)? as usize,
194        2 => offset_data.read_at::<u16>(data_offset)? as usize,
195        3 => offset_data.read_at::<Uint24>(data_offset)?.to_u32() as usize,
196        4 => offset_data.read_at::<u32>(data_offset)? as usize,
197        _ => return Err(Error::InvalidIndexOffsetSize(offset_size)),
198    }
199    // As above, subtract one to get the actual offset.
200    .checked_sub(1)
201    .ok_or(Error::ZeroOffsetInIndex)
202}
203
204#[cfg(test)]
205mod tests {
206    use font_test_data::bebuffer::BeBuffer;
207
208    use super::*;
209
210    enum IndexParams {
211        Format1 { off_size: u8, count: usize },
212        Format2 { off_size: u8, count: usize },
213    }
214
215    #[test]
216    fn index_format1_offsize1_count4() {
217        test_index(IndexParams::Format1 {
218            off_size: 1,
219            count: 4,
220        });
221    }
222
223    #[test]
224    fn index_format1_offsize2_count64() {
225        test_index(IndexParams::Format1 {
226            off_size: 2,
227            count: 64,
228        });
229    }
230
231    #[test]
232    fn index_format1_offsize3_count128() {
233        test_index(IndexParams::Format1 {
234            off_size: 3,
235            count: 128,
236        });
237    }
238
239    #[test]
240    fn index_format1_offsize4_count256() {
241        test_index(IndexParams::Format1 {
242            off_size: 4,
243            count: 256,
244        });
245    }
246
247    #[test]
248    fn index_format2_offsize1_count4() {
249        test_index(IndexParams::Format2 {
250            off_size: 4,
251            count: 256,
252        });
253    }
254
255    #[test]
256    fn index_format2_offsize2_count64() {
257        test_index(IndexParams::Format2 {
258            off_size: 2,
259            count: 64,
260        });
261    }
262
263    #[test]
264    fn index_format2_offsize3_count128() {
265        test_index(IndexParams::Format2 {
266            off_size: 3,
267            count: 128,
268        });
269    }
270
271    #[test]
272    fn index_format2_offsize4_count256() {
273        test_index(IndexParams::Format2 {
274            off_size: 4,
275            count: 256,
276        });
277    }
278
279    fn test_index(params: IndexParams) {
280        let (fmt, off_size, count) = match params {
281            IndexParams::Format1 { off_size, count } => (1, off_size, count),
282            IndexParams::Format2 { off_size, count } => (2, off_size, count),
283        };
284        let buf = make_index(fmt, off_size, count);
285        let index = Index::new(buf.data(), fmt == 2).unwrap();
286        let built_off_size = match &index {
287            Index::Empty => 0,
288            Index::Format1(v1) => v1.off_size(),
289            Index::Format2(v2) => v2.off_size(),
290        };
291        assert_eq!(built_off_size, off_size);
292        assert_eq!(index.count(), count as u32);
293        for i in 0..count {
294            let object = index.get(i).unwrap();
295            let expected_len = (i + 1) * 10;
296            let expected_bytes = vec![i as u8; expected_len];
297            assert_eq!(object, expected_bytes);
298        }
299    }
300
301    fn make_index(fmt: u8, off_size: u8, count: usize) -> BeBuffer {
302        // We'll add `count` objects to the INDEX, each containing
303        // `(i + 1) * 10` bytes of the value `i`.
304        let mut buf = BeBuffer::new();
305        match fmt {
306            1 => buf = buf.push(count as u16),
307            2 => buf = buf.push(count as u32),
308            _ => panic!("INDEX fmt should be 1 or 2"),
309        }
310        if count == 0 {
311            return buf;
312        }
313        buf = buf.push(off_size);
314        // Offsets start at 1.
315        let mut offset = 1usize;
316        for i in 0..count + 1 {
317            buf = match off_size {
318                1 => buf.push(offset as u8),
319                2 => buf.push(offset as u16),
320                3 => buf.push(Uint24::checked_new(offset as u32).unwrap()),
321                4 => buf.push(offset as u32),
322                _ => panic!("off_size should be 1-4"),
323            };
324            offset += (i + 1) * 10;
325        }
326        // Now the data
327        for i in 0..count {
328            buf = buf.extend(std::iter::repeat(i as u8).take((i + 1) * 10));
329        }
330        buf
331    }
332}