rustybuzz/hb/
ot_layout_gsubgpos.rs

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