allsorts_subset_browser/tables/variable_fonts/
fvar.rs1#![deny(missing_docs)]
2
3use crate::binary::read::{
8 ReadArray, ReadBinary, ReadBinaryDep, ReadCtxt, ReadFrom, ReadScope, ReadUnchecked,
9};
10use crate::binary::{U16Be, U32Be};
11use crate::error::ParseError;
12use crate::tables::variable_fonts::avar::AvarTable;
13use crate::tables::variable_fonts::UserTuple;
14use crate::tables::{F2Dot14, Fixed};
15use tinyvec::TinyVec;
16
17pub struct FvarTable<'a> {
21 pub major_version: u16,
23 pub minor_version: u16,
25 axes: ReadArray<'a, VariationAxisRecord>,
27 instance_count: u16,
30 instance_size: u16,
32 instance_array: &'a [u8],
33}
34
35#[derive(Eq, PartialEq, Debug)]
39pub struct VariationAxisRecord {
40 pub axis_tag: u32,
42 pub min_value: Fixed,
44 pub default_value: Fixed,
46 pub max_value: Fixed,
48 pub flags: u16,
50 pub axis_name_id: u16,
53}
54
55#[derive(Debug)]
62pub struct InstanceRecord<'a> {
63 pub subfamily_name_id: u16,
66 pub flags: u16,
68 pub coordinates: UserTuple<'a>,
70 pub post_script_name_id: Option<u16>,
73}
74
75#[derive(Debug)]
101pub struct OwnedTuple(TinyVec<[F2Dot14; 4]>);
102
103#[derive(Debug, Copy, Clone)]
105pub struct Tuple<'a>(&'a [F2Dot14]);
106
107impl FvarTable<'_> {
108 pub fn axes(&self) -> impl Iterator<Item = VariationAxisRecord> + '_ {
110 self.axes.iter()
111 }
112
113 pub fn axis_count(&self) -> u16 {
115 self.axes.len() as u16
117 }
118
119 pub fn instances(&self) -> impl Iterator<Item = Result<InstanceRecord<'_>, ParseError>> + '_ {
121 let instance_array = self.instance_array;
124 let axis_count = self.axis_count();
125 let instance_size = usize::from(self.instance_size);
126 (0..usize::from(self.instance_count)).map(move |i| {
127 let offset = i * instance_size;
128 instance_array
129 .get(offset..(offset + instance_size))
130 .ok_or(ParseError::BadIndex)
131 .and_then(|data| {
132 ReadScope::new(data).read_dep::<InstanceRecord<'_>>((instance_size, axis_count))
133 })
134 })
135 }
136
137 pub fn normalize(
139 &self,
140 user_tuple: impl ExactSizeIterator<Item = Fixed>,
141 avar: Option<&AvarTable<'_>>,
142 ) -> Result<OwnedTuple, ParseError> {
143 if user_tuple.len() != usize::from(self.axis_count()) {
144 return Err(ParseError::BadValue);
145 }
146
147 let mut tuple = TinyVec::with_capacity(user_tuple.len());
148 let mut avar_iter = avar.map(|avar| avar.segment_maps());
149 for (axis, user_value) in self.axes().zip(user_tuple) {
150 let mut normalized_value = default_normalize(&axis, user_value);
151
152 if let Some(avar) = avar_iter.as_mut() {
154 let segment_map = avar.next().ok_or(ParseError::BadIndex)?;
155 normalized_value = segment_map.normalize(normalized_value);
156 normalized_value = normalized_value.clamp(Fixed::from(-1), Fixed::from(1));
158 }
159
160 tuple.push(F2Dot14::from(normalized_value));
162 }
163 Ok(OwnedTuple(tuple))
164 }
165
166 pub fn owned_tuple(&self, values: &[F2Dot14]) -> Option<OwnedTuple> {
171 (values.len() == usize::from(self.axis_count())).then(|| OwnedTuple(TinyVec::from(values)))
172 }
173}
174
175fn default_normalize(axis: &VariationAxisRecord, coord: Fixed) -> Fixed {
176 let coord = coord.clamp(axis.min_value, axis.max_value);
178
179 let normalised_value = if coord < axis.default_value {
181 -(axis.default_value - coord) / (axis.default_value - axis.min_value)
182 } else if coord > axis.default_value {
183 (coord - axis.default_value) / (axis.max_value - axis.default_value)
184 } else {
185 Fixed::from(0)
186 };
187
188 normalised_value.clamp(Fixed::from(-1), Fixed::from(1))
192}
193
194impl<'b> ReadBinary for FvarTable<'b> {
195 type HostType<'a> = FvarTable<'a>;
196
197 fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
198 let scope = ctxt.scope();
199 let major_version = ctxt.read_u16be()?;
200 ctxt.check_version(major_version == 1)?;
201 let minor_version = ctxt.read_u16be()?;
202 let axes_array_offset = ctxt.read_u16be()?;
203 let _reserved = ctxt.read_u16be()?;
204 let axis_count = ctxt.read_u16be()?;
205 let axis_size = ctxt.read_u16be()?;
206 let instance_count = ctxt.read_u16be()?;
207 let instance_size = ctxt.read_u16be()?;
208 let instance_length = usize::from(instance_count) * usize::from(instance_size);
209 let mut data_ctxt = scope.offset(usize::from(axes_array_offset)).ctxt();
210 let axes = data_ctxt.read_array_stride(usize::from(axis_count), usize::from(axis_size))?;
211 let instance_array = data_ctxt.read_slice(instance_length)?;
212
213 Ok(FvarTable {
214 major_version,
215 minor_version,
216 axes,
217 instance_count,
218 instance_size,
219 instance_array,
220 })
221 }
222}
223
224pub struct FvarAxisCount;
226
227impl<'b> ReadBinary for FvarAxisCount {
228 type HostType<'a> = u16;
229
230 fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
231 let major_version = ctxt.read_u16be()?;
232 ctxt.check_version(major_version == 1)?;
233 let _minor_version = ctxt.read_u16be()?;
234 let _axes_array_offset = ctxt.read_u16be()?;
235 let _reserved = ctxt.read_u16be()?;
236 let axis_count = ctxt.read_u16be()?;
237
238 Ok(axis_count)
239 }
240}
241
242impl ReadFrom for VariationAxisRecord {
243 type ReadType = ((U32Be, Fixed, Fixed), (Fixed, U16Be, U16Be));
244
245 fn read_from(
246 ((axis_tag, min_value, default_value), (max_value, flags, axis_name_id)): (
247 (u32, Fixed, Fixed),
248 (Fixed, u16, u16),
249 ),
250 ) -> Self {
251 VariationAxisRecord {
252 axis_tag,
253 min_value,
254 default_value,
255 max_value,
256 flags,
257 axis_name_id,
258 }
259 }
260}
261
262impl ReadBinaryDep for InstanceRecord<'_> {
263 type Args<'a> = (usize, u16);
264 type HostType<'a> = InstanceRecord<'a>;
265
266 fn read_dep<'a>(
267 ctxt: &mut ReadCtxt<'a>,
268 (record_size, axis_count): (usize, u16),
269 ) -> Result<Self::HostType<'a>, ParseError> {
270 let axis_count = usize::from(axis_count);
271 let subfamily_name_id = ctxt.read_u16be()?;
272 let flags = ctxt.read_u16be()?;
273 let coordinates = ctxt.read_array(axis_count).map(UserTuple)?;
274 let post_script_name_id = (record_size > axis_count * Fixed::SIZE + 4)
277 .then(|| ctxt.read_u16be())
278 .transpose()?;
279
280 Ok(InstanceRecord {
281 subfamily_name_id,
282 flags,
283 coordinates,
284 post_script_name_id,
285 })
286 }
287}
288
289impl OwnedTuple {
290 pub fn as_tuple(&self) -> Tuple<'_> {
292 Tuple(&self.0)
293 }
294}
295
296impl std::ops::Deref for OwnedTuple {
297 type Target = [F2Dot14];
298
299 fn deref(&self) -> &Self::Target {
300 &self.0
301 }
302}
303
304impl<'a> Tuple<'a> {
305 pub unsafe fn from_raw_parts(data: *const F2Dot14, length: usize) -> Tuple<'a> {
315 Tuple(std::slice::from_raw_parts(data, length))
316 }
317
318 pub fn get(&self, index: u16) -> Option<F2Dot14> {
320 self.0.get(usize::from(index)).copied()
321 }
322}
323
324impl std::ops::Deref for Tuple<'_> {
325 type Target = [F2Dot14];
326
327 fn deref(&self) -> &Self::Target {
328 self.0
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use crate::error::ReadWriteError;
336 use crate::font_data::FontData;
337 use crate::tables::{FontTableProvider, NameTable};
338 use crate::tag;
339 use crate::tests::read_fixture;
340
341 #[test]
342 fn fvar() {
343 let buffer = read_fixture("tests/fonts/opentype/NotoSans-VF.abc.ttf");
344 let scope = ReadScope::new(&buffer);
345 let font_file = scope
346 .read::<FontData<'_>>()
347 .expect("unable to parse font file");
348 let table_provider = font_file
349 .table_provider(0)
350 .expect("unable to create font provider");
351 let fvar_data = table_provider
352 .read_table_data(tag::FVAR)
353 .expect("unable to read fvar table data");
354 let fvar = ReadScope::new(&fvar_data).read::<FvarTable<'_>>().unwrap();
355 let name_table_data = table_provider
356 .read_table_data(tag::NAME)
357 .expect("unable to read name table data");
358 let name_table = ReadScope::new(&name_table_data)
359 .read::<NameTable<'_>>()
360 .unwrap();
361
362 let expected = [
363 VariationAxisRecord {
364 axis_tag: tag!(b"wght"),
365 min_value: <Fixed as From<i32>>::from(100),
366 default_value: <Fixed as From<i32>>::from(400),
367 max_value: <Fixed as From<i32>>::from(900),
368 flags: 0,
369 axis_name_id: 279,
370 },
371 VariationAxisRecord {
372 axis_tag: tag!(b"wdth"),
373 min_value: <Fixed as From<f32>>::from(62.5),
374 default_value: <Fixed as From<i32>>::from(100),
375 max_value: <Fixed as From<i32>>::from(100),
376 flags: 0,
377 axis_name_id: 280,
378 },
379 VariationAxisRecord {
380 axis_tag: tag!(b"CTGR"),
381 min_value: <Fixed as From<i32>>::from(0),
382 default_value: <Fixed as From<i32>>::from(0),
383 max_value: <Fixed as From<i32>>::from(100),
384 flags: 0,
385 axis_name_id: 281,
386 },
387 ];
388 assert_eq!(fvar.axes().collect::<Vec<_>>(), expected);
389
390 let instances = fvar.instances().collect::<Result<Vec<_>, _>>().unwrap();
391 assert_eq!(instances.len(), 72);
392 let first = instances.first().unwrap();
393 let subfamily_name = name_table.string_for_id(first.subfamily_name_id).unwrap();
394 assert_eq!(subfamily_name, "Thin");
395 let coordinates = [
397 <Fixed as From<f32>>::from(100.),
398 <Fixed as From<f32>>::from(100.),
399 <Fixed as From<f32>>::from(0.),
400 ];
401 assert_eq!(first.coordinates.0.iter().collect::<Vec<_>>(), coordinates);
402
403 let last = instances.last().unwrap();
404 let subfamily_name = name_table.string_for_id(last.subfamily_name_id).unwrap();
405 assert_eq!(subfamily_name, "Display ExtraCondensed Black");
406 let coordinates = [
409 <Fixed as From<f32>>::from(900.),
410 <Fixed as From<f32>>::from(62.5),
411 <Fixed as From<f32>>::from(100.),
412 ];
413 assert_eq!(last.coordinates.0.iter().collect::<Vec<_>>(), coordinates);
414 }
415
416 #[test]
417 fn test_fvar_normalization() -> Result<(), ReadWriteError> {
418 let buffer = read_fixture("tests/fonts/opentype/NotoSans-VF.abc.ttf");
419 let scope = ReadScope::new(&buffer);
420 let font_file = scope.read::<FontData<'_>>()?;
421 let provider = font_file.table_provider(0)?;
422 let fvar_data = provider
423 .read_table_data(tag::FVAR)
424 .expect("unable to read fvar table data");
425 let fvar = ReadScope::new(&fvar_data).read::<FvarTable<'_>>().unwrap();
426 let avar_data = provider.table_data(tag::AVAR)?;
427 let avar = avar_data
428 .as_ref()
429 .map(|avar_data| ReadScope::new(avar_data).read::<AvarTable<'_>>())
430 .transpose()?;
431 let name_table_data = provider
432 .read_table_data(tag::NAME)
433 .expect("unable to read name table data");
434 let name_table = ReadScope::new(&name_table_data)
435 .read::<NameTable<'_>>()
436 .unwrap();
437
438 let mut instance = None;
440 for inst in fvar.instances() {
441 let inst = inst?;
442 let subfamily = name_table.string_for_id(inst.subfamily_name_id);
443 if subfamily.as_deref() == Some("Display Condensed Thin") {
444 instance = Some(inst);
450 break;
451 }
452 }
453 let instance = instance.unwrap();
454
455 let tuple = fvar.normalize(instance.coordinates.iter(), avar.as_ref())?;
458 assert_eq!(
459 tuple.0.as_slice(),
460 &[
461 F2Dot14::from(-1.0),
462 F2Dot14::from(-0.7000122),
463 F2Dot14::from(1.0)
464 ]
465 );
466
467 Ok(())
468 }
469
470 #[test]
471 fn test_default_normalization() {
472 let axis = VariationAxisRecord {
474 axis_tag: tag!(b"wght"),
475 min_value: Fixed::from(100),
476 default_value: Fixed::from(400),
477 max_value: Fixed::from(900),
478 flags: 0,
479 axis_name_id: 0,
480 };
481 let user_coord = Fixed::from(250);
482 assert_eq!(default_normalize(&axis, user_coord), Fixed::from(-0.5))
483 }
484}