1#![deny(missing_docs)]
2
3use std::fmt;
14
15use bitflags::bitflags;
16
17use crate::binary::read::{
18 ReadArray, ReadBinary, ReadBinaryDep, ReadCtxt, ReadFixedSizeDep, ReadFrom, ReadScope,
19 ReadUnchecked,
20};
21use crate::binary::{U16Be, U32Be};
22use crate::error::ParseError;
23use crate::tables::Fixed;
24use crate::tag::DisplayTag;
25use crate::{size, SafeFrom};
26
27pub struct StatTable<'a> {
31 pub major_version: u16,
33 pub minor_version: u16,
35 design_axis_size: u16,
37 design_axis_count: u16,
44 design_axes_array: &'a [u8],
46 axis_value_scope: ReadScope<'a>,
50 axis_value_offsets: ReadArray<'a, U16Be>,
52 pub elided_fallback_name_id: Option<u16>,
55}
56
57#[derive(Eq, PartialEq, Copy, Clone)]
61pub struct AxisRecord {
62 pub axis_tag: u32,
64 pub axis_name_id: u16,
67 pub axis_ordering: u16,
71}
72
73#[derive(Debug)]
82pub enum AxisValueTable<'a> {
83 Format1(AxisValueTableFormat1),
85 Format2(AxisValueTableFormat2),
87 Format3(AxisValueTableFormat3),
90 Format4(AxisValueTableFormat4<'a>),
93}
94
95#[derive(Debug, Eq, PartialEq)]
99pub struct AxisValueTableFormat1 {
100 pub axis_index: u16,
103 flags: AxisValueTableFlags,
105 value_name_id: u16,
108 pub value: Fixed,
110}
111
112#[derive(Debug, Eq, PartialEq)]
116pub struct AxisValueTableFormat2 {
117 pub axis_index: u16,
120 flags: AxisValueTableFlags,
122 value_name_id: u16,
125 pub nominal_value: Fixed,
127 pub range_min_value: Fixed,
129 pub range_max_value: Fixed,
131}
132
133#[derive(Debug, Eq, PartialEq)]
138pub struct AxisValueTableFormat3 {
139 pub axis_index: u16,
142 flags: AxisValueTableFlags,
144 value_name_id: u16,
147 pub value: Fixed,
149 pub linked_value: Fixed,
151}
152
153#[derive(Debug)]
158pub struct AxisValueTableFormat4<'a> {
159 flags: AxisValueTableFlags,
161 value_name_id: u16,
164 pub axis_values: ReadArray<'a, AxisValue>,
167}
168
169#[derive(Debug, Copy, Clone)]
171pub struct AxisValue {
172 pub axis_index: u16,
175 pub value: Fixed,
177}
178
179bitflags! {
180 pub struct AxisValueTableFlags: u16 {
184 const OLDER_SIBLING_FONT_ATTRIBUTE = 0x0001;
190 const ELIDABLE_AXIS_VALUE_NAME = 0x0002;
193 }
195}
196
197#[derive(Copy, Clone, Eq, PartialEq, Debug)]
201pub enum ElidableName {
202 Include,
204 Exclude,
206}
207
208impl<'a> StatTable<'a> {
209 pub fn design_axes(&'a self) -> impl Iterator<Item = Result<AxisRecord, ParseError>> + '_ {
211 (0..usize::from(self.design_axis_count)).map(move |i| self.design_axis(i))
212 }
213
214 pub fn design_axis(&self, index: usize) -> Result<AxisRecord, ParseError> {
216 let design_axis_size = usize::from(self.design_axis_size);
217 let offset = index * design_axis_size;
218 self.design_axes_array
219 .get(offset..(offset + design_axis_size))
220 .ok_or(ParseError::BadIndex)
221 .and_then(|data| ReadScope::new(data).read::<AxisRecord>())
222 }
223
224 pub fn axis_value_tables(
226 &'a self,
227 ) -> impl Iterator<Item = Result<AxisValueTable<'_>, ParseError>> {
228 self.axis_value_offsets.iter().filter_map(move |offset| {
229 let res = self
230 .axis_value_scope
231 .offset(usize::from(offset))
232 .read_dep::<AxisValueTable<'_>>(self.design_axis_count);
233 match res {
234 Ok(table) => Some(Ok(table)),
235 Err(ParseError::BadVersion) => None,
237 Err(err) => Some(Err(err)),
238 }
239 })
240 }
241
242 pub fn name_for_axis_value(
248 &'a self,
249 axis_index: u16,
250 value: Fixed,
251 include_elidable: ElidableName,
252 ) -> Option<u16> {
253 let mut best: Option<(Fixed, u16, bool)> = None;
255 for table in self.axis_value_tables() {
256 let Ok(table) = table else { continue };
257
258 match &table {
259 AxisValueTable::Format1(t) if t.axis_index == axis_index => consider(
260 &mut best,
261 t.value,
262 t.value_name_id,
263 table.is_elidable(),
264 value,
265 ),
266 AxisValueTable::Format2(t) if t.axis_index == axis_index => {
267 if (t.range_min_value..=t.range_max_value).contains(&value) {
268 consider(
269 &mut best,
270 t.nominal_value,
271 t.value_name_id,
272 table.is_elidable(),
273 value,
274 )
275 }
276 }
277 AxisValueTable::Format3(t) if t.axis_index == axis_index => consider(
278 &mut best,
279 t.value,
280 t.value_name_id,
281 table.is_elidable(),
282 value,
283 ),
284 AxisValueTable::Format4(t) => {
285 let Some(axis_value) = t.axis_values.iter_res().find_map(|value| {
287 value
288 .ok()
289 .and_then(|value| (value.axis_index == axis_index).then(|| value))
290 }) else {
291 continue;
292 };
293 consider(
294 &mut best,
295 axis_value.value,
296 t.value_name_id,
297 table.is_elidable(),
298 value,
299 )
300 }
301 AxisValueTable::Format1(_)
302 | AxisValueTable::Format2(_)
303 | AxisValueTable::Format3(_) => {}
304 }
305 }
306
307 best.and_then(|(_best_val, name, is_elidable)| match include_elidable {
308 ElidableName::Include => Some(name),
309 ElidableName::Exclude => (!is_elidable).then(|| name),
311 })
312 }
313}
314
315fn consider(
316 best: &mut Option<(Fixed, u16, bool)>,
317 candidate_value: Fixed,
318 candidate_name_id: u16,
319 is_elidable: bool,
320 value: Fixed,
321) {
322 match best {
323 Some((best_val, _name, _is_elidable)) => {
324 if (candidate_value - value).abs() < (*best_val - value).abs() {
325 *best = Some((candidate_value, candidate_name_id, is_elidable));
326 }
327 }
328 None => *best = Some((candidate_value, candidate_name_id, is_elidable)),
329 }
330}
331
332impl<'b> ReadBinary for StatTable<'b> {
333 type HostType<'a> = StatTable<'a>;
334
335 fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<StatTable<'a>, ParseError> {
336 let scope = ctxt.scope();
337 let major_version = ctxt.read_u16be()?;
338 ctxt.check_version(major_version == 1)?;
339 let minor_version = ctxt.read_u16be()?;
340 let design_axis_size = ctxt.read_u16be()?;
341 let design_axis_count = ctxt.read_u16be()?;
342 let design_axes_offset = ctxt.read_u32be()?;
343 let design_axes_array = if design_axis_count > 0 {
344 let design_axes_length = usize::from(design_axis_count) * usize::from(design_axis_size);
345 scope
346 .offset(usize::safe_from(design_axes_offset))
347 .ctxt()
348 .read_slice(design_axes_length)?
349 } else {
350 &[]
351 };
352
353 let axis_value_count = ctxt.read_u16be()?;
354 let offset_to_axis_value_offsets = ctxt.read_u32be()?;
355 let (axis_value_scope, axis_value_offsets) = if axis_value_count > 0 {
356 let axis_value_scope = scope.offset(usize::safe_from(offset_to_axis_value_offsets));
357 (
358 axis_value_scope,
359 axis_value_scope
360 .ctxt()
361 .read_array(usize::from(axis_value_count))?,
362 )
363 } else {
364 (ReadScope::new(&[]), ReadArray::empty())
365 };
366 let elided_fallback_name_id = (minor_version > 0).then(|| ctxt.read_u16be()).transpose()?;
367
368 Ok(StatTable {
369 major_version,
370 minor_version,
371 design_axis_size,
372 design_axis_count,
373 design_axes_array,
374 axis_value_scope,
375 axis_value_offsets,
376 elided_fallback_name_id,
377 })
378 }
379}
380
381impl ReadFrom for AxisRecord {
382 type ReadType = (U32Be, U16Be, U16Be);
383
384 fn read_from((axis_tag, axis_name_id, axis_ordering): (u32, u16, u16)) -> Self {
385 AxisRecord {
386 axis_tag,
387 axis_name_id,
388 axis_ordering,
389 }
390 }
391}
392
393impl ReadFrom for AxisValueTableFlags {
394 type ReadType = U16Be;
395
396 fn read_from(flag: u16) -> Self {
397 AxisValueTableFlags::from_bits_truncate(flag)
398 }
399}
400
401impl AxisValueTable<'_> {
402 pub fn flags(&self) -> AxisValueTableFlags {
404 match self {
405 AxisValueTable::Format1(AxisValueTableFormat1 { flags, .. })
406 | AxisValueTable::Format2(AxisValueTableFormat2 { flags, .. })
407 | AxisValueTable::Format3(AxisValueTableFormat3 { flags, .. })
408 | AxisValueTable::Format4(AxisValueTableFormat4 { flags, .. }) => *flags,
409 }
410 }
411
412 pub fn value_name_id(&self) -> u16 {
414 match self {
415 AxisValueTable::Format1(AxisValueTableFormat1 { value_name_id, .. })
416 | AxisValueTable::Format2(AxisValueTableFormat2 { value_name_id, .. })
417 | AxisValueTable::Format3(AxisValueTableFormat3 { value_name_id, .. })
418 | AxisValueTable::Format4(AxisValueTableFormat4 { value_name_id, .. }) => {
419 *value_name_id
420 }
421 }
422 }
423
424 pub fn is_elidable(&self) -> bool {
427 self.flags()
428 .contains(AxisValueTableFlags::ELIDABLE_AXIS_VALUE_NAME)
429 }
430}
431
432impl ReadBinaryDep for AxisValueTable<'_> {
433 type Args<'a> = u16;
434 type HostType<'a> = AxisValueTable<'a>;
435
436 fn read_dep<'a>(
437 ctxt: &mut ReadCtxt<'a>,
438 design_axis_count: u16,
439 ) -> Result<Self::HostType<'a>, ParseError> {
440 let format = ctxt.read_u16be()?;
441 match format {
442 1 => {
443 let axis_index = ctxt.read_u16be()?;
444 ctxt.check_index(axis_index < design_axis_count)?;
445 let flags = ctxt.read::<AxisValueTableFlags>()?;
446 let value_name_id = ctxt.read_u16be()?;
447 let value = ctxt.read::<Fixed>()?;
448 Ok(AxisValueTable::Format1(AxisValueTableFormat1 {
449 axis_index,
450 flags,
451 value_name_id,
452 value,
453 }))
454 }
455 2 => {
456 let axis_index = ctxt.read_u16be()?;
457 ctxt.check_index(axis_index < design_axis_count)?;
458 let flags = ctxt.read::<AxisValueTableFlags>()?;
459 let value_name_id = ctxt.read_u16be()?;
460 let nominal_value = ctxt.read::<Fixed>()?;
461 let range_min_value = ctxt.read::<Fixed>()?;
462 let range_max_value = ctxt.read::<Fixed>()?;
463 Ok(AxisValueTable::Format2(AxisValueTableFormat2 {
464 axis_index,
465 flags,
466 value_name_id,
467 nominal_value,
468 range_min_value,
469 range_max_value,
470 }))
471 }
472 3 => {
473 let axis_index = ctxt.read_u16be()?;
474 ctxt.check_index(axis_index < design_axis_count)?;
475 let flags = ctxt.read::<AxisValueTableFlags>()?;
476 let value_name_id = ctxt.read_u16be()?;
477 let value = ctxt.read::<Fixed>()?;
478 let linked_value = ctxt.read::<Fixed>()?;
479 Ok(AxisValueTable::Format3(AxisValueTableFormat3 {
480 axis_index,
481 flags,
482 value_name_id,
483 value,
484 linked_value,
485 }))
486 }
487 4 => {
488 let axis_count = ctxt.read_u16be()?;
489 let flags = ctxt.read::<AxisValueTableFlags>()?;
490 let value_name_id = ctxt.read_u16be()?;
491 let axis_values =
492 ctxt.read_array_dep(usize::from(axis_count), design_axis_count)?;
493 Ok(AxisValueTable::Format4(AxisValueTableFormat4 {
494 flags,
495 value_name_id,
496 axis_values,
497 }))
498 }
499 _ => Err(ParseError::BadVersion),
500 }
501 }
502}
503
504impl ReadBinaryDep for AxisValue {
505 type Args<'a> = u16;
506 type HostType<'a> = AxisValue;
507
508 fn read_dep<'a>(
509 ctxt: &mut ReadCtxt<'a>,
510 design_axis_count: u16,
511 ) -> Result<Self::HostType<'a>, ParseError> {
512 let axis_index = ctxt.read_u16be()?;
513 ctxt.check_index(axis_index < design_axis_count)?;
514 let value = ctxt.read::<Fixed>()?;
515 Ok(AxisValue { axis_index, value })
516 }
517}
518
519impl ReadFixedSizeDep for AxisValue {
520 fn size(_args: Self::Args<'_>) -> usize {
521 size::U16 + Fixed::SIZE
522 }
523}
524
525impl fmt::Debug for AxisRecord {
526 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
527 let tag = format!("{:?} ({})", self.axis_tag, DisplayTag(self.axis_tag));
528 f.debug_struct("AxisRecord")
529 .field("axis_tag", &tag)
530 .field("axis_name_id", &self.axis_name_id)
531 .field("axis_ordering", &self.axis_ordering)
532 .finish()
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539 use crate::font_data::FontData;
540 use crate::tables::{FontTableProvider, NameTable};
541 use crate::tag;
542 use crate::tests::read_fixture;
543
544 #[test]
545 fn stat() {
546 let buffer = read_fixture("tests/fonts/opentype/NotoSans-VF.abc.ttf");
547 let scope = ReadScope::new(&buffer);
548 let font_file = scope
549 .read::<FontData<'_>>()
550 .expect("unable to parse font file");
551 let table_provider = font_file
552 .table_provider(0)
553 .expect("unable to create font provider");
554 let stat_data = table_provider
555 .read_table_data(tag::STAT)
556 .expect("unable to read fvar table data");
557 let stat = ReadScope::new(&stat_data).read::<StatTable<'_>>().unwrap();
558 let name_table_data = table_provider
559 .read_table_data(tag::NAME)
560 .expect("unable to read name table data");
561 let name_table = ReadScope::new(&name_table_data)
562 .read::<NameTable<'_>>()
563 .unwrap();
564
565 let expected = [
566 AxisRecord {
567 axis_tag: tag!(b"wght"),
568 axis_name_id: 261,
569 axis_ordering: 0,
570 },
571 AxisRecord {
572 axis_tag: tag!(b"wdth"),
573 axis_name_id: 271,
574 axis_ordering: 1,
575 },
576 AxisRecord {
577 axis_tag: tag!(b"CTGR"),
578 axis_name_id: 276,
579 axis_ordering: 2,
580 },
581 ];
582 let design_axes = stat.design_axes().collect::<Result<Vec<_>, _>>().unwrap();
583 assert_eq!(design_axes, expected);
584
585 let axis_value_tables = stat
586 .axis_value_tables()
587 .collect::<Result<Vec<_>, _>>()
588 .unwrap();
589 assert_eq!(axis_value_tables.len(), 15);
590 let first = axis_value_tables.first().unwrap();
591 let AxisValueTable::Format1(table) = first else {
592 panic!("expected AxisValueTableFormat1")
593 };
594 let expected = AxisValueTableFormat1 {
595 axis_index: 0,
596 flags: AxisValueTableFlags::empty(),
597 value_name_id: 262,
598 value: <Fixed as From<f32>>::from(100.),
599 };
600 assert_eq!(table, &expected);
601 let value_name = name_table.string_for_id(first.value_name_id()).unwrap();
602 assert_eq!(value_name, "Thin");
603
604 let last = axis_value_tables.last().unwrap();
605 let AxisValueTable::Format1(table) = last else {
606 panic!("expected AxisValueTableFormat1")
607 };
608 let expected = AxisValueTableFormat1 {
609 axis_index: 2,
610 flags: AxisValueTableFlags::empty(),
611 value_name_id: 278,
612 value: <Fixed as From<f32>>::from(100.),
613 };
614 assert_eq!(table, &expected);
615 let value_name = name_table.string_for_id(last.value_name_id()).unwrap();
616 assert_eq!(value_name, "Display");
617 }
618}