Skip to content

Commit

Permalink
Add feat table.
Browse files Browse the repository at this point in the history
  • Loading branch information
RazrFalcon committed Dec 30, 2021
1 parent 8ddb549 commit d8e5b2d
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- `apple-layout` build feature.
- `feat` table.

## [0.14.0] - 2021-12-28
### Changed
Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Expand Up @@ -13,13 +13,15 @@ edition = "2018"
exclude = ["benches/**"]

[features]
default = ["std", "opentype-layout", "variable-fonts", "glyph-names"]
default = ["std", "opentype-layout", "apple-layout", "variable-fonts", "glyph-names"]
std = []
# Enables variable fonts support. Increases binary size almost twice.
# Includes avar, CFF2, fvar, gvar, HVAR, MVAR and VVAR tables.
variable-fonts = []
# Enables GDEF, GPOS and GSUB tables.
opentype-layout = []
# Enables feat tables.
apple-layout = []
# Enables glyph name query via `Face::glyph_name`.
# TrueType fonts do not store default glyph names, to reduce file size,
# which means we have to store them in ttf-parser. And there are almost 500 of them.
Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -68,6 +68,7 @@ There are roughly three types of TrueType tables:
| `cmap` table | ~ (no 8) || ~ (no 2,8,10,14; Unicode-only) |
| `EBDT` table | || |
| `EBLC` table | || |
| `feat` table || | |
| `fvar` table ||| |
| `gasp` table | || |
| `GDEF` table | ~ | | |
Expand Down
9 changes: 9 additions & 0 deletions src/lib.rs
Expand Up @@ -68,6 +68,7 @@ pub use tables::CFFError;
pub use tables::{cmap, kern, sbix, maxp, hmtx, name, os2, loca, svg, vorg, post, head, hhea, glyf};
pub use tables::{cff1 as cff, vhea, cbdt, cblc};
#[cfg(feature = "opentype-layout")] pub use tables::{gdef, gpos, gsub};
#[cfg(feature = "apple-layout")] pub use tables::{feat};
#[cfg(feature = "variable-fonts")] pub use tables::{cff2, avar, fvar, gvar, hvar, mvar};

#[cfg(feature = "opentype-layout")]
Expand Down Expand Up @@ -568,6 +569,8 @@ pub struct RawFaceTables<'a> {
#[cfg(feature = "opentype-layout")] pub gpos: Option<&'a [u8]>,
#[cfg(feature = "opentype-layout")] pub gsub: Option<&'a [u8]>,

#[cfg(feature = "apple-layout")] pub feat: Option<&'a [u8]>,

#[cfg(feature = "variable-fonts")] pub avar: Option<&'a [u8]>,
#[cfg(feature = "variable-fonts")] pub cff2: Option<&'a [u8]>,
#[cfg(feature = "variable-fonts")] pub fvar: Option<&'a [u8]>,
Expand Down Expand Up @@ -612,6 +615,8 @@ pub struct FaceTables<'a> {
#[cfg(feature = "opentype-layout")] pub gpos: Option<opentype_layout::LayoutTable<'a>>,
#[cfg(feature = "opentype-layout")] pub gsub: Option<opentype_layout::LayoutTable<'a>>,

#[cfg(feature = "apple-layout")] pub feat: Option<feat::Table<'a>>,

#[cfg(feature = "variable-fonts")] pub avar: Option<avar::Table<'a>>,
#[cfg(feature = "variable-fonts")] pub cff2: Option<cff2::Table<'a>>,
#[cfg(feature = "variable-fonts")] pub fvar: Option<fvar::Table<'a>>,
Expand Down Expand Up @@ -748,6 +753,8 @@ impl<'a> Face<'a> {
#[cfg(feature = "variable-fonts")]
b"avar" => tables.avar = table_data,
b"cmap" => tables.cmap = table_data,
#[cfg(feature = "apple-fonts")]
b"feat" => tables.feat = table_data,
#[cfg(feature = "variable-fonts")]
b"fvar" => tables.fvar = table_data,
b"glyf" => tables.glyf = table_data,
Expand Down Expand Up @@ -847,6 +854,8 @@ impl<'a> Face<'a> {
#[cfg(feature = "opentype-layout")] gpos: raw_tables.gpos.and_then(opentype_layout::LayoutTable::parse),
#[cfg(feature = "opentype-layout")] gsub: raw_tables.gsub.and_then(opentype_layout::LayoutTable::parse),

#[cfg(feature = "apple-layout")] feat: raw_tables.feat.and_then(feat::Table::parse),

#[cfg(feature = "variable-fonts")] avar: raw_tables.avar.and_then(avar::Table::parse),
#[cfg(feature = "variable-fonts")] cff2: raw_tables.cff2.and_then(cff2::Table::parse),
#[cfg(feature = "variable-fonts")] fvar: raw_tables.fvar.and_then(fvar::Table::parse),
Expand Down
180 changes: 180 additions & 0 deletions src/tables/feat.rs
@@ -0,0 +1,180 @@
//! A [Feature Name Table](
//! https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6feat.html) implementation.

use crate::parser::{FromData, LazyArray16, Offset, Offset32, Stream};

#[derive(Clone, Copy, Debug)]
struct FeatureNameRecord {
feature: u16,
setting_table_records_count: u16,
// Offset from the beginning of the table.
setting_table_offset: Offset32,
flags: u8,
default_setting_index: u8,
name_index: u16,
}

impl FromData for FeatureNameRecord {
const SIZE: usize = 12;

#[inline]
fn parse(data: &[u8]) -> Option<Self> {
let mut s = Stream::new(data);
Some(FeatureNameRecord {
feature: s.read::<u16>()?,
setting_table_records_count: s.read::<u16>()?,
setting_table_offset: s.read::<Offset32>()?,
flags: s.read::<u8>()?,
default_setting_index: s.read::<u8>()?,
name_index: s.read::<u16>()?,
})
}
}


/// A setting name.
#[derive(Clone, Copy, Debug)]
pub struct SettingName {
/// The setting.
pub setting: u16,
/// The `name` table index for the feature's name in a 256..32768 range.
pub name_index: u16,
}

impl FromData for SettingName {
const SIZE: usize = 4;

#[inline]
fn parse(data: &[u8]) -> Option<Self> {
let mut s = Stream::new(data);
Some(SettingName {
setting: s.read::<u16>()?,
name_index: s.read::<u16>()?,
})
}
}


/// A feature names.
#[derive(Clone, Copy, Debug)]
pub struct FeatureName<'a> {
/// The feature's ID.
pub feature: u16,
/// The feature's setting names.
pub setting_names: LazyArray16<'a, SettingName>,
/// The index of the default setting in the `setting_names`.
pub default_setting_index: u8,
/// The feature's exclusive settings. If set, the feature settings are mutually exclusive.
pub exclusive: bool,
/// The `name` table index for the feature's name in a 256..32768 range.
pub name_index: u16,
}


/// A list fo feature names.
#[derive(Clone, Copy)]
pub struct FeatureNames<'a> {
data: &'a [u8],
records: LazyArray16<'a, FeatureNameRecord>,
}

impl<'a> FeatureNames<'a> {
/// Returns a feature name at an index.
pub fn get(&self, index: u16) -> Option<FeatureName<'a>> {
let record = self.records.get(index)?;
let data = self.data.get(record.setting_table_offset.to_usize()..)?;
let mut s = Stream::new(data);
let setting_names = s.read_array16::<SettingName>(record.setting_table_records_count)?;
Some(FeatureName {
feature: record.feature,
setting_names,
default_setting_index:
if record.flags & 0x40 != 0 { record.default_setting_index } else { 0 },
exclusive: record.flags & 0x80 != 0,
name_index: record.name_index,
})
}

/// Finds a feature name by ID.
pub fn find(&self, feature: u16) -> Option<FeatureName<'a>> {
let index = self.records
.binary_search_by(|name| name.feature.cmp(&feature)).map(|(i, _)| i)?;
self.get(index)
}

/// Returns the number of feature names.
pub fn len(&self) -> u16 {
self.records.len()
}
}

impl<'a> core::fmt::Debug for FeatureNames<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_list().entries(self.into_iter()).finish()
}
}

impl<'a> IntoIterator for FeatureNames<'a> {
type Item = FeatureName<'a>;
type IntoIter = FeatureNamesIter<'a>;

#[inline]
fn into_iter(self) -> Self::IntoIter {
FeatureNamesIter {
names: self,
index: 0,
}
}
}

/// An iterator over [`FeatureNames`].
#[allow(missing_debug_implementations)]
pub struct FeatureNamesIter<'a> {
names: FeatureNames<'a>,
index: u16,
}

impl<'a> Iterator for FeatureNamesIter<'a> {
type Item = FeatureName<'a>;

fn next(&mut self) -> Option<Self::Item> {
if self.index < self.names.len() {
self.index += 1;
self.names.get(self.index - 1)
} else {
None
}
}
}


/// A [Feature Name Table](
/// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6feat.html).
#[derive(Clone, Copy, Debug)]
pub struct Table<'a> {
/// A list of feature names. Sorted by `FeatureName.feature`.
pub names: FeatureNames<'a>,
}

impl<'a> Table<'a> {
/// Parses a table from raw data.
pub fn parse(data: &'a [u8]) -> Option<Self> {
let mut s = Stream::new(data);

let version = s.read::<u32>()?;
if version != 0x00010000 {
return None;
}

let count = s.read::<u16>()?;
s.advance_checked(6)?; // reserved
let records = s.read_array16::<FeatureNameRecord>(count)?;

Some(Table {
names: FeatureNames {
data,
records,
}
})
}
}
2 changes: 2 additions & 0 deletions src/tables/mod.rs
Expand Up @@ -21,6 +21,8 @@ pub mod vorg;
#[cfg(feature = "opentype-layout")] pub mod gsub;
#[cfg(feature = "opentype-layout")] pub mod gpos;

#[cfg(feature = "apple-layout")] pub mod feat;

#[cfg(feature = "variable-fonts")] pub mod avar;
#[cfg(feature = "variable-fonts")] pub mod fvar;
#[cfg(feature = "variable-fonts")] pub mod gvar;
Expand Down
82 changes: 82 additions & 0 deletions tests/tables/feat.rs
@@ -0,0 +1,82 @@
use ttf_parser::feat::Table;

#[test]
fn basic() {
let data = &[
0x00, 0x01, 0x00, 0x00, // version: 1
0x00, 0x04, // number of features: 4
0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved

// Feature Name [0]
0x00, 0x00, // feature: 0
0x00, 0x01, // number of settings: 1
0x00, 0x00, 0x00, 0x3C, // offset to settings table: 60
0x00, 0x00, // flags: none
0x01, 0x04, // name index: 260

// Feature Name [1]
0x00, 0x01, // feature: 1
0x00, 0x01, // number of settings: 1
0x00, 0x00, 0x00, 0x40, // offset to settings table: 64
0x00, 0x00, // flags: none
0x01, 0x00, // name index: 256

// Feature Name [2]
0x00, 0x03, // feature: 3
0x00, 0x03, // number of settings: 3
0x00, 0x00, 0x00, 0x44, // offset to settings table: 68
0x80, 0x00, // flags: exclusive
0x01, 0x06, // name index: 262

// Feature Name [3]
0x00, 0x06, // feature: 6
0x00, 0x01, // number of settings: 2
0x00, 0x00, 0x00, 0x50, // offset to settings table: 80
0xC0, 0x01, // flags: exclusive and other
0x01, 0x02, // name index: 258

// Setting Name [0]
0x00, 0x00, // setting: 0
0x01, 0x05, // name index: 261

// Setting Name [1]
0x00, 0x02, // setting: 2
0x01, 0x01, // name index: 257

// Setting Name [2]
0x00, 0x00, // setting: 0
0x01, 0x0C, // name index: 268
0x00, 0x03, // setting: 3
0x01, 0x08, // name index: 264
0x00, 0x04, // setting: 4
0x01, 0x09, // name index: 265

// Setting Name [3]
0x00, 0x00, // setting: 0
0x01, 0x03, // name index: 259
0x00, 0x00, // setting: 1
0x01, 0x04, // name index: 260
];

let table = Table::parse(data).unwrap();
assert_eq!(table.names.len(), 4);

let feature0 = table.names.get(0).unwrap();
assert_eq!(feature0.feature, 0);
assert_eq!(feature0.setting_names.len(), 1);
assert_eq!(feature0.exclusive, false);
assert_eq!(feature0.name_index, 260);

let feature2 = table.names.get(2).unwrap();
assert_eq!(feature2.feature, 3);
assert_eq!(feature2.setting_names.len(), 3);
assert_eq!(feature2.exclusive, true);

assert_eq!(feature2.setting_names.get(1).unwrap().setting, 3);
assert_eq!(feature2.setting_names.get(1).unwrap().name_index, 264);

let feature3 = table.names.get(3).unwrap();
assert_eq!(feature3.default_setting_index, 1);
assert_eq!(feature3.exclusive, true);
}
1 change: 1 addition & 0 deletions tests/tables/main.rs
@@ -1,5 +1,6 @@
mod cff1;
mod cmap;
mod feat;
mod hmtx;
mod maxp;
mod sbix;
Expand Down

0 comments on commit d8e5b2d

Please sign in to comment.