read_fonts/tables/
instance_record.rs

1//! An fvar InstanceRecord
2
3use types::{BigEndian, Fixed, FixedSize, NameId};
4
5#[cfg(feature = "experimental_traverse")]
6use crate::traversal::{Field, RecordResolver, SomeRecord};
7use crate::{ComputeSize, FontData, FontReadWithArgs, ReadArgs, ReadError};
8
9/// The [InstanceRecord](https://learn.microsoft.com/en-us/typography/opentype/spec/fvar#instancerecord)
10#[derive(Clone, Debug)]
11pub struct InstanceRecord<'a> {
12    /// The name ID for entries in the 'name' table that provide subfamily names for this instance.
13    pub subfamily_name_id: NameId,
14    /// Reserved for future use — set to 0.
15    pub flags: u16,
16    /// The coordinates array for this instance.
17    pub coordinates: &'a [BigEndian<Fixed>],
18    /// Optional. The name ID for entries in the 'name' table that provide PostScript names for this instance.
19    pub post_script_name_id: Option<NameId>,
20}
21
22impl ReadArgs for InstanceRecord<'_> {
23    type Args = (u16, u16);
24}
25
26impl<'a> InstanceRecord<'a> {
27    /// Parse an instance record with a known axis_count and instance_size
28    pub fn read(
29        data: FontData<'a>,
30        axis_count: u16,
31        instance_size: u16,
32    ) -> Result<Self, ReadError> {
33        let args = (axis_count, instance_size);
34        Self::read_with_args(data, &args)
35    }
36}
37
38impl<'a> FontReadWithArgs<'a> for InstanceRecord<'a> {
39    fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
40        let axis_count = args.0 as usize;
41        let instance_size = args.1 as usize;
42        let mut cursor = data.cursor();
43        let subfamily_name_id = cursor.read()?;
44        let flags = cursor.read()?;
45        let coordinates = cursor.read_array(axis_count)?;
46        // Size of common fields (subfamily_name_id and flags) plus axis coordinates.
47        let common_byte_len = u16::RAW_BYTE_LEN * 2 + (axis_count * Fixed::RAW_BYTE_LEN);
48        // The instance contains a post_script_name_id field if the instance size is greater than
49        // or equal to the common size plus the 2 bytes for the optional field.
50        let has_post_script_name_id = instance_size >= common_byte_len + u16::RAW_BYTE_LEN;
51        let post_script_name_id = if has_post_script_name_id {
52            let id: NameId = cursor.read()?;
53            // From <https://learn.microsoft.com/en-us/typography/opentype/spec/fvar#instancerecord>:
54            // "If the value is 0xFFFF, then the value is ignored, and no
55            // PostScript name equivalent is provided for the instance."
56            (id.to_u16() != 0xFFFF).then_some(id)
57        } else {
58            None
59        };
60        Ok(InstanceRecord {
61            subfamily_name_id,
62            flags,
63            coordinates,
64            post_script_name_id,
65        })
66    }
67}
68
69impl ComputeSize for InstanceRecord<'_> {
70    #[inline]
71    fn compute_size(args: &(u16, u16)) -> Result<usize, ReadError> {
72        Ok(args.1 as usize)
73    }
74}
75
76#[cfg(feature = "experimental_traverse")]
77impl<'a> InstanceRecord<'a> {
78    pub(crate) fn get_field(&self, idx: usize, _data: FontData<'a>) -> Option<Field<'a>> {
79        match idx {
80            0 => Some(Field::new("subfamily_name_id", self.subfamily_name_id)),
81            1 => Some(Field::new("flags", self.flags)),
82            2 => Some(Field::new("coordinates", self.coordinates)),
83            3 => Some(Field::new("post_script_name_id", self.post_script_name_id?)),
84            _ => None,
85        }
86    }
87}
88
89#[cfg(feature = "experimental_traverse")]
90impl<'a> SomeRecord<'a> for InstanceRecord<'a> {
91    fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
92        RecordResolver {
93            name: "InstanceRecord",
94            data,
95            get_field: Box::new(move |idx, data| self.get_field(idx, data)),
96        }
97    }
98}