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    pub fn off_size(&self) -> u8 {
81        match self {
82            Self::Empty => 0,
83            Self::Format1(ix) => ix.off_size(),
84            Self::Format2(ix) => ix.off_size(),
85        }
86    }
87}
88
89impl<'a> From<Index1<'a>> for Index<'a> {
90    fn from(value: Index1<'a>) -> Self {
91        Self::Format1(value)
92    }
93}
94
95impl<'a> From<Index2<'a>> for Index<'a> {
96    fn from(value: Index2<'a>) -> Self {
97        Self::Format2(value)
98    }
99}
100
101impl Default for Index<'_> {
102    fn default() -> Self {
103        Self::Empty
104    }
105}
106
107impl<'a> Index1<'a> {
108    /// Returns the total size in bytes of the index table.
109    pub fn size_in_bytes(&self) -> Result<usize, ReadError> {
110        // 2 byte count + 1 byte off_size
111        const HEADER_SIZE: usize = 3;
112        // An empty CFF index contains only a 2 byte count field
113        const EMPTY_SIZE: usize = 2;
114        let count = self.count() as usize;
115        Ok(match count {
116            0 => EMPTY_SIZE,
117            _ => {
118                HEADER_SIZE
119                    + self.offsets().len()
120                    + self.get_offset(count).map_err(|_| ReadError::OutOfBounds)?
121            }
122        })
123    }
124
125    /// Returns the offset of the object at the given index.
126    pub fn get_offset(&self, index: usize) -> Result<usize, Error> {
127        read_offset(
128            index,
129            self.count() as usize,
130            self.off_size(),
131            self.offsets(),
132        )
133    }
134
135    /// Returns the data for the object at the given index.
136    pub fn get(&self, index: usize) -> Result<&'a [u8], Error> {
137        self.data()
138            .get(self.get_offset(index)?..self.get_offset(index + 1)?)
139            .ok_or(ReadError::OutOfBounds.into())
140    }
141}
142
143impl<'a> Index2<'a> {
144    /// Returns the total size in bytes of the index table.
145    pub fn size_in_bytes(&self) -> Result<usize, ReadError> {
146        // 4 byte count + 1 byte off_size
147        const HEADER_SIZE: usize = 5;
148        // An empty CFF2 index contains only a 4 byte count field
149        const EMPTY_SIZE: usize = 4;
150        let count = self.count() as usize;
151        Ok(match count {
152            0 => EMPTY_SIZE,
153            _ => {
154                HEADER_SIZE
155                    + self.offsets().len()
156                    + self.get_offset(count).map_err(|_| ReadError::OutOfBounds)?
157            }
158        })
159    }
160
161    /// Returns the offset of the object at the given index.
162    pub fn get_offset(&self, index: usize) -> Result<usize, Error> {
163        read_offset(
164            index,
165            self.count() as usize,
166            self.off_size(),
167            self.offsets(),
168        )
169    }
170
171    /// Returns the data for the object at the given index.
172    pub fn get(&self, index: usize) -> Result<&'a [u8], Error> {
173        self.data()
174            .get(self.get_offset(index)?..self.get_offset(index + 1)?)
175            .ok_or(ReadError::OutOfBounds.into())
176    }
177}
178
179/// Reads an offset which is encoded as a variable sized integer.
180fn read_offset(
181    index: usize,
182    count: usize,
183    offset_size: u8,
184    offset_data: &[u8],
185) -> Result<usize, Error> {
186    // There are actually count + 1 entries in the offset array.
187    //
188    // "Offsets in the offset array are relative to the byte that precedes
189    // the object data. Therefore the first element of the offset array is
190    // always 1. (This ensures that every object has a corresponding offset
191    // which is always nonzero and permits the efficient implementation of
192    // dynamic object loading.)"
193    //
194    // See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#table-7-index-format>
195    if index > count {
196        Err(ReadError::OutOfBounds)?;
197    }
198    let data_offset = index * offset_size as usize;
199    let offset_data = FontData::new(offset_data);
200    match offset_size {
201        1 => offset_data.read_at::<u8>(data_offset)? as usize,
202        2 => offset_data.read_at::<u16>(data_offset)? as usize,
203        3 => offset_data.read_at::<Uint24>(data_offset)?.to_u32() as usize,
204        4 => offset_data.read_at::<u32>(data_offset)? as usize,
205        _ => return Err(Error::InvalidIndexOffsetSize(offset_size)),
206    }
207    // As above, subtract one to get the actual offset.
208    .checked_sub(1)
209    .ok_or(Error::ZeroOffsetInIndex)
210}
211
212#[cfg(test)]
213mod tests {
214    use font_test_data::bebuffer::BeBuffer;
215
216    use super::*;
217
218    enum IndexParams {
219        Format1 { off_size: u8, count: usize },
220        Format2 { off_size: u8, count: usize },
221    }
222
223    #[test]
224    fn index_format1_offsize1_count4() {
225        test_index(IndexParams::Format1 {
226            off_size: 1,
227            count: 4,
228        });
229    }
230
231    #[test]
232    fn index_format1_offsize2_count64() {
233        test_index(IndexParams::Format1 {
234            off_size: 2,
235            count: 64,
236        });
237    }
238
239    #[test]
240    fn index_format1_offsize3_count128() {
241        test_index(IndexParams::Format1 {
242            off_size: 3,
243            count: 128,
244        });
245    }
246
247    #[test]
248    fn index_format1_offsize4_count256() {
249        test_index(IndexParams::Format1 {
250            off_size: 4,
251            count: 256,
252        });
253    }
254
255    #[test]
256    fn index_format2_offsize1_count4() {
257        test_index(IndexParams::Format2 {
258            off_size: 4,
259            count: 256,
260        });
261    }
262
263    #[test]
264    fn index_format2_offsize2_count64() {
265        test_index(IndexParams::Format2 {
266            off_size: 2,
267            count: 64,
268        });
269    }
270
271    #[test]
272    fn index_format2_offsize3_count128() {
273        test_index(IndexParams::Format2 {
274            off_size: 3,
275            count: 128,
276        });
277    }
278
279    #[test]
280    fn index_format2_offsize4_count256() {
281        test_index(IndexParams::Format2 {
282            off_size: 4,
283            count: 256,
284        });
285    }
286
287    fn test_index(params: IndexParams) {
288        let (fmt, off_size, count) = match params {
289            IndexParams::Format1 { off_size, count } => (1, off_size, count),
290            IndexParams::Format2 { off_size, count } => (2, off_size, count),
291        };
292        let buf = make_index(fmt, off_size, count);
293        let index = Index::new(buf.data(), fmt == 2).unwrap();
294        let built_off_size = match &index {
295            Index::Empty => 0,
296            Index::Format1(v1) => v1.off_size(),
297            Index::Format2(v2) => v2.off_size(),
298        };
299        assert_eq!(built_off_size, off_size);
300        assert_eq!(index.count(), count as u32);
301        for i in 0..count {
302            let object = index.get(i).unwrap();
303            let expected_len = (i + 1) * 10;
304            let expected_bytes = vec![i as u8; expected_len];
305            assert_eq!(object, expected_bytes);
306        }
307    }
308
309    fn make_index(fmt: u8, off_size: u8, count: usize) -> BeBuffer {
310        // We'll add `count` objects to the INDEX, each containing
311        // `(i + 1) * 10` bytes of the value `i`.
312        let mut buf = BeBuffer::new();
313        match fmt {
314            1 => buf = buf.push(count as u16),
315            2 => buf = buf.push(count as u32),
316            _ => panic!("INDEX fmt should be 1 or 2"),
317        }
318        if count == 0 {
319            return buf;
320        }
321        buf = buf.push(off_size);
322        // Offsets start at 1.
323        let mut offset = 1usize;
324        for i in 0..count + 1 {
325            buf = match off_size {
326                1 => buf.push(offset as u8),
327                2 => buf.push(offset as u16),
328                3 => buf.push(Uint24::checked_new(offset as u32).unwrap()),
329                4 => buf.push(offset as u32),
330                _ => panic!("off_size should be 1-4"),
331            };
332            offset += (i + 1) * 10;
333        }
334        // Now the data
335        for i in 0..count {
336            buf = buf.extend(std::iter::repeat(i as u8).take((i + 1) * 10));
337        }
338        buf
339    }
340}