Skip to content

Commit f5052ba

Browse files
committed
Use fontations for shape planning
Replaces the ttf-parser code for script, langsys and feature lookup. GSUB/GPOS is now fully fontations.
1 parent cdb55cc commit f5052ba

File tree

6 files changed

+286
-68
lines changed

6 files changed

+286
-68
lines changed

src/hb/face.rs

+12-27
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ use core_maths::CoreFloat;
44

55
use crate::hb::paint_extents::hb_paint_extents_context_t;
66
use ttf_parser::gdef::GlyphClass;
7-
use ttf_parser::opentype_layout::LayoutTable;
87
use ttf_parser::{GlyphId, RgbaColor};
98

109
use super::buffer::GlyphPropsFlags;
1110
use super::fonta;
1211
use super::ot_layout::TableIndex;
13-
use super::ot_layout_common::{PositioningTable, SubstitutionTable};
1412
use crate::Variation;
1513

1614
/// A font face handle.
@@ -21,8 +19,6 @@ pub struct hb_font_t<'a> {
2119
pub(crate) units_per_em: u16,
2220
pixels_per_em: Option<(u16, u16)>,
2321
pub(crate) points_per_em: Option<f32>,
24-
pub(crate) gsub: Option<SubstitutionTable<'a>>,
25-
pub(crate) gpos: Option<PositioningTable<'a>>,
2622
}
2723

2824
impl<'a> AsRef<ttf_parser::Face<'a>> for hb_font_t<'a> {
@@ -67,28 +63,10 @@ impl<'a> hb_font_t<'a> {
6763
units_per_em: face.units_per_em(),
6864
pixels_per_em: None,
6965
points_per_em: None,
70-
gsub: face.tables().gsub.map(SubstitutionTable::new),
71-
gpos: face.tables().gpos.map(PositioningTable::new),
7266
ttfp_face: face,
7367
})
7468
}
7569

76-
/// Creates a new [`Face`] from [`ttf_parser::Face`].
77-
///
78-
/// Data will be referenced, not owned.
79-
pub fn from_face(face: ttf_parser::Face<'a>) -> Self {
80-
let font = fonta::Font::new(face.raw_face().data, 0).unwrap();
81-
hb_font_t {
82-
font,
83-
units_per_em: face.units_per_em(),
84-
pixels_per_em: None,
85-
points_per_em: None,
86-
gsub: face.tables().gsub.map(SubstitutionTable::new),
87-
gpos: face.tables().gpos.map(PositioningTable::new),
88-
ttfp_face: face,
89-
}
90-
}
91-
9270
// TODO: remove
9371
/// Returns face’s units per EM.
9472
#[inline]
@@ -338,17 +316,24 @@ impl<'a> hb_font_t<'a> {
338316
}
339317
}
340318

341-
pub(crate) fn layout_table(&self, table_index: TableIndex) -> Option<&LayoutTable<'a>> {
319+
pub(crate) fn layout_table(
320+
&self,
321+
table_index: TableIndex,
322+
) -> Option<fonta::ot::LayoutTable<'a>> {
342323
match table_index {
343-
TableIndex::GSUB => self.gsub.as_ref().map(|table| &table.inner),
344-
TableIndex::GPOS => self.gpos.as_ref().map(|table| &table.inner),
324+
TableIndex::GSUB => Some(fonta::ot::LayoutTable::Gsub(
325+
self.font.ot.gsub.as_ref()?.table.clone(),
326+
)),
327+
TableIndex::GPOS => Some(fonta::ot::LayoutTable::Gpos(
328+
self.font.ot.gpos.as_ref()?.table.clone(),
329+
)),
345330
}
346331
}
347332

348333
pub(crate) fn layout_tables(
349334
&self,
350-
) -> impl Iterator<Item = (TableIndex, &LayoutTable<'a>)> + '_ {
351-
TableIndex::iter().filter_map(move |idx| self.layout_table(idx).map(|table| (idx, table)))
335+
) -> impl Iterator<Item = (TableIndex, fonta::ot::LayoutTable<'a>)> + '_ {
336+
TableIndex::iter().filter_map(move |idx| Some((idx, self.layout_table(idx)?)))
352337
}
353338
}
354339

src/hb/fonta/font.rs

+5-7
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,12 @@ impl<'a> Font<'a> {
5454
pub(crate) fn set_coords(&mut self, coords: &[NormalizedCoordinate]) {
5555
self.coords.clear();
5656
if !coords.is_empty() && !coords.iter().all(|coord| coord.get() == 0) {
57+
self.coords.extend(
58+
coords
59+
.iter()
60+
.map(|coord| NormalizedCoord::from_bits(coord.get())),
61+
);
5762
let ivs = self.ivs.take().or_else(|| self.ot.item_variation_store());
58-
if ivs.is_some() {
59-
self.coords.extend(
60-
coords
61-
.iter()
62-
.map(|coord| NormalizedCoord::from_bits(coord.get())),
63-
);
64-
}
6563
self.ivs = ivs;
6664
} else {
6765
self.ivs = None;

src/hb/fonta/ot/mod.rs

+259-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
use crate::hb::{
2-
hb_font_t,
2+
common::TagExt,
3+
hb_font_t, hb_tag_t,
34
ot_layout::LayoutLookup,
45
ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext, OT::hb_ot_apply_context_t},
56
set_digest::hb_set_digest_ext,
67
};
78
use skrifa::raw::{
89
tables::{
910
gdef::Gdef,
10-
layout::{ClassDef, CoverageTable},
11+
gpos::Gpos,
12+
gsub::{FeatureList, FeatureVariations, Gsub, ScriptList},
13+
layout::{ClassDef, Condition, CoverageTable, Feature, LangSys, Script},
1114
variations::ItemVariationStore,
1215
},
16+
types::{F2Dot14, Scalar},
1317
ReadError, TableProvider,
1418
};
1519
use ttf_parser::GlyphId;
@@ -159,6 +163,259 @@ impl LookupInfo {
159163
}
160164
}
161165

166+
pub enum LayoutTable<'a> {
167+
Gsub(Gsub<'a>),
168+
Gpos(Gpos<'a>),
169+
}
170+
171+
fn conv_tag(tag: hb_tag_t) -> skrifa::raw::types::Tag {
172+
skrifa::raw::types::Tag::from_u32(tag.0)
173+
}
174+
175+
impl<'a> LayoutTable<'a> {
176+
fn script_list(&self) -> Option<ScriptList<'a>> {
177+
match self {
178+
Self::Gsub(gsub) => gsub.script_list().ok(),
179+
Self::Gpos(gpos) => gpos.script_list().ok(),
180+
}
181+
}
182+
183+
fn feature_list(&self) -> Option<FeatureList<'a>> {
184+
match self {
185+
Self::Gsub(gsub) => gsub.feature_list().ok(),
186+
Self::Gpos(gpos) => gpos.feature_list().ok(),
187+
}
188+
}
189+
190+
fn feature_variations(&self) -> Option<FeatureVariations<'a>> {
191+
match self {
192+
Self::Gsub(gsub) => gsub.feature_variations(),
193+
Self::Gpos(gpos) => gpos.feature_variations(),
194+
}
195+
.transpose()
196+
.ok()
197+
.flatten()
198+
}
199+
200+
fn script_index(&self, tag: hb_tag_t) -> Option<u16> {
201+
let list = self.script_list()?;
202+
let tag = conv_tag(tag);
203+
list.script_records()
204+
.binary_search_by_key(&tag, |rec| rec.script_tag())
205+
.map(|index| index as u16)
206+
.ok()
207+
}
208+
209+
fn script(&self, index: u16) -> Option<Script<'a>> {
210+
let list = self.script_list()?;
211+
let record = list.script_records().get(index as usize)?;
212+
record.script(list.offset_data()).ok()
213+
}
214+
215+
fn langsys_index(&self, script_index: u16, tag: hb_tag_t) -> Option<u16> {
216+
let script = self.script(script_index)?;
217+
let tag = conv_tag(tag);
218+
script
219+
.lang_sys_records()
220+
.binary_search_by_key(&tag, |rec| rec.lang_sys_tag())
221+
.map(|index| index as u16)
222+
.ok()
223+
}
224+
225+
fn langsys(&self, script_index: u16, langsys_index: Option<u16>) -> Option<LangSys<'a>> {
226+
let script = self.script(script_index)?;
227+
if let Some(index) = langsys_index {
228+
let record = script.lang_sys_records().get(index as usize)?;
229+
record.lang_sys(script.offset_data()).ok()
230+
} else {
231+
script.default_lang_sys().transpose().ok().flatten()
232+
}
233+
}
234+
235+
pub(crate) fn feature(&self, index: u16) -> Option<Feature<'a>> {
236+
let list = self.feature_list()?;
237+
let record = list.feature_records().get(index as usize)?;
238+
record.feature(list.offset_data()).ok()
239+
}
240+
241+
fn feature_tag(&self, index: u16) -> Option<hb_tag_t> {
242+
let list = self.feature_list()?;
243+
let record = list.feature_records().get(index as usize)?;
244+
Some(hb_tag_t(u32::from_be_bytes(record.feature_tag().to_raw())))
245+
}
246+
247+
pub(crate) fn feature_variation_index(&self, coords: &[F2Dot14]) -> Option<u32> {
248+
let feature_variations = self.feature_variations()?;
249+
for (index, rec) in feature_variations
250+
.feature_variation_records()
251+
.iter()
252+
.enumerate()
253+
{
254+
// If the ConditionSet offset is 0, this is treated as the
255+
// universal condition: all contexts are matched.
256+
if rec.condition_set_offset().is_null() {
257+
return Some(index as u32);
258+
}
259+
let Some(Ok(condition_set)) = rec.condition_set(feature_variations.offset_data())
260+
else {
261+
continue;
262+
};
263+
// Otherwise, all conditions must be satisfied.
264+
if condition_set
265+
.conditions()
266+
.iter()
267+
// .. except we ignore errors
268+
.filter_map(|cond| cond.ok())
269+
.all(|cond| match cond {
270+
Condition::Format1AxisRange(format1) => {
271+
let coord = coords
272+
.get(format1.axis_index() as usize)
273+
.copied()
274+
.unwrap_or_default();
275+
coord >= format1.filter_range_min_value()
276+
&& coord <= format1.filter_range_max_value()
277+
}
278+
_ => false,
279+
})
280+
{
281+
return Some(index as u32);
282+
}
283+
}
284+
None
285+
}
286+
287+
pub(crate) fn feature_substitution(
288+
&self,
289+
variation_index: u32,
290+
feature_index: u16,
291+
) -> Option<Feature<'a>> {
292+
let feature_variations = self.feature_variations()?;
293+
let record = feature_variations
294+
.feature_variation_records()
295+
.get(variation_index as usize)?;
296+
let subst_table = record
297+
.feature_table_substitution(feature_variations.offset_data())?
298+
.ok()?;
299+
let subst_records = subst_table.substitutions();
300+
match subst_records.binary_search_by_key(&feature_index, |subst| subst.feature_index()) {
301+
Ok(ix) => Some(
302+
subst_records
303+
.get(ix)?
304+
.alternate_feature(subst_table.offset_data())
305+
.ok()?,
306+
),
307+
_ => None,
308+
}
309+
}
310+
311+
pub(crate) fn feature_index(&self, tag: hb_tag_t) -> Option<u16> {
312+
let list = self.feature_list()?;
313+
let tag = conv_tag(tag);
314+
for (index, feature) in list.feature_records().iter().enumerate() {
315+
if feature.feature_tag() == tag {
316+
return Some(index as u16);
317+
}
318+
}
319+
None
320+
}
321+
322+
pub(crate) fn lookup_count(&self) -> u16 {
323+
match self {
324+
Self::Gsub(gsub) => gsub
325+
.lookup_list()
326+
.map(|list| list.lookup_count())
327+
.unwrap_or_default(),
328+
Self::Gpos(gpos) => gpos
329+
.lookup_list()
330+
.map(|list| list.lookup_count())
331+
.unwrap_or_default(),
332+
}
333+
}
334+
}
335+
336+
impl crate::hb::ot_layout::LayoutTableExt for LayoutTable<'_> {
337+
// hb_ot_layout_table_select_script
338+
/// Returns true + index and tag of the first found script tag in the given GSUB or GPOS table
339+
/// or false + index and tag if falling back to a default script.
340+
fn select_script(&self, script_tags: &[hb_tag_t]) -> Option<(bool, u16, hb_tag_t)> {
341+
for &tag in script_tags {
342+
if let Some(index) = self.script_index(tag) {
343+
return Some((true, index, tag));
344+
}
345+
}
346+
347+
for &tag in &[
348+
// try finding 'DFLT'
349+
hb_tag_t::default_script(),
350+
// try with 'dflt'; MS site has had typos and many fonts use it now :(
351+
hb_tag_t::default_language(),
352+
// try with 'latn'; some old fonts put their features there even though
353+
// they're really trying to support Thai, for example :(
354+
hb_tag_t::from_bytes(b"latn"),
355+
] {
356+
if let Some(index) = self.script_index(tag) {
357+
return Some((false, index, tag));
358+
}
359+
}
360+
361+
None
362+
}
363+
364+
// hb_ot_layout_script_select_language
365+
/// Returns the index of the first found language tag in the given GSUB or GPOS table,
366+
/// underneath the specified script index.
367+
fn select_script_language(&self, script_index: u16, lang_tags: &[hb_tag_t]) -> Option<u16> {
368+
for &tag in lang_tags {
369+
if let Some(index) = self.langsys_index(script_index, tag) {
370+
return Some(index);
371+
}
372+
}
373+
374+
// try finding 'dflt'
375+
if let Some(index) = self.langsys_index(script_index, hb_tag_t::default_language()) {
376+
return Some(index);
377+
}
378+
379+
None
380+
}
381+
382+
// hb_ot_layout_language_get_required_feature
383+
/// Returns the index and tag of a required feature in the given GSUB or GPOS table,
384+
/// underneath the specified script and language.
385+
fn get_required_language_feature(
386+
&self,
387+
script_index: u16,
388+
lang_index: Option<u16>,
389+
) -> Option<(u16, hb_tag_t)> {
390+
let sys = self.langsys(script_index, lang_index)?;
391+
let idx = sys.required_feature_index();
392+
if idx == 0xFFFF {
393+
return None;
394+
}
395+
let tag = self.feature_tag(idx)?;
396+
Some((idx, tag))
397+
}
398+
399+
// hb_ot_layout_language_find_feature
400+
/// Returns the index of a given feature tag in the given GSUB or GPOS table,
401+
/// underneath the specified script and language.
402+
fn find_language_feature(
403+
&self,
404+
script_index: u16,
405+
lang_index: Option<u16>,
406+
feature_tag: hb_tag_t,
407+
) -> Option<u16> {
408+
let sys = self.langsys(script_index, lang_index)?;
409+
for index in sys.feature_indices() {
410+
let index = index.get();
411+
if self.feature_tag(index) == Some(feature_tag) {
412+
return Some(index);
413+
}
414+
}
415+
None
416+
}
417+
}
418+
162419
fn coverage_index(coverage: Result<CoverageTable, ReadError>, gid: GlyphId) -> Option<u16> {
163420
let gid = skrifa::GlyphId16::new(gid.0);
164421
coverage.ok().and_then(|coverage| coverage.get(gid))

0 commit comments

Comments
 (0)