Skip to main content

read_fonts/tables/
value_record.rs

1//! A GPOS ValueRecord
2
3use font_types::Nullable;
4use types::{FixedSize, Offset16};
5
6use super::ValueFormat;
7use crate::{tables::layout::DeviceOrVariationIndex, ResolveNullableOffset};
8
9use crate::{ComputeSize, FontData, FontReadAt, ReadArgs, ReadError};
10
11impl ValueFormat {
12    /// A mask with all the device/variation index bits set
13    pub const ANY_DEVICE_OR_VARIDX: Self = ValueFormat {
14        bits: 0x0010 | 0x0020 | 0x0040 | 0x0080,
15    };
16
17    /// Return the number of bytes required to store a [`ValueRecord`] in this format.
18    #[inline]
19    pub fn record_byte_len(self) -> usize {
20        self.bits().count_ones() as usize * u16::RAW_BYTE_LEN
21    }
22}
23
24/// A GPOS ValueRecord, with fields read on demand.
25///
26/// The contents of a value record are described by a [`ValueFormat`] stored in
27/// the parent table, so a record cannot be located or interpreted on its own.
28/// This type stores the position of the record within its parent table's data
29/// alongside that format, and reads individual fields only when they are asked
30/// for; constructing one performs no reads at all.
31#[derive(Copy, Clone, Default)]
32pub struct ValueRecord<'a> {
33    /// The offset data of the table *containing* the record.
34    data: FontData<'a>,
35    /// The position of the record within `data`.
36    offset: u32,
37    format: ValueFormat,
38}
39
40impl<'a> ValueRecord<'a> {
41    /// Creates a value record positioned at `offset` within `data`.
42    ///
43    /// `data` must be the offset data of the table containing the record, and
44    /// not merely the bytes of the record itself: the device and variation
45    /// index offsets in a value record are resolved relative to that table.
46    #[inline]
47    pub fn new(data: FontData<'a>, offset: usize, format: ValueFormat) -> Self {
48        Self {
49            data,
50            // an offset that doesn't fit is out of bounds by definition, and
51            // saturating here lets every subsequent read fail cleanly
52            offset: u32::try_from(offset).unwrap_or(u32::MAX),
53            format,
54        }
55    }
56
57    /// The format describing which fields this record contains.
58    #[inline]
59    pub fn format(&self) -> ValueFormat {
60        self.format
61    }
62
63    /// The number of bytes occupied by this record.
64    #[inline]
65    pub fn byte_len(&self) -> usize {
66        self.format.record_byte_len()
67    }
68
69    /// Returns `true` if this record contains no fields.
70    #[inline]
71    pub fn is_empty(&self) -> bool {
72        self.format.is_empty()
73    }
74
75    /// The offset data of the table containing this record.
76    #[inline]
77    pub fn offset_data(&self) -> FontData<'a> {
78        self.data
79    }
80
81    /// The position of this record within [`offset_data`](Self::offset_data).
82    #[inline]
83    pub fn offset(&self) -> usize {
84        self.offset as usize
85    }
86
87    #[inline]
88    pub fn x_placement(&self) -> Option<i16> {
89        self.read_i16(ValueFormat::X_PLACEMENT)
90    }
91
92    #[inline]
93    pub fn y_placement(&self) -> Option<i16> {
94        self.read_i16(ValueFormat::Y_PLACEMENT)
95    }
96
97    #[inline]
98    pub fn x_advance(&self) -> Option<i16> {
99        self.read_i16(ValueFormat::X_ADVANCE)
100    }
101
102    #[inline]
103    pub fn y_advance(&self) -> Option<i16> {
104        self.read_i16(ValueFormat::Y_ADVANCE)
105    }
106
107    #[inline]
108    pub fn x_placement_device(&self) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
109        self.read_device(ValueFormat::X_PLACEMENT_DEVICE)
110    }
111
112    #[inline]
113    pub fn y_placement_device(&self) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
114        self.read_device(ValueFormat::Y_PLACEMENT_DEVICE)
115    }
116
117    #[inline]
118    pub fn x_advance_device(&self) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
119        self.read_device(ValueFormat::X_ADVANCE_DEVICE)
120    }
121
122    #[inline]
123    pub fn y_advance_device(&self) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
124        self.read_device(ValueFormat::Y_ADVANCE_DEVICE)
125    }
126
127    /// Returns the raw, unresolved offset for the given device field.
128    ///
129    /// The returned offset is relative to [`offset_data`](Self::offset_data).
130    /// Returns `None` if the field is not present in this record's format, or
131    /// if the record is truncated.
132    #[inline]
133    pub fn device_offset(&self, field: ValueFormat) -> Option<Nullable<Offset16>> {
134        self.data.read_at(self.field_offset(field)?).ok()
135    }
136
137    /// The raw bytes of this record, or `None` if the data is truncated.
138    #[inline]
139    pub fn bytes(&self) -> Option<&'a [u8]> {
140        let start = self.offset as usize;
141        self.data
142            .as_bytes()
143            .get(start..start.checked_add(self.byte_len())?)
144    }
145
146    /// Returns the position of `field` within [`offset_data`](Self::offset_data),
147    /// or `None` if this record's format doesn't include it.
148    ///
149    /// Fields are laid out in the order of their format bits and each occupies
150    /// two bytes, so a field's position is fixed by the number of lower format
151    /// bits that are set.
152    #[inline]
153    fn field_offset(&self, field: ValueFormat) -> Option<usize> {
154        if !self.format.contains(field) {
155            return None;
156        }
157        let preceding = (self.format.bits() & (field.bits() - 1)).count_ones() as usize;
158        Some(self.offset as usize + preceding * u16::RAW_BYTE_LEN)
159    }
160
161    #[inline]
162    fn read_i16(&self, field: ValueFormat) -> Option<i16> {
163        self.data.read_at(self.field_offset(field)?).ok()
164    }
165
166    #[inline]
167    fn read_device(
168        &self,
169        field: ValueFormat,
170    ) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
171        let pos = self.field_offset(field)?;
172        match self.data.read_at::<Nullable<Offset16>>(pos) {
173            Ok(offset) => offset.resolve(self.data),
174            Err(err) => Some(Err(err)),
175        }
176    }
177}
178
179impl ReadArgs for ValueRecord<'_> {
180    type Args = ValueFormat;
181}
182
183impl ComputeSize for ValueRecord<'_> {
184    #[inline]
185    fn compute_size(args: ValueFormat) -> Result<usize, ReadError> {
186        Ok(args.record_byte_len())
187    }
188}
189
190impl<'a> FontReadAt<'a> for ValueRecord<'a> {
191    #[inline]
192    fn read_at(data: FontData<'a>, offset: usize, args: ValueFormat) -> Result<Self, ReadError> {
193        Ok(Self::new(data, offset, args))
194    }
195}
196
197/// Two records are equal when they describe the same positioning: same format,
198/// and the same bytes for the fields that format selects.
199impl PartialEq for ValueRecord<'_> {
200    fn eq(&self, other: &Self) -> bool {
201        self.format == other.format && self.bytes() == other.bytes()
202    }
203}
204
205impl Eq for ValueRecord<'_> {}
206
207impl std::fmt::Debug for ValueRecord<'_> {
208    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
209        let mut f = f.debug_struct("ValueRecord");
210        self.x_placement().map(|x| f.field("x_placement", &x));
211        self.y_placement().map(|y| f.field("y_placement", &y));
212        self.x_advance().map(|x| f.field("x_advance", &x));
213        self.y_advance().map(|y| f.field("y_advance", &y));
214        for (name, field) in [
215            ("x_placement_device", ValueFormat::X_PLACEMENT_DEVICE),
216            ("y_placement_device", ValueFormat::Y_PLACEMENT_DEVICE),
217            ("x_advance_device", ValueFormat::X_ADVANCE_DEVICE),
218            ("y_advance_device", ValueFormat::Y_ADVANCE_DEVICE),
219        ] {
220            match self.device_offset(field) {
221                Some(offset) if !offset.is_null() => {
222                    f.field(name, &offset);
223                }
224                _ => (),
225            }
226        }
227        f.finish()
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn sanity_check_format_const() {
237        let format = ValueFormat::X_ADVANCE_DEVICE
238            | ValueFormat::Y_ADVANCE_DEVICE
239            | ValueFormat::Y_PLACEMENT_DEVICE
240            | ValueFormat::X_PLACEMENT_DEVICE;
241        assert_eq!(format, ValueFormat::ANY_DEVICE_OR_VARIDX);
242        assert_eq!(format.record_byte_len(), 4 * 2);
243    }
244
245    /// Walks the fields in order, the way the spec describes the layout, and
246    /// returns the position of each present field. This is the straightforward
247    /// reading of the format that [`ValueRecord`] replaces with a popcount.
248    fn reference_field_positions(format: ValueFormat) -> Vec<(ValueFormat, usize)> {
249        let mut pos = 0;
250        let mut out = Vec::new();
251        for field in [
252            ValueFormat::X_PLACEMENT,
253            ValueFormat::Y_PLACEMENT,
254            ValueFormat::X_ADVANCE,
255            ValueFormat::Y_ADVANCE,
256            ValueFormat::X_PLACEMENT_DEVICE,
257            ValueFormat::Y_PLACEMENT_DEVICE,
258            ValueFormat::X_ADVANCE_DEVICE,
259            ValueFormat::Y_ADVANCE_DEVICE,
260        ] {
261            if format.contains(field) {
262                out.push((field, pos));
263                pos += 2;
264            }
265        }
266        out
267    }
268
269    /// A value record locates its fields with a popcount over the format bits.
270    /// That must agree with walking the fields in order, for every combination
271    /// of bits.
272    #[test]
273    fn field_offsets_match_sequential_layout() {
274        // give every field slot a distinct, recognizable value
275        let bytes: Vec<u8> = (0..8u16).flat_map(|i| (0x1100 + i).to_be_bytes()).collect();
276        // pad the front so we exercise a non-zero record offset
277        const PAD: usize = 6;
278        let mut padded = vec![0u8; PAD];
279        padded.extend_from_slice(&bytes);
280        let data = FontData::new(&padded);
281
282        for bits in 0..=u8::MAX {
283            let format = ValueFormat { bits: bits as u16 };
284            let record = ValueRecord::new(data, PAD, format);
285
286            assert_eq!(record.format(), format);
287            assert_eq!(record.byte_len(), format.record_byte_len());
288
289            let expected = reference_field_positions(format);
290            assert_eq!(expected.len() * 2, record.byte_len(), "{format:?}");
291
292            for (field, offset) in expected {
293                // the value planted at that position in the record
294                let want = 0x1100u16 + (offset / 2) as u16;
295                let got = match field {
296                    ValueFormat::X_PLACEMENT => record.x_placement().map(|v| v as u16),
297                    ValueFormat::Y_PLACEMENT => record.y_placement().map(|v| v as u16),
298                    ValueFormat::X_ADVANCE => record.x_advance().map(|v| v as u16),
299                    ValueFormat::Y_ADVANCE => record.y_advance().map(|v| v as u16),
300                    other => record
301                        .device_offset(other)
302                        .map(|off| off.offset().to_u32() as u16),
303                };
304                assert_eq!(got, Some(want), "{format:?} {field:?} at {offset}");
305            }
306
307            // absent fields must report absent, not read a neighbour
308            for field in [
309                ValueFormat::X_PLACEMENT,
310                ValueFormat::Y_PLACEMENT,
311                ValueFormat::X_ADVANCE,
312                ValueFormat::Y_ADVANCE,
313            ] {
314                if !format.contains(field) {
315                    let got = match field {
316                        ValueFormat::X_PLACEMENT => record.x_placement(),
317                        ValueFormat::Y_PLACEMENT => record.y_placement(),
318                        ValueFormat::X_ADVANCE => record.x_advance(),
319                        _ => record.y_advance(),
320                    };
321                    assert_eq!(got, None, "{format:?} {field:?}");
322                }
323            }
324            for field in [
325                ValueFormat::X_PLACEMENT_DEVICE,
326                ValueFormat::Y_PLACEMENT_DEVICE,
327                ValueFormat::X_ADVANCE_DEVICE,
328                ValueFormat::Y_ADVANCE_DEVICE,
329            ] {
330                if !format.contains(field) {
331                    assert_eq!(record.device_offset(field), None, "{format:?} {field:?}");
332                }
333            }
334        }
335    }
336
337    /// Reads past the end of the data must fail cleanly rather than panic or
338    /// read a neighbouring field.
339    #[test]
340    fn lazy_fields_out_of_bounds() {
341        let format = ValueFormat::X_PLACEMENT | ValueFormat::Y_ADVANCE;
342        // only enough room for the first of the two fields
343        let bytes = [0u8, 1, 0, 2];
344        let lazy = ValueRecord::new(FontData::new(&bytes), 2, format);
345        assert_eq!(lazy.x_placement(), Some(2));
346        assert_eq!(lazy.y_advance(), None);
347
348        // an offset that can't even be represented
349        let huge = ValueRecord::new(FontData::new(&bytes), usize::MAX, format);
350        assert_eq!(huge.x_placement(), None);
351    }
352}