Skip to content

Commit 34a7e49

Browse files
committed
Add contextual lookups
Adds the missing contextual lookups for fontations and removes all of the ttf-parser OT lookup code. This is a huge patch but all tests pass and we're now running almost all of OT through fontations. The remaining ttf-parser OT code revolves around shape planning: script, langsys, features, etc.
1 parent 940cc74 commit 34a7e49

36 files changed

+347
-1488
lines changed

src/hb/fonta/ot/contextual.rs

+260-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,212 @@
1+
use super::{coverage_index, covered, glyph_class};
12
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
23
use crate::hb::ot_layout_gsubgpos::{
34
apply_lookup, match_backtrack, match_func_t, match_glyph, match_input, match_lookahead, Apply,
45
WouldApply, WouldApplyContext,
56
};
67
use skrifa::raw::tables::layout::{
78
ChainedSequenceContextFormat1, ChainedSequenceContextFormat2, ChainedSequenceContextFormat3,
9+
SequenceContextFormat1, SequenceContextFormat2, SequenceContextFormat3,
810
};
911
use skrifa::raw::types::BigEndian;
1012
use ttf_parser::{opentype_layout::SequenceLookupRecord, GlyphId};
1113

14+
impl WouldApply for SequenceContextFormat1<'_> {
15+
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
16+
coverage_index(self.coverage(), ctx.glyphs[0])
17+
.and_then(|index| {
18+
self.seq_rule_sets()
19+
.get(index as usize)
20+
.transpose()
21+
.ok()
22+
.flatten()
23+
})
24+
.map(|set| {
25+
set.seq_rules().iter().any(|rule| {
26+
rule.map(|rule| {
27+
let input = rule.input_sequence();
28+
ctx.glyphs.len() == input.len() + 1
29+
&& input.iter().enumerate().all(|(i, value)| {
30+
match_glyph(ctx.glyphs[i + 1], value.get().to_u16())
31+
})
32+
})
33+
.unwrap_or(false)
34+
})
35+
})
36+
.unwrap_or_default()
37+
}
38+
}
39+
40+
impl Apply for SequenceContextFormat1<'_> {
41+
fn apply(
42+
&self,
43+
ctx: &mut crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t,
44+
) -> Option<()> {
45+
let glyph = skrifa::GlyphId::from(ctx.buffer.cur(0).as_glyph().0);
46+
let index = self.coverage().ok()?.get(glyph)? as usize;
47+
let set = self.seq_rule_sets().get(index)?.ok()?;
48+
for rule in set.seq_rules().iter().filter_map(|rule| rule.ok()) {
49+
let input = rule.input_sequence();
50+
if apply_context(
51+
ctx,
52+
input,
53+
&match_glyph,
54+
rule.seq_lookup_records()
55+
.iter()
56+
.map(|rec| SequenceLookupRecord {
57+
sequence_index: rec.sequence_index(),
58+
lookup_list_index: rec.lookup_list_index(),
59+
}),
60+
)
61+
.is_some()
62+
{
63+
return Some(());
64+
}
65+
}
66+
None
67+
}
68+
}
69+
70+
impl WouldApply for SequenceContextFormat2<'_> {
71+
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
72+
let class_def = self.class_def().ok();
73+
let match_fn = &match_class(&class_def);
74+
let class = glyph_class(self.class_def(), ctx.glyphs[0]);
75+
self.class_seq_rule_sets()
76+
.get(class as usize)
77+
.transpose()
78+
.ok()
79+
.flatten()
80+
.map(|set| {
81+
set.class_seq_rules().iter().any(|rule| {
82+
rule.map(|rule| {
83+
let input = rule.input_sequence();
84+
ctx.glyphs.len() == input.len() + 1
85+
&& input
86+
.iter()
87+
.enumerate()
88+
.all(|(i, value)| match_fn(ctx.glyphs[i + 1], value.get()))
89+
})
90+
.unwrap_or(false)
91+
})
92+
})
93+
.unwrap_or_default()
94+
}
95+
}
96+
97+
impl Apply for SequenceContextFormat2<'_> {
98+
fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
99+
let input_classes = self.class_def().ok();
100+
let glyph = ctx.buffer.cur(0).as_skrifa_glyph16();
101+
self.coverage().ok()?.get(glyph)?;
102+
let index = input_classes.as_ref()?.get(glyph) as usize;
103+
let set = self.class_seq_rule_sets().get(index)?.ok()?;
104+
for rule in set.class_seq_rules().iter().filter_map(|rule| rule.ok()) {
105+
let input = rule.input_sequence();
106+
if apply_context(
107+
ctx,
108+
input,
109+
&match_class(&input_classes),
110+
rule.seq_lookup_records()
111+
.iter()
112+
.map(|rec| SequenceLookupRecord {
113+
sequence_index: rec.sequence_index(),
114+
lookup_list_index: rec.lookup_list_index(),
115+
}),
116+
)
117+
.is_some()
118+
{
119+
return Some(());
120+
}
121+
}
122+
None
123+
}
124+
}
125+
126+
impl WouldApply for SequenceContextFormat3<'_> {
127+
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
128+
let coverages = self.coverages();
129+
ctx.glyphs.len() == usize::from(coverages.len()) + 1
130+
&& coverages
131+
.iter()
132+
.enumerate()
133+
.all(|(i, coverage)| covered(coverage, ctx.glyphs[i + 1]))
134+
}
135+
}
136+
137+
impl Apply for SequenceContextFormat3<'_> {
138+
fn apply(
139+
&self,
140+
ctx: &mut crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t,
141+
) -> Option<()> {
142+
let glyph = skrifa::GlyphId::from(ctx.buffer.cur(0).as_glyph().0);
143+
let input_coverages = self.coverages();
144+
input_coverages.get(0).ok()?.get(glyph)?;
145+
let input = |glyph: GlyphId, index: u16| {
146+
input_coverages
147+
.get(index as usize + 1)
148+
.map(|cov| cov.get(skrifa::GlyphId::from(glyph.0)).is_some())
149+
.unwrap_or_default()
150+
};
151+
let mut match_end = 0;
152+
let mut match_positions = smallvec::SmallVec::from_elem(0, 4);
153+
if match_input(
154+
ctx,
155+
input_coverages.len() as u16 - 1,
156+
&input,
157+
&mut match_end,
158+
&mut match_positions,
159+
None,
160+
) {
161+
ctx.buffer
162+
.unsafe_to_break_from_outbuffer(Some(ctx.buffer.idx), Some(match_end));
163+
apply_lookup(
164+
ctx,
165+
input_coverages.len() - 1,
166+
&mut match_positions,
167+
match_end,
168+
self.seq_lookup_records()
169+
.iter()
170+
.map(|rec| SequenceLookupRecord {
171+
sequence_index: rec.sequence_index(),
172+
lookup_list_index: rec.lookup_list_index(),
173+
}),
174+
);
175+
Some(())
176+
} else {
177+
ctx.buffer
178+
.unsafe_to_concat(Some(ctx.buffer.idx), Some(match_end));
179+
None
180+
}
181+
}
182+
}
183+
12184
impl WouldApply for ChainedSequenceContextFormat1<'_> {
13-
fn would_apply(&self, _ctx: &WouldApplyContext) -> bool {
14-
false
185+
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
186+
coverage_index(self.coverage(), ctx.glyphs[0])
187+
.and_then(|index| {
188+
self.chained_seq_rule_sets()
189+
.get(index as usize)
190+
.transpose()
191+
.ok()
192+
.flatten()
193+
})
194+
.map(|set| {
195+
set.chained_seq_rules().iter().any(|rule| {
196+
rule.map(|rule| {
197+
let input = rule.input_sequence();
198+
(!ctx.zero_context
199+
|| (rule.backtrack_glyph_count() == 0
200+
&& rule.lookahead_glyph_count() == 0))
201+
&& ctx.glyphs.len() == input.len() + 1
202+
&& input.iter().enumerate().all(|(i, value)| {
203+
match_glyph(ctx.glyphs[i + 1], value.get().to_u16())
204+
})
205+
})
206+
.unwrap_or(false)
207+
})
208+
})
209+
.unwrap_or_default()
15210
}
16211
}
17212

@@ -47,8 +242,32 @@ impl Apply for ChainedSequenceContextFormat1<'_> {
47242
}
48243

49244
impl WouldApply for ChainedSequenceContextFormat2<'_> {
50-
fn would_apply(&self, _ctx: &WouldApplyContext) -> bool {
51-
false
245+
fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
246+
let class_def = self.input_class_def().ok();
247+
let match_fn = &match_class(&class_def);
248+
let class = glyph_class(self.input_class_def(), ctx.glyphs[0]);
249+
self.chained_class_seq_rule_sets()
250+
.get(class as usize)
251+
.transpose()
252+
.ok()
253+
.flatten()
254+
.map(|set| {
255+
set.chained_class_seq_rules().iter().any(|rule| {
256+
rule.map(|rule| {
257+
let input = rule.input_sequence();
258+
(!ctx.zero_context
259+
|| (rule.backtrack_glyph_count() == 0
260+
&& rule.lookahead_glyph_count() == 0))
261+
&& ctx.glyphs.len() == input.len() + 1
262+
&& input
263+
.iter()
264+
.enumerate()
265+
.all(|(i, value)| match_fn(ctx.glyphs[i + 1], value.get()))
266+
})
267+
.unwrap_or(false)
268+
})
269+
})
270+
.unwrap_or_default()
52271
}
53272
}
54273

@@ -235,6 +454,43 @@ impl ToU16 for BigEndian<u16> {
235454
}
236455
}
237456

457+
fn apply_context<T: ToU16>(
458+
ctx: &mut hb_ot_apply_context_t,
459+
input: &[T],
460+
match_func: &match_func_t,
461+
lookups: impl Iterator<Item = SequenceLookupRecord>,
462+
) -> Option<()> {
463+
let match_func = |glyph, index| {
464+
let value = input.get(index as usize).unwrap().to_u16();
465+
match_func(glyph, value)
466+
};
467+
468+
let mut match_end = 0;
469+
let mut match_positions = smallvec::SmallVec::from_elem(0, 4);
470+
471+
if match_input(
472+
ctx,
473+
input.len() as _,
474+
&match_func,
475+
&mut match_end,
476+
&mut match_positions,
477+
None,
478+
) {
479+
ctx.buffer
480+
.unsafe_to_break(Some(ctx.buffer.idx), Some(match_end));
481+
apply_lookup(
482+
ctx,
483+
usize::from(input.len()),
484+
&mut match_positions,
485+
match_end,
486+
lookups.into_iter(),
487+
);
488+
return Some(());
489+
}
490+
491+
None
492+
}
493+
238494
fn apply_chain_context<T: ToU16>(
239495
ctx: &mut hb_ot_apply_context_t,
240496
backtrack: &[T],

src/hb/fonta/ot/gpos/mod.rs

+5-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//! OpenType GPOS lookups.
22
33
use super::{LookupCache, LookupInfo};
4-
use crate::hb::{
5-
ot_layout::TableIndex, ot_layout_gpos_table::ValueRecordExt,
6-
ot_layout_gsubgpos::OT::hb_ot_apply_context_t,
4+
use crate::{
5+
hb::{ot_layout::TableIndex, ot_layout_gsubgpos::OT::hb_ot_apply_context_t},
6+
GlyphPosition,
77
};
88
use skrifa::raw::{
99
tables::{
@@ -53,7 +53,7 @@ struct Value<'a> {
5353
data: FontData<'a>,
5454
}
5555

56-
impl ValueRecordExt for Value<'_> {
56+
impl Value<'_> {
5757
fn is_empty(&self) -> bool {
5858
self.record.x_placement.is_none()
5959
&& self.record.y_placement.is_none()
@@ -72,11 +72,7 @@ impl ValueRecordExt for Value<'_> {
7272
worked
7373
}
7474

75-
fn apply_to_pos(
76-
&self,
77-
ctx: &mut hb_ot_apply_context_t,
78-
pos: &mut crate::GlyphPosition,
79-
) -> bool {
75+
fn apply_to_pos(&self, ctx: &mut hb_ot_apply_context_t, pos: &mut GlyphPosition) -> bool {
8076
let horizontal = ctx.buffer.direction.is_horizontal();
8177
let mut worked = false;
8278

src/hb/fonta/ot/gpos/pair.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::hb::ot_layout_gpos_table::ValueRecordExt;
21
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
32
use crate::hb::ot_layout_gsubgpos::{skipping_iterator_t, Apply};
43
use skrifa::raw::tables::gpos::{PairPosFormat1, PairPosFormat2, PairValueRecord};

src/hb/fonta/ot/gpos/single.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use super::Value;
2-
use crate::hb::ot_layout_gpos_table::ValueRecordExt;
32
use crate::hb::ot_layout_gsubgpos::Apply;
43
use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
54
use skrifa::raw::tables::gpos::{SinglePosFormat1, SinglePosFormat2};

src/hb/fonta/ot/lookup_cache.rs

-23
Original file line numberDiff line numberDiff line change
@@ -182,29 +182,6 @@ impl LookupCache {
182182
lookup_type: subtable_kind as u8,
183183
digest: Default::default(),
184184
};
185-
// TODO: update as we add more subtables
186-
let is_supported = match (data.is_subst, subtable_kind) {
187-
// (true, 1) | (true, 2) | (true, 3) | (true, 4) => true,
188-
(true, 1) | (true, 2) | (true, 3) | (true, 4) => true,
189-
(false, 4) | (false, 6) => true,
190-
// single pos
191-
(false, 1) => true,
192-
// pair pos
193-
(false, 2) => true,
194-
// cursive pos
195-
(false, 3) => true,
196-
// mark lig pos
197-
(false, 5) => true,
198-
// chained sequence context
199-
(true, 6) => true,
200-
(false, 8) => true,
201-
// reverse chained context
202-
(true, 8) => true,
203-
_ => false,
204-
};
205-
if !is_supported {
206-
return Err(ReadError::MalformedData("unsupported subtable"));
207-
}
208185
let subtable = subtable_info.materialize(data.table_data.as_bytes())?;
209186
let (coverage, coverage_offset) = subtable.coverage_and_offset()?;
210187
add_coverage_to_digest(&coverage, &mut subtable_info.digest);

0 commit comments

Comments
 (0)