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