Skip to main content

harfrust/hb/ot/
contextual.rs

1use super::{coverage_binary_cached, coverage_index, covered, glyph_class};
2use crate::hb::buffer::GlyphInfo;
3use crate::hb::ot::{ClassDefInfo, CoverageInfo};
4use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
5use crate::hb::ot_layout_gsubgpos::{
6    apply_lookup, match_always, match_backtrack, match_glyph, match_input, match_lookahead,
7    may_skip_t, skipping_iterator_t, Apply, BinaryCache, ChainContextFormat2Cache,
8    ContextFormat2Cache, SubtableExternalCache, SubtableExternalCacheMode, WouldApply,
9    WouldApplyContext,
10};
11use read_fonts::tables::gsub::ClassDef;
12use read_fonts::tables::layout::{
13    ChainedClassSequenceRule, ChainedSequenceContextFormat1, ChainedSequenceContextFormat2,
14    ChainedSequenceContextFormat3, ChainedSequenceRule, ClassSequenceRule, SequenceContextFormat1,
15    SequenceContextFormat2, SequenceContextFormat3, SequenceLookupRecord, SequenceRule,
16};
17use read_fonts::types::{BigEndian, FixedSize, GlyphId, Offset16};
18use read_fonts::{ArrayOfOffsets, FontData, FontRead};
19
20impl WouldApply for SequenceContextFormat1<'_> {
21    fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
22        coverage_index(self.coverage(), ctx.glyphs[0])
23            .and_then(|index| {
24                self.seq_rule_sets()
25                    .get(index as usize)
26                    .transpose()
27                    .ok()
28                    .flatten()
29            })
30            .is_some_and(|set| {
31                set.seq_rules().iter().any(|rule| {
32                    rule.is_ok_and(|rule| {
33                        let input = rule.input_sequence();
34                        ctx.glyphs.len() == input.len() + 1
35                            && input.iter().enumerate().all(|(i, value)| {
36                                let mut info = GlyphInfo {
37                                    glyph_id: ctx.glyphs[i + 1].into(),
38                                    ..GlyphInfo::default()
39                                };
40                                match_glyph(&mut info, value.get().to_u32())
41                            })
42                    })
43                })
44            })
45    }
46}
47
48impl Apply for SequenceContextFormat1<'_> {
49    fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
50        let glyph = ctx.buffer.cur(0).as_glyph();
51        let index = self.coverage().ok()?.get(glyph)? as usize;
52        let set = self.seq_rule_sets().get(index)?.ok()?;
53        apply_context_rules(ctx, &set.seq_rules(), match_glyph)
54    }
55}
56
57impl WouldApply for SequenceContextFormat2<'_> {
58    fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
59        let class_def = self.class_def().ok();
60        let match_fn = &match_class(&class_def);
61        let class = glyph_class(self.class_def(), ctx.glyphs[0]);
62        self.class_seq_rule_sets()
63            .get(class as usize)
64            .transpose()
65            .ok()
66            .flatten()
67            .is_some_and(|set| {
68                set.class_seq_rules().iter().any(|rule| {
69                    rule.is_ok_and(|rule| {
70                        let input = rule.input_sequence();
71                        ctx.glyphs.len() == input.len() + 1
72                            && input.iter().enumerate().all(|(i, value)| {
73                                let mut info = GlyphInfo {
74                                    glyph_id: ctx.glyphs[i + 1].into(),
75                                    ..GlyphInfo::default()
76                                };
77                                match_fn(&mut info, value.get() as u32)
78                            })
79                    })
80                })
81            })
82    }
83}
84
85impl Apply for SequenceContextFormat2<'_> {
86    fn apply_with_external_cache(
87        &self,
88        ctx: &mut hb_ot_apply_context_t,
89        external_cache: &SubtableExternalCache,
90    ) -> Option<()> {
91        let glyph = ctx.buffer.cur(0).as_glyph();
92        let SubtableExternalCache::ContextFormat2Cache(cache) = external_cache else {
93            return None;
94        };
95        let offset_data = self.offset_data();
96        coverage_binary_cached(
97            |gid| cache.coverage.index(&offset_data, gid),
98            glyph,
99            &cache.coverage_cache,
100        )?;
101        let input_class = |gid| cache.input.class(&offset_data, gid);
102        let index = input_class(glyph) as usize;
103        let set = self.class_seq_rule_sets().get(index)?.ok()?;
104        apply_context_rules(ctx, &set.class_seq_rules(), |info, value| {
105            u32::from(input_class(info.as_glyph())) == value
106        })
107    }
108
109    fn apply_cached(
110        &self,
111        ctx: &mut hb_ot_apply_context_t,
112        external_cache: &SubtableExternalCache,
113    ) -> Option<()> {
114        let glyph = ctx.buffer.cur(0).as_glyph();
115        let SubtableExternalCache::ContextFormat2Cache(cache) = external_cache else {
116            return None;
117        };
118        let offset_data = self.offset_data();
119        coverage_binary_cached(
120            |gid| cache.coverage.index(&offset_data, gid),
121            glyph,
122            &cache.coverage_cache,
123        )?;
124        let input_class = |gid| cache.input.class(&offset_data, gid);
125        let index = get_class_cached(&input_class, &mut ctx.buffer.info[ctx.buffer.idx]) as usize;
126        let set = self.class_seq_rule_sets().get(index)?.ok()?;
127        apply_context_rules(
128            ctx,
129            &set.class_seq_rules(),
130            match_class_cached(&input_class),
131        )
132    }
133
134    fn cache_cost(&self) -> u32 {
135        self.class_def()
136            .ok()
137            .map_or(0, |class_def| class_def.cost())
138    }
139
140    fn external_cache_create(&self, _mode: SubtableExternalCacheMode) -> SubtableExternalCache {
141        let data = self.offset_data();
142        SubtableExternalCache::ContextFormat2Cache(ContextFormat2Cache {
143            coverage_cache: BinaryCache::new(),
144            coverage: CoverageInfo::new(&data, self.coverage_offset().to_u32() as u16)
145                .unwrap_or_default(),
146            input: ClassDefInfo::new(&data, self.class_def_offset().to_u32() as u16)
147                .unwrap_or_default(),
148        })
149    }
150}
151
152impl WouldApply for SequenceContextFormat3<'_> {
153    fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
154        let coverages = self.coverages();
155        ctx.glyphs.len() == coverages.len() + 1
156            && coverages
157                .iter()
158                .enumerate()
159                .all(|(i, coverage)| covered(coverage, ctx.glyphs[i + 1]))
160    }
161}
162
163impl Apply for SequenceContextFormat3<'_> {
164    fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
165        let glyph = ctx.buffer.cur(0).as_glyph();
166        let input_coverages = self.coverages();
167        input_coverages.get(0).ok()?.get(glyph)?;
168        let input = |info: &mut GlyphInfo, index: u32| {
169            input_coverages
170                .get(index as usize + 1)
171                .is_ok_and(|cov| cov.get(info.glyph_id).is_some())
172        };
173        let mut match_end = 0;
174        if match_input(
175            ctx,
176            input_coverages.len() as u16 - 1,
177            input,
178            &mut match_end,
179            None,
180        ) {
181            ctx.buffer
182                .unsafe_to_break(Some(ctx.buffer.idx), Some(match_end));
183            apply_lookup(
184                ctx,
185                input_coverages.len() - 1,
186                match_end,
187                self.seq_lookup_records(),
188            );
189            Some(())
190        } else {
191            ctx.buffer
192                .unsafe_to_concat(Some(ctx.buffer.idx), Some(match_end));
193            None
194        }
195    }
196}
197
198impl WouldApply for ChainedSequenceContextFormat1<'_> {
199    fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
200        coverage_index(self.coverage(), ctx.glyphs[0])
201            .and_then(|index| {
202                self.chained_seq_rule_sets()
203                    .get(index as usize)
204                    .transpose()
205                    .ok()
206                    .flatten()
207            })
208            .is_some_and(|set| {
209                set.chained_seq_rules().iter().any(|rule| {
210                    rule.is_ok_and(|rule| {
211                        let input = rule.input_sequence();
212                        (!ctx.zero_context
213                            || (rule.backtrack_glyph_count() == 0
214                                && rule.lookahead_glyph_count() == 0))
215                            && ctx.glyphs.len() == input.len() + 1
216                            && input.iter().enumerate().all(|(i, value)| {
217                                let mut info = GlyphInfo {
218                                    glyph_id: ctx.glyphs[i + 1].into(),
219                                    ..GlyphInfo::default()
220                                };
221                                match_glyph(&mut info, value.get().to_u32())
222                            })
223                    })
224                })
225            })
226    }
227}
228
229impl Apply for ChainedSequenceContextFormat1<'_> {
230    fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
231        let glyph = ctx.buffer.cur(0).as_glyph();
232        let index = self.coverage().ok()?.get(glyph)? as usize;
233        let set = self.chained_seq_rule_sets().get(index)?.ok()?;
234        apply_chain_context_rules(
235            ctx,
236            &set.chained_seq_rules(),
237            (match_glyph, match_glyph, match_glyph),
238        )
239    }
240}
241
242impl WouldApply for ChainedSequenceContextFormat2<'_> {
243    fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
244        let class_def = self.input_class_def().ok();
245        let match_fn = &match_class(&class_def);
246        let class = glyph_class(self.input_class_def(), ctx.glyphs[0]);
247        self.chained_class_seq_rule_sets()
248            .get(class as usize)
249            .transpose()
250            .ok()
251            .flatten()
252            .is_some_and(|set| {
253                set.chained_class_seq_rules().iter().any(|rule| {
254                    rule.is_ok_and(|rule| {
255                        let input = rule.input_sequence();
256                        (!ctx.zero_context
257                            || (rule.backtrack_glyph_count() == 0
258                                && rule.lookahead_glyph_count() == 0))
259                            && ctx.glyphs.len() == input.len() + 1
260                            && input.iter().enumerate().all(|(i, value)| {
261                                let mut info = GlyphInfo {
262                                    glyph_id: ctx.glyphs[i + 1].into(),
263                                    ..GlyphInfo::default()
264                                };
265                                match_fn(&mut info, value.get() as u32)
266                            })
267                    })
268                })
269            })
270    }
271}
272
273/// Value represents glyph class.
274fn match_class<'a>(
275    class_def: &'a Option<ClassDef<'a>>,
276) -> impl Fn(&mut GlyphInfo, u32) -> bool + 'a {
277    |&mut info, value| {
278        class_def
279            .as_ref()
280            .is_some_and(|class_def| u32::from(class_def.get(info.as_glyph())) == value)
281    }
282}
283
284fn get_class_cached(class_def: &impl Fn(GlyphId) -> u16, info: &mut GlyphInfo) -> u16 {
285    let mut klass = info.syllable() as u16;
286    if klass < 255 {
287        return klass;
288    }
289    klass = class_def(info.as_glyph());
290    if klass < 255 {
291        info.set_syllable(klass as u8);
292    }
293
294    klass
295}
296
297fn match_class_cached<'a>(
298    class_def: impl Fn(GlyphId) -> u16 + 'a,
299) -> impl Fn(&mut GlyphInfo, u32) -> bool + 'a {
300    move |info: &mut GlyphInfo, value| u32::from(get_class_cached(&class_def, info)) == value
301}
302
303fn get_class_cached1(class_def: &impl Fn(GlyphId) -> u16, info: &mut GlyphInfo) -> u16 {
304    let mut klass = (info.syllable() & 0x0F) as u16;
305    if klass < 15 {
306        return klass;
307    }
308
309    klass = class_def(info.as_glyph());
310
311    if klass < 15 {
312        info.set_syllable((info.syllable() & 0xF0) | klass as u8);
313    }
314
315    klass
316}
317
318fn match_class_cached1<'a>(
319    class_def: impl Fn(GlyphId) -> u16 + 'a,
320) -> impl Fn(&mut GlyphInfo, u32) -> bool + 'a {
321    move |info: &mut GlyphInfo, value| u32::from(get_class_cached1(&class_def, info)) == value
322}
323
324fn get_class_cached2(class_def: &impl Fn(GlyphId) -> u16, info: &mut GlyphInfo) -> u16 {
325    let mut klass = (info.syllable() & 0xF0) as u16 >> 4;
326    if klass < 15 {
327        return klass;
328    }
329    klass = class_def(info.as_glyph());
330    if klass < 15 {
331        info.set_syllable((info.syllable() & 0x0F) | ((klass as u8) << 4));
332    }
333    klass
334}
335
336fn match_class_cached2<'a>(
337    class_def: impl Fn(GlyphId) -> u16 + 'a,
338) -> impl Fn(&mut GlyphInfo, u32) -> bool + 'a {
339    move |info: &mut GlyphInfo, value| u32::from(get_class_cached2(&class_def, info)) == value
340}
341
342impl Apply for ChainedSequenceContextFormat2<'_> {
343    fn apply_with_external_cache(
344        &self,
345        ctx: &mut hb_ot_apply_context_t,
346        external_cache: &SubtableExternalCache,
347    ) -> Option<()> {
348        let glyph = ctx.buffer.cur(0).as_glyph();
349        let SubtableExternalCache::ChainContextFormat2Cache(cache) = external_cache else {
350            return None;
351        };
352        let offset_data = self.offset_data();
353        coverage_binary_cached(
354            |gid| cache.coverage.index(&offset_data, gid),
355            glyph,
356            &cache.coverage_cache,
357        )?;
358        let index = cache.input.class(&offset_data, glyph) as usize;
359        let set = self.chained_class_seq_rule_sets().get(index)?.ok()?;
360        apply_chain_context_rules(
361            ctx,
362            &set.chained_class_seq_rules(),
363            (
364                |info, val| u32::from(cache.backtrack.class(&offset_data, info.as_glyph())) == val,
365                |info, val| u32::from(cache.input.class(&offset_data, info.as_glyph())) == val,
366                |info, val| u32::from(cache.lookahead.class(&offset_data, info.as_glyph())) == val,
367            ),
368        )
369    }
370    fn apply_cached(
371        &self,
372        ctx: &mut hb_ot_apply_context_t,
373        external_cache: &SubtableExternalCache,
374    ) -> Option<()> {
375        let glyph = ctx.buffer.cur(0).as_glyph();
376        let SubtableExternalCache::ChainContextFormat2Cache(cache) = external_cache else {
377            return None;
378        };
379        let offset_data = self.offset_data();
380        coverage_binary_cached(
381            |gid| cache.coverage.index(&offset_data, gid),
382            glyph,
383            &cache.coverage_cache,
384        )?;
385        let input_class = |gid| cache.input.class(&offset_data, gid);
386        let lookahead_class = |gid| cache.lookahead.class(&offset_data, gid);
387        let index = get_class_cached2(&input_class, &mut ctx.buffer.info[ctx.buffer.idx]) as usize;
388        let set = self.chained_class_seq_rule_sets().get(index)?.ok()?;
389        apply_chain_context_rules(
390            ctx,
391            &set.chained_class_seq_rules(),
392            (
393                |info, val| u32::from(cache.backtrack.class(&offset_data, info.as_glyph())) == val,
394                match_class_cached2(&input_class),
395                match_class_cached1(&lookahead_class),
396            ),
397        )
398    }
399    fn cache_cost(&self) -> u32 {
400        self.input_class_def()
401            .ok()
402            .map_or(0, |class_def| class_def.cost())
403            + self
404                .lookahead_class_def()
405                .ok()
406                .map_or(0, |class_def| class_def.cost())
407    }
408
409    fn external_cache_create(&self, _mode: SubtableExternalCacheMode) -> SubtableExternalCache {
410        let data = self.offset_data();
411        SubtableExternalCache::ChainContextFormat2Cache(ChainContextFormat2Cache {
412            coverage_cache: BinaryCache::new(),
413            coverage: CoverageInfo::new(&data, self.coverage_offset().to_u32() as u16)
414                .unwrap_or_default(),
415            backtrack: ClassDefInfo::new(&data, self.backtrack_class_def_offset().to_u32() as u16)
416                .unwrap_or_default(),
417            input: ClassDefInfo::new(&data, self.input_class_def_offset().to_u32() as u16)
418                .unwrap_or_default(),
419            lookahead: ClassDefInfo::new(&data, self.lookahead_class_def_offset().to_u32() as u16)
420                .unwrap_or_default(),
421        })
422    }
423}
424
425impl WouldApply for ChainedSequenceContextFormat3<'_> {
426    fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
427        let input_coverages = self.input_coverages();
428        (!ctx.zero_context
429            || (self.backtrack_coverage_offsets().is_empty()
430                && self.lookahead_coverage_offsets().is_empty()))
431            && (ctx.glyphs.len() == input_coverages.len()
432                && input_coverages
433                    .iter()
434                    .skip(1)
435                    .enumerate()
436                    .all(|(i, coverage)| {
437                        coverage.is_ok_and(|cov| cov.get(ctx.glyphs[i + 1]).is_some())
438                    }))
439    }
440}
441
442impl Apply for ChainedSequenceContextFormat3<'_> {
443    fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
444        let glyph = ctx.buffer.cur(0).as_glyph();
445
446        let input_coverages = self.input_coverages();
447        input_coverages.get(0).ok()?.get(glyph)?;
448
449        let backtrack_coverages = self.backtrack_coverages();
450        let lookahead_coverages = self.lookahead_coverages();
451
452        let back = |info: &mut GlyphInfo, index: u32| {
453            backtrack_coverages
454                .get(index as usize)
455                .is_ok_and(|cov| cov.get(info.glyph_id).is_some())
456        };
457
458        let ahead = |info: &mut GlyphInfo, index: u32| {
459            lookahead_coverages
460                .get(index as usize)
461                .is_ok_and(|cov| cov.get(info.glyph_id).is_some())
462        };
463
464        let input = |info: &mut GlyphInfo, index: u32| {
465            input_coverages
466                .get(index as usize + 1)
467                .is_ok_and(|cov| cov.get(info.glyph_id).is_some())
468        };
469
470        let mut end_index = ctx.buffer.idx;
471        let mut match_end = 0;
472
473        let input_matches = match_input(
474            ctx,
475            input_coverages.len() as u16 - 1,
476            input,
477            &mut match_end,
478            None,
479        );
480
481        if input_matches {
482            end_index = match_end;
483        }
484
485        if !(input_matches
486            && match_lookahead(
487                ctx,
488                lookahead_coverages.len() as u16,
489                ahead,
490                match_end,
491                &mut end_index,
492            ))
493        {
494            ctx.buffer
495                .unsafe_to_concat(Some(ctx.buffer.idx), Some(end_index));
496            return None;
497        }
498
499        let mut start_index = ctx.buffer.out_len;
500
501        if !match_backtrack(
502            ctx,
503            backtrack_coverages.len() as u16,
504            back,
505            &mut start_index,
506        ) {
507            ctx.buffer
508                .unsafe_to_concat_from_outbuffer(Some(start_index), Some(end_index));
509            return None;
510        }
511
512        ctx.buffer
513            .unsafe_to_break_from_outbuffer(Some(start_index), Some(end_index));
514        apply_lookup(
515            ctx,
516            input_coverages.len() - 1,
517            match_end,
518            self.seq_lookup_records(),
519        );
520
521        Some(())
522    }
523}
524
525/// All of a context rule's fields, parsed in a single pass.
526///
527/// The generated getters re-derive the positions of all preceding fields on
528/// every call, so fetching fields individually in the rule-matching loops
529/// re-reads the leading counts many times over; this parses the whole rule
530/// once instead. Glyph ids and classes are both read as raw u16s.
531#[derive(Clone, Copy, Default)]
532struct ParsedRule<'a> {
533    backtrack: &'a [BigEndian<u16>],
534    input: &'a [BigEndian<u16>],
535    lookahead: &'a [BigEndian<u16>],
536    records: &'a [SequenceLookupRecord],
537}
538
539impl<'a> ParsedRule<'a> {
540    /// Parse a SequenceRule or ClassSequenceRule.
541    fn from_rule_data(data: FontData<'a>) -> Option<Self> {
542        let glyph_count: u16 = data.read_at(0).ok()?;
543        let record_count: u16 = data.read_at(2).ok()?;
544        let input_end = 4 + (glyph_count as usize).saturating_sub(1) * u16::RAW_BYTE_LEN;
545        let records_end = input_end + record_count as usize * SequenceLookupRecord::RAW_BYTE_LEN;
546        Some(ParsedRule {
547            input: data.read_array(4..input_end).ok()?,
548            records: data.read_array(input_end..records_end).ok()?,
549            ..ParsedRule::default()
550        })
551    }
552
553    /// Parse a ChainedSequenceRule or ChainedClassSequenceRule.
554    fn from_chain_rule_data(data: FontData<'a>) -> Option<Self> {
555        let backtrack_count: u16 = data.read_at(0).ok()?;
556        let backtrack_end = 2 + backtrack_count as usize * u16::RAW_BYTE_LEN;
557        let input_count: u16 = data.read_at(backtrack_end).ok()?;
558        let input_end =
559            backtrack_end + 2 + (input_count as usize).saturating_sub(1) * u16::RAW_BYTE_LEN;
560        let lookahead_count: u16 = data.read_at(input_end).ok()?;
561        let lookahead_end = input_end + 2 + lookahead_count as usize * u16::RAW_BYTE_LEN;
562        let record_count: u16 = data.read_at(lookahead_end).ok()?;
563        let records_end =
564            lookahead_end + 2 + record_count as usize * SequenceLookupRecord::RAW_BYTE_LEN;
565        Some(ParsedRule {
566            backtrack: data.read_array(2..backtrack_end).ok()?,
567            input: data.read_array(backtrack_end + 2..input_end).ok()?,
568            lookahead: data.read_array(input_end + 2..lookahead_end).ok()?,
569            records: data.read_array(lookahead_end + 2..records_end).ok()?,
570        })
571    }
572
573    /// Match this rule's input sequence and apply its lookup records.
574    ///
575    /// Backtrack/lookahead are not consulted; chain rules go through
576    /// [`apply_chain_with_sequences`] instead.
577    fn apply(
578        &self,
579        ctx: &mut hb_ot_apply_context_t,
580        match_func: &impl Fn(&mut GlyphInfo, u32) -> bool,
581    ) -> Option<()> {
582        let inputs = self.input;
583        let match_func = |info: &mut GlyphInfo, index| {
584            inputs
585                .get(index as usize)
586                .is_some_and(|value| match_func(info, value.get() as u32))
587        };
588
589        let mut match_end = 0;
590
591        if match_input(ctx, inputs.len() as _, match_func, &mut match_end, None) {
592            ctx.buffer
593                .unsafe_to_break(Some(ctx.buffer.idx), Some(match_end));
594            apply_lookup(ctx, inputs.len(), match_end, self.records);
595            return Some(());
596        }
597        None
598    }
599}
600
601trait ContextRule<'a>: FontRead<'a> {
602    /// Parse all of the rule's fields in one pass.
603    fn parse(&self) -> ParsedRule<'a>;
604
605    /// The first glyph/class of the input sequence, or `None` if the input
606    /// sequence is empty.
607    ///
608    /// Much cheaper than a full parse; used by the skip-ahead loops, which
609    /// discard most of the rules they visit.
610    fn first_input(&self) -> Option<u16>;
611}
612
613/// `first_input` for SequenceRule/ClassSequenceRule.
614///
615/// The glyph count is at offset 0 and the input sequence starts at offset 4,
616/// after the lookup record count. (The input sequence's first entry covers
617/// the *second* glyph of the matched sequence.)
618fn plain_rule_first_input(data: &FontData) -> Option<u16> {
619    let glyph_count: u16 = data.read_at(0).ok()?;
620    if glyph_count <= 1 {
621        return None;
622    }
623    data.read_at(4).ok()
624}
625
626/// `first_input` for ChainedSequenceRule/ChainedClassSequenceRule.
627///
628/// The input glyph count follows the variable-length backtrack sequence, and
629/// the input sequence follows it directly.
630fn chain_rule_first_input(data: &FontData) -> Option<u16> {
631    let backtrack_count: u16 = data.read_at(0).ok()?;
632    let count_pos = 2 + backtrack_count as usize * u16::RAW_BYTE_LEN;
633    let input_count: u16 = data.read_at(count_pos).ok()?;
634    if input_count <= 1 {
635        return None;
636    }
637    data.read_at(count_pos + 2).ok()
638}
639
640impl<'a> ContextRule<'a> for SequenceRule<'a> {
641    fn parse(&self) -> ParsedRule<'a> {
642        ParsedRule::from_rule_data(self.offset_data()).unwrap_or_default()
643    }
644
645    fn first_input(&self) -> Option<u16> {
646        plain_rule_first_input(&self.offset_data())
647    }
648}
649
650impl<'a> ContextRule<'a> for ClassSequenceRule<'a> {
651    fn parse(&self) -> ParsedRule<'a> {
652        ParsedRule::from_rule_data(self.offset_data()).unwrap_or_default()
653    }
654
655    fn first_input(&self) -> Option<u16> {
656        plain_rule_first_input(&self.offset_data())
657    }
658}
659
660impl<'a> ContextRule<'a> for ChainedSequenceRule<'a> {
661    fn parse(&self) -> ParsedRule<'a> {
662        ParsedRule::from_chain_rule_data(self.offset_data()).unwrap_or_default()
663    }
664
665    fn first_input(&self) -> Option<u16> {
666        chain_rule_first_input(&self.offset_data())
667    }
668}
669
670impl<'a> ContextRule<'a> for ChainedClassSequenceRule<'a> {
671    fn parse(&self) -> ParsedRule<'a> {
672        ParsedRule::from_chain_rule_data(self.offset_data()).unwrap_or_default()
673    }
674
675    fn first_input(&self) -> Option<u16> {
676        chain_rule_first_input(&self.offset_data())
677    }
678}
679
680fn apply_context_rules<'a, 'b, R: ContextRule<'a>>(
681    ctx: &mut hb_ot_apply_context_t,
682    rules: &'b ArrayOfOffsets<'a, R, Offset16>,
683    match_func: impl Fn(&mut GlyphInfo, u32) -> bool,
684) -> Option<()> {
685    // TODO: In HarfBuzz, the following condition makes NotoNastaliqUrdu
686    // faster. But our lookup code is slower, so NOT using this condition
687    // makes us faster.  Reconsider when lookup code is faster.
688    //if rules.len() <= 4 {
689    if false {
690        for rule in rules.iter().filter_map(|r| r.ok()) {
691            if rule.parse().apply(ctx, &match_func).is_some() {
692                return Some(());
693            }
694        }
695        return None;
696    }
697    // This version is optimized for speed by matching the first & second
698    // components of the rule here, instead of calling into the matching code.
699    //
700    // We use the iter_context instead of iter_input, to avoid skipping
701    // default-ignorables and such.
702    //
703    // Related: https://github.com/harfbuzz/harfbuzz/issues/4813
704    let mut skippy_iter = skipping_iterator_t::with_match_fn(ctx, true, Some(match_always));
705    skippy_iter.reset(skippy_iter.buffer.idx);
706    skippy_iter.set_glyph_data(0);
707    let mut unsafe_to = None;
708    let unsafe_to1;
709    let mut unsafe_to2 = 0;
710    let mut second = None;
711    let first = if skippy_iter.next(None) {
712        let g1 = skippy_iter.index();
713        if skippy_iter.may_skip(&skippy_iter.buffer.info[g1]) != may_skip_t::SKIP_NO {
714            // Can't use the fast path if eg. the next char is a default-ignorable
715            // or other skippable.
716            for rule in rules.iter().filter_map(|r| r.ok()) {
717                if rule.parse().apply(ctx, &match_func).is_some() {
718                    return Some(());
719                }
720            }
721            return None;
722        }
723        unsafe_to1 = skippy_iter.index() + 1;
724        g1
725    } else {
726        // Failed to match a next glyph. Only try applying rules that have no
727        // further impact.
728        for rule in rules
729            .iter()
730            .filter_map(|r| r.ok().map(|r| r.parse()))
731            .filter(|r| r.input.len() <= 1)
732        {
733            if rule.apply(ctx, &match_func).is_some() {
734                return Some(());
735            }
736        }
737        return None;
738    };
739    let matched = skippy_iter.next(None);
740    let g2 = skippy_iter.index();
741    if matched {
742        second = Some(g2);
743        unsafe_to2 = skippy_iter.index() + 1;
744        if skippy_iter.may_skip(&skippy_iter.buffer.info[g2]) != may_skip_t::SKIP_NO {
745            // Can't use the fast path if eg. the next char is a default-ignorable
746            // or other skippable.
747            for rule in rules.iter().filter_map(|r| r.ok()) {
748                if rule.parse().apply(ctx, &match_func).is_some() {
749                    return Some(());
750                }
751            }
752            return None;
753        }
754    }
755    let mut rules_iter = rules.iter().filter_map(|r| r.ok());
756    let mut rule_box = rules_iter.next().map(|r| r.parse());
757    while let Some(rule) = rule_box {
758        let inputs = rule.input;
759        let match_func2 = |info: &mut GlyphInfo, index| {
760            if let Some(value) = inputs.get(index as usize).map(|v| v.get()) {
761                match_func(info, u32::from(value))
762            } else {
763                false
764            }
765        };
766        if inputs.len() <= 1 || match_func2(&mut ctx.buffer.info[first], 0) {
767            if second.is_none()
768                || (inputs.len() <= 2 || match_func2(&mut ctx.buffer.info[second.unwrap()], 1))
769            {
770                if rule.apply(ctx, &match_func).is_some() {
771                    if let Some(unsafe_to) = unsafe_to {
772                        ctx.buffer
773                            .unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to));
774                    }
775                    return Some(());
776                }
777            } else {
778                unsafe_to = Some(unsafe_to2);
779            }
780            rule_box = rules_iter.next().map(|r| r.parse());
781        } else {
782            if unsafe_to.is_none() {
783                unsafe_to = Some(unsafe_to1);
784            }
785
786            // Skip ahead to next possible first glyph match.
787            let first_glyph_value = inputs.first().unwrap().get();
788            loop {
789                let Some(next_rule) = rules_iter.next() else {
790                    rule_box = None;
791                    break;
792                };
793                if next_rule.first_input() != Some(first_glyph_value) {
794                    rule_box = Some(next_rule.parse());
795                    break;
796                }
797            }
798        }
799    }
800    if let Some(unsafe_to) = unsafe_to {
801        ctx.buffer
802            .unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to));
803    }
804    None
805}
806
807fn apply_chain_with_sequences<
808    F1: Fn(&mut GlyphInfo, u32) -> bool,
809    F2: Fn(&mut GlyphInfo, u32) -> bool,
810    F3: Fn(&mut GlyphInfo, u32) -> bool,
811>(
812    ctx: &mut hb_ot_apply_context_t,
813    rule: &ParsedRule<'_>,
814    match_funcs: &(F1, F2, F3),
815) -> Option<()> {
816    let input = rule.input;
817    let f3 = |info: &mut GlyphInfo, index| {
818        input
819            .get(index as usize)
820            .is_some_and(|value| match_funcs.1(info, value.get() as u32))
821    };
822
823    let mut end_index = ctx.buffer.idx;
824    let mut match_end = 0;
825
826    let input_matches = match_input(ctx, input.len() as u16, f3, &mut match_end, None);
827
828    if input_matches {
829        end_index = match_end;
830    } else {
831        ctx.buffer
832            .unsafe_to_concat(Some(ctx.buffer.idx), Some(end_index));
833        return None;
834    }
835
836    let lookahead = rule.lookahead;
837    let f2 = |info: &mut GlyphInfo, index| {
838        lookahead
839            .get(index as usize)
840            .is_some_and(|value| match_funcs.2(info, value.get() as u32))
841    };
842
843    if !match_lookahead(ctx, lookahead.len() as u16, f2, match_end, &mut end_index) {
844        ctx.buffer
845            .unsafe_to_concat(Some(ctx.buffer.idx), Some(end_index));
846        return None;
847    }
848
849    let mut start_index = ctx.buffer.out_len;
850
851    let backtrack = rule.backtrack;
852    let f1 = |info: &mut GlyphInfo, index| {
853        backtrack
854            .get(index as usize)
855            .is_some_and(|value| match_funcs.0(info, value.get() as u32))
856    };
857
858    if !match_backtrack(ctx, backtrack.len() as u16, f1, &mut start_index) {
859        ctx.buffer
860            .unsafe_to_concat_from_outbuffer(Some(start_index), Some(end_index));
861        return None;
862    }
863
864    ctx.buffer
865        .unsafe_to_break_from_outbuffer(Some(start_index), Some(end_index));
866    apply_lookup(ctx, input.len(), match_end, rule.records);
867
868    Some(())
869}
870
871fn apply_chain_context_rules<
872    'a,
873    'b,
874    R: ContextRule<'a>,
875    F1: Fn(&mut GlyphInfo, u32) -> bool,
876    F2: Fn(&mut GlyphInfo, u32) -> bool,
877    F3: Fn(&mut GlyphInfo, u32) -> bool,
878>(
879    ctx: &mut hb_ot_apply_context_t,
880    rules: &'b ArrayOfOffsets<'a, R, Offset16>,
881    match_funcs: (F1, F2, F3),
882) -> Option<()> {
883    if rules.len() <= 4 {
884        for rule in rules.iter().filter_map(|r| r.ok()) {
885            if apply_chain_with_sequences(ctx, &rule.parse(), &match_funcs).is_some() {
886                return Some(());
887            }
888        }
889        return None;
890    }
891    // This version is optimized for speed by matching the first & second
892    // components of the rule here, instead of calling into the matching code.
893    //
894    // We use the iter_context instead of iter_input, to avoid skipping
895    // default-ignorables and such.
896    //
897    // Related: https://github.com/harfbuzz/harfbuzz/issues/4813
898    let mut skippy_iter = skipping_iterator_t::with_match_fn(ctx, true, Some(match_always));
899    skippy_iter.reset(skippy_iter.buffer.idx);
900    skippy_iter.set_glyph_data(0);
901    let mut unsafe_to = None;
902    let unsafe_to1;
903    let mut unsafe_to2 = 0;
904    let mut second = None;
905    let first = if skippy_iter.next(None) {
906        let g1 = skippy_iter.index();
907        if skippy_iter.may_skip(&skippy_iter.buffer.info[g1]) != may_skip_t::SKIP_NO {
908            // Can't use the fast path if eg. the next char is a default-ignorable
909            // or other skippable.
910            for rule in rules.iter().filter_map(|r| r.ok()) {
911                if apply_chain_with_sequences(ctx, &rule.parse(), &match_funcs).is_some() {
912                    return Some(());
913                }
914            }
915            return None;
916        }
917        unsafe_to1 = skippy_iter.index() + 1;
918        g1
919    } else {
920        // Failed to match a next glyph. Only try applying rules that have no
921        // further impact.
922        for rule in rules
923            .iter()
924            .filter_map(|r| r.ok().map(|r| r.parse()))
925            .filter(|r| r.input.len() <= 1 && r.lookahead.is_empty())
926        {
927            if apply_chain_with_sequences(ctx, &rule, &match_funcs).is_some() {
928                return Some(());
929            }
930        }
931        return None;
932    };
933    let matched = skippy_iter.next(None);
934    let g2 = skippy_iter.index();
935    if matched {
936        second = Some(g2);
937        unsafe_to2 = skippy_iter.index() + 1;
938        if skippy_iter.may_skip(&skippy_iter.buffer.info[g2]) != may_skip_t::SKIP_NO {
939            // Can't use the fast path if eg. the next char is a default-ignorable
940            // or other skippable.
941            for rule in rules.iter().filter_map(|r| r.ok()) {
942                if apply_chain_with_sequences(ctx, &rule.parse(), &match_funcs).is_some() {
943                    return Some(());
944                }
945            }
946            return None;
947        }
948    }
949    let mut rules_iter = rules.iter().filter_map(|r| r.ok());
950    let mut rule_box = rules_iter.next().map(|r| r.parse());
951    while let Some(rule) = rule_box {
952        let input = rule.input;
953        let lookahead = rule.lookahead;
954        let match_input = |info: &mut GlyphInfo, index: usize| {
955            input
956                .get(index)
957                .is_some_and(|v| match_funcs.1(info, u32::from(v.get())))
958        };
959        let match_lookahead = |info: &mut GlyphInfo, index: usize| {
960            lookahead
961                .get(index)
962                .is_some_and(|v| match_funcs.2(info, u32::from(v.get())))
963        };
964        let len_p1 = input.len() + 1;
965        let matched_first = if len_p1 > 1 {
966            match_input(&mut ctx.buffer.info[first], 0)
967        } else {
968            lookahead.is_empty() || match_lookahead(&mut ctx.buffer.info[first], 0)
969        };
970        if matched_first {
971            let matched_second = if let Some(second) = second {
972                if len_p1 > 2 {
973                    match_input(&mut ctx.buffer.info[second], 1)
974                } else {
975                    (lookahead.len() <= 2 - len_p1)
976                        || match_lookahead(&mut ctx.buffer.info[second], 2 - len_p1)
977                }
978            } else {
979                true
980            };
981            if matched_second {
982                if apply_chain_with_sequences(ctx, &rule, &match_funcs).is_some() {
983                    if let Some(unsafe_to) = unsafe_to {
984                        ctx.buffer
985                            .unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to));
986                    }
987                    return Some(());
988                }
989            } else {
990                unsafe_to = Some(unsafe_to2);
991            }
992
993            rule_box = rules_iter.next().map(|r| r.parse());
994        } else {
995            if unsafe_to.is_none() {
996                unsafe_to = Some(unsafe_to1);
997            }
998
999            if len_p1 > 1 {
1000                // Skip ahead to next possible first glyph match.
1001                let first_glyph_value = input.first().unwrap().get();
1002                loop {
1003                    let Some(next_rule) = rules_iter.next() else {
1004                        rule_box = None;
1005                        break;
1006                    };
1007                    if next_rule.first_input() != Some(first_glyph_value) {
1008                        rule_box = Some(next_rule.parse());
1009                        break;
1010                    }
1011                }
1012            } else {
1013                rule_box = rules_iter.next().map(|r| r.parse());
1014            }
1015        }
1016    }
1017    if let Some(unsafe_to) = unsafe_to {
1018        ctx.buffer
1019            .unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to));
1020    }
1021    None
1022}