mod anchor;
mod cursive_pos;
mod mark_array;
mod mark_base_pos;
mod mark_lig_pos;
mod mark_mark_pos;
mod pair_pos;
mod single_pos;
mod value_record;
use crate::{
collect_features_with_retained_subs, find_duplicate_features,
offset::SerializeSubset,
prune_features, remap_feature_indices, remap_indices,
serialize::{SerializeErrorFlags, Serializer},
CollectVariationIndices, LayoutClosure, NameIdClosure, Plan, PruneLangSysContext, Subset,
SubsetError, SubsetLayoutContext, SubsetState, SubsetTable,
};
use fnv::FnvHashMap;
use fontcull_write_fonts::{
read::{
collections::IntSet,
tables::{
gpos::{Gpos, PositionLookup, PositionSubtables},
layout::{ExtensionLookup, Intersect, LookupFlag, Subtables},
},
types::Tag,
FontRead, FontRef, TopLevelTable,
},
types::{MajorMinor, Offset16, Offset32},
FontBuilder,
};
impl NameIdClosure for Gpos<'_> {
fn collect_name_ids(&self, plan: &mut Plan) {
let Ok(feature_list) = self.feature_list() else {
return;
};
for (i, feature_record) in feature_list.feature_records().iter().enumerate() {
if !plan.gpos_features.contains_key(&(i as u16)) {
continue;
}
let Ok(feature) = feature_record.feature(feature_list.offset_data()) else {
continue;
};
feature.collect_name_ids(plan);
}
}
}
impl LayoutClosure for Gpos<'_> {
fn prune_features(
&self,
lookup_indices: &IntSet<u16>,
feature_indices: IntSet<u16>,
) -> IntSet<u16> {
let alternate_features = if let Some(Ok(feature_variations)) = self.feature_variations() {
collect_features_with_retained_subs(&feature_variations, lookup_indices)
} else {
IntSet::empty()
};
let Ok(feature_list) = self.feature_list() else {
return IntSet::empty();
};
prune_features(
&feature_list,
&alternate_features,
lookup_indices,
feature_indices,
)
}
fn find_duplicate_features(
&self,
lookup_indices: &IntSet<u16>,
feature_indices: IntSet<u16>,
) -> FnvHashMap<u16, u16> {
let Ok(feature_list) = self.feature_list() else {
return FnvHashMap::default();
};
find_duplicate_features(&feature_list, lookup_indices, feature_indices)
}
fn prune_langsys(
&self,
duplicate_feature_index_map: &FnvHashMap<u16, u16>,
layout_scripts: &IntSet<Tag>,
) -> (FnvHashMap<u16, IntSet<u16>>, IntSet<u16>) {
let mut c = PruneLangSysContext::new(duplicate_feature_index_map);
let Ok(script_list) = self.script_list() else {
return (c.script_langsys_map(), c.feature_indices());
};
c.prune_langsys(&script_list, layout_scripts)
}
fn closure_glyphs_lookups_features(&self, plan: &mut Plan) {
let Ok(feature_indices) =
self.collect_features(&plan.layout_scripts, &IntSet::all(), &plan.layout_features)
else {
return;
};
let Ok(mut lookup_indices) = self.collect_lookups(&feature_indices) else {
return;
};
let Ok(_) = self.closure_lookups(&plan.glyphset_gsub, &mut lookup_indices) else {
return;
};
let feature_indices = self.prune_features(&lookup_indices, feature_indices);
let duplicate_feature_index_map =
self.find_duplicate_features(&lookup_indices, feature_indices);
let (script_langsys_map, feature_indices) =
self.prune_langsys(&duplicate_feature_index_map, &plan.layout_scripts);
plan.gpos_lookups = remap_indices(lookup_indices);
(plan.gpos_features, plan.gpos_features_w_duplicates) =
remap_feature_indices(&feature_indices, &duplicate_feature_index_map);
plan.gpos_script_langsys = script_langsys_map;
}
}
impl Subset for Gpos<'_> {
fn subset_with_state(
&self,
plan: &Plan,
font: &FontRef,
state: &mut SubsetState,
s: &mut Serializer,
_builder: &mut FontBuilder,
) -> Result<(), SubsetError> {
subset_gpos(self, plan, font, state, s)
.map_err(|_| SubsetError::SubsetTableError(Gpos::TAG))
}
}
fn subset_gpos(
gpos: &Gpos,
plan: &Plan,
font: &FontRef,
state: &SubsetState,
s: &mut Serializer,
) -> Result<(), SerializeErrorFlags> {
let version_pos = s.embed(gpos.version())?;
let script_list_offset_pos = s.embed(0_u16)?;
let script_list = gpos
.script_list()
.map_err(|_| s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR))?;
let mut c = SubsetLayoutContext::new(Gpos::TAG);
Offset16::serialize_subset(&script_list, s, plan, &mut c, script_list_offset_pos)?;
let feature_list_offset_pos = s.embed(0_u16)?;
let feature_list = gpos
.feature_list()
.map_err(|_| s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR))?;
Offset16::serialize_subset(&feature_list, s, plan, &mut c, feature_list_offset_pos)?;
let lookup_list_offset_pos = s.embed(0_u16)?;
let lookup_list = gpos
.lookup_list()
.map_err(|_| s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR))?;
Offset16::serialize_subset(
&lookup_list,
s,
plan,
(state, font, &plan.gpos_lookups),
lookup_list_offset_pos,
)?;
if let Some(feature_variations) = gpos
.feature_variations()
.transpose()
.map_err(|_| SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR)?
{
let snap = s.snapshot();
let feature_vars_offset_pos = s.embed(0_u32)?;
match Offset32::serialize_subset(
&feature_variations,
s,
plan,
&mut c,
feature_vars_offset_pos,
) {
Ok(()) => (),
Err(SerializeErrorFlags::SERIALIZE_ERROR_EMPTY) => {
s.revert_snapshot(snap);
s.copy_assign(version_pos, MajorMinor::VERSION_1_0);
}
Err(e) => return Err(e),
}
}
Ok(())
}
impl<'a> SubsetTable<'a> for PositionLookup<'_> {
type ArgsForSubset = (&'a SubsetState, &'a FontRef<'a>, &'a FnvHashMap<u16, u16>);
type Output = ();
fn subset(
&self,
plan: &Plan,
s: &mut Serializer,
args: Self::ArgsForSubset,
) -> Result<(), SerializeErrorFlags> {
s.embed(self.lookup_type())?;
let lookup_flag = self.lookup_flag();
let lookup_flag_pos = s.embed(lookup_flag)?;
let lookup_count_pos = s.embed(0_u16)?;
let subtables = self
.subtables()
.map_err(|_| s.set_err(SerializeErrorFlags::SERIALIZE_ERROR_READ_ERROR))?;
let lookup_count = subtables.subset(plan, s, args)?;
s.copy_assign(lookup_count_pos, lookup_count);
if let Some(mark_filtering_set) = self.mark_filtering_set() {
if let Some(new_idx) = plan.used_mark_sets_map.get(&mark_filtering_set) {
s.embed(*new_idx)?;
} else {
let new_flag =
(lookup_flag - LookupFlag::USE_MARK_FILTERING_SET) | LookupFlag::IGNORE_MARKS;
s.copy_assign(lookup_flag_pos, new_flag);
}
}
Ok(())
}
}
impl<'a> SubsetTable<'a> for PositionSubtables<'a> {
type ArgsForSubset = (&'a SubsetState, &'a FontRef<'a>, &'a FnvHashMap<u16, u16>);
type Output = u16;
fn subset(
&self,
plan: &Plan,
s: &mut Serializer,
args: Self::ArgsForSubset,
) -> Result<u16, SerializeErrorFlags> {
match self {
PositionSubtables::Single(subtables) => subtables.subset(plan, s, args),
PositionSubtables::Pair(subtables) => subtables.subset(plan, s, args),
PositionSubtables::Cursive(subtables) => subtables.subset(plan, s, args),
PositionSubtables::MarkToBase(subtables) => subtables.subset(plan, s, args),
PositionSubtables::MarkToLig(subtables) => subtables.subset(plan, s, args),
PositionSubtables::MarkToMark(subtables) => subtables.subset(plan, s, args),
PositionSubtables::Contextual(subtables) => subtables.subset(plan, s, args),
PositionSubtables::ChainContextual(subtables) => subtables.subset(plan, s, args),
}
}
}
impl CollectVariationIndices for Gpos<'_> {
fn collect_variation_indices(&self, plan: &Plan, varidx_set: &mut IntSet<u32>) {
let Ok(lookups) = self.lookup_list() else {
return;
};
let lookups = lookups.lookups();
for i in plan.gpos_lookups.keys() {
let Ok(lookup) = lookups.get(*i as usize) else {
return;
};
let Ok(subtables) = lookup.subtables() else {
return;
};
subtables.collect_variation_indices(plan, varidx_set);
}
}
}
impl CollectVariationIndices for PositionSubtables<'_> {
fn collect_variation_indices(&self, plan: &Plan, varidx_set: &mut IntSet<u32>) {
match self {
PositionSubtables::Single(subtables) => {
subtables.collect_variation_indices(plan, varidx_set)
}
PositionSubtables::Pair(subtables) => {
subtables.collect_variation_indices(plan, varidx_set)
}
PositionSubtables::Cursive(subtables) => {
subtables.collect_variation_indices(plan, varidx_set)
}
PositionSubtables::MarkToBase(subtables) => {
subtables.collect_variation_indices(plan, varidx_set)
}
PositionSubtables::MarkToLig(subtables) => {
subtables.collect_variation_indices(plan, varidx_set)
}
PositionSubtables::MarkToMark(subtables) => {
subtables.collect_variation_indices(plan, varidx_set)
}
_ => (),
}
}
}
impl<'a, T, Ext> CollectVariationIndices for Subtables<'a, T, Ext>
where
T: CollectVariationIndices + Intersect + FontRead<'a> + 'a,
Ext: ExtensionLookup<'a, T> + 'a,
{
fn collect_variation_indices(&self, plan: &Plan, varidx_set: &mut IntSet<u32>) {
for t in self.iter() {
let Ok(t) = t else {
return;
};
let Ok(intersect) = t.intersects(&plan.glyphset_gsub) else {
return;
};
if !intersect {
continue;
}
t.collect_variation_indices(plan, varidx_set);
}
}
}
#[cfg(test)]
mod test {
use super::*;
use fontcull_write_fonts::read::{FontRef, TableProvider};
#[test]
fn test_prune_langsys() {
let font = FontRef::new(include_bytes!("../test-data/fonts/Amiri-Regular.ttf")).unwrap();
let gpos = font.gpos().unwrap();
let mut layout_scripts = IntSet::all();
let mut duplicate_feature_index_map = FnvHashMap::default();
duplicate_feature_index_map.insert(0, 0);
duplicate_feature_index_map.insert(2, 2);
duplicate_feature_index_map.insert(4, 2);
let (script_langsys_map, features) =
gpos.prune_langsys(&duplicate_feature_index_map, &layout_scripts);
assert!(script_langsys_map.is_empty());
assert_eq!(features.len(), 3);
assert!(features.contains(0));
assert!(features.contains(2));
assert!(features.contains(4));
layout_scripts.clear();
layout_scripts.insert(Tag::new(b"arab"));
let (script_langsys_map, features) =
gpos.prune_langsys(&duplicate_feature_index_map, &layout_scripts);
assert!(script_langsys_map.is_empty());
assert_eq!(features.len(), 1);
assert!(features.contains(0));
}
#[test]
fn test_find_duplicate_features() {
let font = FontRef::new(include_bytes!("../test-data/fonts/Amiri-Regular.ttf")).unwrap();
let gpos = font.gpos().unwrap();
let mut lookups = IntSet::empty();
lookups.insert(0_u16);
let mut feature_indices = IntSet::empty();
feature_indices.insert(1_u16);
feature_indices.insert(2_u16);
feature_indices.insert(3_u16);
feature_indices.insert(4_u16);
let feature_index_map = gpos.find_duplicate_features(&lookups, feature_indices);
assert_eq!(feature_index_map.len(), 4);
assert_eq!(feature_index_map.get(&1), Some(&1));
assert_eq!(feature_index_map.get(&2), Some(&1));
assert_eq!(feature_index_map.get(&3), Some(&3));
assert_eq!(feature_index_map.get(&4), Some(&3));
lookups.insert(82_u16);
lookups.insert(81_u16);
let mut feature_indices = IntSet::empty();
feature_indices.insert(1_u16);
feature_indices.insert(2_u16);
feature_indices.insert(3_u16);
feature_indices.insert(4_u16);
let feature_index_map = gpos.find_duplicate_features(&lookups, feature_indices);
assert_eq!(feature_index_map.len(), 4);
assert_eq!(feature_index_map.get(&1), Some(&1));
assert_eq!(feature_index_map.get(&2), Some(&2));
assert_eq!(feature_index_map.get(&3), Some(&3));
assert_eq!(feature_index_map.get(&4), Some(&4));
}
}