1use ttf_parser::opentype_layout::*;
2use ttf_parser::{GlyphId, LazyArray16};
3
4use super::apply::{Apply, ApplyContext, WouldApply, WouldApplyContext};
5use super::matching::{
6 match_backtrack, match_glyph, match_input, match_lookahead, MatchFunc, Matched,
7};
8use super::MAX_CONTEXT_LENGTH;
9
10impl WouldApply for ContextLookup<'_> {
11 fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
12 let glyph: GlyphId = ctx.glyphs[0];
13 match *self {
14 Self::Format1 { coverage: Coverage<'_>, sets: LazyOffsetArray16<'_, LazyOffsetArray16<'_, …>> } => coverage
15 .get(glyph)
16 .and_then(|index| sets.get(index))
17 .map_or(default:false, |set: LazyOffsetArray16<'_, SequenceRule<'_>>| set.would_apply(ctx, &match_glyph)),
18 Self::Format2 { classes: ClassDefinition<'_>, sets: LazyOffsetArray16<'_, LazyOffsetArray16<'_, …>>, .. } => {
19 let class: u16 = classes.get(glyph);
20 sets.get(class)
21 .map_or(default:false, |set: LazyOffsetArray16<'_, SequenceRule<'_>>| set.would_apply(ctx, &match_class(class_def:classes)))
22 }
23 Self::Format3 { coverages: LazyOffsetArray16<'_, Coverage<'_>>, .. } => {
24 ctx.glyphs.len() == usize::from(coverages.len()) + 1
25 && coveragesimpl Iterator
26 .into_iter()
27 .enumerate()
28 .all(|(i: usize, coverage: Coverage<'_>)| coverage.get(glyph:ctx.glyphs[i + 1]).is_some())
29 }
30 }
31 }
32}
33
34impl Apply for ContextLookup<'_> {
35 fn apply(&self, ctx: &mut ApplyContext) -> Option<()> {
36 let glyph = ctx.buffer.cur(0).as_glyph();
37 match *self {
38 Self::Format1 { coverage, sets } => {
39 coverage.get(glyph)?;
40 let set = coverage.get(glyph).and_then(|index| sets.get(index))?;
41 set.apply(ctx, &match_glyph)
42 }
43 Self::Format2 {
44 coverage,
45 classes,
46 sets,
47 } => {
48 coverage.get(glyph)?;
49 let class = classes.get(glyph);
50 let set = sets.get(class)?;
51 set.apply(ctx, &match_class(classes))
52 }
53 Self::Format3 {
54 coverage,
55 coverages,
56 lookups,
57 } => {
58 coverage.get(glyph)?;
59 let coverages_len = coverages.len();
60
61 let match_func = |glyph, num_items| {
62 let index = coverages_len - num_items;
63 let coverage = coverages.get(index).unwrap();
64 coverage.get(glyph).is_some()
65 };
66
67 match_input(ctx, coverages_len as u16, &match_func).map(|matched| {
68 ctx.buffer
69 .unsafe_to_break(ctx.buffer.idx, ctx.buffer.idx + matched.len);
70 apply_lookup(ctx, usize::from(coverages_len), matched, lookups);
71 })
72 }
73 }
74 }
75}
76
77trait SequenceRuleSetExt {
78 fn would_apply(&self, ctx: &WouldApplyContext, match_func: &MatchFunc) -> bool;
79 fn apply(&self, ctx: &mut ApplyContext, match_func: &MatchFunc) -> Option<()>;
80}
81
82impl SequenceRuleSetExt for SequenceRuleSet<'_> {
83 fn would_apply(&self, ctx: &WouldApplyContext, match_func: &MatchFunc) -> bool {
84 self.into_iter()
85 .any(|rule: SequenceRule<'_>| rule.would_apply(ctx, match_func))
86 }
87
88 fn apply(&self, ctx: &mut ApplyContext, match_func: &MatchFunc) -> Option<()> {
89 if self
90 .into_iter()
91 .any(|rule: SequenceRule<'_>| rule.apply(ctx, match_func).is_some())
92 {
93 Some(())
94 } else {
95 None
96 }
97 }
98}
99
100trait SequenceRuleExt {
101 fn would_apply(&self, ctx: &WouldApplyContext, match_func: &MatchFunc) -> bool;
102 fn apply(&self, ctx: &mut ApplyContext, match_func: &MatchFunc) -> Option<()>;
103}
104
105impl SequenceRuleExt for SequenceRule<'_> {
106 fn would_apply(&self, ctx: &WouldApplyContext, match_func: &MatchFunc) -> bool {
107 ctx.glyphs.len() == usize::from(self.input.len()) + 1
108 && self
109 .input
110 .into_iter()
111 .enumerate()
112 .all(|(i: usize, value: u16)| match_func(ctx.glyphs[i + 1], value))
113 }
114
115 fn apply(&self, ctx: &mut ApplyContext, match_func: &MatchFunc) -> Option<()> {
116 apply_context(ctx, self.input, match_func, self.lookups)
117 }
118}
119
120impl WouldApply for ChainedContextLookup<'_> {
121 fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
122 let glyph_id = ctx.glyphs[0];
123 match *self {
124 Self::Format1 { coverage, sets } => coverage
125 .get(glyph_id)
126 .and_then(|index| sets.get(index))
127 .map_or(false, |set| set.would_apply(ctx, &match_glyph)),
128 Self::Format2 {
129 input_classes,
130 sets,
131 ..
132 } => {
133 let class = input_classes.get(glyph_id);
134 sets.get(class).map_or(false, |set| {
135 set.would_apply(ctx, &match_class(input_classes))
136 })
137 }
138 Self::Format3 {
139 backtrack_coverages,
140 input_coverages,
141 lookahead_coverages,
142 ..
143 } => {
144 (!ctx.zero_context
145 || (backtrack_coverages.len() == 0 && lookahead_coverages.len() == 0))
146 && (ctx.glyphs.len() == usize::from(input_coverages.len()) + 1
147 && input_coverages
148 .into_iter()
149 .enumerate()
150 .all(|(i, coverage)| coverage.contains(ctx.glyphs[i + 1])))
151 }
152 }
153 }
154}
155
156impl Apply for ChainedContextLookup<'_> {
157 fn apply(&self, ctx: &mut ApplyContext) -> Option<()> {
158 let glyph = ctx.buffer.cur(0).as_glyph();
159 match *self {
160 Self::Format1 { coverage, sets } => {
161 let index = coverage.get(glyph)?;
162 let set = sets.get(index)?;
163 set.apply(ctx, [&match_glyph, &match_glyph, &match_glyph])
164 }
165 Self::Format2 {
166 coverage,
167 backtrack_classes,
168 input_classes,
169 lookahead_classes,
170 sets,
171 } => {
172 coverage.get(glyph)?;
173 let class = input_classes.get(glyph);
174 let set = sets.get(class)?;
175 set.apply(
176 ctx,
177 [
178 &match_class(backtrack_classes),
179 &match_class(input_classes),
180 &match_class(lookahead_classes),
181 ],
182 )
183 }
184 Self::Format3 {
185 coverage,
186 backtrack_coverages,
187 input_coverages,
188 lookahead_coverages,
189 lookups,
190 } => {
191 coverage.get(glyph)?;
192
193 let back = |glyph, num_items| {
194 let index = backtrack_coverages.len() - num_items;
195 let coverage = backtrack_coverages.get(index).unwrap();
196 coverage.contains(glyph)
197 };
198
199 let ahead = |glyph, num_items| {
200 let index = lookahead_coverages.len() - num_items;
201 let coverage = lookahead_coverages.get(index).unwrap();
202 coverage.contains(glyph)
203 };
204
205 let input = |glyph, num_items| {
206 let index = input_coverages.len() - num_items;
207 let coverage = input_coverages.get(index).unwrap();
208 coverage.contains(glyph)
209 };
210
211 if let Some(matched) = match_input(ctx, input_coverages.len(), &input) {
212 if let Some(start_idx) = match_backtrack(ctx, backtrack_coverages.len(), &back)
213 {
214 if let Some(end_idx) =
215 match_lookahead(ctx, lookahead_coverages.len(), &ahead, matched.len)
216 {
217 ctx.buffer
218 .unsafe_to_break_from_outbuffer(start_idx, end_idx);
219 apply_lookup(ctx, usize::from(input_coverages.len()), matched, lookups);
220 return Some(());
221 }
222 }
223 }
224
225 None
226 }
227 }
228 }
229}
230
231trait ChainRuleSetExt {
232 fn would_apply(&self, ctx: &WouldApplyContext, match_func: &MatchFunc) -> bool;
233 fn apply(&self, ctx: &mut ApplyContext, match_funcs: [&MatchFunc; 3]) -> Option<()>;
234}
235
236impl ChainRuleSetExt for ChainedSequenceRuleSet<'_> {
237 fn would_apply(&self, ctx: &WouldApplyContext, match_func: &MatchFunc) -> bool {
238 self.into_iter()
239 .any(|rule: ChainedSequenceRule<'_>| rule.would_apply(ctx, match_func))
240 }
241
242 fn apply(&self, ctx: &mut ApplyContext, match_funcs: [&MatchFunc; 3]) -> Option<()> {
243 if self
244 .into_iter()
245 .any(|rule: ChainedSequenceRule<'_>| rule.apply(ctx, match_funcs).is_some())
246 {
247 Some(())
248 } else {
249 None
250 }
251 }
252}
253
254trait ChainRuleExt {
255 fn would_apply(&self, ctx: &WouldApplyContext, match_func: &MatchFunc) -> bool;
256 fn apply(&self, ctx: &mut ApplyContext, match_funcs: [&MatchFunc; 3]) -> Option<()>;
257}
258
259impl ChainRuleExt for ChainedSequenceRule<'_> {
260 fn would_apply(&self, ctx: &WouldApplyContext, match_func: &MatchFunc) -> bool {
261 (!ctx.zero_context || (self.backtrack.len() == 0 && self.lookahead.len() == 0))
262 && (ctx.glyphs.len() == usize::from(self.input.len()) + 1
263 && self
264 .input
265 .into_iter()
266 .enumerate()
267 .all(|(i: usize, value: u16)| match_func(ctx.glyphs[i + 1], value)))
268 }
269
270 fn apply(&self, ctx: &mut ApplyContext, match_funcs: [&MatchFunc; 3]) -> Option<()> {
271 apply_chain_context(
272 ctx,
273 self.backtrack,
274 self.input,
275 self.lookahead,
276 match_funcs,
277 self.lookups,
278 )
279 }
280}
281
282fn apply_context(
283 ctx: &mut ApplyContext,
284 input: LazyArray16<u16>,
285 match_func: &MatchFunc,
286 lookups: LazyArray16<SequenceLookupRecord>,
287) -> Option<()> {
288 let match_func: impl Fn(GlyphId, u16) -> … = |glyph: GlyphId, num_items: u16| {
289 let index: u16 = input.len() - num_items;
290 let value: u16 = input.get(index).unwrap();
291 match_func(glyph, value)
292 };
293
294 match_input(ctx, input_len:input.len(), &match_func).map(|matched: Matched| {
295 ctx.buffer
296 .unsafe_to_break(start:ctx.buffer.idx, end:ctx.buffer.idx + matched.len);
297 apply_lookup(ctx, input_len:usize::from(input.len()), matched, lookups);
298 })
299}
300
301fn apply_chain_context(
302 ctx: &mut ApplyContext,
303 backtrack: LazyArray16<u16>,
304 input: LazyArray16<u16>,
305 lookahead: LazyArray16<u16>,
306 match_funcs: [&MatchFunc; 3],
307 lookups: LazyArray16<SequenceLookupRecord>,
308) -> Option<()> {
309 let f1 = |glyph, num_items| {
310 let index = backtrack.len() - num_items;
311 let value = backtrack.get(index).unwrap();
312 match_funcs[0](glyph, value)
313 };
314
315 let f2 = |glyph, num_items| {
316 let index = lookahead.len() - num_items;
317 let value = lookahead.get(index).unwrap();
318 match_funcs[2](glyph, value)
319 };
320
321 let f3 = |glyph, num_items| {
322 let index = input.len() - num_items;
323 let value = input.get(index).unwrap();
324 match_funcs[1](glyph, value)
325 };
326
327 if let Some(matched) = match_input(ctx, input.len(), &f3) {
328 if let Some(start_idx) = match_backtrack(ctx, backtrack.len(), &f1) {
329 if let Some(end_idx) = match_lookahead(ctx, lookahead.len(), &f2, matched.len) {
330 ctx.buffer
331 .unsafe_to_break_from_outbuffer(start_idx, end_idx);
332 apply_lookup(ctx, usize::from(input.len()), matched, lookups);
333 return Some(());
334 }
335 }
336 }
337
338 None
339}
340
341fn apply_lookup(
342 ctx: &mut ApplyContext,
343 input_len: usize,
344 mut matched: Matched,
345 lookups: LazyArray16<SequenceLookupRecord>,
346) {
347 let mut count = input_len + 1;
348
349 // All positions are distance from beginning of *output* buffer.
350 // Adjust.
351 let mut end = {
352 let backtrack_len = ctx.buffer.backtrack_len();
353 let delta = backtrack_len as isize - ctx.buffer.idx as isize;
354
355 // Convert positions to new indexing.
356 for j in 0..count {
357 matched.positions[j] = (matched.positions[j] as isize + delta) as _;
358 }
359
360 backtrack_len + matched.len
361 };
362
363 for record in lookups {
364 if !ctx.buffer.successful {
365 break;
366 }
367
368 let idx = usize::from(record.sequence_index);
369 if idx >= count {
370 continue;
371 }
372
373 // Don't recurse to ourself at same position.
374 // Note that this test is too naive, it doesn't catch longer loops.
375 if idx == 0 && record.lookup_list_index == ctx.lookup_index {
376 continue;
377 }
378
379 if !ctx.buffer.move_to(matched.positions[idx]) {
380 break;
381 }
382
383 if ctx.buffer.max_ops <= 0 {
384 break;
385 }
386
387 let orig_len = ctx.buffer.backtrack_len() + ctx.buffer.lookahead_len();
388 if ctx.recurse(record.lookup_list_index).is_none() {
389 continue;
390 }
391
392 let new_len = ctx.buffer.backtrack_len() + ctx.buffer.lookahead_len();
393 let mut delta = new_len as isize - orig_len as isize;
394 if delta == 0 {
395 continue;
396 }
397
398 // Recursed lookup changed buffer len. Adjust.
399 //
400 // TODO:
401 //
402 // Right now, if buffer length increased by n, we assume n new glyphs
403 // were added right after the current position, and if buffer length
404 // was decreased by n, we assume n match positions after the current
405 // one where removed. The former (buffer length increased) case is
406 // fine, but the decrease case can be improved in at least two ways,
407 // both of which are significant:
408 //
409 // - If recursed-to lookup is MultipleSubst and buffer length
410 // decreased, then it's current match position that was deleted,
411 // NOT the one after it.
412 //
413 // - If buffer length was decreased by n, it does not necessarily
414 // mean that n match positions where removed, as there might
415 // have been marks and default-ignorables in the sequence. We
416 // should instead drop match positions between current-position
417 // and current-position + n instead.
418 //
419 // It should be possible to construct tests for both of these cases.
420
421 end = (end as isize + delta) as _;
422 if end <= matched.positions[idx] {
423 // End might end up being smaller than match_positions[idx] if the recursed
424 // lookup ended up removing many items, more than we have had matched.
425 // Just never rewind end back and get out of here.
426 // https://bugs.chromium.org/p/chromium/issues/detail?id=659496
427 end = matched.positions[idx];
428
429 // There can't be any further changes.
430 break;
431 }
432
433 // next now is the position after the recursed lookup.
434 let mut next = idx + 1;
435
436 if delta > 0 {
437 if delta as usize + count > MAX_CONTEXT_LENGTH {
438 break;
439 }
440 } else {
441 // NOTE: delta is negative.
442 delta = delta.max(next as isize - count as isize);
443 next = (next as isize - delta) as _;
444 }
445
446 // Shift!
447 matched
448 .positions
449 .copy_within(next..count, (next as isize + delta) as _);
450 next = (next as isize + delta) as _;
451 count = (count as isize + delta) as _;
452
453 // Fill in new entries.
454 for j in idx + 1..next {
455 matched.positions[j] = matched.positions[j - 1] + 1;
456 }
457
458 // And fixup the rest.
459 while next < count {
460 matched.positions[next] = (matched.positions[next] as isize + delta) as _;
461 next += 1;
462 }
463 }
464
465 ctx.buffer.move_to(end);
466}
467
468/// Value represents glyph class.
469fn match_class<'a>(class_def: ClassDefinition<'a>) -> impl Fn(GlyphId, u16) -> bool + 'a {
470 move |glyph: GlyphId, value: u16| class_def.get(glyph) == value
471}
472