From 5a64a6613dfda4a09c4d82eedb23007c50aab58d Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Mon, 12 Aug 2024 02:42:12 -0700 Subject: [PATCH 01/17] Add support for the STAT table. https://learn.microsoft.com/en-us/typography/opentype/spec/stat --- examples/font-info.rs | 56 ++++++++ src/lib.rs | 7 +- src/tables/mod.rs | 1 + src/tables/stat.rs | 314 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 src/tables/stat.rs diff --git a/examples/font-info.rs b/examples/font-info.rs index 1697eae3..03370ae2 100644 --- a/examples/font-info.rs +++ b/examples/font-info.rs @@ -1,3 +1,5 @@ +use ttf_parser::stat::AxisValue; + fn main() { let args: Vec<_> = std::env::args().collect(); if args.len() != 2 { @@ -84,6 +86,60 @@ fn main() { } } + if let Some(stat) = face.tables().stat { + let axis_names = stat + .axes + .into_iter() + .map(|axis| axis.tag) + .collect::>(); + + println!("Style attributes:"); + + println!(" Axes:"); + for axis in axis_names.iter() { + println!(" {}", axis); + } + + println!(" Axis Values:"); + for value in stat.values() { + // println!(" {value:?}"); + match value { + AxisValue::Format1(value) => { + let name = face + .names() + .into_iter() + .filter(|name| name.name_id == value.value_name_id) + .map(|name| name.to_string().unwrap()) + .collect::>() + .join(", "); + + println!( + " {}={:?}={name:?} flags={:?}", + &axis_names[value.axis_index as usize], value.value, value.flags + ); + } + AxisValue::Format2(_) => todo!(), + AxisValue::Format3(value) => { + let name = face + .names() + .into_iter() + .filter(|name| name.name_id == value.value_name_id) + .map(|name| name.to_string().unwrap()) + .collect::>() + .join(", "); + + println!( + " {} {:?}<=>{:?} = {name:?} flags={:?}", + &axis_names[value.axis_index as usize], + value.value, + value.linked_value, + value.flags + ); + } + } + } + } + println!("Elapsed: {}us", now.elapsed().as_micros()); } diff --git a/src/lib.rs b/src/lib.rs index dcac033a..f0d5f36d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,7 +84,8 @@ pub use tables::{ankr, feat, kerx, morx, trak}; pub use tables::{avar, cff2, fvar, gvar, hvar, mvar, vvar}; pub use tables::{cbdt, cblc, cff1 as cff, vhea}; pub use tables::{ - cmap, colr, cpal, glyf, head, hhea, hmtx, kern, loca, maxp, name, os2, post, sbix, svg, vorg, + cmap, colr, cpal, glyf, head, hhea, hmtx, kern, loca, maxp, name, os2, post, sbix, stat, svg, + vorg, }; #[cfg(feature = "opentype-layout")] pub use tables::{gdef, gpos, gsub, math}; @@ -938,6 +939,7 @@ pub struct RawFaceTables<'a> { pub os2: Option<&'a [u8]>, pub post: Option<&'a [u8]>, pub sbix: Option<&'a [u8]>, + pub stat: Option<&'a [u8]>, pub svg: Option<&'a [u8]>, pub vhea: Option<&'a [u8]>, pub vmtx: Option<&'a [u8]>, @@ -1008,6 +1010,7 @@ pub struct FaceTables<'a> { pub os2: Option>, pub post: Option>, pub sbix: Option>, + pub stat: Option>, pub svg: Option>, pub vhea: Option, pub vmtx: Option>, @@ -1189,6 +1192,7 @@ impl<'a> Face<'a> { b"name" => tables.name = table_data, b"post" => tables.post = table_data, b"sbix" => tables.sbix = table_data, + b"STAT" => tables.stat = table_data, #[cfg(feature = "apple-layout")] b"trak" => tables.trak = table_data, b"vhea" => tables.vhea = table_data, @@ -1305,6 +1309,7 @@ impl<'a> Face<'a> { sbix: raw_tables .sbix .and_then(|data| sbix::Table::parse(maxp.number_of_glyphs, data)), + stat: raw_tables.stat.and_then(stat::Table::parse), svg: raw_tables.svg.and_then(svg::Table::parse), vhea: raw_tables.vhea.and_then(vhea::Table::parse), vmtx, diff --git a/src/tables/mod.rs b/src/tables/mod.rs index d00548c4..9079e052 100644 --- a/src/tables/mod.rs +++ b/src/tables/mod.rs @@ -15,6 +15,7 @@ pub mod name; pub mod os2; pub mod post; pub mod sbix; +pub mod stat; pub mod svg; pub mod vhea; pub mod vorg; diff --git a/src/tables/stat.rs b/src/tables/stat.rs new file mode 100644 index 00000000..317ec7e7 --- /dev/null +++ b/src/tables/stat.rs @@ -0,0 +1,314 @@ +//! A [Style Attributes Table](https://docs.microsoft.com/en-us/typography/opentype/spec/stat) implementation. + +use crate::{ + parser::{Offset, Offset16, Offset32, Stream}, + Fixed, FromData, LazyArray16, Tag, +}; + +/// Axis-value pairing. +#[derive(Clone, Copy, Debug)] +pub struct AxisValue { + /// Zero-based index into [`Table::axes`]. + pub axis_index: u16, + /// Numeric value for this axis. + pub value: Fixed, +} + +impl FromData for AxisValue { + const SIZE: usize = 6; + + fn parse(data: &[u8]) -> Option { + let mut s = Stream::new(data); + let axis_index = s.read::()?; + let value = s.read::()?; + + Some(AxisValue { axis_index, value }) + } +} + +/// List of axis value tables. +#[derive(Clone, Debug)] +pub struct AxisValueTables<'a> { + data: Stream<'a>, + start: Offset32, + offsets: LazyArray16<'a, Offset16>, + index: u16, +} + +impl<'a> Iterator for AxisValueTables<'a> { + type Item = AxisValueTable<'a>; + + #[inline] + fn next(&mut self) -> Option { + if self.index >= self.offsets.len() { + return None; + } + + let mut s = Stream::new_at( + self.data.tail()?, + self.offsets.get(self.index)?.to_usize() + self.start.to_usize(), + )?; + self.index += 1; + + let format_variant = s.read::()?; + + let value = match format_variant { + 1 => { + let value = s.read::()?; + Self::Item::Format1(value) + } + 2 => { + let value = s.read::()?; + Self::Item::Format2(value) + } + 3 => { + let value = s.read::()?; + Self::Item::Format3(value) + } + 4 => { + let value = AxisValueTableFormat4::parse(s.tail()?)?; + Self::Item::Format4(value) + } + _ => return None, + }; + + Some(value) + } +} + +/// The [axis record](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-records) struct provides information about a single design axis. +#[derive(Clone, Copy, Debug)] +pub struct AxisRecord { + /// Axis tag. + pub tag: Tag, + /// The name ID for entries in the 'name' table that provide a display string for this axis. + pub name_id: u16, + /// Sort order for e.g. composing font family or face names. + pub ordering: u16, +} + +/// [Flags](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#flags) for [`AxisValue`]. +#[derive(Clone, Copy)] +pub struct AxisValueFlags(u16); + +#[rustfmt::skip] +impl AxisValueFlags { + /// If set, this value also applies to older versions of this font. + #[inline] pub fn older_sibling_attribute(self) -> bool { self.0 & (1 << 0) != 0 } + + /// If set, this value is the normal (a.k.a. "regular") value for the font family. + #[inline] pub fn elidable(self) -> bool { self.0 & (1 << 1) != 0 } +} + +impl core::fmt::Debug for AxisValueFlags { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut dbg = f.debug_set(); + + if self.older_sibling_attribute() { + dbg.entry(&"OLDER_SIBLING_FONT_ATTRIBUTE"); + } + if self.elidable() { + dbg.entry(&"ELIDABLE_AXIS_VALUE_NAME"); + } + + dbg.finish() + } +} + +/// Axis value table format 1 +#[derive(Clone, Copy, Debug)] +pub struct AxisValueTableFormat1 { + /// Zero-based index into [`Table::axes`]. + pub axis_index: u16, + /// Flags for AxisValue. + pub flags: AxisValueFlags, + /// The name ID of the display string. + pub value_name_id: u16, + /// Numeric value for this record. + pub value: Fixed, +} + +impl FromData for AxisValueTableFormat1 { + const SIZE: usize = 10; + + #[inline] + fn parse(data: &[u8]) -> Option { + let mut s = Stream::new(data); + Some(AxisValueTableFormat1 { + axis_index: s.read::()?, + flags: AxisValueFlags(s.read::()?), + value_name_id: s.read::()?, + value: s.read::()?, + }) + } +} + +/// Axis value table format 2 +#[derive(Clone, Copy, Debug)] +pub struct AxisValueTableFormat2 { + /// Zero-based index into [`Table::axes`]. + pub axis_index: u16, + /// Flags for AxisValue. + pub flags: AxisValueFlags, + /// The name ID of the display string. + pub value_name_id: u16, + /// Nominal numeric value for this record. + pub nominal_value: Fixed, + /// The minimum value for this record. + pub range_min_value: Fixed, + /// The maximum value for this record. + pub range_max_value: Fixed, +} + +impl FromData for AxisValueTableFormat2 { + const SIZE: usize = 18; + + #[inline] + fn parse(data: &[u8]) -> Option { + let mut s = Stream::new(data); + Some(AxisValueTableFormat2 { + axis_index: s.read::()?, + flags: AxisValueFlags(s.read::()?), + value_name_id: s.read::()?, + nominal_value: s.read::()?, + range_min_value: s.read::()?, + range_max_value: s.read::()?, + }) + } +} + +/// Axis value table format 3 +#[derive(Clone, Copy, Debug)] +pub struct AxisValueTableFormat3 { + /// Zero-based index into [`Table::axes`]. + pub axis_index: u16, + /// Flags for AxisValue. + pub flags: AxisValueFlags, + /// The name ID of the display string. + pub value_name_id: u16, + /// Numeric value for this record. + pub value: Fixed, + /// Numeric value for a style-linked mapping. + pub linked_value: Fixed, +} + +impl FromData for AxisValueTableFormat3 { + const SIZE: usize = 14; + + #[inline] + fn parse(data: &[u8]) -> Option { + let mut s = Stream::new(data); + Some(AxisValueTableFormat3 { + axis_index: s.read::()?, + flags: AxisValueFlags(s.read::()?), + value_name_id: s.read::()?, + value: s.read::()?, + linked_value: s.read::()?, + }) + } +} + +/// Axis value table format 4 +#[derive(Clone, Copy, Debug)] +pub struct AxisValueTableFormat4<'a> { + /// Flags for AxisValue. + pub flags: u16, + /// The name ID of the display string. + pub value_name_id: u16, + /// List of axis-value pairings. + pub values: LazyArray16<'a, AxisValue>, +} + +impl<'a> AxisValueTableFormat4<'a> { + fn parse(data: &'a [u8]) -> Option { + let mut s = Stream::new(data); + let axis_count = s.read::()?; + let flags = s.read::()?; + let value_name_id = s.read::()?; + let values = s.read_array16::(axis_count)?; + + Some(AxisValueTableFormat4 { + flags, + value_name_id, + values, + }) + } +} + +/// An [axis value table](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-tables). +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug)] +pub enum AxisValueTable<'a> { + Format1(AxisValueTableFormat1), + Format2(AxisValueTableFormat2), + Format3(AxisValueTableFormat3), + Format4(AxisValueTableFormat4<'a>), +} + +impl FromData for AxisRecord { + const SIZE: usize = 8; + + #[inline] + fn parse(data: &[u8]) -> Option { + let mut s = Stream::new(data); + Some(AxisRecord { + tag: s.read::()?, + name_id: s.read::()?, + ordering: s.read::()?, + }) + } +} + +/// A [Style Attributes Table](https://docs.microsoft.com/en-us/typography/opentype/spec/stat). +#[derive(Clone, Copy, Debug)] +pub struct Table<'a> { + /// List of axes + pub axes: LazyArray16<'a, AxisRecord>, + data: &'a [u8], + value_lookup_start: Offset32, + value_offsets: LazyArray16<'a, Offset16>, +} + +impl<'a> Table<'a> { + /// Parses a table from raw data. + pub fn parse(data: &'a [u8]) -> Option { + let mut s = Stream::new(data); + let major = s.read::()?; + let minor = s.read::()?; + + match (major, minor) { + (1, 0) | (1, 1) | (1, 2) => {} + _ => return None, + } + + let _axis_size = s.read::()?; + let axis_count = s.read::()?; + let axis_offset = s.read::()?.to_usize(); + + let value_count = s.read::()?; + let value_lookup_start = s.read::()?; + + let mut s = Stream::new_at(data, axis_offset)?; + let axes = s.read_array16::(axis_count)?; + + let mut s = Stream::new_at(data, value_lookup_start.to_usize())?; + let value_offsets = s.read_array16::(value_count)?; + + Some(Self { + axes, + data, + value_lookup_start, + value_offsets, + }) + } + + /// Iterator over the collection of axis value tables. + pub fn tables(&self) -> AxisValueTables<'a> { + AxisValueTables { + data: Stream::new(self.data), + start: self.value_lookup_start, + offsets: self.value_offsets, + index: 0, + } + } +} From 32faa90fe993e5874eafad0695b8f5c1a59e5124 Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Mon, 12 Aug 2024 16:35:27 -0700 Subject: [PATCH 02/17] Add v1.1 and v1.2 fields. --- src/tables/stat.rs | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/tables/stat.rs b/src/tables/stat.rs index 317ec7e7..5cd24cf1 100644 --- a/src/tables/stat.rs +++ b/src/tables/stat.rs @@ -33,6 +33,7 @@ pub struct AxisValueTables<'a> { start: Offset32, offsets: LazyArray16<'a, Offset16>, index: u16, + version: u32, } impl<'a> Iterator for AxisValueTables<'a> { @@ -66,6 +67,11 @@ impl<'a> Iterator for AxisValueTables<'a> { Self::Item::Format3(value) } 4 => { + // Format 4 tables didn't exist until v1.2. + if self.version < 0x00010002 { + return None; + } + let value = AxisValueTableFormat4::parse(s.tail()?)?; Self::Item::Format4(value) } @@ -264,6 +270,10 @@ impl FromData for AxisRecord { pub struct Table<'a> { /// List of axes pub axes: LazyArray16<'a, AxisRecord>, + /// Fallback name when everything can be elided. + pub fallback_name_id: Option, + /// Version of the style attributes table. + pub version: u32, data: &'a [u8], value_lookup_start: Offset32, value_offsets: LazyArray16<'a, Offset16>, @@ -273,12 +283,14 @@ impl<'a> Table<'a> { /// Parses a table from raw data. pub fn parse(data: &'a [u8]) -> Option { let mut s = Stream::new(data); - let major = s.read::()?; - let minor = s.read::()?; + let version = s.read::()?; - match (major, minor) { - (1, 0) | (1, 1) | (1, 2) => {} - _ => return None, + // Supported versions are: + // - 1.0 + // - 1.1 adds elidedFallbackNameId + // - 1.2 format 4 axis value table + if !(version == 0x00010000 || version == 0x00010001 || version == 0x00010002) { + return None; } let _axis_size = s.read::()?; @@ -288,6 +300,13 @@ impl<'a> Table<'a> { let value_count = s.read::()?; let value_lookup_start = s.read::()?; + let fallback_name_id = if version >= 0x00010001 { + // If version >= 1.1 the field is required + Some(s.read::()?) + } else { + None + }; + let mut s = Stream::new_at(data, axis_offset)?; let axes = s.read_array16::(axis_count)?; @@ -299,6 +318,8 @@ impl<'a> Table<'a> { data, value_lookup_start, value_offsets, + fallback_name_id, + version, }) } @@ -309,6 +330,7 @@ impl<'a> Table<'a> { start: self.value_lookup_start, offsets: self.value_offsets, index: 0, + version: self.version, } } } From a4636cf7d2784bb479da7af3143861aa8de4482d Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Mon, 12 Aug 2024 16:46:05 -0700 Subject: [PATCH 03/17] Impl format 3 and 4 support in font-info example. --- examples/font-info.rs | 74 +++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/examples/font-info.rs b/examples/font-info.rs index 03370ae2..df97d2e1 100644 --- a/examples/font-info.rs +++ b/examples/font-info.rs @@ -1,4 +1,4 @@ -use ttf_parser::stat::AxisValue; +use ttf_parser::stat::{AxisValue, AxisValueTable}; fn main() { let args: Vec<_> = std::env::args().collect(); @@ -101,41 +101,75 @@ fn main() { } println!(" Axis Values:"); - for value in stat.values() { - // println!(" {value:?}"); - match value { - AxisValue::Format1(value) => { - let name = face + for table in stat.tables() { + match table { + AxisValueTable::Format1(table) => { + let value_name = face .names() .into_iter() - .filter(|name| name.name_id == value.value_name_id) + .filter(|name| name.name_id == table.value_name_id) .map(|name| name.to_string().unwrap()) .collect::>() .join(", "); - println!( - " {}={:?}={name:?} flags={:?}", - &axis_names[value.axis_index as usize], value.value, value.flags - ); + let axis_name = &axis_names[table.axis_index as usize]; + let value = table.value; + let flags = table.flags; + + println!(" {axis_name} {value:?}={value_name:?} flags={flags:?}"); + } + AxisValueTable::Format2(table) => { + let value_name = face + .names() + .into_iter() + .filter(|name| name.name_id == table.value_name_id) + .map(|name| name.to_string().unwrap()) + .collect::>() + .join(", "); + + let axis_name = &axis_names[table.axis_index as usize]; + let nominal_value = table.nominal_value; + let min_value = table.range_min_value; + let max_value = table.range_max_value; + let flags = table.flags; + + println!(" {axis_name} {min_value:?}..{max_value:?}={value_name:?} nominal={nominal_value:?} flags={flags:?}"); } - AxisValue::Format2(_) => todo!(), - AxisValue::Format3(value) => { - let name = face + AxisValueTable::Format3(table) => { + let value_name = face .names() .into_iter() - .filter(|name| name.name_id == value.value_name_id) + .filter(|name| name.name_id == table.value_name_id) .map(|name| name.to_string().unwrap()) .collect::>() .join(", "); + let axis_name = &axis_names[table.axis_index as usize]; + let value = table.value; + let linked_value = table.linked_value; + let flags = table.flags; + println!( - " {} {:?}<=>{:?} = {name:?} flags={:?}", - &axis_names[value.axis_index as usize], - value.value, - value.linked_value, - value.flags + " {axis_name} {value:?}<=>{linked_value:?} = {value_name:?} flags={flags:?}", ); } + AxisValueTable::Format4(table) => { + let value_name = face + .names() + .into_iter() + .filter(|name| name.name_id == table.value_name_id) + .map(|name| name.to_string().unwrap()) + .collect::>() + .join(", "); + + let flags = table.flags; + + println!(" {value_name:?} flags={flags:?}"); + for pair in table.values { + let AxisValue { axis_index, value } = pair; + println!(" {axis_index} = {value:?}") + } + } } } } From da5ca3e33f31905733554ce991c4029be6b7e65d Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Tue, 13 Aug 2024 10:22:23 -0700 Subject: [PATCH 04/17] Add `STAT` table to the README. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7e280d2d..a3c5604e 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,7 @@ There are roughly three types of TrueType tables: | `OS/2` table | ✓ | ✓ | | | `post` table | ✓ | ✓ | | | `sbix` table | ~ (PNG only) | ~ (PNG only) | | +| `STAT` table | ✓ | | | | `SVG ` table | ✓ | ✓ | ✓ | | `trak` table | ✓ | | | | `vhea` table | ✓ | ✓ | | From 64df42c6fc4547ccf70ab2b95093573adbaa72a1 Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Tue, 13 Aug 2024 10:28:05 -0700 Subject: [PATCH 05/17] =?UTF-8?q?Table::tables=20=E2=86=92=20Table::subtab?= =?UTF-8?q?les?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/font-info.rs | 2 +- src/tables/stat.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/font-info.rs b/examples/font-info.rs index df97d2e1..a7e33939 100644 --- a/examples/font-info.rs +++ b/examples/font-info.rs @@ -101,7 +101,7 @@ fn main() { } println!(" Axis Values:"); - for table in stat.tables() { + for table in stat.subtables() { match table { AxisValueTable::Format1(table) => { let value_name = face diff --git a/src/tables/stat.rs b/src/tables/stat.rs index 5cd24cf1..fac28b2b 100644 --- a/src/tables/stat.rs +++ b/src/tables/stat.rs @@ -324,7 +324,7 @@ impl<'a> Table<'a> { } /// Iterator over the collection of axis value tables. - pub fn tables(&self) -> AxisValueTables<'a> { + pub fn subtables(&self) -> AxisValueTables<'a> { AxisValueTables { data: Stream::new(self.data), start: self.value_lookup_start, From bcdc1ba84cd0e4df789ad93c58bb13190cb6d27b Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Wed, 14 Aug 2024 17:02:36 -0700 Subject: [PATCH 06/17] =?UTF-8?q?`AxisValueTables`=20=E2=86=92=20`AxisValu?= =?UTF-8?q?eSubtables`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tables/stat.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tables/stat.rs b/src/tables/stat.rs index fac28b2b..3153cc61 100644 --- a/src/tables/stat.rs +++ b/src/tables/stat.rs @@ -26,9 +26,9 @@ impl FromData for AxisValue { } } -/// List of axis value tables. +/// List of axis value subtables. #[derive(Clone, Debug)] -pub struct AxisValueTables<'a> { +pub struct AxisValueSubtables<'a> { data: Stream<'a>, start: Offset32, offsets: LazyArray16<'a, Offset16>, @@ -36,7 +36,7 @@ pub struct AxisValueTables<'a> { version: u32, } -impl<'a> Iterator for AxisValueTables<'a> { +impl<'a> Iterator for AxisValueSubtables<'a> { type Item = AxisValueTable<'a>; #[inline] @@ -324,8 +324,8 @@ impl<'a> Table<'a> { } /// Iterator over the collection of axis value tables. - pub fn subtables(&self) -> AxisValueTables<'a> { - AxisValueTables { + pub fn subtables(&self) -> AxisValueSubtables<'a> { + AxisValueSubtables { data: Stream::new(self.data), start: self.value_lookup_start, offsets: self.value_offsets, From 919a8b6f3375b6ba8e23222cc91e69351afa9642 Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Wed, 14 Aug 2024 17:20:01 -0700 Subject: [PATCH 07/17] `Table`: make the version field private. --- src/tables/stat.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tables/stat.rs b/src/tables/stat.rs index 3153cc61..83a1bffe 100644 --- a/src/tables/stat.rs +++ b/src/tables/stat.rs @@ -272,8 +272,7 @@ pub struct Table<'a> { pub axes: LazyArray16<'a, AxisRecord>, /// Fallback name when everything can be elided. pub fallback_name_id: Option, - /// Version of the style attributes table. - pub version: u32, + version: u32, data: &'a [u8], value_lookup_start: Offset32, value_offsets: LazyArray16<'a, Offset16>, From 0518cd1f685359885a78bed912928e4bf68461eb Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Wed, 14 Aug 2024 17:42:49 -0700 Subject: [PATCH 08/17] Fix grammar nit in a comment --- src/tables/stat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tables/stat.rs b/src/tables/stat.rs index 83a1bffe..0a17f9ca 100644 --- a/src/tables/stat.rs +++ b/src/tables/stat.rs @@ -287,7 +287,7 @@ impl<'a> Table<'a> { // Supported versions are: // - 1.0 // - 1.1 adds elidedFallbackNameId - // - 1.2 format 4 axis value table + // - 1.2 adds format 4 axis value table if !(version == 0x00010000 || version == 0x00010001 || version == 0x00010002) { return None; } From ea16b26481d5ed17495a8d9cb9ce9959181296da Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Thu, 15 Aug 2024 00:52:29 -0700 Subject: [PATCH 09/17] =?UTF-8?q?`AxisValueTable`=20=E2=86=92=20`AxisValue?= =?UTF-8?q?Subtable`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/font-info.rs | 10 +++++----- src/tables/stat.rs | 44 +++++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/examples/font-info.rs b/examples/font-info.rs index a7e33939..e9206be8 100644 --- a/examples/font-info.rs +++ b/examples/font-info.rs @@ -1,4 +1,4 @@ -use ttf_parser::stat::{AxisValue, AxisValueTable}; +use ttf_parser::stat::{AxisValue, AxisValueSubtable}; fn main() { let args: Vec<_> = std::env::args().collect(); @@ -103,7 +103,7 @@ fn main() { println!(" Axis Values:"); for table in stat.subtables() { match table { - AxisValueTable::Format1(table) => { + AxisValueSubtable::Format1(table) => { let value_name = face .names() .into_iter() @@ -118,7 +118,7 @@ fn main() { println!(" {axis_name} {value:?}={value_name:?} flags={flags:?}"); } - AxisValueTable::Format2(table) => { + AxisValueSubtable::Format2(table) => { let value_name = face .names() .into_iter() @@ -135,7 +135,7 @@ fn main() { println!(" {axis_name} {min_value:?}..{max_value:?}={value_name:?} nominal={nominal_value:?} flags={flags:?}"); } - AxisValueTable::Format3(table) => { + AxisValueSubtable::Format3(table) => { let value_name = face .names() .into_iter() @@ -153,7 +153,7 @@ fn main() { " {axis_name} {value:?}<=>{linked_value:?} = {value_name:?} flags={flags:?}", ); } - AxisValueTable::Format4(table) => { + AxisValueSubtable::Format4(table) => { let value_name = face .names() .into_iter() diff --git a/src/tables/stat.rs b/src/tables/stat.rs index 0a17f9ca..ef4181ef 100644 --- a/src/tables/stat.rs +++ b/src/tables/stat.rs @@ -37,7 +37,7 @@ pub struct AxisValueSubtables<'a> { } impl<'a> Iterator for AxisValueSubtables<'a> { - type Item = AxisValueTable<'a>; + type Item = AxisValueSubtable<'a>; #[inline] fn next(&mut self) -> Option { @@ -55,15 +55,15 @@ impl<'a> Iterator for AxisValueSubtables<'a> { let value = match format_variant { 1 => { - let value = s.read::()?; + let value = s.read::()?; Self::Item::Format1(value) } 2 => { - let value = s.read::()?; + let value = s.read::()?; Self::Item::Format2(value) } 3 => { - let value = s.read::()?; + let value = s.read::()?; Self::Item::Format3(value) } 4 => { @@ -72,7 +72,7 @@ impl<'a> Iterator for AxisValueSubtables<'a> { return None; } - let value = AxisValueTableFormat4::parse(s.tail()?)?; + let value = AxisValueSubtableFormat4::parse(s.tail()?)?; Self::Item::Format4(value) } _ => return None, @@ -123,7 +123,7 @@ impl core::fmt::Debug for AxisValueFlags { /// Axis value table format 1 #[derive(Clone, Copy, Debug)] -pub struct AxisValueTableFormat1 { +pub struct AxisValueSubtableFormat1 { /// Zero-based index into [`Table::axes`]. pub axis_index: u16, /// Flags for AxisValue. @@ -134,13 +134,13 @@ pub struct AxisValueTableFormat1 { pub value: Fixed, } -impl FromData for AxisValueTableFormat1 { +impl FromData for AxisValueSubtableFormat1 { const SIZE: usize = 10; #[inline] fn parse(data: &[u8]) -> Option { let mut s = Stream::new(data); - Some(AxisValueTableFormat1 { + Some(AxisValueSubtableFormat1 { axis_index: s.read::()?, flags: AxisValueFlags(s.read::()?), value_name_id: s.read::()?, @@ -151,7 +151,7 @@ impl FromData for AxisValueTableFormat1 { /// Axis value table format 2 #[derive(Clone, Copy, Debug)] -pub struct AxisValueTableFormat2 { +pub struct AxisValueSubtableFormat2 { /// Zero-based index into [`Table::axes`]. pub axis_index: u16, /// Flags for AxisValue. @@ -166,13 +166,13 @@ pub struct AxisValueTableFormat2 { pub range_max_value: Fixed, } -impl FromData for AxisValueTableFormat2 { +impl FromData for AxisValueSubtableFormat2 { const SIZE: usize = 18; #[inline] fn parse(data: &[u8]) -> Option { let mut s = Stream::new(data); - Some(AxisValueTableFormat2 { + Some(AxisValueSubtableFormat2 { axis_index: s.read::()?, flags: AxisValueFlags(s.read::()?), value_name_id: s.read::()?, @@ -185,7 +185,7 @@ impl FromData for AxisValueTableFormat2 { /// Axis value table format 3 #[derive(Clone, Copy, Debug)] -pub struct AxisValueTableFormat3 { +pub struct AxisValueSubtableFormat3 { /// Zero-based index into [`Table::axes`]. pub axis_index: u16, /// Flags for AxisValue. @@ -198,13 +198,13 @@ pub struct AxisValueTableFormat3 { pub linked_value: Fixed, } -impl FromData for AxisValueTableFormat3 { +impl FromData for AxisValueSubtableFormat3 { const SIZE: usize = 14; #[inline] fn parse(data: &[u8]) -> Option { let mut s = Stream::new(data); - Some(AxisValueTableFormat3 { + Some(AxisValueSubtableFormat3 { axis_index: s.read::()?, flags: AxisValueFlags(s.read::()?), value_name_id: s.read::()?, @@ -216,7 +216,7 @@ impl FromData for AxisValueTableFormat3 { /// Axis value table format 4 #[derive(Clone, Copy, Debug)] -pub struct AxisValueTableFormat4<'a> { +pub struct AxisValueSubtableFormat4<'a> { /// Flags for AxisValue. pub flags: u16, /// The name ID of the display string. @@ -225,7 +225,7 @@ pub struct AxisValueTableFormat4<'a> { pub values: LazyArray16<'a, AxisValue>, } -impl<'a> AxisValueTableFormat4<'a> { +impl<'a> AxisValueSubtableFormat4<'a> { fn parse(data: &'a [u8]) -> Option { let mut s = Stream::new(data); let axis_count = s.read::()?; @@ -233,7 +233,7 @@ impl<'a> AxisValueTableFormat4<'a> { let value_name_id = s.read::()?; let values = s.read_array16::(axis_count)?; - Some(AxisValueTableFormat4 { + Some(AxisValueSubtableFormat4 { flags, value_name_id, values, @@ -244,11 +244,11 @@ impl<'a> AxisValueTableFormat4<'a> { /// An [axis value table](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-tables). #[allow(missing_docs)] #[derive(Clone, Copy, Debug)] -pub enum AxisValueTable<'a> { - Format1(AxisValueTableFormat1), - Format2(AxisValueTableFormat2), - Format3(AxisValueTableFormat3), - Format4(AxisValueTableFormat4<'a>), +pub enum AxisValueSubtable<'a> { + Format1(AxisValueSubtableFormat1), + Format2(AxisValueSubtableFormat2), + Format3(AxisValueSubtableFormat3), + Format4(AxisValueSubtableFormat4<'a>), } impl FromData for AxisRecord { From 80991a2302a3e4010e2b0d0b8ff53cc4f0d138e8 Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Thu, 15 Aug 2024 03:02:24 -0700 Subject: [PATCH 10/17] Add a query interface to the `STAT` table. --- examples/font-info.rs | 100 ++++++------------------------------------ src/tables/stat.rs | 66 ++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 87 deletions(-) diff --git a/examples/font-info.rs b/examples/font-info.rs index e9206be8..935d9410 100644 --- a/examples/font-info.rs +++ b/examples/font-info.rs @@ -1,5 +1,3 @@ -use ttf_parser::stat::{AxisValue, AxisValueSubtable}; - fn main() { let args: Vec<_> = std::env::args().collect(); if args.len() != 2 { @@ -87,91 +85,7 @@ fn main() { } if let Some(stat) = face.tables().stat { - let axis_names = stat - .axes - .into_iter() - .map(|axis| axis.tag) - .collect::>(); - - println!("Style attributes:"); - - println!(" Axes:"); - for axis in axis_names.iter() { - println!(" {}", axis); - } - - println!(" Axis Values:"); - for table in stat.subtables() { - match table { - AxisValueSubtable::Format1(table) => { - let value_name = face - .names() - .into_iter() - .filter(|name| name.name_id == table.value_name_id) - .map(|name| name.to_string().unwrap()) - .collect::>() - .join(", "); - - let axis_name = &axis_names[table.axis_index as usize]; - let value = table.value; - let flags = table.flags; - - println!(" {axis_name} {value:?}={value_name:?} flags={flags:?}"); - } - AxisValueSubtable::Format2(table) => { - let value_name = face - .names() - .into_iter() - .filter(|name| name.name_id == table.value_name_id) - .map(|name| name.to_string().unwrap()) - .collect::>() - .join(", "); - - let axis_name = &axis_names[table.axis_index as usize]; - let nominal_value = table.nominal_value; - let min_value = table.range_min_value; - let max_value = table.range_max_value; - let flags = table.flags; - - println!(" {axis_name} {min_value:?}..{max_value:?}={value_name:?} nominal={nominal_value:?} flags={flags:?}"); - } - AxisValueSubtable::Format3(table) => { - let value_name = face - .names() - .into_iter() - .filter(|name| name.name_id == table.value_name_id) - .map(|name| name.to_string().unwrap()) - .collect::>() - .join(", "); - - let axis_name = &axis_names[table.axis_index as usize]; - let value = table.value; - let linked_value = table.linked_value; - let flags = table.flags; - - println!( - " {axis_name} {value:?}<=>{linked_value:?} = {value_name:?} flags={flags:?}", - ); - } - AxisValueSubtable::Format4(table) => { - let value_name = face - .names() - .into_iter() - .filter(|name| name.name_id == table.value_name_id) - .map(|name| name.to_string().unwrap()) - .collect::>() - .join(", "); - - let flags = table.flags; - - println!(" {value_name:?} flags={flags:?}"); - for pair in table.values { - let AxisValue { axis_index, value } = pair; - println!(" {axis_index} = {value:?}") - } - } - } - } + print_opentype_style_attributes(&stat) } println!("Elapsed: {}us", now.elapsed().as_micros()); @@ -201,3 +115,15 @@ fn print_opentype_layout(name: &str, table: &ttf_parser::opentype_layout::Layout println!(" {}", feature); } } + +fn print_opentype_style_attributes(table: &ttf_parser::stat::Table) { + println!("Style attributes:"); + + println!(" Axes:"); + for axis in table.axes { + println!(" {}", axis.tag); + if let Some(subtable) = table.subtable_for_axis(axis.tag, None) { + println!(" {subtable:?}") + } + } +} diff --git a/src/tables/stat.rs b/src/tables/stat.rs index ef4181ef..541705cf 100644 --- a/src/tables/stat.rs +++ b/src/tables/stat.rs @@ -332,4 +332,70 @@ impl<'a> Table<'a> { version: self.version, } } + + /// Returns the first matching subtable for a given axis. If no match value is given the first + /// subtable for the axis is returned. If a match value is given, the first subtable for the + /// axis where the value matches is returned. A value matches if it is equal to the subtable's + /// value or contained within the range defined by the subtable. If no matches are found `None` + /// is returned. Typically a match value is not specified for non-variable fonts as multiple + /// subtables for a given axis ought not exist. For variable fonts a non-`None` match value + /// should be specified as multiple records for the variation axes exist. + pub fn subtable_for_axis( + &self, + axis: Tag, + match_value: Option, + ) -> Option { + for subtable in self.subtables() { + match subtable { + AxisValueSubtable::Format1(AxisValueSubtableFormat1 { + axis_index, value, .. + }) + | AxisValueSubtable::Format3(AxisValueSubtableFormat3 { + axis_index, value, .. + }) => { + if self.axes.get(axis_index)?.tag != axis { + continue; + } + + match match_value { + Some(match_value) => { + if match_value.0 == value.0 { + return Some(subtable); + } + } + None => return Some(subtable), + } + } + AxisValueSubtable::Format2(AxisValueSubtableFormat2 { + axis_index, + range_min_value, + range_max_value, + .. + }) => { + if self.axes.get(axis_index)?.tag == axis { + continue; + } + + match match_value { + Some(match_value) => { + if match_value.0 >= range_min_value.0 + && match_value.0 < range_max_value.0 + { + return Some(subtable); + } + } + None => return Some(subtable), + } + } + AxisValueSubtable::Format4(_) => { + // A query that's intended to search format 4 subtables can be performed + // across multiple axes. A separate function that takes a collection of + // axis-value pairs is more sutable than this. + continue; + } + } + } + + None + } } From 6da274d8cc1ac993b70a1cb62d2670d8e8bc028a Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Thu, 15 Aug 2024 13:41:15 -0700 Subject: [PATCH 11/17] Reformat docs for `subtable_for_axis`. --- src/tables/stat.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/tables/stat.rs b/src/tables/stat.rs index 541705cf..8db16841 100644 --- a/src/tables/stat.rs +++ b/src/tables/stat.rs @@ -333,13 +333,17 @@ impl<'a> Table<'a> { } } - /// Returns the first matching subtable for a given axis. If no match value is given the first - /// subtable for the axis is returned. If a match value is given, the first subtable for the - /// axis where the value matches is returned. A value matches if it is equal to the subtable's - /// value or contained within the range defined by the subtable. If no matches are found `None` - /// is returned. Typically a match value is not specified for non-variable fonts as multiple - /// subtables for a given axis ought not exist. For variable fonts a non-`None` match value - /// should be specified as multiple records for the variation axes exist. + /// Returns the first matching subtable for a given axis. + /// + /// If no match value is given the first subtable for the axis is returned. If a match value is + /// given, the first subtable for the axis where the value matches is returned. A value matches + /// if it is equal to the subtable's value or contained within the range defined by the + /// subtable. If no matches are found `None` is returned. Typically a match value is not + /// specified for non-variable fonts as multiple subtables for a given axis ought not exist. For + /// variable fonts a non-`None` match value should be specified as multiple records for the + /// variation axes exist. + /// + /// Note: Format 4 subtables are explicitly ignored in this function. pub fn subtable_for_axis( &self, axis: Tag, From e670923284cb027ccf8bf0837bee96ff7ea59827 Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Thu, 15 Aug 2024 13:42:14 -0700 Subject: [PATCH 12/17] Refactor example. --- examples/font-info.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/examples/font-info.rs b/examples/font-info.rs index 935d9410..089b8b33 100644 --- a/examples/font-info.rs +++ b/examples/font-info.rs @@ -85,7 +85,15 @@ fn main() { } if let Some(stat) = face.tables().stat { - print_opentype_style_attributes(&stat) + println!("Style attributes:"); + + println!(" Axes:"); + for axis in stat.axes { + println!(" {}", axis.tag); + if let Some(subtable) = stat.subtable_for_axis(axis.tag, None) { + println!(" {subtable:?}") + } + } } println!("Elapsed: {}us", now.elapsed().as_micros()); @@ -115,15 +123,3 @@ fn print_opentype_layout(name: &str, table: &ttf_parser::opentype_layout::Layout println!(" {}", feature); } } - -fn print_opentype_style_attributes(table: &ttf_parser::stat::Table) { - println!("Style attributes:"); - - println!(" Axes:"); - for axis in table.axes { - println!(" {}", axis.tag); - if let Some(subtable) = table.subtable_for_axis(axis.tag, None) { - println!(" {subtable:?}") - } - } -} From 6cc68f95569e23621d8ea6b0fc9a192c609acab1 Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Thu, 15 Aug 2024 15:05:36 -0700 Subject: [PATCH 13/17] Place `AxisRecord` impl next to the definition. --- src/tables/stat.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/tables/stat.rs b/src/tables/stat.rs index 8db16841..7b36e267 100644 --- a/src/tables/stat.rs +++ b/src/tables/stat.rs @@ -93,6 +93,20 @@ pub struct AxisRecord { pub ordering: u16, } +impl FromData for AxisRecord { + const SIZE: usize = 8; + + #[inline] + fn parse(data: &[u8]) -> Option { + let mut s = Stream::new(data); + Some(AxisRecord { + tag: s.read::()?, + name_id: s.read::()?, + ordering: s.read::()?, + }) + } +} + /// [Flags](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#flags) for [`AxisValue`]. #[derive(Clone, Copy)] pub struct AxisValueFlags(u16); @@ -251,20 +265,6 @@ pub enum AxisValueSubtable<'a> { Format4(AxisValueSubtableFormat4<'a>), } -impl FromData for AxisRecord { - const SIZE: usize = 8; - - #[inline] - fn parse(data: &[u8]) -> Option { - let mut s = Stream::new(data); - Some(AxisRecord { - tag: s.read::()?, - name_id: s.read::()?, - ordering: s.read::()?, - }) - } -} - /// A [Style Attributes Table](https://docs.microsoft.com/en-us/typography/opentype/spec/stat). #[derive(Clone, Copy, Debug)] pub struct Table<'a> { From 2ca1ceaffed6013ad9562cf2f3c868017fa2efc0 Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Thu, 15 Aug 2024 15:16:06 -0700 Subject: [PATCH 14/17] =?UTF-8?q?Format4=20flags:=20`u16`=20=E2=86=92=20`A?= =?UTF-8?q?xisValueFlags`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tables/stat.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tables/stat.rs b/src/tables/stat.rs index 7b36e267..53f4ac5e 100644 --- a/src/tables/stat.rs +++ b/src/tables/stat.rs @@ -232,7 +232,7 @@ impl FromData for AxisValueSubtableFormat3 { #[derive(Clone, Copy, Debug)] pub struct AxisValueSubtableFormat4<'a> { /// Flags for AxisValue. - pub flags: u16, + pub flags: AxisValueFlags, /// The name ID of the display string. pub value_name_id: u16, /// List of axis-value pairings. @@ -243,7 +243,7 @@ impl<'a> AxisValueSubtableFormat4<'a> { fn parse(data: &'a [u8]) -> Option { let mut s = Stream::new(data); let axis_count = s.read::()?; - let flags = s.read::()?; + let flags = AxisValueFlags(s.read::()?); let value_name_id = s.read::()?; let values = s.read_array16::(axis_count)?; From 47839418abfdb2e8d6ed32520e0f5921e2799350 Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Thu, 15 Aug 2024 16:53:26 -0700 Subject: [PATCH 15/17] Documentation updates. --- src/tables/stat.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/tables/stat.rs b/src/tables/stat.rs index 53f4ac5e..653760b8 100644 --- a/src/tables/stat.rs +++ b/src/tables/stat.rs @@ -5,7 +5,7 @@ use crate::{ Fixed, FromData, LazyArray16, Tag, }; -/// Axis-value pairing. +/// Axis-value pairing for [`AxisValueSubtableFormat4`]. #[derive(Clone, Copy, Debug)] pub struct AxisValue { /// Zero-based index into [`Table::axes`]. @@ -26,7 +26,7 @@ impl FromData for AxisValue { } } -/// List of axis value subtables. +/// Iterator over axis value subtables. #[derive(Clone, Debug)] pub struct AxisValueSubtables<'a> { data: Stream<'a>, @@ -107,7 +107,7 @@ impl FromData for AxisRecord { } } -/// [Flags](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#flags) for [`AxisValue`]. +/// [Flags](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#flags) for [`AxisValueSubtable`]. #[derive(Clone, Copy)] pub struct AxisValueFlags(u16); @@ -135,12 +135,12 @@ impl core::fmt::Debug for AxisValueFlags { } } -/// Axis value table format 1 +/// Axis value subtable [format 1](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-1). #[derive(Clone, Copy, Debug)] pub struct AxisValueSubtableFormat1 { /// Zero-based index into [`Table::axes`]. pub axis_index: u16, - /// Flags for AxisValue. + /// Flags for [`AxisValueSubtable`]. pub flags: AxisValueFlags, /// The name ID of the display string. pub value_name_id: u16, @@ -163,12 +163,12 @@ impl FromData for AxisValueSubtableFormat1 { } } -/// Axis value table format 2 +/// Axis value subtable [format 2](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-2). #[derive(Clone, Copy, Debug)] pub struct AxisValueSubtableFormat2 { /// Zero-based index into [`Table::axes`]. pub axis_index: u16, - /// Flags for AxisValue. + /// Flags for [`AxisValueSubtable`]. pub flags: AxisValueFlags, /// The name ID of the display string. pub value_name_id: u16, @@ -197,12 +197,12 @@ impl FromData for AxisValueSubtableFormat2 { } } -/// Axis value table format 3 +/// Axis value subtable [format 3](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-3). #[derive(Clone, Copy, Debug)] pub struct AxisValueSubtableFormat3 { /// Zero-based index into [`Table::axes`]. pub axis_index: u16, - /// Flags for AxisValue. + /// Flags for [`AxisValueSubtable`]. pub flags: AxisValueFlags, /// The name ID of the display string. pub value_name_id: u16, @@ -228,10 +228,10 @@ impl FromData for AxisValueSubtableFormat3 { } } -/// Axis value table format 4 +/// Axis value subtable [format 4](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-table-format-4). #[derive(Clone, Copy, Debug)] pub struct AxisValueSubtableFormat4<'a> { - /// Flags for AxisValue. + /// Flags for [`AxisValueSubtable`]. pub flags: AxisValueFlags, /// The name ID of the display string. pub value_name_id: u16, @@ -255,7 +255,7 @@ impl<'a> AxisValueSubtableFormat4<'a> { } } -/// An [axis value table](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-tables). +/// An [axis value subtable](https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-value-tables). #[allow(missing_docs)] #[derive(Clone, Copy, Debug)] pub enum AxisValueSubtable<'a> { @@ -322,7 +322,7 @@ impl<'a> Table<'a> { }) } - /// Iterator over the collection of axis value tables. + /// Returns an iterator over the collection of axis value tables. pub fn subtables(&self) -> AxisValueSubtables<'a> { AxisValueSubtables { data: Stream::new(self.data), @@ -340,8 +340,8 @@ impl<'a> Table<'a> { /// if it is equal to the subtable's value or contained within the range defined by the /// subtable. If no matches are found `None` is returned. Typically a match value is not /// specified for non-variable fonts as multiple subtables for a given axis ought not exist. For - /// variable fonts a non-`None` match value should be specified as multiple records for the - /// variation axes exist. + /// variable fonts a non-`None` match value should be specified as multiple records for each of + /// the variation axes exist. /// /// Note: Format 4 subtables are explicitly ignored in this function. pub fn subtable_for_axis( From 447caa6a626cbf3fc336aa4a3e904b942e44b827 Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Fri, 16 Aug 2024 00:03:09 -0700 Subject: [PATCH 16/17] Don't use named parameters in format strings. --- examples/font-info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/font-info.rs b/examples/font-info.rs index 089b8b33..7c0d4226 100644 --- a/examples/font-info.rs +++ b/examples/font-info.rs @@ -91,7 +91,7 @@ fn main() { for axis in stat.axes { println!(" {}", axis.tag); if let Some(subtable) = stat.subtable_for_axis(axis.tag, None) { - println!(" {subtable:?}") + println!(" {:?}", subtable) } } } From 0240f7c9ad24eb471a3635cd69d7e3fdab4d265d Mon Sep 17 00:00:00 2001 From: Alex Zepeda Date: Sat, 17 Aug 2024 18:05:08 -0700 Subject: [PATCH 17/17] Add accessors for `AxisValueSubtable`. --- src/tables/stat.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/tables/stat.rs b/src/tables/stat.rs index 653760b8..77553428 100644 --- a/src/tables/stat.rs +++ b/src/tables/stat.rs @@ -265,6 +265,78 @@ pub enum AxisValueSubtable<'a> { Format4(AxisValueSubtableFormat4<'a>), } +impl<'a> AxisValueSubtable<'a> { + /// Returns the value from an axis value subtable. + /// + /// For formats 1 and 3 the value is returned, for formats 2 and 4 `None` is returned as there + /// is no single value associated with those formats. + pub fn value(&self) -> Option { + match self { + Self::Format1(AxisValueSubtableFormat1 { value, .. }) + | Self::Format3(AxisValueSubtableFormat3 { value, .. }) => Some(*value), + _ => None, + } + } + + /// Returns `true` if the axis subtable either is the value or is a range that contains the + /// value passed in as an argument. + /// + /// Note: this will always return false for format 4 subtables as they may contain multiple + /// axes. + pub fn contains(&self, value: Fixed) -> bool { + if let Some(subtable_value) = self.value() { + if subtable_value.0 == value.0 { + return true; + } + } + + if let Self::Format2(AxisValueSubtableFormat2 { + range_min_value, + range_max_value, + .. + }) = self + { + // core::ops::Range doesn't work here because Fixed doesn't implement + // the required comparison traits. + if value.0 >= range_min_value.0 && value.0 < range_max_value.0 { + return true; + } + } + + false + } + + /// Returns the associated name ID. + pub fn name_id(&self) -> u16 { + match self { + Self::Format1(AxisValueSubtableFormat1 { value_name_id, .. }) + | Self::Format2(AxisValueSubtableFormat2 { value_name_id, .. }) + | Self::Format3(AxisValueSubtableFormat3 { value_name_id, .. }) + | Self::Format4(AxisValueSubtableFormat4 { value_name_id, .. }) => *value_name_id, + } + } + + #[inline] + fn flags(&self) -> AxisValueFlags { + match self { + Self::Format1(AxisValueSubtableFormat1 { flags, .. }) + | Self::Format2(AxisValueSubtableFormat2 { flags, .. }) + | Self::Format3(AxisValueSubtableFormat3 { flags, .. }) + | Self::Format4(AxisValueSubtableFormat4 { flags, .. }) => *flags, + } + } + + /// Returns `true` if the axis subtable has the `ELIDABLE_AXIS_VALUE_NAME` flag set. + pub fn is_elidable(&self) -> bool { + self.flags().elidable() + } + + /// Returns `true` if the axis subtable has the `OLDER_SIBLING_FONT_ATTRIBUTE` flag set. + pub fn is_older_sibling(&self) -> bool { + self.flags().older_sibling_attribute() + } +} + /// A [Style Attributes Table](https://docs.microsoft.com/en-us/typography/opentype/spec/stat). #[derive(Clone, Copy, Debug)] pub struct Table<'a> {