Skip to main content

read_fonts/tables/layout/
header.rs

1//! Header-selected lookup lists and offset arrays of more than one width.
2
3use super::*;
4
5/// An offset array whose serialized offsets have one of the layout widths.
6#[derive(Clone)]
7pub enum LayoutOffsetArray<'a, T: FontRead<'a, Args = ()>> {
8    /// 16-bit offsets.
9    Offset16(ArrayOfOffsets<'a, T, Offset16>),
10    /// 24-bit offsets.
11    Offset24(ArrayOfOffsets<'a, T, Offset24>),
12    /// 32-bit offsets.
13    Offset32(ArrayOfOffsets<'a, T, Offset32>),
14}
15
16impl<'a, T: FontRead<'a, Args = ()> + 'a> LayoutOffsetArray<'a, T> {
17    /// Returns the number of offsets.
18    pub fn len(&self) -> usize {
19        match self {
20            Self::Offset16(array) => array.len(),
21            Self::Offset24(array) => array.len(),
22            Self::Offset32(array) => array.len(),
23        }
24    }
25
26    /// Returns `true` if there are no offsets.
27    pub fn is_empty(&self) -> bool {
28        self.len() == 0
29    }
30
31    /// Resolves the offset at `index`.
32    pub fn get(&self, index: usize) -> Result<T, ReadError> {
33        match self {
34            Self::Offset16(array) => array.get(index),
35            Self::Offset24(array) => array.get(index),
36            Self::Offset32(array) => array.get(index),
37        }
38    }
39
40    /// Iterates over the resolved offsets.
41    pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
42        let (one, two, three) = match self {
43            Self::Offset16(array) => (Some(array.iter()), None, None),
44            Self::Offset24(array) => (None, Some(array.iter()), None),
45            Self::Offset32(array) => (None, None, Some(array.iter())),
46        };
47        one.into_iter()
48            .flatten()
49            .chain(two.into_iter().flatten())
50            .chain(three.into_iter().flatten())
51    }
52
53    /// Iterates over the resolved offsets, with null offsets as `None`.
54    pub fn iter_as_nullable(&self) -> impl Iterator<Item = Option<Result<T, ReadError>>> + 'a {
55        self.iter().map(|result| match result {
56            Err(ReadError::NullOffset) => None,
57            other => Some(other),
58        })
59    }
60}
61
62/// The lookup list of a GSUB or GPOS table: the original LookupList, or
63/// the LookupList2 of a version 1.2 header.
64#[derive(Clone)]
65pub enum SelectedLookupList<'a, T: FontRead<'a, Args = ()>> {
66    /// A LookupList, with 16-bit offsets.
67    Original(LookupList<'a, T>),
68    /// A LookupList2, with 32-bit offsets.
69    Extended(LookupList2<'a, T>),
70}
71
72impl<'a, T: FontRead<'a, Args = ()>> From<LookupList<'a, T>> for SelectedLookupList<'a, T> {
73    fn from(list: LookupList<'a, T>) -> Self {
74        Self::Original(list)
75    }
76}
77
78impl<'a, T: FontRead<'a, Args = ()>> From<LookupList2<'a, T>> for SelectedLookupList<'a, T> {
79    fn from(list: LookupList2<'a, T>) -> Self {
80        Self::Extended(list)
81    }
82}
83
84impl<'a, T: FontRead<'a, Args = ()> + 'a> SelectedLookupList<'a, T> {
85    /// Returns the number of lookups.
86    pub fn lookup_count(&self) -> u16 {
87        match self {
88            Self::Original(table) => table.lookup_count(),
89            Self::Extended(table) => table.lookup_count(),
90        }
91    }
92
93    /// Returns the offsets to the lookups.
94    pub fn lookups(&self) -> LayoutOffsetArray<'a, T> {
95        match self {
96            Self::Original(table) => LayoutOffsetArray::Offset16(table.lookups()),
97            Self::Extended(table) => LayoutOffsetArray::Offset32(table.lookups()),
98        }
99    }
100
101    /// Returns the data the lookup offsets are relative to.
102    pub fn offset_data(&self) -> FontData<'a> {
103        match self {
104            Self::Original(table) => table.offset_data(),
105            Self::Extended(table) => table.offset_data(),
106        }
107    }
108}
109
110/// The offset a header field resolves through, for headers that carry both
111/// an original 16-bit offset and an extended 32-bit one.
112///
113/// The extended offset applies when the header version has it and it is
114/// nonzero; otherwise the original one does.
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116pub(crate) enum HeaderOffset {
117    Original(Offset32),
118    Extended(Offset32),
119}
120
121impl HeaderOffset {
122    /// Chooses between the two offsets of a header field. An extended header
123    /// too short to hold its extended offset is an error.
124    pub(crate) fn select(
125        original: Nullable<Offset16>,
126        extended: Option<Nullable<Offset32>>,
127        has_extended: bool,
128    ) -> Result<Self, ReadError> {
129        if has_extended {
130            let extended = *extended.ok_or(ReadError::OutOfBounds)?.offset();
131            if !extended.is_null() {
132                return Ok(Self::Extended(extended));
133            }
134        }
135        Ok(Self::Original(Offset32::new(original.offset().to_u32())))
136    }
137
138    pub(crate) fn offset(self) -> Offset32 {
139        match self {
140            Self::Original(offset) | Self::Extended(offset) => offset,
141        }
142    }
143
144    pub(crate) fn is_null(self) -> bool {
145        self.offset().is_null()
146    }
147
148    /// Resolves the offset, or returns `None` if it is null.
149    pub(crate) fn resolve<'a, T: FontRead<'a, Args = ()>>(
150        self,
151        data: FontData<'a>,
152    ) -> Option<Result<T, ReadError>> {
153        (!self.is_null()).then(|| self.offset().resolve(data))
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    fn original(offset: u16) -> Nullable<Offset16> {
162        Scalar::from_raw(offset.to_be_bytes())
163    }
164
165    fn extended(offset: u32) -> Option<Nullable<Offset32>> {
166        Some(Scalar::from_raw(offset.to_be_bytes()))
167    }
168
169    #[test]
170    fn header_offset_prefers_nonzero_extended_offset() {
171        assert_eq!(
172            HeaderOffset::select(original(10), extended(70000), true),
173            Ok(HeaderOffset::Extended(Offset32::new(70000)))
174        );
175    }
176
177    #[test]
178    fn header_offset_falls_back_to_original_offset() {
179        // A zero extended offset defers to the original one.
180        assert_eq!(
181            HeaderOffset::select(original(10), extended(0), true),
182            Ok(HeaderOffset::Original(Offset32::new(10)))
183        );
184        // A header version without the extended field ignores it.
185        assert_eq!(
186            HeaderOffset::select(original(10), extended(70000), false),
187            Ok(HeaderOffset::Original(Offset32::new(10)))
188        );
189        assert!(HeaderOffset::select(original(0), extended(0), true)
190            .unwrap()
191            .is_null());
192    }
193
194    #[test]
195    fn header_offset_requires_extended_field_in_extended_header() {
196        assert_eq!(
197            HeaderOffset::select(original(10), None, true),
198            Err(ReadError::OutOfBounds)
199        );
200        assert_eq!(
201            HeaderOffset::select(original(10), None, false),
202            Ok(HeaderOffset::Original(Offset32::new(10)))
203        );
204    }
205}