diff --git a/src/hb/face.rs b/src/hb/face.rs index 286737b..19c5a2c 100644 --- a/src/hb/face.rs +++ b/src/hb/face.rs @@ -4,13 +4,11 @@ use core_maths::CoreFloat; use crate::hb::paint_extents::hb_paint_extents_context_t; use ttf_parser::gdef::GlyphClass; -use ttf_parser::opentype_layout::LayoutTable; use ttf_parser::{GlyphId, RgbaColor}; use super::buffer::GlyphPropsFlags; use super::fonta; use super::ot_layout::TableIndex; -use super::ot_layout_common::{PositioningTable, SubstitutionTable}; use crate::Variation; /// A font face handle. @@ -21,8 +19,6 @@ pub struct hb_font_t<'a> { pub(crate) units_per_em: u16, pixels_per_em: Option<(u16, u16)>, pub(crate) points_per_em: Option, - pub(crate) gsub: Option>, - pub(crate) gpos: Option>, } impl<'a> AsRef> for hb_font_t<'a> { @@ -67,28 +63,10 @@ impl<'a> hb_font_t<'a> { units_per_em: face.units_per_em(), pixels_per_em: None, points_per_em: None, - gsub: face.tables().gsub.map(SubstitutionTable::new), - gpos: face.tables().gpos.map(PositioningTable::new), ttfp_face: face, }) } - /// Creates a new [`Face`] from [`ttf_parser::Face`]. - /// - /// Data will be referenced, not owned. - pub fn from_face(face: ttf_parser::Face<'a>) -> Self { - let font = fonta::Font::new(face.raw_face().data, 0).unwrap(); - hb_font_t { - font, - units_per_em: face.units_per_em(), - pixels_per_em: None, - points_per_em: None, - gsub: face.tables().gsub.map(SubstitutionTable::new), - gpos: face.tables().gpos.map(PositioningTable::new), - ttfp_face: face, - } - } - // TODO: remove /// Returns face’s units per EM. #[inline] @@ -338,17 +316,24 @@ impl<'a> hb_font_t<'a> { } } - pub(crate) fn layout_table(&self, table_index: TableIndex) -> Option<&LayoutTable<'a>> { + pub(crate) fn layout_table( + &self, + table_index: TableIndex, + ) -> Option> { match table_index { - TableIndex::GSUB => self.gsub.as_ref().map(|table| &table.inner), - TableIndex::GPOS => self.gpos.as_ref().map(|table| &table.inner), + TableIndex::GSUB => Some(fonta::ot::LayoutTable::Gsub( + self.font.ot.gsub.as_ref()?.table.clone(), + )), + TableIndex::GPOS => Some(fonta::ot::LayoutTable::Gpos( + self.font.ot.gpos.as_ref()?.table.clone(), + )), } } pub(crate) fn layout_tables( &self, - ) -> impl Iterator)> + '_ { - TableIndex::iter().filter_map(move |idx| self.layout_table(idx).map(|table| (idx, table))) + ) -> impl Iterator)> + '_ { + TableIndex::iter().filter_map(move |idx| Some((idx, self.layout_table(idx)?))) } } diff --git a/src/hb/fonta/font.rs b/src/hb/fonta/font.rs index ad314d5..dad0feb 100644 --- a/src/hb/fonta/font.rs +++ b/src/hb/fonta/font.rs @@ -54,14 +54,12 @@ impl<'a> Font<'a> { pub(crate) fn set_coords(&mut self, coords: &[NormalizedCoordinate]) { self.coords.clear(); if !coords.is_empty() && !coords.iter().all(|coord| coord.get() == 0) { + self.coords.extend( + coords + .iter() + .map(|coord| NormalizedCoord::from_bits(coord.get())), + ); let ivs = self.ivs.take().or_else(|| self.ot.item_variation_store()); - if ivs.is_some() { - self.coords.extend( - coords - .iter() - .map(|coord| NormalizedCoord::from_bits(coord.get())), - ); - } self.ivs = ivs; } else { self.ivs = None; diff --git a/src/hb/fonta/ot/mod.rs b/src/hb/fonta/ot/mod.rs index b37cc38..963a7c5 100644 --- a/src/hb/fonta/ot/mod.rs +++ b/src/hb/fonta/ot/mod.rs @@ -1,5 +1,6 @@ use crate::hb::{ - hb_font_t, + common::TagExt, + hb_font_t, hb_tag_t, ot_layout::LayoutLookup, ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext, OT::hb_ot_apply_context_t}, set_digest::hb_set_digest_ext, @@ -7,9 +8,12 @@ use crate::hb::{ use skrifa::raw::{ tables::{ gdef::Gdef, - layout::{ClassDef, CoverageTable}, + gpos::Gpos, + gsub::{FeatureList, FeatureVariations, Gsub, ScriptList}, + layout::{ClassDef, Condition, CoverageTable, Feature, LangSys, Script}, variations::ItemVariationStore, }, + types::{F2Dot14, Scalar}, ReadError, TableProvider, }; use ttf_parser::GlyphId; @@ -159,6 +163,259 @@ impl LookupInfo { } } +pub enum LayoutTable<'a> { + Gsub(Gsub<'a>), + Gpos(Gpos<'a>), +} + +fn conv_tag(tag: hb_tag_t) -> skrifa::raw::types::Tag { + skrifa::raw::types::Tag::from_u32(tag.0) +} + +impl<'a> LayoutTable<'a> { + fn script_list(&self) -> Option> { + match self { + Self::Gsub(gsub) => gsub.script_list().ok(), + Self::Gpos(gpos) => gpos.script_list().ok(), + } + } + + fn feature_list(&self) -> Option> { + match self { + Self::Gsub(gsub) => gsub.feature_list().ok(), + Self::Gpos(gpos) => gpos.feature_list().ok(), + } + } + + fn feature_variations(&self) -> Option> { + match self { + Self::Gsub(gsub) => gsub.feature_variations(), + Self::Gpos(gpos) => gpos.feature_variations(), + } + .transpose() + .ok() + .flatten() + } + + fn script_index(&self, tag: hb_tag_t) -> Option { + let list = self.script_list()?; + let tag = conv_tag(tag); + list.script_records() + .binary_search_by_key(&tag, |rec| rec.script_tag()) + .map(|index| index as u16) + .ok() + } + + fn script(&self, index: u16) -> Option> { + let list = self.script_list()?; + let record = list.script_records().get(index as usize)?; + record.script(list.offset_data()).ok() + } + + fn langsys_index(&self, script_index: u16, tag: hb_tag_t) -> Option { + let script = self.script(script_index)?; + let tag = conv_tag(tag); + script + .lang_sys_records() + .binary_search_by_key(&tag, |rec| rec.lang_sys_tag()) + .map(|index| index as u16) + .ok() + } + + fn langsys(&self, script_index: u16, langsys_index: Option) -> Option> { + let script = self.script(script_index)?; + if let Some(index) = langsys_index { + let record = script.lang_sys_records().get(index as usize)?; + record.lang_sys(script.offset_data()).ok() + } else { + script.default_lang_sys().transpose().ok().flatten() + } + } + + pub(crate) fn feature(&self, index: u16) -> Option> { + let list = self.feature_list()?; + let record = list.feature_records().get(index as usize)?; + record.feature(list.offset_data()).ok() + } + + fn feature_tag(&self, index: u16) -> Option { + let list = self.feature_list()?; + let record = list.feature_records().get(index as usize)?; + Some(hb_tag_t(u32::from_be_bytes(record.feature_tag().to_raw()))) + } + + pub(crate) fn feature_variation_index(&self, coords: &[F2Dot14]) -> Option { + let feature_variations = self.feature_variations()?; + for (index, rec) in feature_variations + .feature_variation_records() + .iter() + .enumerate() + { + // If the ConditionSet offset is 0, this is treated as the + // universal condition: all contexts are matched. + if rec.condition_set_offset().is_null() { + return Some(index as u32); + } + let Some(Ok(condition_set)) = rec.condition_set(feature_variations.offset_data()) + else { + continue; + }; + // Otherwise, all conditions must be satisfied. + if condition_set + .conditions() + .iter() + // .. except we ignore errors + .filter_map(|cond| cond.ok()) + .all(|cond| match cond { + Condition::Format1AxisRange(format1) => { + let coord = coords + .get(format1.axis_index() as usize) + .copied() + .unwrap_or_default(); + coord >= format1.filter_range_min_value() + && coord <= format1.filter_range_max_value() + } + _ => false, + }) + { + return Some(index as u32); + } + } + None + } + + pub(crate) fn feature_substitution( + &self, + variation_index: u32, + feature_index: u16, + ) -> Option> { + let feature_variations = self.feature_variations()?; + let record = feature_variations + .feature_variation_records() + .get(variation_index as usize)?; + let subst_table = record + .feature_table_substitution(feature_variations.offset_data())? + .ok()?; + let subst_records = subst_table.substitutions(); + match subst_records.binary_search_by_key(&feature_index, |subst| subst.feature_index()) { + Ok(ix) => Some( + subst_records + .get(ix)? + .alternate_feature(subst_table.offset_data()) + .ok()?, + ), + _ => None, + } + } + + pub(crate) fn feature_index(&self, tag: hb_tag_t) -> Option { + let list = self.feature_list()?; + let tag = conv_tag(tag); + for (index, feature) in list.feature_records().iter().enumerate() { + if feature.feature_tag() == tag { + return Some(index as u16); + } + } + None + } + + pub(crate) fn lookup_count(&self) -> u16 { + match self { + Self::Gsub(gsub) => gsub + .lookup_list() + .map(|list| list.lookup_count()) + .unwrap_or_default(), + Self::Gpos(gpos) => gpos + .lookup_list() + .map(|list| list.lookup_count()) + .unwrap_or_default(), + } + } +} + +impl crate::hb::ot_layout::LayoutTableExt for LayoutTable<'_> { + // hb_ot_layout_table_select_script + /// Returns true + index and tag of the first found script tag in the given GSUB or GPOS table + /// or false + index and tag if falling back to a default script. + fn select_script(&self, script_tags: &[hb_tag_t]) -> Option<(bool, u16, hb_tag_t)> { + for &tag in script_tags { + if let Some(index) = self.script_index(tag) { + return Some((true, index, tag)); + } + } + + for &tag in &[ + // try finding 'DFLT' + hb_tag_t::default_script(), + // try with 'dflt'; MS site has had typos and many fonts use it now :( + hb_tag_t::default_language(), + // try with 'latn'; some old fonts put their features there even though + // they're really trying to support Thai, for example :( + hb_tag_t::from_bytes(b"latn"), + ] { + if let Some(index) = self.script_index(tag) { + return Some((false, index, tag)); + } + } + + None + } + + // hb_ot_layout_script_select_language + /// Returns the index of the first found language tag in the given GSUB or GPOS table, + /// underneath the specified script index. + fn select_script_language(&self, script_index: u16, lang_tags: &[hb_tag_t]) -> Option { + for &tag in lang_tags { + if let Some(index) = self.langsys_index(script_index, tag) { + return Some(index); + } + } + + // try finding 'dflt' + if let Some(index) = self.langsys_index(script_index, hb_tag_t::default_language()) { + return Some(index); + } + + None + } + + // hb_ot_layout_language_get_required_feature + /// Returns the index and tag of a required feature in the given GSUB or GPOS table, + /// underneath the specified script and language. + fn get_required_language_feature( + &self, + script_index: u16, + lang_index: Option, + ) -> Option<(u16, hb_tag_t)> { + let sys = self.langsys(script_index, lang_index)?; + let idx = sys.required_feature_index(); + if idx == 0xFFFF { + return None; + } + let tag = self.feature_tag(idx)?; + Some((idx, tag)) + } + + // hb_ot_layout_language_find_feature + /// Returns the index of a given feature tag in the given GSUB or GPOS table, + /// underneath the specified script and language. + fn find_language_feature( + &self, + script_index: u16, + lang_index: Option, + feature_tag: hb_tag_t, + ) -> Option { + let sys = self.langsys(script_index, lang_index)?; + for index in sys.feature_indices() { + let index = index.get(); + if self.feature_tag(index) == Some(feature_tag) { + return Some(index); + } + } + None + } +} + fn coverage_index(coverage: Result, gid: GlyphId) -> Option { let gid = skrifa::GlyphId16::new(gid.0); coverage.ok().and_then(|coverage| coverage.get(gid)) diff --git a/src/hb/ot_layout_common.rs b/src/hb/ot_layout_common.rs index 4166ad2..0852c35 100644 --- a/src/hb/ot_layout_common.rs +++ b/src/hb/ot_layout_common.rs @@ -8,25 +8,3 @@ pub mod lookup_flags { pub const USE_MARK_FILTERING_SET: u16 = 0x0010; pub const MARK_ATTACHMENT_TYPE_MASK: u16 = 0xFF00; } - -#[derive(Clone)] -pub struct PositioningTable<'a> { - pub inner: ttf_parser::opentype_layout::LayoutTable<'a>, -} - -impl<'a> PositioningTable<'a> { - pub fn new(inner: ttf_parser::opentype_layout::LayoutTable<'a>) -> Self { - Self { inner } - } -} - -#[derive(Clone)] -pub struct SubstitutionTable<'a> { - pub inner: ttf_parser::opentype_layout::LayoutTable<'a>, -} - -impl<'a> SubstitutionTable<'a> { - pub fn new(inner: ttf_parser::opentype_layout::LayoutTable<'a>) -> Self { - Self { inner } - } -} diff --git a/src/hb/ot_map.rs b/src/hb/ot_map.rs index a28589e..0e9d3a9 100644 --- a/src/hb/ot_map.rs +++ b/src/hb/ot_map.rs @@ -394,7 +394,7 @@ impl<'a> hb_ot_map_builder_t<'a> { if !found && info.flags & F_GLOBAL_SEARCH != 0 { // hb_ot_layout_table_find_feature for (table_index, table) in self.face.layout_tables() { - if let Some(idx) = table.features.index(info.tag) { + if let Some(idx) = table.feature_index(info.tag) { feature_index[table_index] = Some(idx); found = true; } @@ -491,11 +491,11 @@ impl<'a> hb_ot_map_builder_t<'a> { let mut stage_index = 0; let mut last_lookup = 0; - let coords = self.face.ttfp_face.variation_coordinates(); + let coords = &self.face.font.coords; let variation_index = self .face .layout_table(table_index) - .and_then(|t| t.variations?.find_index(coords)); + .and_then(|t| t.feature_variation_index(coords)); for stage in 0..self.current_stage[table_index] { if let Some(feature_index) = required_feature_index[table_index] { @@ -586,16 +586,16 @@ impl<'a> hb_ot_map_builder_t<'a> { ) -> Option<()> { let table = self.face.layout_table(table_index)?; - let lookup_count = table.lookups.len(); + let lookup_count = table.lookup_count(); let feature = match variation_index { Some(idx) => table - .variations - .and_then(|var| var.find_substitute(feature_index, idx)) - .or_else(|| table.features.get(feature_index))?, - None => table.features.get(feature_index)?, + .feature_substitution(idx, feature_index) + .or_else(|| table.feature(feature_index))?, + None => table.feature(feature_index)?, }; - for index in feature.lookup_indices { + for index in feature.lookup_list_indices() { + let index = index.get(); if index < lookup_count { lookups.push(lookup_map_t { mask, diff --git a/src/hb/ot_shape.rs b/src/hb/ot_shape.rs index ef6aa8d..ae67dcf 100644 --- a/src/hb/ot_shape.rs +++ b/src/hb/ot_shape.rs @@ -52,7 +52,7 @@ impl<'a> hb_ot_shape_planner_t<'a> { // https://github.com/harfbuzz/harfbuzz/issues/2124 let apply_morx = - face.tables().morx.is_some() && (direction.is_horizontal() || face.gsub.is_none()); + face.tables().morx.is_some() && (direction.is_horizontal() || face.font.ot.gsub.is_none()); // https://github.com/harfbuzz/harfbuzz/issues/1528 if apply_morx && shaper as *const _ != &DEFAULT_SHAPER as *const _ {