1 | use ttf_parser::opentype_layout::*; |
2 | use ttf_parser::{GlyphId, LazyArray16}; |
3 | |
4 | use super::apply::{Apply, ApplyContext, WouldApply, WouldApplyContext}; |
5 | use super::matching::{match_backtrack, match_glyph, match_input, match_lookahead, MatchFunc}; |
6 | use super::MAX_CONTEXT_LENGTH; |
7 | |
8 | impl 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 | |
32 | impl 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 | |
96 | trait 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 | |
101 | impl 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 | |
119 | trait 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 | |
124 | impl 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 | |
139 | impl 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 | |
175 | impl 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 | |
285 | trait 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 | |
290 | impl 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 | |
308 | trait 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 | |
313 | impl 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 | |
336 | fn 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 | |
374 | fn 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 | |
446 | fn 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. |
573 | fn 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 | |