read_fonts/tables/
value_record.rs

1//! A GPOS ValueRecord
2
3use font_types::Nullable;
4use types::{BigEndian, F2Dot14, FixedSize, Offset16};
5
6use super::ValueFormat;
7use crate::{
8    tables::{
9        layout::DeviceOrVariationIndex,
10        variations::{DeltaSetIndex, ItemVariationStore},
11    },
12    ResolveNullableOffset,
13};
14
15#[cfg(feature = "experimental_traverse")]
16use crate::traversal::{Field, FieldType, RecordResolver, SomeRecord};
17use crate::{ComputeSize, FontData, FontReadWithArgs, ReadArgs, ReadError};
18
19impl ValueFormat {
20    /// A mask with all the device/variation index bits set
21    pub const ANY_DEVICE_OR_VARIDX: Self = ValueFormat {
22        bits: 0x0010 | 0x0020 | 0x0040 | 0x0080,
23    };
24
25    /// Return the number of bytes required to store a [`ValueRecord`] in this format.
26    #[inline]
27    pub fn record_byte_len(self) -> usize {
28        self.bits().count_ones() as usize * u16::RAW_BYTE_LEN
29    }
30}
31
32/// A context for resolving [`Value`]s and [`ValueRecord`]s.
33///
34/// In particular, this handles processing of the embedded
35/// [`DeviceOrVariationIndex`] tables.
36#[derive(Clone, Default)]
37pub struct ValueContext<'a> {
38    coords: &'a [F2Dot14],
39    var_store: Option<ItemVariationStore<'a>>,
40}
41
42impl<'a> ValueContext<'a> {
43    /// Creates a new value context that doesn't do any additional processing.
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Sets the normalized variation coordinates for this value context.
49    pub fn with_coords(mut self, coords: &'a [F2Dot14]) -> Self {
50        self.coords = coords;
51        self
52    }
53
54    /// Sets the item variation store for this value context.
55    ///
56    /// This comes from the [`Gdef`](super::super::gdef::Gdef) table.
57    pub fn with_var_store(mut self, var_store: Option<ItemVariationStore<'a>>) -> Self {
58        self.var_store = var_store;
59        self
60    }
61
62    fn var_store_and_coords(&self) -> Option<(&ItemVariationStore<'a>, &'a [F2Dot14])> {
63        Some((self.var_store.as_ref()?, self.coords))
64    }
65}
66
67/// A fully resolved [`ValueRecord`].
68#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
69pub struct Value {
70    pub format: ValueFormat,
71    pub x_placement: i16,
72    pub y_placement: i16,
73    pub x_advance: i16,
74    pub y_advance: i16,
75    pub x_placement_delta: i32,
76    pub y_placement_delta: i32,
77    pub x_advance_delta: i32,
78    pub y_advance_delta: i32,
79}
80
81impl Value {
82    /// Reads a value directly from font data.
83    ///
84    /// The `offset_data` parameter must be the offset data for the table
85    /// containing the value record.
86    #[inline]
87    pub fn read(
88        offset_data: FontData,
89        offset: usize,
90        format: ValueFormat,
91        context: &ValueContext,
92    ) -> Result<Self, ReadError> {
93        let mut value = Self {
94            format,
95            ..Default::default()
96        };
97        let mut cursor = offset_data.cursor();
98        cursor.advance_by(offset);
99        if format.contains(ValueFormat::X_PLACEMENT) {
100            value.x_placement = cursor.read()?;
101        }
102        if format.contains(ValueFormat::Y_PLACEMENT) {
103            value.y_placement = cursor.read()?;
104        }
105        if format.contains(ValueFormat::X_ADVANCE) {
106            value.x_advance = cursor.read()?;
107        }
108        if format.contains(ValueFormat::Y_ADVANCE) {
109            value.y_advance = cursor.read()?;
110        }
111        if !format.contains(ValueFormat::ANY_DEVICE_OR_VARIDX) {
112            return Ok(value);
113        }
114        if let Some((ivs, coords)) = context.var_store_and_coords() {
115            let compute_delta = |offset: u16| {
116                let rec_offset = offset_data.read_at::<u16>(offset as usize).ok()? as usize;
117                let format = offset_data.read_at::<u16>(rec_offset + 4).ok()?;
118                // DeltaFormat specifier for a VariationIndex table
119                // See <https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#device-and-variationindex-tables>
120                const VARIATION_INDEX_FORMAT: u16 = 0x8000;
121                if format != VARIATION_INDEX_FORMAT {
122                    return Some(0);
123                }
124                let outer = offset_data.read_at::<u16>(rec_offset).ok()?;
125                let inner = offset_data.read_at::<u16>(rec_offset + 2).ok()?;
126                ivs.compute_delta(DeltaSetIndex { outer, inner }, coords)
127                    .ok()
128            };
129            if format.contains(ValueFormat::X_PLACEMENT_DEVICE) {
130                value.x_placement_delta = compute_delta(cursor.read()?).unwrap_or_default();
131            }
132            if format.contains(ValueFormat::Y_PLACEMENT_DEVICE) {
133                value.y_placement_delta = compute_delta(cursor.read()?).unwrap_or_default();
134            }
135            if format.contains(ValueFormat::X_ADVANCE_DEVICE) {
136                value.x_advance_delta = compute_delta(cursor.read()?).unwrap_or_default();
137            }
138            if format.contains(ValueFormat::Y_ADVANCE_DEVICE) {
139                value.y_advance_delta = compute_delta(cursor.read()?).unwrap_or_default();
140            }
141        }
142        Ok(value)
143    }
144}
145
146/// A Positioning ValueRecord.
147///
148/// NOTE: we create these manually, since parsing is weird and depends on the
149/// associated valueformat. That said, this isn't a great representation?
150/// we could definitely do something much more in the zero-copy mode..
151#[derive(Clone, Default, Eq)]
152pub struct ValueRecord {
153    pub x_placement: Option<BigEndian<i16>>,
154    pub y_placement: Option<BigEndian<i16>>,
155    pub x_advance: Option<BigEndian<i16>>,
156    pub y_advance: Option<BigEndian<i16>>,
157    pub x_placement_device: BigEndian<Nullable<Offset16>>,
158    pub y_placement_device: BigEndian<Nullable<Offset16>>,
159    pub x_advance_device: BigEndian<Nullable<Offset16>>,
160    pub y_advance_device: BigEndian<Nullable<Offset16>>,
161    #[doc(hidden)]
162    // exposed so that we can preserve format when we round-trip a value record
163    pub format: ValueFormat,
164}
165
166// we ignore the format for the purpose of equality testing, it's redundant
167impl PartialEq for ValueRecord {
168    fn eq(&self, other: &Self) -> bool {
169        self.x_placement == other.x_placement
170            && self.y_placement == other.y_placement
171            && self.x_advance == other.x_advance
172            && self.y_advance == other.y_advance
173            && self.x_placement_device == other.x_placement_device
174            && self.y_placement_device == other.y_placement_device
175            && self.x_advance_device == other.x_advance_device
176            && self.y_advance_device == other.y_advance_device
177    }
178}
179
180impl ValueRecord {
181    pub fn read(data: FontData, format: ValueFormat) -> Result<Self, ReadError> {
182        let mut this = ValueRecord {
183            format,
184            ..Default::default()
185        };
186        let mut cursor = data.cursor();
187
188        if format.contains(ValueFormat::X_PLACEMENT) {
189            this.x_placement = Some(cursor.read_be()?);
190        }
191        if format.contains(ValueFormat::Y_PLACEMENT) {
192            this.y_placement = Some(cursor.read_be()?);
193        }
194        if format.contains(ValueFormat::X_ADVANCE) {
195            this.x_advance = Some(cursor.read_be()?);
196        }
197        if format.contains(ValueFormat::Y_ADVANCE) {
198            this.y_advance = Some(cursor.read_be()?);
199        }
200        if format.contains(ValueFormat::X_PLACEMENT_DEVICE) {
201            this.x_placement_device = cursor.read_be()?;
202        }
203        if format.contains(ValueFormat::Y_PLACEMENT_DEVICE) {
204            this.y_placement_device = cursor.read_be()?;
205        }
206        if format.contains(ValueFormat::X_ADVANCE_DEVICE) {
207            this.x_advance_device = cursor.read_be()?;
208        }
209        if format.contains(ValueFormat::Y_ADVANCE_DEVICE) {
210            this.y_advance_device = cursor.read_be()?;
211        }
212        Ok(this)
213    }
214
215    pub fn x_placement(&self) -> Option<i16> {
216        self.x_placement.map(|val| val.get())
217    }
218
219    pub fn y_placement(&self) -> Option<i16> {
220        self.y_placement.map(|val| val.get())
221    }
222
223    pub fn x_advance(&self) -> Option<i16> {
224        self.x_advance.map(|val| val.get())
225    }
226
227    pub fn y_advance(&self) -> Option<i16> {
228        self.y_advance.map(|val| val.get())
229    }
230
231    pub fn x_placement_device<'a>(
232        &self,
233        data: FontData<'a>,
234    ) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
235        self.x_placement_device.get().resolve(data)
236    }
237
238    pub fn y_placement_device<'a>(
239        &self,
240        data: FontData<'a>,
241    ) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
242        self.y_placement_device.get().resolve(data)
243    }
244
245    pub fn x_advance_device<'a>(
246        &self,
247        data: FontData<'a>,
248    ) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
249        self.x_advance_device.get().resolve(data)
250    }
251
252    pub fn y_advance_device<'a>(
253        &self,
254        data: FontData<'a>,
255    ) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
256        self.y_advance_device.get().resolve(data)
257    }
258
259    /// Returns a resolved value for the given normalized coordinates and
260    /// item variation store.
261    ///
262    /// The `offset_data` parameter must be the offset data for the table
263    /// containing the value record.
264    pub fn value(&self, offset_data: FontData, context: &ValueContext) -> Result<Value, ReadError> {
265        let mut value = Value {
266            format: self.format,
267            x_placement: self.x_placement.unwrap_or_default().get(),
268            y_placement: self.y_placement.unwrap_or_default().get(),
269            x_advance: self.x_advance.unwrap_or_default().get(),
270            y_advance: self.y_advance.unwrap_or_default().get(),
271            ..Default::default()
272        };
273        if let Some((ivs, coords)) = context.var_store_and_coords() {
274            let compute_delta = |value: DeviceOrVariationIndex| match value {
275                DeviceOrVariationIndex::VariationIndex(var_idx) => {
276                    let outer = var_idx.delta_set_outer_index();
277                    let inner = var_idx.delta_set_inner_index();
278                    ivs.compute_delta(DeltaSetIndex { outer, inner }, coords)
279                        .ok()
280                }
281                _ => None,
282            };
283            if let Some(device) = self.x_placement_device(offset_data) {
284                value.x_placement_delta = compute_delta(device?).unwrap_or_default();
285            }
286            if let Some(device) = self.y_placement_device(offset_data) {
287                value.y_placement_delta = compute_delta(device?).unwrap_or_default();
288            }
289            if let Some(device) = self.x_advance_device(offset_data) {
290                value.x_advance_delta = compute_delta(device?).unwrap_or_default();
291            }
292            if let Some(device) = self.y_advance_device(offset_data) {
293                value.y_advance_delta = compute_delta(device?).unwrap_or_default();
294            }
295        }
296        Ok(value)
297    }
298}
299
300impl ReadArgs for ValueRecord {
301    type Args = ValueFormat;
302}
303
304impl<'a> FontReadWithArgs<'a> for ValueRecord {
305    fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
306        ValueRecord::read(data, *args)
307    }
308}
309
310impl std::fmt::Debug for ValueRecord {
311    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
312        let mut f = f.debug_struct("ValueRecord");
313        self.x_placement.map(|x| f.field("x_placement", &x));
314        self.y_placement.map(|y| f.field("y_placement", &y));
315        self.x_advance.map(|x| f.field("x_advance", &x));
316        self.y_advance.map(|y| f.field("y_advance", &y));
317        if !self.x_placement_device.get().is_null() {
318            f.field("x_placement_device", &self.x_placement_device.get());
319        }
320        if !self.y_placement_device.get().is_null() {
321            f.field("y_placement_device", &self.y_placement_device.get());
322        }
323        if !self.x_advance_device.get().is_null() {
324            f.field("x_advance_device", &self.x_advance_device.get());
325        }
326        if !self.y_advance_device.get().is_null() {
327            f.field("y_advance_device", &self.y_advance_device.get());
328        }
329        f.finish()
330    }
331}
332
333impl ComputeSize for ValueRecord {
334    #[inline]
335    fn compute_size(args: &ValueFormat) -> Result<usize, ReadError> {
336        Ok(args.record_byte_len())
337    }
338}
339
340#[cfg(feature = "experimental_traverse")]
341impl<'a> ValueRecord {
342    pub(crate) fn traversal_type(&self, data: FontData<'a>) -> FieldType<'a> {
343        FieldType::Record(self.clone().traverse(data))
344    }
345
346    pub(crate) fn get_field(&self, idx: usize, data: FontData<'a>) -> Option<Field<'a>> {
347        let fields = [
348            self.x_placement.is_some().then_some("x_placement"),
349            self.y_placement.is_some().then_some("y_placement"),
350            self.x_advance.is_some().then_some("x_advance"),
351            self.y_advance.is_some().then_some("y_advance"),
352            (!self.x_placement_device.get().is_null()).then_some("x_placement_device"),
353            (!self.y_placement_device.get().is_null()).then_some("y_placement_device"),
354            (!self.x_advance_device.get().is_null()).then_some("x_advance_device"),
355            (!self.y_advance_device.get().is_null()).then_some("y_advance_device"),
356        ];
357
358        let name = fields.iter().filter_map(|x| *x).nth(idx)?;
359        let typ: FieldType = match name {
360            "x_placement" => self.x_placement().unwrap().into(),
361            "y_placement" => self.y_placement().unwrap().into(),
362            "x_advance" => self.x_advance().unwrap().into(),
363            "y_advance" => self.y_advance().unwrap().into(),
364            "x_placement_device" => {
365                FieldType::offset(self.x_placement_device.get(), self.x_placement_device(data))
366            }
367            "y_placement_device" => {
368                FieldType::offset(self.y_placement_device.get(), self.y_placement_device(data))
369            }
370            "x_advance_device" => {
371                FieldType::offset(self.x_advance_device.get(), self.x_advance_device(data))
372            }
373            "y_advance_device" => {
374                FieldType::offset(self.y_advance_device.get(), self.y_advance_device(data))
375            }
376            _ => panic!("hmm"),
377        };
378
379        Some(Field::new(name, typ))
380    }
381}
382
383#[cfg(feature = "experimental_traverse")]
384impl<'a> SomeRecord<'a> for ValueRecord {
385    fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
386        RecordResolver {
387            name: "ValueRecord",
388            data,
389            get_field: Box::new(move |idx, data| self.get_field(idx, data)),
390        }
391    }
392}
393
394#[cfg(test)]
395mod tests {
396    use super::*;
397
398    #[test]
399    fn sanity_check_format_const() {
400        let format = ValueFormat::X_ADVANCE_DEVICE
401            | ValueFormat::Y_ADVANCE_DEVICE
402            | ValueFormat::Y_PLACEMENT_DEVICE
403            | ValueFormat::X_PLACEMENT_DEVICE;
404        assert_eq!(format, ValueFormat::ANY_DEVICE_OR_VARIDX);
405        assert_eq!(format.record_byte_len(), 4 * 2);
406    }
407}