Skip to main content

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