rustybuzz/hb/
ot_layout_gsubgpos.rs

1//! Matching of glyph patterns.
2
3use ttf_parser::opentype_layout::*;
4use ttf_parser::{GlyphId, LazyArray16};
5
6use super::buffer::hb_glyph_info_t;
7use super::buffer::{hb_buffer_t, GlyphPropsFlags};
8use super::hb_font_t;
9use super::hb_mask_t;
10use super::ot_layout::LayoutTable;
11use super::ot_layout::*;
12use super::ot_layout_common::*;
13use super::unicode::hb_unicode_general_category_t;
14
15/// Value represents glyph id.
16pub fn match_glyph(glyph: GlyphId, value: u16) -> bool {
17    glyph == GlyphId(value)
18}
19
20pub fn match_input(
21    ctx: &mut hb_ot_apply_context_t,
22    input_len: u16,
23    match_func: &match_func_t,
24    end_position: &mut usize,
25    match_positions: &mut smallvec::SmallVec<[usize; 4]>,
26    p_total_component_count: Option<&mut u8>,
27) -> bool {
28    // This is perhaps the trickiest part of OpenType...  Remarks:
29    //
30    // - If all components of the ligature were marks, we call this a mark ligature.
31    //
32    // - If there is no GDEF, and the ligature is NOT a mark ligature, we categorize
33    //   it as a ligature glyph.
34    //
35    // - Ligatures cannot be formed across glyphs attached to different components
36    //   of previous ligatures.  Eg. the sequence is LAM,SHADDA,LAM,FATHA,HEH, and
37    //   LAM,LAM,HEH form a ligature, leaving SHADDA,FATHA next to eachother.
38    //   However, it would be wrong to ligate that SHADDA,FATHA sequence.
39    //   There are a couple of exceptions to this:
40    //
41    //   o If a ligature tries ligating with marks that belong to it itself, go ahead,
42    //     assuming that the font designer knows what they are doing (otherwise it can
43    //     break Indic stuff when a matra wants to ligate with a conjunct,
44    //
45    //   o If two marks want to ligate and they belong to different components of the
46    //     same ligature glyph, and said ligature glyph is to be ignored according to
47    //     mark-filtering rules, then allow.
48    //     https://github.com/harfbuzz/harfbuzz/issues/545
49
50    #[derive(PartialEq)]
51    enum Ligbase {
52        NotChecked,
53        MayNotSkip,
54        MaySkip,
55    }
56
57    let count = usize::from(input_len) + 1;
58    if count > MAX_CONTEXT_LENGTH {
59        return false;
60    }
61
62    if count > match_positions.len() {
63        match_positions.resize(count, 0);
64    }
65
66    let mut iter = skipping_iterator_t::new(ctx, ctx.buffer.idx, false);
67    iter.set_glyph_data(0);
68    iter.enable_matching(match_func);
69
70    let first = ctx.buffer.cur(0);
71    let first_lig_id = _hb_glyph_info_get_lig_id(first);
72    let first_lig_comp = _hb_glyph_info_get_lig_comp(first);
73    let mut total_component_count = 0;
74    let mut ligbase = Ligbase::NotChecked;
75
76    for position in &mut match_positions[1..count] {
77        let mut unsafe_to = 0;
78        if !iter.next(Some(&mut unsafe_to)) {
79            *end_position = unsafe_to;
80            return false;
81        }
82
83        *position = iter.index();
84
85        let this = ctx.buffer.info[iter.index()];
86        let this_lig_id = _hb_glyph_info_get_lig_id(&this);
87        let this_lig_comp = _hb_glyph_info_get_lig_comp(&this);
88
89        if first_lig_id != 0 && first_lig_comp != 0 {
90            // If first component was attached to a previous ligature component,
91            // all subsequent components should be attached to the same ligature
92            // component, otherwise we shouldn't ligate them...
93            if first_lig_id != this_lig_id || first_lig_comp != this_lig_comp {
94                // ...unless, we are attached to a base ligature and that base
95                // ligature is ignorable.
96                if ligbase == Ligbase::NotChecked {
97                    let out = ctx.buffer.out_info();
98                    let mut j = ctx.buffer.out_len;
99                    let mut found = false;
100                    while j > 0 && _hb_glyph_info_get_lig_id(&out[j - 1]) == first_lig_id {
101                        if _hb_glyph_info_get_lig_comp(&out[j - 1]) == 0 {
102                            j -= 1;
103                            found = true;
104                            break;
105                        }
106                        j -= 1;
107                    }
108
109                    ligbase = if found && iter.may_skip(&out[j]) == may_skip_t::SKIP_YES {
110                        Ligbase::MaySkip
111                    } else {
112                        Ligbase::MayNotSkip
113                    };
114                }
115
116                if ligbase == Ligbase::MayNotSkip {
117                    return false;
118                }
119            }
120        } else {
121            // If first component was NOT attached to a previous ligature component,
122            // all subsequent components should also NOT be attached to any ligature
123            // component, unless they are attached to the first component itself!
124            if this_lig_id != 0 && this_lig_comp != 0 && (this_lig_id != first_lig_id) {
125                return false;
126            }
127        }
128
129        total_component_count += _hb_glyph_info_get_lig_num_comps(&this);
130    }
131
132    *end_position = iter.index() + 1;
133
134    if let Some(p_total_component_count) = p_total_component_count {
135        total_component_count += _hb_glyph_info_get_lig_num_comps(first);
136        *p_total_component_count = total_component_count;
137    }
138
139    match_positions[0] = ctx.buffer.idx;
140
141    true
142}
143
144pub fn match_backtrack(
145    ctx: &mut hb_ot_apply_context_t,
146    backtrack_len: u16,
147    match_func: &match_func_t,
148    match_start: &mut usize,
149) -> bool {
150    let mut iter = skipping_iterator_t::new(ctx, ctx.buffer.backtrack_len(), true);
151    iter.set_glyph_data(0);
152    iter.enable_matching(match_func);
153
154    for _ in 0..backtrack_len {
155        let mut unsafe_from = 0;
156        if !iter.prev(Some(&mut unsafe_from)) {
157            *match_start = unsafe_from;
158            return false;
159        }
160    }
161
162    *match_start = iter.index();
163    true
164}
165
166pub fn match_lookahead(
167    ctx: &mut hb_ot_apply_context_t,
168    lookahead_len: u16,
169    match_func: &match_func_t,
170    start_index: usize,
171    end_index: &mut usize,
172) -> bool {
173    // Function should always be called with a non-zero starting index
174    // c.f. https://github.com/harfbuzz/rustybuzz/issues/142
175    assert!(start_index >= 1);
176    let mut iter = skipping_iterator_t::new(ctx, start_index - 1, true);
177    iter.set_glyph_data(0);
178    iter.enable_matching(match_func);
179
180    for _ in 0..lookahead_len {
181        let mut unsafe_to = 0;
182        if !iter.next(Some(&mut unsafe_to)) {
183            *end_index = unsafe_to;
184            return false;
185        }
186    }
187
188    *end_index = iter.index() + 1;
189    true
190}
191
192pub type match_func_t<'a> = dyn Fn(GlyphId, u16) -> bool + 'a;
193
194// In harfbuzz, skipping iterator works quite differently than it works here. In harfbuzz,
195// hb_ot_apply_context contains a skipping iterator that itself contains another reference to
196// the apply_context, meaning that we have a circular reference. Due to ownership rules in Rust,
197// we cannot copy this approach. Because of this, we basically create a new skipping iterator
198// when needed, and we do not have the `reset` and `init` methods that exist in harfbuzz. This makes
199// backporting related changes very hard, but it seems unavoidable, unfortunately.
200pub struct skipping_iterator_t<'a, 'b> {
201    ctx: &'a hb_ot_apply_context_t<'a, 'b>,
202    lookup_props: u32,
203    ignore_zwnj: bool,
204    ignore_zwj: bool,
205    ignore_hidden: bool,
206    mask: hb_mask_t,
207    syllable: u8,
208    matching: Option<&'a match_func_t<'a>>,
209    buf_len: usize,
210    glyph_data: u16,
211    pub(crate) buf_idx: usize,
212}
213
214#[derive(PartialEq, Eq, Copy, Clone)]
215pub enum match_t {
216    MATCH,
217    NOT_MATCH,
218    SKIP,
219}
220
221#[derive(PartialEq, Eq, Copy, Clone)]
222enum may_match_t {
223    MATCH_NO,
224    MATCH_YES,
225    MATCH_MAYBE,
226}
227
228#[derive(PartialEq, Eq, Copy, Clone)]
229enum may_skip_t {
230    SKIP_NO,
231    SKIP_YES,
232    SKIP_MAYBE,
233}
234
235impl<'a, 'b> skipping_iterator_t<'a, 'b> {
236    pub fn new(
237        ctx: &'a hb_ot_apply_context_t<'a, 'b>,
238        start_buf_index: usize,
239        context_match: bool,
240    ) -> Self {
241        skipping_iterator_t {
242            ctx,
243            lookup_props: ctx.lookup_props,
244            // Ignore ZWNJ if we are matching GPOS, or matching GSUB context and asked to.
245            ignore_zwnj: ctx.table_index == TableIndex::GPOS || (context_match && ctx.auto_zwnj),
246            // Ignore ZWJ if we are matching context, or asked to.
247            ignore_zwj: context_match || ctx.auto_zwj,
248            // Ignore hidden glyphs (like CGJ) during GPOS.
249            ignore_hidden: ctx.table_index == TableIndex::GPOS,
250            mask: if context_match {
251                u32::MAX
252            } else {
253                ctx.lookup_mask()
254            },
255            syllable: if ctx.buffer.idx == start_buf_index && ctx.per_syllable {
256                ctx.buffer.cur(0).syllable()
257            } else {
258                0
259            },
260            glyph_data: 0,
261            matching: None,
262            buf_len: ctx.buffer.len,
263            buf_idx: start_buf_index,
264        }
265    }
266
267    pub fn set_glyph_data(&mut self, glyph_data: u16) {
268        self.glyph_data = glyph_data
269    }
270
271    fn advance_glyph_data(&mut self) {
272        self.glyph_data += 1;
273    }
274
275    pub fn set_lookup_props(&mut self, lookup_props: u32) {
276        self.lookup_props = lookup_props;
277    }
278
279    pub fn enable_matching(&mut self, func: &'a match_func_t<'a>) {
280        self.matching = Some(func);
281    }
282
283    pub fn index(&self) -> usize {
284        self.buf_idx
285    }
286
287    pub fn next(&mut self, unsafe_to: Option<&mut usize>) -> bool {
288        let stop = self.buf_len as i32 - 1;
289
290        while (self.buf_idx as i32) < stop {
291            self.buf_idx += 1;
292            let info = &self.ctx.buffer.info[self.buf_idx];
293
294            match self.match_(info) {
295                match_t::MATCH => {
296                    self.advance_glyph_data();
297                    return true;
298                }
299                match_t::NOT_MATCH => {
300                    if let Some(unsafe_to) = unsafe_to {
301                        *unsafe_to = self.buf_idx + 1;
302                    }
303
304                    return false;
305                }
306                match_t::SKIP => continue,
307            }
308        }
309
310        if let Some(unsafe_to) = unsafe_to {
311            *unsafe_to = self.buf_idx + 1;
312        }
313
314        false
315    }
316
317    pub fn prev(&mut self, unsafe_from: Option<&mut usize>) -> bool {
318        let stop: usize = 0;
319
320        while self.buf_idx > stop {
321            self.buf_idx -= 1;
322            let info = &self.ctx.buffer.out_info()[self.buf_idx];
323
324            match self.match_(info) {
325                match_t::MATCH => {
326                    self.advance_glyph_data();
327                    return true;
328                }
329                match_t::NOT_MATCH => {
330                    if let Some(unsafe_from) = unsafe_from {
331                        *unsafe_from = self.buf_idx.max(1) - 1;
332                    }
333
334                    return false;
335                }
336                match_t::SKIP => {
337                    continue;
338                }
339            }
340        }
341
342        if let Some(unsafe_from) = unsafe_from {
343            *unsafe_from = 0;
344        }
345
346        false
347    }
348
349    pub fn match_(&self, info: &hb_glyph_info_t) -> match_t {
350        let skip = self.may_skip(info);
351
352        if skip == may_skip_t::SKIP_YES {
353            return match_t::SKIP;
354        }
355
356        let _match = self.may_match(info);
357
358        if _match == may_match_t::MATCH_YES
359            || (_match == may_match_t::MATCH_MAYBE && skip == may_skip_t::SKIP_NO)
360        {
361            return match_t::MATCH;
362        }
363
364        if skip == may_skip_t::SKIP_NO {
365            return match_t::NOT_MATCH;
366        }
367
368        match_t::SKIP
369    }
370
371    fn may_match(&self, info: &hb_glyph_info_t) -> may_match_t {
372        if (info.mask & self.mask) == 0 || (self.syllable != 0 && self.syllable != info.syllable())
373        {
374            return may_match_t::MATCH_NO;
375        }
376
377        if let Some(match_func) = self.matching {
378            return if match_func(info.as_glyph(), self.glyph_data) {
379                may_match_t::MATCH_YES
380            } else {
381                may_match_t::MATCH_NO
382            };
383        }
384
385        may_match_t::MATCH_MAYBE
386    }
387
388    fn may_skip(&self, info: &hb_glyph_info_t) -> may_skip_t {
389        if !self.ctx.check_glyph_property(info, self.lookup_props) {
390            return may_skip_t::SKIP_YES;
391        }
392
393        if _hb_glyph_info_is_default_ignorable(info)
394            && (self.ignore_zwnj || !_hb_glyph_info_is_zwnj(info))
395            && (self.ignore_zwj || !_hb_glyph_info_is_zwj(info))
396            && (self.ignore_hidden || !_hb_glyph_info_is_hidden(info))
397        {
398            return may_skip_t::SKIP_MAYBE;
399        }
400
401        may_skip_t::SKIP_NO
402    }
403}
404
405impl WouldApply for ContextLookup<'_> {
406    fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
407        let glyph = ctx.glyphs[0];
408        match *self {
409            Self::Format1 { coverage, sets } => coverage
410                .get(glyph)
411                .and_then(|index| sets.get(index))
412                .map_or(false, |set| set.would_apply(ctx, &match_glyph)),
413            Self::Format2 { classes, sets, .. } => {
414                let class = classes.get(glyph);
415                sets.get(class)
416                    .map_or(false, |set| set.would_apply(ctx, &match_class(classes)))
417            }
418            Self::Format3 { coverages, .. } => {
419                ctx.glyphs.len() == usize::from(coverages.len()) + 1
420                    && coverages
421                        .into_iter()
422                        .enumerate()
423                        .all(|(i, coverage)| coverage.get(ctx.glyphs[i + 1]).is_some())
424            }
425        }
426    }
427}
428
429impl Apply for ContextLookup<'_> {
430    fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
431        let glyph = ctx.buffer.cur(0).as_glyph();
432        match *self {
433            Self::Format1 { coverage, sets } => {
434                coverage.get(glyph)?;
435                let set = coverage.get(glyph).and_then(|index| sets.get(index))?;
436                set.apply(ctx, &match_glyph)
437            }
438            Self::Format2 {
439                coverage,
440                classes,
441                sets,
442            } => {
443                coverage.get(glyph)?;
444                let class = classes.get(glyph);
445                let set = sets.get(class)?;
446                set.apply(ctx, &match_class(classes))
447            }
448            Self::Format3 {
449                coverage,
450                coverages,
451                lookups,
452            } => {
453                coverage.get(glyph)?;
454                let coverages_len = coverages.len();
455
456                let match_func = |glyph, index| {
457                    let coverage = coverages.get(index).unwrap();
458                    coverage.get(glyph).is_some()
459                };
460
461                let mut match_end = 0;
462                let mut match_positions = smallvec::SmallVec::from_elem(0, 4);
463
464                if match_input(
465                    ctx,
466                    coverages_len,
467                    &match_func,
468                    &mut match_end,
469                    &mut match_positions,
470                    None,
471                ) {
472                    ctx.buffer
473                        .unsafe_to_break(Some(ctx.buffer.idx), Some(match_end));
474                    apply_lookup(
475                        ctx,
476                        usize::from(coverages_len),
477                        &mut match_positions,
478                        match_end,
479                        lookups,
480                    );
481                    Some(())
482                } else {
483                    ctx.buffer
484                        .unsafe_to_concat(Some(ctx.buffer.idx), Some(match_end));
485                    None
486                }
487            }
488        }
489    }
490}
491
492trait SequenceRuleSetExt {
493    fn would_apply(&self, ctx: &WouldApplyContext, match_func: &match_func_t) -> bool;
494    fn apply(&self, ctx: &mut hb_ot_apply_context_t, match_func: &match_func_t) -> Option<()>;
495}
496
497impl SequenceRuleSetExt for SequenceRuleSet<'_> {
498    fn would_apply(&self, ctx: &WouldApplyContext, match_func: &match_func_t) -> bool {
499        self.into_iter()
500            .any(|rule| rule.would_apply(ctx, match_func))
501    }
502
503    fn apply(&self, ctx: &mut hb_ot_apply_context_t, match_func: &match_func_t) -> Option<()> {
504        if self
505            .into_iter()
506            .any(|rule| rule.apply(ctx, match_func).is_some())
507        {
508            Some(())
509        } else {
510            None
511        }
512    }
513}
514
515trait SequenceRuleExt {
516    fn would_apply(&self, ctx: &WouldApplyContext, match_func: &match_func_t) -> bool;
517    fn apply(&self, ctx: &mut hb_ot_apply_context_t, match_func: &match_func_t) -> Option<()>;
518}
519
520impl SequenceRuleExt for SequenceRule<'_> {
521    fn would_apply(&self, ctx: &WouldApplyContext, match_func: &match_func_t) -> bool {
522        ctx.glyphs.len() == usize::from(self.input.len()) + 1
523            && self
524                .input
525                .into_iter()
526                .enumerate()
527                .all(|(i, value)| match_func(ctx.glyphs[i + 1], value))
528    }
529
530    fn apply(&self, ctx: &mut hb_ot_apply_context_t, match_func: &match_func_t) -> Option<()> {
531        apply_context(ctx, self.input, match_func, self.lookups)
532
533        // TODO: Port optimized version from https://github.com/harfbuzz/harfbuzz/commit/645fabd10
534    }
535}
536
537impl WouldApply for ChainedContextLookup<'_> {
538    fn would_apply(&self, ctx: &WouldApplyContext) -> bool {
539        let glyph_id = ctx.glyphs[0];
540        match *self {
541            Self::Format1 { coverage, sets } => coverage
542                .get(glyph_id)
543                .and_then(|index| sets.get(index))
544                .map_or(false, |set| set.would_apply(ctx, &match_glyph)),
545            Self::Format2 {
546                input_classes,
547                sets,
548                ..
549            } => {
550                let class = input_classes.get(glyph_id);
551                sets.get(class).map_or(false, |set| {
552                    set.would_apply(ctx, &match_class(input_classes))
553                })
554            }
555            Self::Format3 {
556                backtrack_coverages,
557                input_coverages,
558                lookahead_coverages,
559                ..
560            } => {
561                (!ctx.zero_context
562                    || (backtrack_coverages.is_empty() && lookahead_coverages.is_empty()))
563                    && (ctx.glyphs.len() == usize::from(input_coverages.len()) + 1
564                        && input_coverages
565                            .into_iter()
566                            .enumerate()
567                            .all(|(i, coverage)| coverage.contains(ctx.glyphs[i + 1])))
568            }
569        }
570    }
571}
572
573impl Apply for ChainedContextLookup<'_> {
574    fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
575        let glyph = ctx.buffer.cur(0).as_glyph();
576        match *self {
577            Self::Format1 { coverage, sets } => {
578                let index = coverage.get(glyph)?;
579                let set = sets.get(index)?;
580                set.apply(ctx, [&match_glyph, &match_glyph, &match_glyph])
581            }
582            Self::Format2 {
583                coverage,
584                backtrack_classes,
585                input_classes,
586                lookahead_classes,
587                sets,
588            } => {
589                coverage.get(glyph)?;
590                let class = input_classes.get(glyph);
591                let set = sets.get(class)?;
592                set.apply(
593                    ctx,
594                    [
595                        &match_class(backtrack_classes),
596                        &match_class(input_classes),
597                        &match_class(lookahead_classes),
598                    ],
599                )
600            }
601            Self::Format3 {
602                coverage,
603                backtrack_coverages,
604                input_coverages,
605                lookahead_coverages,
606                lookups,
607            } => {
608                coverage.get(glyph)?;
609
610                let back = |glyph, index| {
611                    let coverage = backtrack_coverages.get(index).unwrap();
612                    coverage.contains(glyph)
613                };
614
615                let ahead = |glyph, index| {
616                    let coverage = lookahead_coverages.get(index).unwrap();
617                    coverage.contains(glyph)
618                };
619
620                let input = |glyph, index| {
621                    let coverage = input_coverages.get(index).unwrap();
622                    coverage.contains(glyph)
623                };
624
625                let mut end_index = ctx.buffer.idx;
626                let mut match_end = 0;
627                let mut match_positions = smallvec::SmallVec::from_elem(0, 4);
628
629                let input_matches = match_input(
630                    ctx,
631                    input_coverages.len(),
632                    &input,
633                    &mut match_end,
634                    &mut match_positions,
635                    None,
636                );
637
638                if input_matches {
639                    end_index = match_end;
640                }
641
642                if !(input_matches
643                    && match_lookahead(
644                        ctx,
645                        lookahead_coverages.len(),
646                        &ahead,
647                        match_end,
648                        &mut end_index,
649                    ))
650                {
651                    ctx.buffer
652                        .unsafe_to_concat(Some(ctx.buffer.idx), Some(end_index));
653                    return None;
654                }
655
656                let mut start_index = ctx.buffer.out_len;
657
658                if !match_backtrack(ctx, backtrack_coverages.len(), &back, &mut start_index) {
659                    ctx.buffer
660                        .unsafe_to_concat_from_outbuffer(Some(start_index), Some(end_index));
661                    return None;
662                }
663
664                ctx.buffer
665                    .unsafe_to_break_from_outbuffer(Some(start_index), Some(end_index));
666                apply_lookup(
667                    ctx,
668                    usize::from(input_coverages.len()),
669                    &mut match_positions,
670                    match_end,
671                    lookups,
672                );
673
674                Some(())
675            }
676        }
677    }
678}
679
680trait ChainRuleSetExt {
681    fn would_apply(&self, ctx: &WouldApplyContext, match_func: &match_func_t) -> bool;
682    fn apply(&self, ctx: &mut hb_ot_apply_context_t, match_funcs: [&match_func_t; 3])
683        -> Option<()>;
684}
685
686impl ChainRuleSetExt for ChainedSequenceRuleSet<'_> {
687    fn would_apply(&self, ctx: &WouldApplyContext, match_func: &match_func_t) -> bool {
688        self.into_iter()
689            .any(|rule| rule.would_apply(ctx, match_func))
690    }
691
692    fn apply(
693        &self,
694        ctx: &mut hb_ot_apply_context_t,
695        match_funcs: [&match_func_t; 3],
696    ) -> Option<()> {
697        if self
698            .into_iter()
699            .any(|rule| rule.apply(ctx, match_funcs).is_some())
700        {
701            Some(())
702        } else {
703            None
704        }
705
706        // TODO: Port optimized version from https://github.com/harfbuzz/harfbuzz/commit/77080f86f
707    }
708}
709
710trait ChainRuleExt {
711    fn would_apply(&self, ctx: &WouldApplyContext, match_func: &match_func_t) -> bool;
712    fn apply(&self, ctx: &mut hb_ot_apply_context_t, match_funcs: [&match_func_t; 3])
713        -> Option<()>;
714}
715
716impl ChainRuleExt for ChainedSequenceRule<'_> {
717    fn would_apply(&self, ctx: &WouldApplyContext, match_func: &match_func_t) -> bool {
718        (!ctx.zero_context || (self.backtrack.is_empty() && self.lookahead.is_empty()))
719            && (ctx.glyphs.len() == usize::from(self.input.len()) + 1
720                && self
721                    .input
722                    .into_iter()
723                    .enumerate()
724                    .all(|(i, value)| match_func(ctx.glyphs[i + 1], value)))
725    }
726
727    fn apply(
728        &self,
729        ctx: &mut hb_ot_apply_context_t,
730        match_funcs: [&match_func_t; 3],
731    ) -> Option<()> {
732        apply_chain_context(
733            ctx,
734            self.backtrack,
735            self.input,
736            self.lookahead,
737            match_funcs,
738            self.lookups,
739        )
740    }
741}
742
743fn apply_context(
744    ctx: &mut hb_ot_apply_context_t,
745    input: LazyArray16<u16>,
746    match_func: &match_func_t,
747    lookups: LazyArray16<SequenceLookupRecord>,
748) -> Option<()> {
749    let match_func = |glyph, index| {
750        let value = input.get(index).unwrap();
751        match_func(glyph, value)
752    };
753
754    let mut match_end = 0;
755    let mut match_positions = smallvec::SmallVec::from_elem(0, 4);
756
757    if match_input(
758        ctx,
759        input.len(),
760        &match_func,
761        &mut match_end,
762        &mut match_positions,
763        None,
764    ) {
765        ctx.buffer
766            .unsafe_to_break(Some(ctx.buffer.idx), Some(match_end));
767        apply_lookup(
768            ctx,
769            usize::from(input.len()),
770            &mut match_positions,
771            match_end,
772            lookups,
773        );
774        return Some(());
775    }
776
777    None
778}
779
780fn apply_chain_context(
781    ctx: &mut hb_ot_apply_context_t,
782    backtrack: LazyArray16<u16>,
783    input: LazyArray16<u16>,
784    lookahead: LazyArray16<u16>,
785    match_funcs: [&match_func_t; 3],
786    lookups: LazyArray16<SequenceLookupRecord>,
787) -> Option<()> {
788    // NOTE: Whenever something in this method changes, we also need to
789    // change it in the `apply` implementation for ChainedContextLookup.
790    let f1 = |glyph, index| {
791        let value = backtrack.get(index).unwrap();
792        match_funcs[0](glyph, value)
793    };
794
795    let f2 = |glyph, index| {
796        let value = lookahead.get(index).unwrap();
797        match_funcs[2](glyph, value)
798    };
799
800    let f3 = |glyph, index| {
801        let value = input.get(index).unwrap();
802        match_funcs[1](glyph, value)
803    };
804
805    let mut end_index = ctx.buffer.idx;
806    let mut match_end = 0;
807    let mut match_positions = smallvec::SmallVec::from_elem(0, 4);
808
809    let input_matches = match_input(
810        ctx,
811        input.len(),
812        &f3,
813        &mut match_end,
814        &mut match_positions,
815        None,
816    );
817
818    if input_matches {
819        end_index = match_end;
820    }
821
822    if !(input_matches && match_lookahead(ctx, lookahead.len(), &f2, match_end, &mut end_index)) {
823        ctx.buffer
824            .unsafe_to_concat(Some(ctx.buffer.idx), Some(end_index));
825        return None;
826    }
827
828    let mut start_index = ctx.buffer.out_len;
829
830    if !match_backtrack(ctx, backtrack.len(), &f1, &mut start_index) {
831        ctx.buffer
832            .unsafe_to_concat_from_outbuffer(Some(start_index), Some(end_index));
833        return None;
834    }
835
836    ctx.buffer
837        .unsafe_to_break_from_outbuffer(Some(start_index), Some(end_index));
838    apply_lookup(
839        ctx,
840        usize::from(input.len()),
841        &mut match_positions,
842        match_end,
843        lookups,
844    );
845
846    Some(())
847}
848
849fn apply_lookup(
850    ctx: &mut hb_ot_apply_context_t,
851    input_len: usize,
852    match_positions: &mut smallvec::SmallVec<[usize; 4]>,
853    match_end: usize,
854    lookups: LazyArray16<SequenceLookupRecord>,
855) {
856    let mut count = input_len + 1;
857
858    if count > match_positions.len() {
859        match_positions.resize(count, 0);
860    }
861
862    // All positions are distance from beginning of *output* buffer.
863    // Adjust.
864    let mut end: isize = {
865        let backtrack_len = ctx.buffer.backtrack_len();
866        let delta = backtrack_len as isize - ctx.buffer.idx as isize;
867
868        // Convert positions to new indexing.
869        for j in 0..count {
870            match_positions[j] = (match_positions[j] as isize + delta) as _;
871        }
872
873        backtrack_len as isize + match_end as isize - ctx.buffer.idx as isize
874    };
875
876    for record in lookups {
877        if !ctx.buffer.successful {
878            break;
879        }
880
881        let idx = usize::from(record.sequence_index);
882        if idx >= count {
883            continue;
884        }
885
886        let orig_len = ctx.buffer.backtrack_len() + ctx.buffer.lookahead_len();
887
888        // This can happen if earlier recursed lookups deleted many entries.
889        if match_positions[idx] >= orig_len {
890            continue;
891        }
892
893        if !ctx.buffer.move_to(match_positions[idx]) {
894            break;
895        }
896
897        if ctx.buffer.max_ops <= 0 {
898            break;
899        }
900
901        if ctx.recurse(record.lookup_list_index).is_none() {
902            continue;
903        }
904
905        let new_len = ctx.buffer.backtrack_len() + ctx.buffer.lookahead_len();
906        let mut delta = new_len as isize - orig_len as isize;
907        if delta == 0 {
908            continue;
909        }
910
911        // Recursed lookup changed buffer len.  Adjust.
912        //
913        // TODO:
914        //
915        // Right now, if buffer length increased by n, we assume n new glyphs
916        // were added right after the current position, and if buffer length
917        // was decreased by n, we assume n match positions after the current
918        // one where removed.  The former (buffer length increased) case is
919        // fine, but the decrease case can be improved in at least two ways,
920        // both of which are significant:
921        //
922        //   - If recursed-to lookup is MultipleSubst and buffer length
923        //     decreased, then it's current match position that was deleted,
924        //     NOT the one after it.
925        //
926        //   - If buffer length was decreased by n, it does not necessarily
927        //     mean that n match positions where removed, as there recursed-to
928        //     lookup might had a different LookupFlag.  Here's a constructed
929        //     case of that:
930        //     https://github.com/harfbuzz/harfbuzz/discussions/3538
931        //
932        // It should be possible to construct tests for both of these cases.
933
934        end += delta;
935        if end < match_positions[idx] as isize {
936            // End might end up being smaller than match_positions[idx] if the recursed
937            // lookup ended up removing many items.
938            // Just never rewind end beyond start of current position, since that is
939            // not possible in the recursed lookup.  Also adjust delta as such.
940            //
941            // https://bugs.chromium.org/p/chromium/issues/detail?id=659496
942            // https://github.com/harfbuzz/harfbuzz/issues/1611
943            //
944            delta += match_positions[idx] as isize - end;
945            end = match_positions[idx] as isize;
946        }
947
948        // next now is the position after the recursed lookup.
949        let mut next = idx + 1;
950
951        if delta > 0 {
952            if delta as usize + count > MAX_CONTEXT_LENGTH {
953                break;
954            }
955
956            if delta as usize + count > match_positions.len() {
957                let inner_max = (core::cmp::max(4, match_positions.len()) as f32 * 1.5) as usize;
958                match_positions.resize(core::cmp::max(delta as usize + count, inner_max), 0);
959            }
960        } else {
961            // NOTE: delta is non-positive.
962            delta = delta.max(next as isize - count as isize);
963            next = (next as isize - delta) as _;
964        }
965
966        // Shift!
967        match_positions.copy_within(next..count, (next as isize + delta) as _);
968        next = (next as isize + delta) as _;
969        count = (count as isize + delta) as _;
970
971        // Fill in new entries.
972        for j in idx + 1..next {
973            match_positions[j] = match_positions[j - 1] + 1;
974        }
975
976        // And fixup the rest.
977        while next < count {
978            match_positions[next] = (match_positions[next] as isize + delta) as _;
979            next += 1;
980        }
981    }
982
983    ctx.buffer.move_to(end.try_into().unwrap());
984}
985
986/// Value represents glyph class.
987fn match_class(class_def: ClassDefinition<'_>) -> impl Fn(GlyphId, u16) -> bool + '_ {
988    move |glyph, value| class_def.get(glyph) == value
989}
990
991/// Find out whether a lookup would be applied.
992pub trait WouldApply {
993    /// Whether the lookup would be applied.
994    fn would_apply(&self, ctx: &WouldApplyContext) -> bool;
995}
996
997/// Apply a lookup.
998pub trait Apply {
999    /// Apply the lookup.
1000    fn apply(&self, ctx: &mut OT::hb_ot_apply_context_t) -> Option<()>;
1001}
1002
1003pub struct WouldApplyContext<'a> {
1004    pub glyphs: &'a [GlyphId],
1005    pub zero_context: bool,
1006}
1007
1008pub mod OT {
1009    use super::*;
1010    use crate::hb::set_digest::{hb_set_digest_ext, hb_set_digest_t};
1011
1012    pub struct hb_ot_apply_context_t<'a, 'b> {
1013        pub table_index: TableIndex,
1014        pub face: &'a hb_font_t<'b>,
1015        pub buffer: &'a mut hb_buffer_t,
1016        lookup_mask: hb_mask_t,
1017        pub per_syllable: bool,
1018        pub lookup_index: LookupIndex,
1019        pub lookup_props: u32,
1020        pub nesting_level_left: usize,
1021        pub auto_zwnj: bool,
1022        pub auto_zwj: bool,
1023        pub random: bool,
1024        pub random_state: u32,
1025        pub last_base: i32,
1026        pub last_base_until: u32,
1027        pub digest: hb_set_digest_t,
1028    }
1029
1030    impl<'a, 'b> hb_ot_apply_context_t<'a, 'b> {
1031        pub fn new(
1032            table_index: TableIndex,
1033            face: &'a hb_font_t<'b>,
1034            buffer: &'a mut hb_buffer_t,
1035        ) -> Self {
1036            let buffer_digest = buffer.digest();
1037            Self {
1038                table_index,
1039                face,
1040                buffer,
1041                lookup_mask: 1,
1042                per_syllable: false,
1043                lookup_index: u16::MAX,
1044                lookup_props: 0,
1045                nesting_level_left: MAX_NESTING_LEVEL,
1046                auto_zwnj: true,
1047                auto_zwj: true,
1048                random: false,
1049                random_state: 1,
1050                last_base: -1,
1051                last_base_until: 0,
1052                digest: buffer_digest,
1053            }
1054        }
1055
1056        pub fn random_number(&mut self) -> u32 {
1057            // http://www.cplusplus.com/reference/random/minstd_rand/
1058            self.random_state = self.random_state.wrapping_mul(48271) % 2147483647;
1059            self.random_state
1060        }
1061
1062        pub fn set_lookup_mask(&mut self, mask: hb_mask_t) {
1063            self.lookup_mask = mask;
1064            self.last_base = -1;
1065            self.last_base_until = 0;
1066        }
1067
1068        pub fn lookup_mask(&self) -> hb_mask_t {
1069            self.lookup_mask
1070        }
1071
1072        pub fn recurse(&mut self, sub_lookup_index: LookupIndex) -> Option<()> {
1073            if self.nesting_level_left == 0 {
1074                self.buffer.shaping_failed = true;
1075                return None;
1076            }
1077
1078            self.buffer.max_ops -= 1;
1079            if self.buffer.max_ops < 0 {
1080                self.buffer.shaping_failed = true;
1081                return None;
1082            }
1083
1084            self.nesting_level_left -= 1;
1085            let saved_props = self.lookup_props;
1086            let saved_index = self.lookup_index;
1087
1088            self.lookup_index = sub_lookup_index;
1089            let applied = match self.table_index {
1090                TableIndex::GSUB => self
1091                    .face
1092                    .gsub
1093                    .as_ref()
1094                    .and_then(|table| table.get_lookup(sub_lookup_index))
1095                    .and_then(|lookup| {
1096                        self.lookup_props = lookup.props();
1097                        lookup.apply(self)
1098                    }),
1099                TableIndex::GPOS => self
1100                    .face
1101                    .gpos
1102                    .as_ref()
1103                    .and_then(|table| table.get_lookup(sub_lookup_index))
1104                    .and_then(|lookup| {
1105                        self.lookup_props = lookup.props();
1106                        lookup.apply(self)
1107                    }),
1108            };
1109
1110            self.lookup_props = saved_props;
1111            self.lookup_index = saved_index;
1112            self.nesting_level_left += 1;
1113            applied
1114        }
1115
1116        pub fn check_glyph_property(&self, info: &hb_glyph_info_t, match_props: u32) -> bool {
1117            let glyph_props = info.glyph_props();
1118
1119            // Lookup flags are lower 16-bit of match props.
1120            let lookup_flags = match_props as u16;
1121
1122            // Not covered, if, for example, glyph class is ligature and
1123            // match_props includes LookupFlags::IgnoreLigatures
1124            if glyph_props & lookup_flags & lookup_flags::IGNORE_FLAGS != 0 {
1125                return false;
1126            }
1127
1128            if glyph_props & GlyphPropsFlags::MARK.bits() != 0 {
1129                // If using mark filtering sets, the high short of
1130                // match_props has the set index.
1131                if lookup_flags & lookup_flags::USE_MARK_FILTERING_SET != 0 {
1132                    let set_index = (match_props >> 16) as u16;
1133                    // TODO: harfbuzz uses a digest here to speed things up if HB_NO_GDEF_CACHE
1134                    // is enabled. But a bit harder to implement for us since it's taken care of by
1135                    // ttf-parser
1136                    if let Some(table) = self.face.tables().gdef {
1137                        return table.is_mark_glyph(info.as_glyph(), Some(set_index));
1138                    } else {
1139                        return false;
1140                    }
1141                }
1142
1143                // The second byte of match_props has the meaning
1144                // "ignore marks of attachment type different than
1145                // the attachment type specified."
1146                if lookup_flags & lookup_flags::MARK_ATTACHMENT_TYPE_MASK != 0 {
1147                    return (lookup_flags & lookup_flags::MARK_ATTACHMENT_TYPE_MASK)
1148                        == (glyph_props & lookup_flags::MARK_ATTACHMENT_TYPE_MASK);
1149                }
1150            }
1151
1152            true
1153        }
1154
1155        fn set_glyph_class(
1156            &mut self,
1157            glyph_id: GlyphId,
1158            class_guess: GlyphPropsFlags,
1159            ligature: bool,
1160            component: bool,
1161        ) {
1162            self.digest.add(glyph_id);
1163
1164            let cur = self.buffer.cur_mut(0);
1165            let mut props = cur.glyph_props();
1166
1167            props |= GlyphPropsFlags::SUBSTITUTED.bits();
1168
1169            if ligature {
1170                props |= GlyphPropsFlags::LIGATED.bits();
1171                // In the only place that the MULTIPLIED bit is used, Uniscribe
1172                // seems to only care about the "last" transformation between
1173                // Ligature and Multiple substitutions.  Ie. if you ligate, expand,
1174                // and ligate again, it forgives the multiplication and acts as
1175                // if only ligation happened.  As such, clear MULTIPLIED bit.
1176                props &= !GlyphPropsFlags::MULTIPLIED.bits();
1177            }
1178
1179            if component {
1180                props |= GlyphPropsFlags::MULTIPLIED.bits();
1181            }
1182
1183            let has_glyph_classes = self
1184                .face
1185                .tables()
1186                .gdef
1187                .map_or(false, |table| table.has_glyph_classes());
1188
1189            if has_glyph_classes {
1190                props &= GlyphPropsFlags::PRESERVE.bits();
1191                cur.set_glyph_props(props | self.face.glyph_props(glyph_id));
1192            } else if !class_guess.is_empty() {
1193                props &= GlyphPropsFlags::PRESERVE.bits();
1194                cur.set_glyph_props(props | class_guess.bits());
1195            } else {
1196                cur.set_glyph_props(props);
1197            }
1198        }
1199
1200        pub fn replace_glyph(&mut self, glyph_id: GlyphId) {
1201            self.set_glyph_class(glyph_id, GlyphPropsFlags::empty(), false, false);
1202            self.buffer.replace_glyph(u32::from(glyph_id.0));
1203        }
1204
1205        pub fn replace_glyph_inplace(&mut self, glyph_id: GlyphId) {
1206            self.set_glyph_class(glyph_id, GlyphPropsFlags::empty(), false, false);
1207            self.buffer.cur_mut(0).glyph_id = u32::from(glyph_id.0);
1208        }
1209
1210        pub fn replace_glyph_with_ligature(
1211            &mut self,
1212            glyph_id: GlyphId,
1213            class_guess: GlyphPropsFlags,
1214        ) {
1215            self.set_glyph_class(glyph_id, class_guess, true, false);
1216            self.buffer.replace_glyph(u32::from(glyph_id.0));
1217        }
1218
1219        pub fn output_glyph_for_component(
1220            &mut self,
1221            glyph_id: GlyphId,
1222            class_guess: GlyphPropsFlags,
1223        ) {
1224            self.set_glyph_class(glyph_id, class_guess, false, true);
1225            self.buffer.output_glyph(u32::from(glyph_id.0));
1226        }
1227    }
1228}
1229
1230use OT::hb_ot_apply_context_t;
1231
1232pub fn ligate_input(
1233    ctx: &mut hb_ot_apply_context_t,
1234    // Including the first glyph
1235    count: usize,
1236    // Including the first glyph
1237    match_positions: &smallvec::SmallVec<[usize; 4]>,
1238    match_end: usize,
1239    total_component_count: u8,
1240    lig_glyph: GlyphId,
1241) {
1242    // - If a base and one or more marks ligate, consider that as a base, NOT
1243    //   ligature, such that all following marks can still attach to it.
1244    //   https://github.com/harfbuzz/harfbuzz/issues/1109
1245    //
1246    // - If all components of the ligature were marks, we call this a mark ligature.
1247    //   If it *is* a mark ligature, we don't allocate a new ligature id, and leave
1248    //   the ligature to keep its old ligature id.  This will allow it to attach to
1249    //   a base ligature in GPOS.  Eg. if the sequence is: LAM,LAM,SHADDA,FATHA,HEH,
1250    //   and LAM,LAM,HEH for a ligature, they will leave SHADDA and FATHA with a
1251    //   ligature id and component value of 2.  Then if SHADDA,FATHA form a ligature
1252    //   later, we don't want them to lose their ligature id/component, otherwise
1253    //   GPOS will fail to correctly position the mark ligature on top of the
1254    //   LAM,LAM,HEH ligature.  See:
1255    //     https://bugzilla.gnome.org/show_bug.cgi?id=676343
1256    //
1257    // - If a ligature is formed of components that some of which are also ligatures
1258    //   themselves, and those ligature components had marks attached to *their*
1259    //   components, we have to attach the marks to the new ligature component
1260    //   positions!  Now *that*'s tricky!  And these marks may be following the
1261    //   last component of the whole sequence, so we should loop forward looking
1262    //   for them and update them.
1263    //
1264    //   Eg. the sequence is LAM,LAM,SHADDA,FATHA,HEH, and the font first forms a
1265    //   'calt' ligature of LAM,HEH, leaving the SHADDA and FATHA with a ligature
1266    //   id and component == 1.  Now, during 'liga', the LAM and the LAM-HEH ligature
1267    //   form a LAM-LAM-HEH ligature.  We need to reassign the SHADDA and FATHA to
1268    //   the new ligature with a component value of 2.
1269    //
1270    //   This in fact happened to a font...  See:
1271    //   https://bugzilla.gnome.org/show_bug.cgi?id=437633
1272    //
1273
1274    let mut buffer = &mut ctx.buffer;
1275    buffer.merge_clusters(buffer.idx, match_end);
1276
1277    let mut is_base_ligature = _hb_glyph_info_is_base_glyph(&buffer.info[match_positions[0]]);
1278    let mut is_mark_ligature = _hb_glyph_info_is_mark(&buffer.info[match_positions[0]]);
1279    for i in 1..count {
1280        if !_hb_glyph_info_is_mark(&buffer.info[match_positions[i]]) {
1281            is_base_ligature = false;
1282            is_mark_ligature = false;
1283        }
1284    }
1285
1286    let is_ligature = !is_base_ligature && !is_mark_ligature;
1287    let class = if is_ligature {
1288        GlyphPropsFlags::LIGATURE
1289    } else {
1290        GlyphPropsFlags::empty()
1291    };
1292    let lig_id = if is_ligature {
1293        buffer.allocate_lig_id()
1294    } else {
1295        0
1296    };
1297    let first = buffer.cur_mut(0);
1298    let mut last_lig_id = _hb_glyph_info_get_lig_id(first);
1299    let mut last_num_comps = _hb_glyph_info_get_lig_num_comps(first);
1300    let mut comps_so_far = last_num_comps;
1301
1302    if is_ligature {
1303        _hb_glyph_info_set_lig_props_for_ligature(first, lig_id, total_component_count);
1304        if _hb_glyph_info_get_general_category(first)
1305            == hb_unicode_general_category_t::NonspacingMark
1306        {
1307            _hb_glyph_info_set_general_category(first, hb_unicode_general_category_t::OtherLetter);
1308        }
1309    }
1310
1311    ctx.replace_glyph_with_ligature(lig_glyph, class);
1312    buffer = &mut ctx.buffer;
1313
1314    for i in 1..count {
1315        while buffer.idx < match_positions[i] && buffer.successful {
1316            if is_ligature {
1317                let cur = buffer.cur_mut(0);
1318                let mut this_comp = _hb_glyph_info_get_lig_comp(cur);
1319                if this_comp == 0 {
1320                    this_comp = last_num_comps;
1321                }
1322                // Avoid the potential for a wrap-around bug when subtracting from an unsigned integer
1323                // c.f. https://github.com/harfbuzz/rustybuzz/issues/142
1324                assert!(comps_so_far >= last_num_comps);
1325                let new_lig_comp = comps_so_far - last_num_comps + this_comp.min(last_num_comps);
1326                _hb_glyph_info_set_lig_props_for_mark(cur, lig_id, new_lig_comp);
1327            }
1328            buffer.next_glyph();
1329        }
1330
1331        let cur = buffer.cur(0);
1332        last_lig_id = _hb_glyph_info_get_lig_id(cur);
1333        last_num_comps = _hb_glyph_info_get_lig_num_comps(cur);
1334        comps_so_far += last_num_comps;
1335
1336        // Skip the base glyph.
1337        buffer.idx += 1;
1338    }
1339
1340    if !is_mark_ligature && last_lig_id != 0 {
1341        // Re-adjust components for any marks following.
1342        for i in buffer.idx..buffer.len {
1343            let info = &mut buffer.info[i];
1344            if last_lig_id != _hb_glyph_info_get_lig_id(info) {
1345                break;
1346            }
1347
1348            let this_comp = _hb_glyph_info_get_lig_comp(info);
1349            if this_comp == 0 {
1350                break;
1351            }
1352
1353            // Avoid the potential for a wrap-around bug when subtracting from an unsigned integer
1354            // c.f. https://github.com/harfbuzz/rustybuzz/issues/142
1355            assert!(comps_so_far >= last_num_comps);
1356            let new_lig_comp = comps_so_far - last_num_comps + this_comp.min(last_num_comps);
1357            _hb_glyph_info_set_lig_props_for_mark(info, lig_id, new_lig_comp)
1358        }
1359    }
1360}