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::{ |
6 | match_backtrack, match_glyph, match_input, match_lookahead, MatchFunc, Matched, |
7 | }; |
8 | use super::MAX_CONTEXT_LENGTH; |
9 | |
10 | impl 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 | |
34 | impl 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 | |
77 | trait 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 | |
82 | impl 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 | |
100 | trait 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 | |
105 | impl 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 | |
120 | impl 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 | |
156 | impl 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 | |
231 | trait 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 | |
236 | impl 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 | |
254 | trait 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 | |
259 | impl 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 | |
282 | fn 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 | |
301 | fn 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 | |
341 | fn 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. |
469 | fn 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 | |