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