1
//! Utilities for performing contextual lookup in gpos and gsub.
2

            
3
use std::marker::PhantomData;
4
use std::ops::RangeInclusive;
5
use std::sync::Arc;
6

            
7
use crate::gdef;
8
use crate::layout::{ClassDef, Coverage, GDEFTable};
9

            
10
#[derive(Debug, Copy, Clone)]
11
pub struct LookupFlag(pub u16);
12

            
13
#[derive(Debug, Copy, Clone, PartialEq)]
14
pub enum IgnoreMarks {
15
    NoIgnoreMarks,
16
    IgnoreAllMarks,
17
    IgnoreMarksExcept(u8),
18
    IgnoreMarksInSet(u16),
19
}
20

            
21
#[derive(Debug, Copy, Clone)]
22
pub struct MatchType {
23
    ignore_bases: bool,
24
    ignore_ligatures: bool,
25
    ignore_marks: IgnoreMarks,
26
}
27

            
28
pub enum GlyphTable<'a> {
29
    Empty,
30
    ById(&'a [u16]),
31
    ByClassDef(Arc<ClassDef>, &'a [u16]),
32
    ByCoverage(&'a [Arc<Coverage>]),
33
}
34

            
35
impl GlyphTable<'_> {
36
431190
    pub fn len(&self) -> usize {
37
431190
        match self {
38
            GlyphTable::Empty => 0,
39
410616
            GlyphTable::ById(arr) => arr.len(),
40
486
            GlyphTable::ByClassDef(_, arr) => arr.len(),
41
20088
            GlyphTable::ByCoverage(vec) => vec.len(),
42
        }
43
431190
    }
44

            
45
    pub fn is_empty(&self) -> bool {
46
        self.len() == 0
47
    }
48
}
49

            
50
pub struct MatchContext<'a> {
51
    pub backtrack_table: GlyphTable<'a>,
52
    pub input_table: GlyphTable<'a>,
53
    pub lookahead_table: GlyphTable<'a>,
54
}
55

            
56
pub struct ContextLookupHelper<'a, T> {
57
    pub match_context: MatchContext<'a>,
58
    pub lookup_array: &'a [(u16, u16)],
59
    pub input_seq: RangeInclusive<usize>,
60
    phantom: PhantomData<T>,
61
}
62

            
63
impl<'a, T> ContextLookupHelper<'a, T> {
64
    pub fn new(
65
        match_context: MatchContext<'a>,
66
        lookup_array: &'a [(u16, u16)],
67
        input_seq: RangeInclusive<usize>,
68
    ) -> ContextLookupHelper<'a, T> {
69
        ContextLookupHelper {
70
            match_context,
71
            lookup_array,
72
            input_seq,
73
            phantom: PhantomData,
74
        }
75
    }
76
}
77

            
78
pub trait Glyph {
79
    fn get_glyph_index(&self) -> u16;
80
}
81

            
82
impl LookupFlag {
83
    pub fn get_rtl(self) -> bool {
84
        (self.0 & 0x0001) != 0
85
    }
86

            
87
256338
    pub fn get_ignore_bases(self) -> bool {
88
256338
        (self.0 & 0x0002) != 0
89
256338
    }
90

            
91
256338
    pub fn get_ignore_ligatures(self) -> bool {
92
256338
        (self.0 & 0x0004) != 0
93
256338
    }
94

            
95
282744
    pub fn use_mark_filtering_set(self) -> bool {
96
282744
        (self.0 & 0x0010) != 0
97
282744
    }
98

            
99
256338
    pub fn get_ignore_marks(self, mark_filtering_set: Option<u16>) -> IgnoreMarks {
100
256338
        if (self.0 & 0x8) != 0 {
101
            IgnoreMarks::IgnoreAllMarks
102
256338
        } else if self.0 & 0xFF00 != 0 {
103
35100
            IgnoreMarks::IgnoreMarksExcept((self.0 >> 8) as u8)
104
221238
        } else if self.use_mark_filtering_set() && mark_filtering_set.is_some() {
105
            // NOTE(unwrap): Safe due to check above
106
            // The combination of mark_filtering_set == None and use_mark_filtering_set == true
107
            // shouldn't occur in practice - if the flag is set then ReadBinary will have read
108
            // mark_filtering_set.
109
            IgnoreMarks::IgnoreMarksInSet(mark_filtering_set.unwrap())
110
        } else {
111
221238
            IgnoreMarks::NoIgnoreMarks
112
        }
113
256338
    }
114
}
115

            
116
impl MatchType {
117
    pub fn ignore_marks() -> MatchType {
118
        MatchType {
119
            ignore_bases: false,
120
            ignore_ligatures: false,
121
            ignore_marks: IgnoreMarks::IgnoreAllMarks,
122
        }
123
    }
124

            
125
29700
    pub fn marks_only() -> MatchType {
126
29700
        MatchType {
127
29700
            ignore_bases: true,
128
29700
            ignore_ligatures: true,
129
29700
            ignore_marks: IgnoreMarks::NoIgnoreMarks,
130
29700
        }
131
29700
    }
132

            
133
256338
    pub fn from_lookup_flag(lookup_flag: LookupFlag, mark_filtering_set: Option<u16>) -> MatchType {
134
256338
        MatchType {
135
256338
            ignore_bases: lookup_flag.get_ignore_bases(),
136
256338
            ignore_ligatures: lookup_flag.get_ignore_ligatures(),
137
256338
            ignore_marks: lookup_flag.get_ignore_marks(mark_filtering_set),
138
256338
        }
139
256338
    }
140

            
141
2408670
    pub fn match_glyph<G: Glyph>(self, opt_gdef_table: Option<&GDEFTable>, glyph: &G) -> bool {
142
2408670
        if !self.ignore_bases
143
2378970
            && !self.ignore_ligatures
144
2378970
            && self.ignore_marks == IgnoreMarks::NoIgnoreMarks
145
        {
146
            // fast path that doesn't require checking glyph_class
147
2041740
            return true;
148
366930
        }
149
366930
        let glyph_class = gdef::glyph_class(opt_gdef_table, glyph.get_glyph_index());
150
366930
        if self.ignore_bases && glyph_class == 1 {
151
            return false;
152
366930
        }
153
366930
        if self.ignore_ligatures && glyph_class == 2 {
154
            return false;
155
366930
        }
156
366930
        match self.ignore_marks {
157
29700
            IgnoreMarks::NoIgnoreMarks => true,
158
            IgnoreMarks::IgnoreAllMarks => glyph_class != 3,
159
337230
            IgnoreMarks::IgnoreMarksExcept(keep_class) => {
160
337230
                let mark_attach_class =
161
337230
                    gdef::mark_attach_class(opt_gdef_table, glyph.get_glyph_index());
162
337230
                (glyph_class != 3) || (mark_attach_class == u16::from(keep_class))
163
            }
164
            IgnoreMarks::IgnoreMarksInSet(index) => {
165
                (glyph_class != 3)
166
                    || gdef::glyph_is_mark_in_set(
167
                        opt_gdef_table,
168
                        glyph.get_glyph_index(),
169
                        index.into(),
170
                    )
171
            }
172
        }
173
2408670
    }
174

            
175
    // searches backwards from glyphs[index-1]
176
    pub fn find_prev<G: Glyph>(
177
        self,
178
        opt_gdef_table: Option<&GDEFTable>,
179
        glyphs: &[G],
180
        mut index: usize,
181
    ) -> Option<usize> {
182
        while index > 0 {
183
            index -= 1;
184
            if self.match_glyph(opt_gdef_table, &glyphs[index]) {
185
                return Some(index);
186
            }
187
        }
188
        None
189
    }
190

            
191
    // searches forwards from glyphs[index+1]
192
926262
    pub fn find_next<G: Glyph>(
193
926262
        self,
194
926262
        opt_gdef_table: Option<&GDEFTable>,
195
926262
        glyphs: &[G],
196
926262
        mut index: usize,
197
926262
    ) -> Option<usize> {
198
926262
        while index + 1 < glyphs.len() {
199
808866
            index += 1;
200
808866
            if self.match_glyph(opt_gdef_table, &glyphs[index]) {
201
808866
                return Some(index);
202
            }
203
        }
204
117396
        None
205
926262
    }
206

            
207
    // count == 0 will return current index
208
    pub fn find_nth<G: Glyph>(
209
        self,
210
        opt_gdef_table: Option<&GDEFTable>,
211
        glyphs: &[G],
212
        mut index: usize,
213
        count: usize,
214
    ) -> Option<usize> {
215
        for _ in 0..count {
216
            match self.find_next(opt_gdef_table, glyphs, index) {
217
                Some(next_index) => index = next_index,
218
                None => return None,
219
            }
220
        }
221
        Some(index)
222
    }
223

            
224
64098
    pub fn find_first<G: Glyph>(
225
64098
        self,
226
64098
        opt_gdef_table: Option<&GDEFTable>,
227
64098
        glyphs: &[G],
228
64098
    ) -> Option<usize> {
229
64098
        for (index, glyph) in glyphs.iter().enumerate() {
230
64098
            if self.match_glyph(opt_gdef_table, glyph) {
231
64098
                return Some(index);
232
            }
233
        }
234
        None
235
64098
    }
236

            
237
    // searches backwards from glyphs[index-1]
238
6858
    pub fn match_back<G: Glyph>(
239
6858
        self,
240
6858
        opt_gdef_table: Option<&GDEFTable>,
241
6858
        glyph_table: &GlyphTable<'_>,
242
6858
        glyphs: &[G],
243
6858
        mut index: usize,
244
6858
    ) -> bool {
245
6858
        for i in 0..glyph_table.len() {
246
            match self.find_prev(opt_gdef_table, glyphs, index) {
247
                Some(prev_index) => {
248
                    index = prev_index;
249
                    let glyph_index = glyphs[index].get_glyph_index();
250
                    if !check_glyph_table(glyph_table, i, glyph_index) {
251
                        return false;
252
                    }
253
                }
254
                None => return false,
255
            }
256
        }
257
6858
        true
258
6858
    }
259

            
260
    // searches forwards from glyphs[index+1]
261
424332
    pub fn match_front<G: Glyph>(
262
424332
        self,
263
424332
        opt_gdef_table: Option<&GDEFTable>,
264
424332
        glyph_table: &GlyphTable<'_>,
265
424332
        glyphs: &[G],
266
424332
        mut index: usize,
267
424332
        last_index: &mut usize,
268
424332
    ) -> Option<RangeInclusive<usize>> {
269
424332
        let start = index;
270
424332
        for i in 0..glyph_table.len() {
271
421686
            match self.find_next(opt_gdef_table, glyphs, index) {
272
368388
                Some(next_index) => {
273
368388
                    index = next_index;
274
368388
                    let glyph_index = glyphs[index].get_glyph_index();
275
368388
                    if !check_glyph_table(glyph_table, i, glyph_index) {
276
362988
                        return None;
277
5400
                    }
278
                }
279
53298
                None => return None,
280
            }
281
        }
282
8046
        *last_index = index;
283
8046
        Some(start..=*last_index)
284
424332
    }
285
}
286

            
287
impl MatchContext<'_> {
288
6858
    pub fn matches<G: Glyph>(
289
6858
        &self,
290
6858
        opt_gdef_table: Option<&GDEFTable>,
291
6858
        match_type: MatchType,
292
6858
        glyphs: &[G],
293
6858
        index: usize,
294
6858
    ) -> Option<RangeInclusive<usize>> {
295
6858
        let mut front_index = index;
296
6858
        let mut range = None;
297
6858
        let matched = match_type.match_back(opt_gdef_table, &self.backtrack_table, glyphs, index)
298
            && {
299
6858
                range = match_type.match_front(
300
6858
                    opt_gdef_table,
301
6858
                    &self.input_table,
302
6858
                    glyphs,
303
6858
                    index,
304
6858
                    &mut front_index,
305
6858
                );
306
6858
                range.is_some()
307
            }
308
6858
            && match_type
309
6858
                .match_front(
310
6858
                    opt_gdef_table,
311
6858
                    &self.lookahead_table,
312
6858
                    glyphs,
313
6858
                    front_index,
314
6858
                    &mut front_index,
315
6858
                )
316
6858
                .is_some();
317
6858
        if matched {
318
            range
319
        } else {
320
6858
            None
321
        }
322
6858
    }
323
}
324

            
325
368388
fn check_glyph_table(glyph_table: &GlyphTable<'_>, i: usize, glyph_index: u16) -> bool {
326
368388
    match glyph_table {
327
        GlyphTable::Empty => false,
328
361692
        GlyphTable::ById(table) => table[i] == glyph_index,
329
162
        GlyphTable::ByClassDef(classdef, table) => {
330
162
            classdef.glyph_class_value(glyph_index) == table[i]
331
        }
332
6534
        GlyphTable::ByCoverage(vec) => vec[i].glyph_coverage_value(glyph_index).is_some(),
333
    }
334
368388
}