oxideav_ttf/tables/
fvar.rs1use crate::parser::{read_i32, read_u16};
50use crate::Error;
51
52const MIN_AXIS_SIZE: u16 = 20;
54const MAX_AXES: u16 = 64;
57const MAX_INSTANCES: u16 = 4096;
59pub const AXIS_FLAG_HIDDEN: u16 = 0x0001;
64
65#[derive(Debug, Clone, PartialEq)]
68pub struct VariationAxis {
69 pub tag: [u8; 4],
70 pub min: f32,
71 pub default: f32,
72 pub max: f32,
73 pub flags: u16,
74 pub name_id: u16,
76}
77
78impl VariationAxis {
79 pub fn is_hidden(&self) -> bool {
83 self.flags & AXIS_FLAG_HIDDEN != 0
84 }
85}
86
87#[derive(Debug, Clone, PartialEq)]
89pub struct NamedInstance {
90 pub subfamily_name_id: u16,
92 pub flags: u16,
93 pub coords: Vec<f32>,
95 pub post_script_name_id: Option<u16>,
99}
100
101#[derive(Debug, Clone)]
102#[doc(hidden)]
104pub struct FvarTable {
105 axes: Vec<VariationAxis>,
106 instances: Vec<NamedInstance>,
107}
108
109impl FvarTable {
110 pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
111 if bytes.len() < 16 {
112 return Err(Error::UnexpectedEof);
113 }
114 let major = read_u16(bytes, 0)?;
115 let minor = read_u16(bytes, 2)?;
116 if major != 1 || minor != 0 {
117 return Err(Error::BadStructure("fvar version not 1.0"));
118 }
119 let axes_array_offset = read_u16(bytes, 4)? as usize;
120 let axis_count = read_u16(bytes, 8)?;
123 let axis_size = read_u16(bytes, 10)?;
124 let instance_count = read_u16(bytes, 12)?;
125 let instance_size = read_u16(bytes, 14)?;
126
127 if axis_count > MAX_AXES {
128 return Err(Error::BadStructure("fvar axisCount exceeds sanity cap"));
129 }
130 if instance_count > MAX_INSTANCES {
131 return Err(Error::BadStructure("fvar instanceCount exceeds sanity cap"));
132 }
133 if axis_size < MIN_AXIS_SIZE {
134 return Err(Error::BadStructure("fvar axisSize < 20"));
135 }
136 let min_instance_size = 4u16
139 .checked_add(
140 axis_count
141 .checked_mul(4)
142 .ok_or(Error::BadStructure("fvar axisCount * 4 overflow"))?,
143 )
144 .ok_or(Error::BadStructure("fvar instanceSize overflow"))?;
145 if instance_size != min_instance_size && instance_size != min_instance_size + 2 {
146 return Err(Error::BadStructure("fvar instanceSize unexpected"));
147 }
148 let has_psname = instance_size == min_instance_size + 2;
149
150 let mut axes = Vec::with_capacity(axis_count as usize);
152 for i in 0..axis_count as usize {
153 let off = axes_array_offset
154 .checked_add(i.checked_mul(axis_size as usize).ok_or(Error::BadOffset)?)
155 .ok_or(Error::BadOffset)?;
156 if off + axis_size as usize > bytes.len() {
157 return Err(Error::UnexpectedEof);
158 }
159 let rec = &bytes[off..off + axis_size as usize];
160 let mut tag = [0u8; 4];
161 tag.copy_from_slice(&rec[0..4]);
162 let min = fixed_to_f32(read_i32(rec, 4)?);
163 let default = fixed_to_f32(read_i32(rec, 8)?);
164 let max = fixed_to_f32(read_i32(rec, 12)?);
165 let flags = read_u16(rec, 16)?;
166 let name_id = read_u16(rec, 18)?;
167 if !(min <= default && default <= max) {
168 return Err(Error::BadStructure("fvar axis min/default/max disorder"));
169 }
170 axes.push(VariationAxis {
171 tag,
172 min,
173 default,
174 max,
175 flags,
176 name_id,
177 });
178 }
179
180 let inst_array_offset = axes_array_offset
182 .checked_add(
183 (axis_count as usize)
184 .checked_mul(axis_size as usize)
185 .ok_or(Error::BadOffset)?,
186 )
187 .ok_or(Error::BadOffset)?;
188 let mut instances = Vec::with_capacity(instance_count as usize);
189 for i in 0..instance_count as usize {
190 let off = inst_array_offset
191 .checked_add(
192 i.checked_mul(instance_size as usize)
193 .ok_or(Error::BadOffset)?,
194 )
195 .ok_or(Error::BadOffset)?;
196 if off + instance_size as usize > bytes.len() {
197 return Err(Error::UnexpectedEof);
198 }
199 let rec = &bytes[off..off + instance_size as usize];
200 let subfamily_name_id = read_u16(rec, 0)?;
201 let flags = read_u16(rec, 2)?;
202 let mut coords = Vec::with_capacity(axis_count as usize);
203 for ai in 0..axis_count as usize {
204 coords.push(fixed_to_f32(read_i32(rec, 4 + ai * 4)?));
205 }
206 let post_script_name_id = if has_psname {
207 Some(read_u16(rec, 4 + axis_count as usize * 4)?)
208 } else {
209 None
210 };
211 instances.push(NamedInstance {
212 subfamily_name_id,
213 flags,
214 coords,
215 post_script_name_id,
216 });
217 }
218
219 Ok(Self { axes, instances })
220 }
221
222 pub fn axes(&self) -> &[VariationAxis] {
223 &self.axes
224 }
225
226 pub fn instances(&self) -> &[NamedInstance] {
227 &self.instances
228 }
229
230 pub fn axis_count(&self) -> usize {
231 self.axes.len()
232 }
233}
234
235#[inline]
236fn fixed_to_f32(raw: i32) -> f32 {
237 raw as f32 / 65536.0
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 fn build_one_axis(min: f32, def: f32, max: f32) -> Vec<u8> {
247 let mut b = vec![0u8; 16 + 20];
248 b[0..2].copy_from_slice(&1u16.to_be_bytes()); b[2..4].copy_from_slice(&0u16.to_be_bytes()); b[4..6].copy_from_slice(&16u16.to_be_bytes()); b[6..8].copy_from_slice(&2u16.to_be_bytes()); b[8..10].copy_from_slice(&1u16.to_be_bytes()); b[10..12].copy_from_slice(&20u16.to_be_bytes()); b[12..14].copy_from_slice(&0u16.to_be_bytes()); b[14..16].copy_from_slice(&8u16.to_be_bytes()); let rec = &mut b[16..36];
257 rec[0..4].copy_from_slice(b"wght");
258 rec[4..8].copy_from_slice(&((min * 65536.0) as i32).to_be_bytes());
259 rec[8..12].copy_from_slice(&((def * 65536.0) as i32).to_be_bytes());
260 rec[12..16].copy_from_slice(&((max * 65536.0) as i32).to_be_bytes());
261 rec[16..18].copy_from_slice(&0u16.to_be_bytes()); rec[18..20].copy_from_slice(&256u16.to_be_bytes()); b
264 }
265
266 #[test]
267 fn fvar_parses_wght_axis_min_default_max() {
268 let raw = build_one_axis(100.0, 400.0, 900.0);
269 let f = FvarTable::parse(&raw).expect("parse fvar");
270 assert_eq!(f.axes().len(), 1);
271 let a = &f.axes()[0];
272 assert_eq!(&a.tag, b"wght");
273 assert_eq!(a.min, 100.0);
274 assert_eq!(a.default, 400.0);
275 assert_eq!(a.max, 900.0);
276 assert_eq!(a.name_id, 256);
277 assert!(!a.is_hidden());
278 assert!(f.instances().is_empty());
279 }
280
281 #[test]
282 fn fvar_rejects_disordered_min_default_max() {
283 let raw = build_one_axis(900.0, 400.0, 100.0);
284 assert!(matches!(
285 FvarTable::parse(&raw),
286 Err(Error::BadStructure(_))
287 ));
288 }
289
290 #[test]
291 fn fvar_parses_named_instance() {
292 let mut b = vec![0u8; 16 + 20 + 12];
295 b[0..2].copy_from_slice(&1u16.to_be_bytes());
296 b[4..6].copy_from_slice(&16u16.to_be_bytes());
297 b[6..8].copy_from_slice(&2u16.to_be_bytes());
298 b[8..10].copy_from_slice(&1u16.to_be_bytes());
299 b[10..12].copy_from_slice(&20u16.to_be_bytes());
300 b[12..14].copy_from_slice(&1u16.to_be_bytes());
301 b[14..16].copy_from_slice(&8u16.to_be_bytes()); let rec = &mut b[16..36];
303 rec[0..4].copy_from_slice(b"wght");
304 rec[4..8].copy_from_slice(&(100i32 << 16).to_be_bytes());
305 rec[8..12].copy_from_slice(&(400i32 << 16).to_be_bytes());
306 rec[12..16].copy_from_slice(&(900i32 << 16).to_be_bytes());
307 rec[18..20].copy_from_slice(&256u16.to_be_bytes());
308 let inst = &mut b[36..44];
309 inst[0..2].copy_from_slice(&257u16.to_be_bytes()); inst[2..4].copy_from_slice(&0u16.to_be_bytes());
311 inst[4..8].copy_from_slice(&(700i32 << 16).to_be_bytes());
312
313 let f = FvarTable::parse(&b).expect("parse");
314 assert_eq!(f.instances().len(), 1);
315 let i = &f.instances()[0];
316 assert_eq!(i.subfamily_name_id, 257);
317 assert_eq!(i.coords, vec![700.0]);
318 assert!(i.post_script_name_id.is_none());
319 }
320
321 #[test]
322 fn fvar_rejects_short_header() {
323 let b = vec![0u8; 8];
324 assert!(matches!(FvarTable::parse(&b), Err(Error::UnexpectedEof)));
325 }
326}