Skip to main content

harfrust/hb/
ot_layout_gsubgpos.rs

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