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