font_subset/font/
fvar.rs

1//! `fvar` table processing.
2
3use super::{
4    types::{Cursor, Fixed},
5    NameTable,
6};
7use crate::{
8    alloc::{String, Vec},
9    write::{VecExt, WriteTable},
10    ParseError, TableTag,
11};
12
13/// Tag of a [`VariationAxis`].
14#[derive(Clone, Copy, PartialEq, Eq, Hash)]
15pub struct VariationAxisTag(pub(crate) [u8; 4]);
16
17impl_tag!(VariationAxisTag);
18
19impl VariationAxisTag {
20    /// Commonly used "weight" axis tag.
21    pub const WEIGHT: Self = Self(*b"wght");
22    /// Commonly used "width" axis tag.
23    pub const WIDTH: Self = Self(*b"wdth");
24}
25
26/// Information about a variation axis of a variable font.
27#[derive(Debug, Clone)]
28#[cfg_attr(test, derive(PartialEq))]
29pub struct VariationAxis {
30    /// Tag of this axis.
31    pub tag: VariationAxisTag,
32    /// Minimum allowed value for the axis.
33    pub min_value: Fixed,
34    /// Maximum allowed value for the axis.
35    pub max_value: Fixed,
36    /// Default value for the axis.
37    pub default_value: Fixed,
38    /// Resolved name of the axis read from the `name` table. `None` if the name uses an unsupported encoding.
39    pub name: Option<String>,
40    pub(super) name_id: u16,
41    flags: u16,
42}
43
44impl VariationAxis {
45    fn parse(cursor: &mut Cursor<'_>) -> Result<Self, ParseError> {
46        let tag = VariationAxisTag(cursor.read_byte_array::<4>()?);
47        let min_value = Fixed(cursor.read_i32()?);
48        let default_value = Fixed(cursor.read_i32()?);
49        let max_value = Fixed(cursor.read_i32()?);
50        let flags = cursor.read_u16()?;
51        let name_id = cursor.read_u16()?;
52
53        #[cfg(feature = "tracing")]
54        tracing::debug!(
55            ?tag,
56            ?min_value,
57            ?max_value,
58            ?default_value,
59            flags,
60            name_id,
61            "parsed variation axis"
62        );
63
64        Ok(Self {
65            tag,
66            min_value,
67            max_value,
68            default_value,
69            flags,
70            name: None,
71            name_id,
72        })
73    }
74
75    fn write_to_vec(&self, buffer: &mut Vec<u8>) {
76        buffer.extend_from_slice(&self.tag.0);
77        buffer.write_i32(self.min_value.0);
78        buffer.write_i32(self.default_value.0);
79        buffer.write_i32(self.max_value.0);
80        buffer.write_u16(self.flags);
81        buffer.write_u16(self.name_id);
82    }
83}
84
85#[derive(Debug, Clone)]
86pub(crate) struct FvarTable<'a> {
87    axes: Vec<VariationAxis>,
88    all_bytes: Option<&'a [u8]>,
89}
90
91impl<'a> FvarTable<'a> {
92    const VERSION: u32 = 0x0001_0000;
93    const AXIS_SIZE: u16 = 20;
94
95    #[cfg_attr(
96        feature = "tracing",
97        tracing::instrument(level = "debug", err, skip_all, fields(range = ?cursor.range()))
98    )]
99    pub(super) fn parse(mut cursor: Cursor<'a>) -> Result<Self, ParseError> {
100        let all_bytes = cursor;
101
102        cursor.read_u32_checked(|version| check_exact!(version, Self::VERSION))?;
103        let axes_array_offset = cursor.read_u16()?;
104        cursor.skip(2)?; // reserved
105        let axis_count = cursor.read_u16()?;
106        cursor.read_u16_checked(|axis_size| check_exact!(axis_size, Self::AXIS_SIZE))?;
107        #[cfg(feature = "tracing")]
108        tracing::debug!(axis_count, "read basic info");
109
110        let mut axes_cursor = all_bytes;
111        axes_cursor.skip(axes_array_offset.into())?;
112        let axes = (0..axis_count)
113            .map(|_| VariationAxis::parse(&mut axes_cursor))
114            .collect::<Result<Vec<_>, _>>()?;
115        Ok(Self {
116            axes,
117            all_bytes: Some(all_bytes.bytes()),
118        })
119    }
120
121    pub(super) fn axis_name_ids(&self) -> Vec<u16> {
122        self.axes.iter().map(|axis| axis.name_id).collect()
123    }
124
125    pub(super) fn resolve_axe_names(&mut self, name: &NameTable<'_>) {
126        for axis in &mut self.axes {
127            axis.name = name.parsed_names.get(&axis.name_id).cloned();
128        }
129    }
130
131    pub(crate) fn axes(&self) -> &[VariationAxis] {
132        &self.axes
133    }
134
135    pub(crate) fn subset(&mut self) {
136        self.all_bytes = None;
137    }
138}
139
140impl WriteTable for FvarTable<'_> {
141    fn tag(&self) -> TableTag {
142        TableTag::FVAR
143    }
144
145    fn write_to_vec(&self, buffer: &mut Vec<u8>) {
146        const AXES_ARRAY_OFFSET: u16 = 16;
147
148        if let Some(bytes) = self.all_bytes {
149            buffer.extend_from_slice(bytes);
150            return;
151        }
152
153        let initial_offset = buffer.len();
154        buffer.write_u32(Self::VERSION);
155        buffer.write_u16(AXES_ARRAY_OFFSET);
156        buffer.write_u16(2); // reserved
157        let axis_count = u16::try_from(self.axes.len()).unwrap();
158        buffer.write_u16(axis_count);
159        buffer.write_u16(Self::AXIS_SIZE);
160        buffer.write_u16(0); // instance count; trimmed to save space (largely on names)
161        let instance_size = axis_count * 4 + 4;
162        buffer.write_u16(instance_size);
163
164        debug_assert_eq!(
165            usize::from(AXES_ARRAY_OFFSET),
166            buffer.len() - initial_offset
167        );
168        for axis in &self.axes {
169            axis.write_to_vec(buffer);
170        }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use crate::{testonly::TestFont, OpenTypeReader, TableTag};
178
179    #[test]
180    fn reading_fvar_table_with_2_axes() {
181        let reader = OpenTypeReader::new(TestFont::ROBOTO.bytes).unwrap();
182        let fvar = reader.table(TableTag::FVAR);
183        let fvar = FvarTable::parse(fvar).unwrap();
184
185        assert_eq!(fvar.axes.len(), 2);
186        assert_eq!(fvar.axes[0].tag, VariationAxisTag::WEIGHT);
187        assert_eq!(fvar.axes[0].min_value, 100_i16.into());
188        assert_eq!(fvar.axes[0].max_value, 900_i16.into());
189        assert_eq!(fvar.axes[0].default_value, 400_i16.into());
190        assert_eq!(fvar.axes[1].tag, VariationAxisTag::WIDTH);
191        assert_eq!(fvar.axes[1].min_value, 75_i16.into());
192        assert_eq!(fvar.axes[1].max_value, 100_i16.into());
193        assert_eq!(fvar.axes[1].default_value, 100_i16.into());
194    }
195
196    #[test]
197    fn reading_fvar_table_with_1_axe() {
198        let reader = OpenTypeReader::new(TestFont::ROBOTO_MONO.bytes).unwrap();
199        let fvar = reader.table(TableTag::FVAR);
200        let fvar = FvarTable::parse(fvar).unwrap();
201
202        assert_eq!(fvar.axes.len(), 1);
203        assert_eq!(fvar.axes[0].tag, VariationAxisTag::WEIGHT);
204        assert_eq!(fvar.axes[0].min_value, 100_i16.into());
205        assert_eq!(fvar.axes[0].max_value, 700_i16.into());
206        assert_eq!(fvar.axes[0].default_value, 400_i16.into());
207    }
208
209    #[test]
210    fn subset_roundtrip() {
211        let reader = OpenTypeReader::new(TestFont::ROBOTO_MONO.bytes).unwrap();
212        let fvar = reader.table(TableTag::FVAR);
213        let mut fvar = FvarTable::parse(fvar).unwrap();
214        fvar.subset();
215
216        let mut buffer = vec![];
217        fvar.write_to_vec(&mut buffer);
218        let subset_fvar = FvarTable::parse(Cursor::new(&buffer)).unwrap();
219        assert_eq!(fvar.axes, subset_fvar.axes);
220    }
221}