1
//! Glyph substitution (`gsub`) implementation.
2
//!
3
//! > The Glyph Substitution (GSUB) table provides data for substition of glyphs for appropriate
4
//! > rendering of scripts, such as cursively-connecting forms in Arabic script, or for advanced
5
//! > typographic effects, such as ligatures.
6
//!
7
//! — <https://docs.microsoft.com/en-us/typography/opentype/spec/gsub>
8

            
9
use std::collections::btree_map::Entry;
10
use std::collections::BTreeMap;
11
use std::fmt::Debug;
12

            
13
use enumflags2::BitFlags;
14
use tinyvec::{tiny_vec, TinyVec};
15

            
16
use crate::context::{ContextLookupHelper, Glyph, GlyphTable, MatchType};
17
use crate::error::{ParseError, ShapingError};
18
use crate::layout::{
19
    chain_context_lookup_info, context_lookup_info, AlternateSet, AlternateSubst,
20
    ChainContextLookup, ContextLookup, FeatureTableSubstitution, GDEFTable, LangSys, LayoutCache,
21
    LayoutTable, Ligature, LigatureSubst, LookupCacheItem, LookupList, MultipleSubst,
22
    ReverseChainSingleSubst, SequenceTable, SingleSubst, SubstLookup, GSUB,
23
};
24
use crate::scripts::{self, ScriptType};
25
use crate::tables::variable_fonts::Tuple;
26
use crate::unicode::VariationSelector;
27
use crate::{tag, GlyphId};
28

            
29
const SUBST_RECURSION_LIMIT: usize = 2;
30
// Matches Harfbuzz:
31
// https://github.com/harfbuzz/harfbuzz/blob/8062c372590980d36d5b4cc720d33dca2662c56e/src/hb-limits.hh#L32
32
pub(crate) const MAX_GLYPHS_FACTOR: usize = 256;
33

            
34
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
35
pub struct FeatureInfo {
36
    pub feature_tag: u32,
37
    pub alternate: Option<usize>,
38
}
39

            
40
type SubstContext<'a> = ContextLookupHelper<'a, GSUB>;
41

            
42
impl Ligature {
43
410616
    pub fn matches<T>(
44
410616
        &self,
45
410616
        match_type: MatchType,
46
410616
        opt_gdef_table: Option<&GDEFTable>,
47
410616
        i: usize,
48
410616
        glyphs: &[RawGlyph<T>],
49
410616
    ) -> bool {
50
410616
        let mut last_index = 0;
51
410616
        match_type
52
410616
            .match_front(
53
410616
                opt_gdef_table,
54
410616
                &GlyphTable::ById(&self.component_glyphs),
55
410616
                glyphs,
56
410616
                i,
57
410616
                &mut last_index,
58
410616
            )
59
410616
            .is_some()
60
410616
    }
61

            
62
1188
    pub fn apply<T: GlyphData>(
63
1188
        &self,
64
1188
        match_type: MatchType,
65
1188
        opt_gdef_table: Option<&GDEFTable>,
66
1188
        i: usize,
67
1188
        glyphs: &mut Vec<RawGlyph<T>>,
68
1188
    ) -> usize {
69
1188
        let mut index = i + 1;
70
1188
        let mut matched = 0;
71
1188
        let mut skip = 0;
72
2376
        while matched < self.component_glyphs.len() {
73
1188
            if index < glyphs.len() {
74
1188
                if match_type.match_glyph(opt_gdef_table, &glyphs[index]) {
75
1188
                    matched += 1;
76
1188
                    let mut matched_glyph = glyphs.remove(index);
77
1188
                    glyphs[i].unicodes.append(&mut matched_glyph.unicodes);
78
1188
                    glyphs[i].extra_data =
79
1188
                        GlyphData::merge(glyphs[i].extra_data.clone(), matched_glyph.extra_data);
80
1188
                    glyphs[i].flags.set(RawGlyphFlag::LIGATURE, true);
81
1188
                } else {
82
                    glyphs[index].liga_component_pos = matched as u16;
83
                    skip += 1;
84
                    index += 1;
85
                }
86
            } else {
87
                panic!("ran out of glyphs");
88
            }
89
        }
90
30888
        while index < glyphs.len()
91
29700
            && MatchType::marks_only().match_glyph(opt_gdef_table, &glyphs[index])
92
29700
        {
93
29700
            glyphs[index].liga_component_pos = matched as u16;
94
29700
            index += 1;
95
29700
        }
96
1188
        glyphs[i].glyph_index = self.ligature_glyph;
97
1188
        glyphs[i].glyph_origin = GlyphOrigin::Direct;
98
1188
        skip
99
1188
    }
100
}
101

            
102
#[derive(Clone, Debug)]
103
pub struct RawGlyph<T> {
104
    pub unicodes: TinyVec<[char; 1]>,
105
    pub glyph_index: GlyphId,
106
    pub liga_component_pos: u16,
107
    pub glyph_origin: GlyphOrigin,
108
    pub flags: RawGlyphFlags,
109
    pub variation: Option<VariationSelector>,
110
    pub extra_data: T,
111
}
112

            
113
#[enumflags2::bitflags]
114
#[repr(u8)]
115
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
116
#[allow(non_camel_case_types)]
117
pub enum RawGlyphFlag {
118
    SMALL_CAPS = 1 << 0,
119
    MULTI_SUBST_DUP = 1 << 1,
120
    IS_VERT_ALT = 1 << 2,
121
    LIGATURE = 1 << 3,
122
    FAKE_BOLD = 1 << 4,
123
    FAKE_ITALIC = 1 << 5,
124
}
125

            
126
pub type RawGlyphFlags = BitFlags<RawGlyphFlag>;
127

            
128
/// `merge` is called during ligature substitution (i.e. merging of glyphs),
129
/// and determines how the `RawGlyph.extra_data` field should be merged
130
pub trait GlyphData: Clone {
131
    fn merge(data1: Self, data2: Self) -> Self;
132
}
133

            
134
impl GlyphData for () {
135
1188
    fn merge(_data1: (), _data2: ()) {}
136
}
137

            
138
#[derive(Clone, Copy, PartialEq, Debug)]
139
pub enum GlyphOrigin {
140
    Char(char),
141
    Direct,
142
}
143

            
144
impl<T> RawGlyph<T> {
145
    pub fn small_caps(&self) -> bool {
146
        self.flags.contains(RawGlyphFlag::SMALL_CAPS)
147
    }
148

            
149
211894
    pub fn multi_subst_dup(&self) -> bool {
150
211894
        self.flags.contains(RawGlyphFlag::MULTI_SUBST_DUP)
151
211894
    }
152

            
153
    pub fn is_vert_alt(&self) -> bool {
154
        self.flags.contains(RawGlyphFlag::IS_VERT_ALT)
155
    }
156

            
157
    pub fn ligature(&self) -> bool {
158
        self.flags.contains(RawGlyphFlag::LIGATURE)
159
    }
160

            
161
    pub fn fake_bold(&self) -> bool {
162
        self.flags.contains(RawGlyphFlag::FAKE_BOLD)
163
    }
164

            
165
    pub fn fake_italic(&self) -> bool {
166
        self.flags.contains(RawGlyphFlag::FAKE_ITALIC)
167
    }
168
}
169

            
170
impl<T> Glyph for RawGlyph<T> {
171
1072548
    fn get_glyph_index(&self) -> u16 {
172
1072548
        self.glyph_index
173
1072548
    }
174
}
175

            
176
pub fn gsub_feature_would_apply<T: GlyphData>(
177
    gsub_cache: &LayoutCache<GSUB>,
178
    gsub_table: &LayoutTable<GSUB>,
179
    opt_gdef_table: Option<&GDEFTable>,
180
    langsys: &LangSys,
181
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
182
    feature_tag: u32,
183
    glyphs: &[RawGlyph<T>],
184
    i: usize,
185
) -> Result<bool, ParseError> {
186
    if let Some(feature_table) =
187
        gsub_table.find_langsys_feature(langsys, feature_tag, feature_variations)?
188
    {
189
        if let Some(ref lookup_list) = gsub_table.opt_lookup_list {
190
            for lookup_index in &feature_table.lookup_indices {
191
                let lookup_index = usize::from(*lookup_index);
192
                let lookup_cache_item = lookup_list.lookup_cache_gsub(gsub_cache, lookup_index)?;
193
                if gsub_lookup_would_apply(opt_gdef_table, &lookup_cache_item, glyphs, i)? {
194
                    return Ok(true);
195
                }
196
            }
197
        }
198
    }
199
    Ok(false)
200
}
201

            
202
pub fn gsub_lookup_would_apply<T: GlyphData>(
203
    opt_gdef_table: Option<&GDEFTable>,
204
    lookup: &LookupCacheItem<SubstLookup>,
205
    glyphs: &[RawGlyph<T>],
206
    i: usize,
207
) -> Result<bool, ParseError> {
208
    let match_type = MatchType::from_lookup_flag(lookup.lookup_flag, lookup.mark_filtering_set);
209
    if i < glyphs.len() && match_type.match_glyph(opt_gdef_table, &glyphs[i]) {
210
        return match lookup.lookup_subtables {
211
            SubstLookup::SingleSubst(ref subtables) => {
212
                match singlesubst_would_apply(subtables, &glyphs[i])? {
213
                    Some(_output_glyph) => Ok(true),
214
                    None => Ok(false),
215
                }
216
            }
217
            SubstLookup::MultipleSubst(ref subtables) => {
218
                match multiplesubst_would_apply(subtables, i, glyphs)? {
219
                    Some(_sequence_table) => Ok(true),
220
                    None => Ok(false),
221
                }
222
            }
223
            SubstLookup::AlternateSubst(ref subtables) => {
224
                match alternatesubst_would_apply(subtables, &glyphs[i])? {
225
                    Some(_alternate_set) => Ok(true),
226
                    None => Ok(false),
227
                }
228
            }
229
            SubstLookup::LigatureSubst(ref subtables) => {
230
                match ligaturesubst_would_apply(opt_gdef_table, subtables, match_type, i, glyphs)? {
231
                    Some(_ligature) => Ok(true),
232
                    None => Ok(false),
233
                }
234
            }
235
            SubstLookup::ContextSubst(ref subtables) => {
236
                match contextsubst_would_apply(opt_gdef_table, subtables, match_type, i, glyphs)? {
237
                    Some(_subst) => Ok(true),
238
                    None => Ok(false),
239
                }
240
            }
241
            SubstLookup::ChainContextSubst(ref subtables) => {
242
                match chaincontextsubst_would_apply(
243
                    opt_gdef_table,
244
                    subtables,
245
                    match_type,
246
                    i,
247
                    glyphs,
248
                )? {
249
                    Some(_subst) => Ok(true),
250
                    None => Ok(false),
251
                }
252
            }
253
            SubstLookup::ReverseChainSingleSubst(ref subtables) => {
254
                match reversechainsinglesubst_would_apply(
255
                    opt_gdef_table,
256
                    subtables,
257
                    match_type,
258
                    i,
259
                    glyphs,
260
                )? {
261
                    Some(_subst) => Ok(true),
262
                    None => Ok(false),
263
                }
264
            }
265
        };
266
    }
267
    Ok(false)
268
}
269

            
270
/// Apply the specified lookup to the given glyphs.
271
///
272
/// ## Arguments
273
///
274
/// * `gsub_cache` - The GSUB layout cache, created via [new_layout_cache][crate::layout::new_layout_cache].
275
/// * `gsub_table` - The GSUB layout table.
276
/// * `opt_gdef_table` - The GDEF table, if available.
277
/// * `lookup_index` - The index of the lookup to apply.
278
/// * `feature_tag` - The feature tag associated with the lookup.
279
/// * `opt_alternate` - The index of an alternate glyph in the alternate set, if available.
280
/// * `glyphs` - The glyphs to apply the lookup to.
281
/// * `max_glyphs` - The limit to which `glyphs` can grow through substitutions.
282
///   The length of `glyphs` will remain less than this value. If the limit is reached,
283
///   further substitutions will not be applied.
284
/// * `start` - The starting index of the glyphs to apply the lookup to.
285
/// * `length` - The length of the input sequence substituted.
286
/// * `pred` - The predicate function to filter the glyphs to apply the lookup to.
287
175122
pub fn gsub_apply_lookup<T: GlyphData>(
288
175122
    gsub_cache: &LayoutCache<GSUB>,
289
175122
    gsub_table: &LayoutTable<GSUB>,
290
175122
    opt_gdef_table: Option<&GDEFTable>,
291
175122
    lookup_index: usize,
292
175122
    feature_tag: u32,
293
175122
    opt_alternate: Option<usize>,
294
175122
    glyphs: &mut Vec<RawGlyph<T>>,
295
175122
    max_glyphs: usize,
296
175122
    start: usize,
297
175122
    mut length: usize,
298
175122
    pred: impl Fn(&RawGlyph<T>) -> bool,
299
175122
) -> Result<usize, ParseError> {
300
175122
    if let Some(ref lookup_list) = gsub_table.opt_lookup_list {
301
175122
        let lookup = lookup_list.lookup_cache_gsub(gsub_cache, lookup_index)?;
302
175122
        let match_type = MatchType::from_lookup_flag(lookup.lookup_flag, lookup.mark_filtering_set);
303
175122
        match lookup.lookup_subtables {
304
            SubstLookup::SingleSubst(ref subtables) => {
305
                for glyph in glyphs[start..(start + length)].iter_mut() {
306
                    if match_type.match_glyph(opt_gdef_table, glyph) && pred(glyph) {
307
                        singlesubst(subtables, feature_tag, glyph)?;
308
                    }
309
                }
310
            }
311
            SubstLookup::MultipleSubst(ref subtables) => {
312
                let mut i = start;
313
                while i < start + length {
314
                    if match_type.match_glyph(opt_gdef_table, &glyphs[i]) && pred(&glyphs[i]) {
315
                        match multiplesubst(subtables, i, glyphs, max_glyphs)? {
316
                            Some(replace_count) => {
317
                                i += replace_count;
318
                                length += replace_count;
319
                                length -= 1;
320
                            }
321
                            None => i += 1,
322
                        }
323
                    } else {
324
                        i += 1;
325
                    }
326
                }
327
            }
328
            SubstLookup::AlternateSubst(ref subtables) => {
329
                for glyph in glyphs[start..(start + length)].iter_mut() {
330
                    if match_type.match_glyph(opt_gdef_table, glyph) && pred(glyph) {
331
                        let alternate = opt_alternate.unwrap_or(0);
332
                        alternatesubst(subtables, alternate, glyph)?;
333
                    }
334
                }
335
            }
336
75924
            SubstLookup::LigatureSubst(ref subtables) => {
337
75924
                let mut i = start;
338
741906
                while i < start + length {
339
665982
                    if match_type.match_glyph(opt_gdef_table, &glyphs[i]) && pred(&glyphs[i]) {
340
665982
                        match ligaturesubst(opt_gdef_table, subtables, match_type, i, glyphs)? {
341
1188
                            Some((removed_count, skip_count)) => {
342
1188
                                i += skip_count + 1;
343
1188
                                length -= removed_count;
344
1188
                            }
345
664794
                            None => i += 1,
346
                        }
347
                    } else {
348
                        i += 1;
349
                    }
350
                }
351
            }
352
            SubstLookup::ContextSubst(ref subtables) => {
353
                let mut i = start;
354
                while i < start + length {
355
                    if match_type.match_glyph(opt_gdef_table, &glyphs[i]) && pred(&glyphs[i]) {
356
                        match contextsubst(
357
                            SUBST_RECURSION_LIMIT,
358
                            gsub_cache,
359
                            lookup_list,
360
                            opt_gdef_table,
361
                            subtables,
362
                            feature_tag,
363
                            match_type,
364
                            i,
365
                            glyphs,
366
                            max_glyphs,
367
                        )? {
368
                            Some((input_length, changes)) => {
369
                                i += input_length;
370
                                length = checked_add(length, changes).unwrap();
371
                            }
372
                            None => i += 1,
373
                        }
374
                    } else {
375
                        i += 1;
376
                    }
377
                }
378
            }
379
99198
            SubstLookup::ChainContextSubst(ref subtables) => {
380
99198
                let mut i = start;
381
938034
                while i < start + length {
382
838836
                    if match_type.match_glyph(opt_gdef_table, &glyphs[i]) && pred(&glyphs[i]) {
383
838836
                        match chaincontextsubst(
384
                            SUBST_RECURSION_LIMIT,
385
838836
                            gsub_cache,
386
838836
                            lookup_list,
387
838836
                            opt_gdef_table,
388
838836
                            subtables,
389
838836
                            feature_tag,
390
838836
                            match_type,
391
838836
                            i,
392
838836
                            glyphs,
393
838836
                            max_glyphs,
394
                        )? {
395
                            Some((input_length, changes)) => {
396
                                i += input_length;
397
                                length = checked_add(length, changes).unwrap();
398
                            }
399
838836
                            None => i += 1,
400
                        }
401
                    } else {
402
                        i += 1;
403
                    }
404
                }
405
            }
406
            SubstLookup::ReverseChainSingleSubst(ref subtables) => {
407
                for i in (start..start + length).rev() {
408
                    if match_type.match_glyph(opt_gdef_table, &glyphs[i]) && pred(&glyphs[i]) {
409
                        reversechainsinglesubst(opt_gdef_table, subtables, match_type, i, glyphs)?;
410
                    }
411
                }
412
            }
413
        }
414
    }
415
175122
    Ok(length)
416
175122
}
417

            
418
fn singlesubst_would_apply<T: GlyphData>(
419
    subtables: &[SingleSubst],
420
    glyph: &RawGlyph<T>,
421
) -> Result<Option<u16>, ParseError> {
422
    let glyph_index = glyph.glyph_index;
423
    for single_subst in subtables {
424
        if let Some(glyph_index) = single_subst.apply_glyph(glyph_index)? {
425
            return Ok(Some(glyph_index));
426
        }
427
    }
428
    Ok(None)
429
}
430

            
431
fn singlesubst<T: GlyphData>(
432
    subtables: &[SingleSubst],
433
    subst_tag: u32,
434
    glyph: &mut RawGlyph<T>,
435
) -> Result<(), ParseError> {
436
    if let Some(output_glyph) = singlesubst_would_apply(subtables, glyph)? {
437
        glyph.glyph_index = output_glyph;
438
        glyph.glyph_origin = GlyphOrigin::Direct;
439
        if subst_tag == tag::VERT || subst_tag == tag::VRT2 {
440
            glyph.flags.set(RawGlyphFlag::IS_VERT_ALT, true);
441
        }
442
    }
443
    Ok(())
444
}
445

            
446
fn multiplesubst_would_apply<'a, T: GlyphData>(
447
    subtables: &'a [MultipleSubst],
448
    i: usize,
449
    glyphs: &[RawGlyph<T>],
450
) -> Result<Option<&'a SequenceTable>, ParseError> {
451
    let glyph_index = glyphs[i].glyph_index;
452
    for multiple_subst in subtables {
453
        if let Some(sequence_table) = multiple_subst.apply_glyph(glyph_index)? {
454
            return Ok(Some(sequence_table));
455
        }
456
    }
457
    Ok(None)
458
}
459

            
460
fn multiplesubst<T: GlyphData>(
461
    subtables: &[MultipleSubst],
462
    i: usize,
463
    glyphs: &mut Vec<RawGlyph<T>>,
464
    max_glyphs: usize,
465
) -> Result<Option<usize>, ParseError> {
466
    match multiplesubst_would_apply(subtables, i, glyphs)? {
467
        Some(sequence_table) => {
468
            if sequence_table.substitute_glyphs.len() + glyphs.len() >= max_glyphs {
469
                // The Unicode text rendering tests say that shaping should stop when the limit is
470
                // reached, but not fail:
471
                //
472
                // "If your implementation is immune to this attack, it should neither crash nor
473
                // hang when rendering lol with this font. Instead, your implementation should stop
474
                // executing once its internal buffer has reached a size limit."
475
                return Ok(Some(0));
476
            }
477

            
478
            if !sequence_table.substitute_glyphs.is_empty() {
479
                let first_glyph_index = sequence_table.substitute_glyphs[0];
480
                glyphs[i].glyph_index = first_glyph_index;
481
                glyphs[i].glyph_origin = GlyphOrigin::Direct;
482
                for j in 1..sequence_table.substitute_glyphs.len() {
483
                    let output_glyph_index = sequence_table.substitute_glyphs[j];
484
                    let mut flags = glyphs[i].flags;
485
                    flags.set(RawGlyphFlag::MULTI_SUBST_DUP, true);
486
                    flags.set(RawGlyphFlag::LIGATURE, false);
487
                    let glyph = RawGlyph {
488
                        unicodes: glyphs[i].unicodes.clone(),
489
                        glyph_index: output_glyph_index,
490
                        liga_component_pos: 0, //glyphs[i].liga_component_pos,
491
                        glyph_origin: GlyphOrigin::Direct,
492
                        flags,
493
                        extra_data: glyphs[i].extra_data.clone(),
494
                        variation: glyphs[i].variation,
495
                    };
496
                    glyphs.insert(i + j, glyph);
497
                }
498
                Ok(Some(sequence_table.substitute_glyphs.len()))
499
            } else {
500
                // the spec forbids this, but implementations all allow it
501
                glyphs.remove(i);
502
                Ok(Some(0))
503
            }
504
        }
505
        None => Ok(None),
506
    }
507
}
508

            
509
fn alternatesubst_would_apply<'a, T: GlyphData>(
510
    subtables: &'a [AlternateSubst],
511
    glyph: &RawGlyph<T>,
512
) -> Result<Option<&'a AlternateSet>, ParseError> {
513
    let glyph_index = glyph.glyph_index;
514
    for alternate_subst in subtables {
515
        if let Some(alternate_set) = alternate_subst.apply_glyph(glyph_index)? {
516
            return Ok(Some(alternate_set));
517
        }
518
    }
519
    Ok(None)
520
}
521

            
522
fn alternatesubst<T: GlyphData>(
523
    subtables: &[AlternateSubst],
524
    alternate: usize,
525
    glyph: &mut RawGlyph<T>,
526
) -> Result<(), ParseError> {
527
    if let Some(alternateset) = alternatesubst_would_apply(subtables, glyph)? {
528
        // TODO allow users to specify which alternate glyph they want
529
        if alternate < alternateset.alternate_glyphs.len() {
530
            glyph.glyph_index = alternateset.alternate_glyphs[alternate];
531
            glyph.glyph_origin = GlyphOrigin::Direct;
532
        }
533
    }
534
    Ok(())
535
}
536

            
537
665982
fn ligaturesubst_would_apply<'a, T: GlyphData>(
538
665982
    opt_gdef_table: Option<&GDEFTable>,
539
665982
    subtables: &'a [LigatureSubst],
540
665982
    match_type: MatchType,
541
665982
    i: usize,
542
665982
    glyphs: &[RawGlyph<T>],
543
665982
) -> Result<Option<&'a Ligature>, ParseError> {
544
665982
    let glyph_index = glyphs[i].glyph_index;
545
1330776
    for ligature_subst in subtables {
546
665982
        if let Some(ligatureset) = ligature_subst.apply_glyph(glyph_index)? {
547
420876
            for ligature in &ligatureset.ligatures {
548
410616
                if ligature.matches(match_type, opt_gdef_table, i, glyphs) {
549
1188
                    return Ok(Some(ligature));
550
409428
                }
551
            }
552
654534
        }
553
    }
554
664794
    Ok(None)
555
665982
}
556

            
557
665982
fn ligaturesubst<T: GlyphData>(
558
665982
    opt_gdef_table: Option<&GDEFTable>,
559
665982
    subtables: &[LigatureSubst],
560
665982
    match_type: MatchType,
561
665982
    i: usize,
562
665982
    glyphs: &mut Vec<RawGlyph<T>>,
563
665982
) -> Result<Option<(usize, usize)>, ParseError> {
564
665982
    match ligaturesubst_would_apply(opt_gdef_table, subtables, match_type, i, glyphs)? {
565
1188
        Some(ligature) => Ok(Some((
566
1188
            ligature.component_glyphs.len(),
567
1188
            ligature.apply(match_type, opt_gdef_table, i, glyphs),
568
1188
        ))),
569
664794
        None => Ok(None),
570
    }
571
665982
}
572

            
573
fn contextsubst_would_apply<'a, T: GlyphData>(
574
    opt_gdef_table: Option<&GDEFTable>,
575
    subtables: &'a [ContextLookup<GSUB>],
576
    match_type: MatchType,
577
    i: usize,
578
    glyphs: &[RawGlyph<T>],
579
) -> Result<Option<Box<SubstContext<'a>>>, ParseError> {
580
    let glyph_index = glyphs[i].glyph_index;
581
    for context_lookup in subtables {
582
        if let Some(context) = context_lookup_info(context_lookup, glyph_index, |context| {
583
            context.matches(opt_gdef_table, match_type, glyphs, i)
584
        })? {
585
            return Ok(Some(context));
586
        }
587
    }
588
    Ok(None)
589
}
590

            
591
fn contextsubst<T: GlyphData>(
592
    recursion_limit: usize,
593
    gsub_cache: &LayoutCache<GSUB>,
594
    lookup_list: &LookupList<GSUB>,
595
    opt_gdef_table: Option<&GDEFTable>,
596
    subtables: &[ContextLookup<GSUB>],
597
    feature_tag: u32,
598
    match_type: MatchType,
599
    i: usize,
600
    glyphs: &mut Vec<RawGlyph<T>>,
601
    max_glyphs: usize,
602
) -> Result<Option<(usize, isize)>, ParseError> {
603
    match contextsubst_would_apply(opt_gdef_table, subtables, match_type, i, glyphs)? {
604
        Some(subst) => apply_subst_context(
605
            recursion_limit,
606
            gsub_cache,
607
            lookup_list,
608
            opt_gdef_table,
609
            feature_tag,
610
            match_type,
611
            &subst,
612
            i,
613
            glyphs,
614
            max_glyphs,
615
        ),
616
        None => Ok(None),
617
    }
618
}
619

            
620
838836
fn chaincontextsubst_would_apply<'a, T: GlyphData>(
621
838836
    opt_gdef_table: Option<&GDEFTable>,
622
838836
    subtables: &'a [ChainContextLookup<GSUB>],
623
838836
    match_type: MatchType,
624
838836
    i: usize,
625
838836
    glyphs: &[RawGlyph<T>],
626
838836
) -> Result<Option<Box<SubstContext<'a>>>, ParseError> {
627
838836
    let glyph_index = glyphs[i].glyph_index;
628
2393496
    for chain_context_lookup in subtables {
629
        if let Some(context) =
630
1554660
            chain_context_lookup_info(chain_context_lookup, glyph_index, |context| {
631
6858
                context.matches(opt_gdef_table, match_type, glyphs, i)
632
6858
            })?
633
        {
634
            return Ok(Some(context));
635
1554660
        }
636
    }
637
838836
    Ok(None)
638
838836
}
639

            
640
838836
fn chaincontextsubst<T: GlyphData>(
641
838836
    recursion_limit: usize,
642
838836
    gsub_cache: &LayoutCache<GSUB>,
643
838836
    lookup_list: &LookupList<GSUB>,
644
838836
    opt_gdef_table: Option<&GDEFTable>,
645
838836
    subtables: &[ChainContextLookup<GSUB>],
646
838836
    feature_tag: u32,
647
838836
    match_type: MatchType,
648
838836
    i: usize,
649
838836
    glyphs: &mut Vec<RawGlyph<T>>,
650
838836
    max_glyphs: usize,
651
838836
) -> Result<Option<(usize, isize)>, ParseError> {
652
838836
    match chaincontextsubst_would_apply(opt_gdef_table, subtables, match_type, i, glyphs)? {
653
        Some(subst) => apply_subst_context(
654
            recursion_limit,
655
            gsub_cache,
656
            lookup_list,
657
            opt_gdef_table,
658
            feature_tag,
659
            match_type,
660
            &subst,
661
            i,
662
            glyphs,
663
            max_glyphs,
664
        ),
665
838836
        None => Ok(None),
666
    }
667
838836
}
668

            
669
fn reversechainsinglesubst_would_apply<T: GlyphData>(
670
    opt_gdef_table: Option<&GDEFTable>,
671
    subtables: &[ReverseChainSingleSubst],
672
    match_type: MatchType,
673
    i: usize,
674
    glyphs: &[RawGlyph<T>],
675
) -> Result<Option<u16>, ParseError> {
676
    let glyph_index = glyphs[i].glyph_index;
677
    for reversechainsinglesubst in subtables {
678
        if let new_glyph_index @ Some(_) =
679
            reversechainsinglesubst.apply_glyph(glyph_index, |context| {
680
                context
681
                    .matches(opt_gdef_table, match_type, glyphs, i)
682
                    .is_some()
683
            })?
684
        {
685
            return Ok(new_glyph_index);
686
        }
687
    }
688
    Ok(None)
689
}
690

            
691
fn reversechainsinglesubst<T: GlyphData>(
692
    opt_gdef_table: Option<&GDEFTable>,
693
    subtables: &[ReverseChainSingleSubst],
694
    match_type: MatchType,
695
    i: usize,
696
    glyphs: &mut [RawGlyph<T>],
697
) -> Result<(), ParseError> {
698
    if let Some(output_glyph_index) =
699
        reversechainsinglesubst_would_apply(opt_gdef_table, subtables, match_type, i, glyphs)?
700
    {
701
        glyphs[i].glyph_index = output_glyph_index;
702
        glyphs[i].glyph_origin = GlyphOrigin::Direct;
703
    }
704
    Ok(())
705
}
706

            
707
fn apply_subst_context<T: GlyphData>(
708
    recursion_limit: usize,
709
    gsub_cache: &LayoutCache<GSUB>,
710
    lookup_list: &LookupList<GSUB>,
711
    opt_gdef_table: Option<&GDEFTable>,
712
    feature_tag: u32,
713
    match_type: MatchType,
714
    subst: &SubstContext<'_>,
715
    i: usize,
716
    glyphs: &mut Vec<RawGlyph<T>>,
717
    max_glyphs: usize,
718
) -> Result<Option<(usize, isize)>, ParseError> {
719
    let mut changes = 0;
720
    let len = match match_type.find_nth(
721
        opt_gdef_table,
722
        glyphs,
723
        i,
724
        subst.match_context.input_table.len(),
725
    ) {
726
        Some(last) => last - i + 1,
727
        None => return Ok(None), // FIXME actually an error/impossible?
728
    };
729
    for (subst_index, subst_lookup_index) in subst.lookup_array {
730
        if let Some(changes0) = apply_subst(
731
            recursion_limit,
732
            gsub_cache,
733
            lookup_list,
734
            opt_gdef_table,
735
            match_type,
736
            usize::from(*subst_index),
737
            usize::from(*subst_lookup_index),
738
            feature_tag,
739
            glyphs,
740
            max_glyphs,
741
            i,
742
        )? {
743
            changes += changes0
744
        }
745
    }
746
    match checked_add(len, changes) {
747
        Some(new_len) => Ok(Some((new_len, changes))),
748
        None => panic!("apply_subst_context: len < 0"),
749
    }
750
}
751

            
752
fn checked_add(base: usize, changes: isize) -> Option<usize> {
753
    if changes < 0 {
754
        base.checked_sub(changes.wrapping_abs() as usize)
755
    } else {
756
        base.checked_add(changes as usize)
757
    }
758
}
759

            
760
fn apply_subst<T: GlyphData>(
761
    recursion_limit: usize,
762
    gsub_cache: &LayoutCache<GSUB>,
763
    lookup_list: &LookupList<GSUB>,
764
    opt_gdef_table: Option<&GDEFTable>,
765
    parent_match_type: MatchType,
766
    subst_index: usize,
767
    lookup_index: usize,
768
    feature_tag: u32,
769
    glyphs: &mut Vec<RawGlyph<T>>,
770
    max_glyphs: usize,
771
    index: usize,
772
) -> Result<Option<isize>, ParseError> {
773
    let lookup = lookup_list.lookup_cache_gsub(gsub_cache, lookup_index)?;
774
    let match_type = MatchType::from_lookup_flag(lookup.lookup_flag, lookup.mark_filtering_set);
775
    let i = match parent_match_type.find_nth(opt_gdef_table, glyphs, index, subst_index) {
776
        Some(index1) => index1,
777
        None => return Ok(None), // FIXME error?
778
    };
779
    match lookup.lookup_subtables {
780
        SubstLookup::SingleSubst(ref subtables) => {
781
            singlesubst(subtables, feature_tag, &mut glyphs[i])?;
782
            Ok(Some(0))
783
        }
784
        SubstLookup::MultipleSubst(ref subtables) => {
785
            match multiplesubst(subtables, i, glyphs, max_glyphs)? {
786
                Some(replace_count) => Ok(Some((replace_count as isize) - 1)),
787
                None => Ok(None),
788
            }
789
        }
790
        SubstLookup::AlternateSubst(ref subtables) => {
791
            alternatesubst(subtables, 0, &mut glyphs[i])?;
792
            Ok(Some(0))
793
        }
794
        SubstLookup::LigatureSubst(ref subtables) => {
795
            match ligaturesubst(opt_gdef_table, subtables, match_type, i, glyphs)? {
796
                Some((removed_count, _skip_count)) => Ok(Some(-(removed_count as isize))),
797
                None => Ok(None), // FIXME error?
798
            }
799
        }
800
        SubstLookup::ContextSubst(ref subtables) => {
801
            if recursion_limit > 0 {
802
                match contextsubst(
803
                    recursion_limit - 1,
804
                    gsub_cache,
805
                    lookup_list,
806
                    opt_gdef_table,
807
                    subtables,
808
                    feature_tag,
809
                    match_type,
810
                    i,
811
                    glyphs,
812
                    max_glyphs,
813
                )? {
814
                    Some((_length, change)) => Ok(Some(change)),
815
                    None => Ok(None),
816
                }
817
            } else {
818
                Err(ParseError::LimitExceeded)
819
            }
820
        }
821
        SubstLookup::ChainContextSubst(ref subtables) => {
822
            if recursion_limit > 0 {
823
                match chaincontextsubst(
824
                    recursion_limit - 1,
825
                    gsub_cache,
826
                    lookup_list,
827
                    opt_gdef_table,
828
                    subtables,
829
                    feature_tag,
830
                    match_type,
831
                    i,
832
                    glyphs,
833
                    max_glyphs,
834
                )? {
835
                    Some((_length, change)) => Ok(Some(change)),
836
                    None => Ok(None),
837
                }
838
            } else {
839
                Err(ParseError::LimitExceeded)
840
            }
841
        }
842
        SubstLookup::ReverseChainSingleSubst(ref subtables) => {
843
            reversechainsinglesubst(opt_gdef_table, subtables, match_type, i, glyphs)?;
844
            Ok(Some(0))
845
        }
846
    }
847
}
848

            
849
fn build_lookups_custom(
850
    gsub_table: &LayoutTable<GSUB>,
851
    langsys: &LangSys,
852
    feature_tags: &[FeatureInfo],
853
    feature_mask: FeatureMask,
854
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
855
) -> Result<BTreeMap<usize, u32>, ParseError> {
856
    let mut lookups = BTreeMap::new();
857
    for feature_info in feature_tags {
858
        // Skip features already applied via the feature mask to avoid
859
        // applying them twice (e.g. font-feature-settings: "ccmp" 1).
860
        if let Some(feature) = Feature::from_tag(feature_info.feature_tag) {
861
            if feature_mask.contains(feature) {
862
                continue;
863
            }
864
        }
865
        if let Some(feature_table) = gsub_table.find_langsys_feature(
866
            langsys,
867
            feature_info.feature_tag,
868
            feature_variations,
869
        )? {
870
            lookups.extend(
871
                feature_table
872
                    .lookup_indices
873
                    .iter()
874
                    .map(|&lookup_index| (usize::from(lookup_index), feature_info.feature_tag)),
875
            );
876
        }
877
    }
878
    Ok(lookups)
879
}
880

            
881
5238
fn build_lookups_default(
882
5238
    gsub_table: &LayoutTable<GSUB>,
883
5238
    langsys: &LangSys,
884
5238
    feature_masks: FeatureMask,
885
5238
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
886
5238
) -> Result<Vec<(usize, u32)>, ParseError> {
887
5238
    let mut lookups = BTreeMap::new();
888
20628
    for feature in feature_masks {
889
15390
        let feature_tag = feature.tag();
890
15390
        if let Some(feature_table) =
891
15390
            gsub_table.find_langsys_feature(langsys, feature_tag, feature_variations)?
892
        {
893
45198
            for lookup_index in &feature_table.lookup_indices {
894
45198
                lookups.insert(usize::from(*lookup_index), feature_tag);
895
45198
            }
896
        } else if feature == Feature::VRT2_OR_VERT {
897
            let vert_tag = tag::VERT;
898
            if let Some(feature_table) =
899
                gsub_table.find_langsys_feature(langsys, vert_tag, feature_variations)?
900
            {
901
                for lookup_index in &feature_table.lookup_indices {
902
                    lookups.insert(usize::from(*lookup_index), vert_tag);
903
                }
904
            }
905
        }
906
    }
907

            
908
    // note: iter() returns sorted by key
909
5238
    Ok(lookups.into_iter().collect())
910
5238
}
911

            
912
5238
fn make_supported_features_mask(
913
5238
    gsub_table: &LayoutTable<GSUB>,
914
5238
    langsys: &LangSys,
915
5238
) -> Result<FeatureMask, ParseError> {
916
5238
    let mut feature_mask = FeatureMask::empty();
917
105462
    for feature_index in langsys.feature_indices_iter() {
918
105462
        let feature_record = gsub_table.feature_by_index(*feature_index)?;
919
105462
        feature_mask |= FeatureMask::from_tag(feature_record.feature_tag);
920
    }
921
5238
    Ok(feature_mask)
922
5238
}
923

            
924
49248
fn lang_tag_key(opt_lang_tag: Option<u32>) -> u32 {
925
    // `DFLT` is not a valid lang tag so we use it to indicate the default
926
49248
    opt_lang_tag.unwrap_or(tag::DFLT)
927
49248
}
928

            
929
24624
fn get_supported_features(
930
24624
    gsub_cache: &LayoutCache<GSUB>,
931
24624
    script_tag: u32,
932
24624
    opt_lang_tag: Option<u32>,
933
24624
) -> Result<FeatureMask, ParseError> {
934
24624
    let feature_mask = match gsub_cache
935
24624
        .supported_features
936
24624
        .lock()
937
24624
        .unwrap()
938
24624
        .entry((script_tag, lang_tag_key(opt_lang_tag)))
939
    {
940
19116
        Entry::Occupied(entry) => BitFlags::from_bits_truncate(*entry.get()),
941
5508
        Entry::Vacant(entry) => {
942
5508
            let gsub_table = &gsub_cache.layout_table;
943
5508
            let feature_mask =
944
5508
                if let Some(script) = gsub_table.find_script_or_default(script_tag)? {
945
5238
                    if let Some(langsys) = script.find_langsys_or_default(opt_lang_tag)? {
946
5238
                        make_supported_features_mask(gsub_table, langsys)?
947
                    } else {
948
                        FeatureMask::empty()
949
                    }
950
                } else {
951
270
                    FeatureMask::empty()
952
                };
953
5508
            entry.insert(feature_mask.bits());
954
5508
            feature_mask
955
        }
956
    };
957
24624
    Ok(feature_mask)
958
24624
}
959

            
960
fn find_alternate(features_list: &[FeatureInfo], feature_tag: u32) -> Option<usize> {
961
    for feature_info in features_list {
962
        if feature_info.feature_tag == feature_tag {
963
            return feature_info.alternate;
964
        }
965
    }
966
    None
967
}
968

            
969
24624
pub fn replace_missing_glyphs<T: GlyphData>(glyphs: &mut [RawGlyph<T>], num_glyphs: u16) {
970
171558
    for glyph in glyphs.iter_mut() {
971
171558
        if glyph.glyph_index >= num_glyphs {
972
            glyph.unicodes = tiny_vec![];
973
            glyph.glyph_index = 0;
974
            glyph.liga_component_pos = 0;
975
            glyph.glyph_origin = GlyphOrigin::Direct;
976
            glyph.flags = RawGlyphFlags::empty();
977
            glyph.variation = None;
978
171558
        }
979
    }
980
24624
}
981

            
982
24624
fn strip_joiners<T: GlyphData>(glyphs: &mut Vec<RawGlyph<T>>) {
983
171558
    glyphs.retain(|g| match g.glyph_origin {
984
        GlyphOrigin::Char('\u{200C}') => false,
985
        GlyphOrigin::Char('\u{200D}') => false,
986
171558
        _ => true,
987
171558
    })
988
24624
}
989

            
990
#[enumflags2::bitflags]
991
#[repr(u64)]
992
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
993
#[allow(non_camel_case_types)]
994
pub enum Feature {
995
    ABVF,
996
    ABVS,
997
    AFRC,
998
    AKHN,
999
    BLWF,
    BLWS,
    C2SC,
    CALT,
    CASE,
    CCMP,
    CFAR,
    CJCT,
    CLIG,
    CPSP,
    CSWH,
    DLIG,
    FINA,
    FIN2,
    FIN3,
    FRAC,
    HALF,
    HALN,
    HIST,
    HLIG,
    INIT,
    ISOL,
    LIGA,
    LNUM,
    LOCL,
    MEDI,
    MED2,
    MSET,
    NUKT,
    ONUM,
    ORDN,
    PNUM,
    PREF,
    PRES,
    PSTF,
    PSTS,
    RCLT,
    RKRF,
    RLIG,
    RPHF,
    SMCP,
    TNUM,
    VATU,
    VRT2_OR_VERT,
    ZERO,
    RVRN,
}
pub type FeatureMask = BitFlags<Feature>;
impl Feature {
    /// Convert a single feature into a `FeatureMask` containing just that feature.
    pub fn mask(self) -> FeatureMask {
        FeatureMask::from(self)
    }
    /// Return the OpenType GSUB feature tag corresponding to this feature.
15390
    pub fn tag(self) -> u32 {
15390
        match self {
            Feature::ABVF => tag::ABVF,
            Feature::ABVS => tag::ABVS,
            Feature::AFRC => tag::AFRC,
            Feature::AKHN => tag::AKHN,
            Feature::BLWF => tag::BLWF,
            Feature::BLWS => tag::BLWS,
            Feature::C2SC => tag::C2SC,
4914
            Feature::CALT => tag::CALT,
            Feature::CASE => tag::CASE,
5238
            Feature::CCMP => tag::CCMP,
            Feature::CFAR => tag::CFAR,
            Feature::CJCT => tag::CJCT,
            Feature::CLIG => tag::CLIG,
            Feature::CPSP => tag::CPSP,
            Feature::CSWH => tag::CSWH,
            Feature::DLIG => tag::DLIG,
            Feature::FINA => tag::FINA,
            Feature::FIN2 => tag::FIN2,
            Feature::FIN3 => tag::FIN3,
            Feature::FRAC => tag::FRAC,
            Feature::HALF => tag::HALF,
            Feature::HALN => tag::HALN,
            Feature::HIST => tag::HIST,
            Feature::HLIG => tag::HLIG,
            Feature::INIT => tag::INIT,
            Feature::ISOL => tag::ISOL,
5238
            Feature::LIGA => tag::LIGA,
            Feature::LNUM => tag::LNUM,
            Feature::LOCL => tag::LOCL,
            Feature::MEDI => tag::MEDI,
            Feature::MED2 => tag::MED2,
            Feature::MSET => tag::MSET,
            Feature::NUKT => tag::NUKT,
            Feature::ONUM => tag::ONUM,
            Feature::ORDN => tag::ORDN,
            Feature::PNUM => tag::PNUM,
            Feature::PREF => tag::PREF,
            Feature::PRES => tag::PRES,
            Feature::PSTF => tag::PSTF,
            Feature::PSTS => tag::PSTS,
            Feature::RCLT => tag::RCLT,
            Feature::RKRF => tag::RKRF,
            Feature::RLIG => tag::RLIG,
            Feature::RPHF => tag::RPHF,
            Feature::RVRN => tag::RVRN,
            Feature::SMCP => tag::SMCP,
            Feature::TNUM => tag::TNUM,
            Feature::VATU => tag::VATU,
            Feature::VRT2_OR_VERT => tag::VRT2,
            Feature::ZERO => tag::ZERO,
        }
15390
    }
105462
    pub fn from_tag(tag: u32) -> Option<Feature> {
105462
        match tag {
            tag::ABVF => Some(Feature::ABVF),
            tag::ABVS => Some(Feature::ABVS),
            tag::AFRC => Some(Feature::AFRC),
            tag::AKHN => Some(Feature::AKHN),
            tag::BLWF => Some(Feature::BLWF),
            tag::BLWS => Some(Feature::BLWS),
            tag::C2SC => Some(Feature::C2SC),
4914
            tag::CALT => Some(Feature::CALT),
5238
            tag::CASE => Some(Feature::CASE),
5238
            tag::CCMP => Some(Feature::CCMP),
            tag::CFAR => Some(Feature::CFAR),
            tag::CJCT => Some(Feature::CJCT),
            tag::CLIG => Some(Feature::CLIG),
            tag::CPSP => Some(Feature::CPSP),
            tag::CSWH => Some(Feature::CSWH),
5238
            tag::DLIG => Some(Feature::DLIG),
            tag::FINA => Some(Feature::FINA),
            tag::FIN2 => Some(Feature::FIN2),
            tag::FIN3 => Some(Feature::FIN3),
4914
            tag::FRAC => Some(Feature::FRAC),
            tag::HALF => Some(Feature::HALF),
            tag::HALN => Some(Feature::HALN),
            tag::HIST => Some(Feature::HIST),
324
            tag::HLIG => Some(Feature::HLIG),
            tag::INIT => Some(Feature::INIT),
            tag::ISOL => Some(Feature::ISOL),
5238
            tag::LIGA => Some(Feature::LIGA),
4914
            tag::LNUM => Some(Feature::LNUM),
            tag::LOCL => Some(Feature::LOCL),
            tag::MEDI => Some(Feature::MEDI),
            tag::MED2 => Some(Feature::MED2),
            tag::MSET => Some(Feature::MSET),
            tag::NUKT => Some(Feature::NUKT),
4914
            tag::ONUM => Some(Feature::ONUM),
4914
            tag::ORDN => Some(Feature::ORDN),
4914
            tag::PNUM => Some(Feature::PNUM),
            tag::PREF => Some(Feature::PREF),
            tag::PRES => Some(Feature::PRES),
            tag::PSTF => Some(Feature::PSTF),
            tag::PSTS => Some(Feature::PSTS),
            tag::RCLT => Some(Feature::RCLT),
            tag::RKRF => Some(Feature::RKRF),
            tag::RLIG => Some(Feature::RLIG),
            tag::RPHF => Some(Feature::RPHF),
            tag::RVRN => Some(Feature::RVRN),
            tag::SMCP => Some(Feature::SMCP),
4914
            tag::TNUM => Some(Feature::TNUM),
            tag::VATU => Some(Feature::VATU),
            tag::VERT => Some(Feature::VRT2_OR_VERT),
            tag::VRT2 => Some(Feature::VRT2_OR_VERT),
            tag::ZERO => Some(Feature::ZERO),
49788
            _ => None,
        }
105462
    }
}
/// Extension trait adding methods to `FeatureMask` (`BitFlags<Feature>`).
pub trait FeatureMaskExt {
    /// Convert a feature tag to a FeatureMask. Returns empty mask for unknown tags.
    fn from_tag(tag: u32) -> FeatureMask;
    /// Return the default FeatureMask for basic Latin/default shaping.
    fn default_mask() -> FeatureMask;
    /// Iterate over the individual features in a FeatureMask.
    fn features(&self) -> impl Iterator<Item = FeatureInfo>;
}
impl FeatureMaskExt for FeatureMask {
105462
    fn from_tag(tag: u32) -> FeatureMask {
105462
        Feature::from_tag(tag).map_or(FeatureMask::empty(), FeatureMask::from)
105462
    }
24168
    fn default_mask() -> FeatureMask {
24168
        Feature::CCMP
24168
            | Feature::RLIG
24168
            | Feature::CLIG
24168
            | Feature::LIGA
24168
            | Feature::LOCL
24168
            | Feature::CALT
24168
    }
23274
    fn features(&self) -> impl Iterator<Item = FeatureInfo> {
23274
        self.iter().map(|feature| FeatureInfo {
            feature_tag: feature.tag(),
            alternate: None,
        })
23274
    }
}
pub fn features_supported(
    gsub_cache: &LayoutCache<GSUB>,
    script_tag: u32,
    opt_lang_tag: Option<u32>,
    feature_mask: FeatureMask,
) -> Result<bool, ShapingError> {
    let supported_features = get_supported_features(gsub_cache, script_tag, opt_lang_tag)?;
    Ok(supported_features.contains(feature_mask))
}
24624
pub fn get_lookups_cache_index(
24624
    gsub_cache: &LayoutCache<GSUB>,
24624
    script_tag: u32,
24624
    opt_lang_tag: Option<u32>,
24624
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
24624
    feature_mask: FeatureMask,
24624
) -> Result<usize, ParseError> {
24624
    let index = match gsub_cache.lookups_index.lock().unwrap().entry((
24624
        script_tag,
24624
        lang_tag_key(opt_lang_tag),
24624
        feature_mask.bits(),
24624
    )) {
19116
        Entry::Occupied(entry) => *entry.get(),
5508
        Entry::Vacant(entry) => {
5508
            let gsub_table = &gsub_cache.layout_table;
5508
            if let Some(script) = gsub_table.find_script_or_default(script_tag)? {
5238
                if let Some(langsys) = script.find_langsys_or_default(opt_lang_tag)? {
5238
                    let lookups = build_lookups_default(
5238
                        gsub_table,
5238
                        langsys,
5238
                        feature_mask,
5238
                        feature_variations,
                    )?;
5238
                    let mut cached_lookups = gsub_cache.cached_lookups.lock().unwrap();
5238
                    let index = cached_lookups.len();
5238
                    cached_lookups.push(lookups);
5238
                    *entry.insert(index)
                } else {
                    *entry.insert(0)
                }
            } else {
270
                *entry.insert(0)
            }
        }
    };
24624
    Ok(index)
24624
}
/// Perform glyph substitution according to the supplied features, script and language.
///
/// `dotted_circle_index` is the glyph index of U+25CC DOTTED CIRCLE: ◌. This is inserted
/// when shaping some complex scripts where the input text contains incomplete syllables.
/// If you have an instance of `FontDataImpl` the glyph index can be retrieved via the
/// `lookup_glyph_index` method.
///
/// ## Example
///
/// The following shows a complete example of loading a font, mapping text to glyphs, and
/// applying glyph substitution.
///
/// ```
/// use std::error::Error;
/// use std::sync::Arc;
///
/// use allsorts::binary::read::ReadScope;
/// use allsorts::error::ParseError;
/// use allsorts::font::{MatchingPresentation};
/// use allsorts::font_data::FontData;
/// use allsorts::gsub::{FeatureMask, FeatureMaskExt, GlyphOrigin, RawGlyph, RawGlyphFlags};
/// use allsorts::tinyvec::tiny_vec;
/// use allsorts::unicode::VariationSelector;
/// use allsorts::DOTTED_CIRCLE;
/// use allsorts::{gsub, tag, Font};
///
/// fn shape(text: &str) -> Result<Vec<RawGlyph<()>>, Box<dyn Error>> {
///     let script = tag::from_string("LATN")?;
///     let lang = tag::from_string("DFLT")?;
///     let buffer = std::fs::read("tests/fonts/opentype/Klei.otf")
///         .expect("unable to read Klei.otf");
///     let scope = ReadScope::new(&buffer);
///     let font_file = scope.read::<FontData<'_>>()?;
///     // Use a different index to access other fonts in a font collection (E.g. TTC)
///     let provider = font_file.table_provider(0)?;
///     let mut font = Font::new(provider)?;
///
///     let opt_gsub_cache = font.gsub_cache()?;
///     let opt_gpos_cache = font.gpos_cache()?;
///     let opt_gdef_table = font.gdef_table()?;
///     let opt_gdef_table = opt_gdef_table.as_ref().map(Arc::as_ref);
///
///     // Map glyphs
///     //
///     // We look ahead in the char stream for variation selectors. If one is found it is used for
///     // mapping the current glyph. When a variation selector is reached in the stream it is
///     // skipped as it was handled as part of the preceding character.
///     let mut chars_iter = text.chars().peekable();
///     let mut glyphs = Vec::new();
///     while let Some(ch) = chars_iter.next() {
///         match VariationSelector::try_from(ch) {
///             Ok(_) => {} // filter out variation selectors
///             Err(()) => {
///                 let vs = chars_iter
///                     .peek()
///                     .and_then(|&next| VariationSelector::try_from(next).ok());
///                 let (glyph_index, used_variation) = font.lookup_glyph_index(
///                     ch,
///                     MatchingPresentation::NotRequired,
///                     vs,
///                 );
///                 let glyph = RawGlyph {
///                     unicodes: tiny_vec![[char; 1] => ch],
///                     glyph_index: glyph_index,
///                     liga_component_pos: 0,
///                     glyph_origin: GlyphOrigin::Char(ch),
///                     flags: RawGlyphFlags::empty(),
///                     extra_data: (),
///                     variation: Some(used_variation),
///                 };
///                 glyphs.push(glyph);
///             }
///         }
///     }
///
///     let (dotted_circle_index, _) = font.lookup_glyph_index(
///         DOTTED_CIRCLE,
///         MatchingPresentation::NotRequired,
///         None,
///     );
///
///     // If the font was a variable font you would want to supply the variation tuple
///     let tuple = None;
///
///     // Apply gsub if table is present
///     let num_glyphs = font.num_glyphs();
///     if let Some(gsub_cache) = opt_gsub_cache {
///         gsub::apply(
///             dotted_circle_index,
///             &gsub_cache,
///             opt_gdef_table,
///             script,
///             Some(lang),
///             FeatureMask::default_mask(),
///             &[],
///             tuple,
///             num_glyphs,
///             &mut glyphs,
///         )?;
///     }
///
///     // This is where you would apply `gpos` if the table is present.
///
///     Ok(glyphs)
/// }
///
/// match shape("This is the first example.") {
///     Ok(glyphs) => {
///         assert!(!glyphs.is_empty());
///     }
///     Err(err) => panic!("Unable to shape text: {}", err),
/// }
/// ```
24624
pub fn apply(
24624
    dotted_circle_index: u16,
24624
    gsub_cache: &LayoutCache<GSUB>,
24624
    opt_gdef_table: Option<&GDEFTable>,
24624
    script_tag: u32,
24624
    opt_lang_tag: Option<u32>,
24624
    mut feature_mask: FeatureMask,
24624
    custom_features: &[FeatureInfo],
24624
    tuple: Option<Tuple<'_>>,
24624
    num_glyphs: u16,
24624
    glyphs: &mut Vec<RawGlyph<()>>,
24624
) -> Result<(), ShapingError> {
24624
    let max_glyphs = glyphs.len().saturating_mul(MAX_GLYPHS_FACTOR);
24624
    let gsub_table = &gsub_cache.layout_table;
24624
    let feature_variations = gsub_table.feature_variations(tuple)?;
24624
    let feature_variations = feature_variations.as_ref();
    // Apply rvrn early if font is variable:
    //
    // "The 'rvrn' feature is mandatory: it should be active by default and not directly exposed to
    // user control."
    //
    // "It should be processed early in GSUB processing, before application of the localized forms
    // feature or features related to shaping of complex scripts or discretionary typographic
    // effects."
    //
    // https://learn.microsoft.com/en-us/typography/opentype/spec/features_pt#tag-rvrn
24624
    if tuple.is_some() {
        apply_rvrn(
            gsub_cache,
            opt_gdef_table,
            script_tag,
            opt_lang_tag,
            feature_variations,
            glyphs,
            max_glyphs,
        )?;
24624
    }
    // Extract optional features requested by the user for script shapers.
24624
    let extra_features = feature_mask & (Feature::DLIG | Feature::HLIG | Feature::HIST);
24624
    match ScriptType::from(script_tag) {
        ScriptType::Arabic => scripts::arabic::gsub_apply_arabic(
            gsub_cache,
            gsub_table,
            opt_gdef_table,
            script_tag,
            opt_lang_tag,
            feature_variations,
            extra_features,
            glyphs,
            max_glyphs,
        )?,
        ScriptType::Indic => scripts::indic::gsub_apply_indic(
            dotted_circle_index,
            gsub_cache,
            gsub_table,
            opt_gdef_table,
            script_tag,
            opt_lang_tag,
            feature_variations,
            extra_features,
            glyphs,
        )?,
        ScriptType::Khmer => scripts::khmer::gsub_apply_khmer(
            dotted_circle_index,
            gsub_cache,
            gsub_table,
            opt_gdef_table,
            script_tag,
            opt_lang_tag,
            feature_variations,
            extra_features,
            glyphs,
        )?,
        ScriptType::Mongolian => scripts::mongolian::gsub_apply_mongolian(
            gsub_cache,
            gsub_table,
            opt_gdef_table,
            script_tag,
            opt_lang_tag,
            feature_variations,
            extra_features,
            glyphs,
            max_glyphs,
        )?,
        ScriptType::Myanmar => scripts::myanmar::gsub_apply_myanmar(
            dotted_circle_index,
            gsub_cache,
            gsub_table,
            opt_gdef_table,
            opt_lang_tag,
            feature_variations,
            extra_features,
            glyphs,
        )?,
        ScriptType::Syriac => scripts::syriac::gsub_apply_syriac(
            gsub_cache,
            gsub_table,
            opt_gdef_table,
            script_tag,
            opt_lang_tag,
            feature_variations,
            extra_features,
            glyphs,
            max_glyphs,
        )?,
        ScriptType::Tibetan => scripts::tibetan::gsub_apply_tibetan(
            gsub_cache,
            gsub_table,
            opt_gdef_table,
            script_tag,
            opt_lang_tag,
            feature_variations,
            extra_features,
            glyphs,
            max_glyphs,
        )?,
        ScriptType::ThaiLao => scripts::thai_lao::gsub_apply_thai_lao(
            gsub_cache,
            gsub_table,
            opt_gdef_table,
            script_tag,
            opt_lang_tag,
            feature_variations,
            extra_features,
            glyphs,
            max_glyphs,
        )?,
        ScriptType::Default => {
24624
            feature_mask |= Feature::CCMP | Feature::RLIG | Feature::LOCL;
24624
            feature_mask &= get_supported_features(gsub_cache, script_tag, opt_lang_tag)?;
24624
            if feature_mask.contains(Feature::FRAC) {
                let index_frac = get_lookups_cache_index(
                    gsub_cache,
                    script_tag,
                    opt_lang_tag,
                    feature_variations,
                    feature_mask,
                )?;
                feature_mask.remove(Feature::FRAC);
                let index = get_lookups_cache_index(
                    gsub_cache,
                    script_tag,
                    opt_lang_tag,
                    feature_variations,
                    feature_mask,
                )?;
                let cached_lookups = gsub_cache.cached_lookups.lock().unwrap();
                let lookups = &cached_lookups[index];
                let lookups_frac = &cached_lookups[index_frac];
                gsub_apply_lookups_frac(
                    gsub_cache,
                    gsub_table,
                    opt_gdef_table,
                    lookups,
                    lookups_frac,
                    glyphs,
                    max_glyphs,
                )?;
            } else {
24624
                let index = get_lookups_cache_index(
24624
                    gsub_cache,
24624
                    script_tag,
24624
                    opt_lang_tag,
24624
                    feature_variations,
24624
                    feature_mask,
                )?;
24624
                let lookups = &gsub_cache.cached_lookups.lock().unwrap()[index];
24624
                gsub_apply_lookups(
24624
                    gsub_cache,
24624
                    gsub_table,
24624
                    opt_gdef_table,
24624
                    lookups,
24624
                    glyphs,
24624
                    max_glyphs,
                )?;
            }
        }
    }
    // Apply custom features (font-variant-alternates) after script-specific
    // shaping but before cleanup.
24624
    gsub_apply_custom_features(
24624
        gsub_cache,
24624
        gsub_table,
24624
        opt_gdef_table,
24624
        script_tag,
24624
        opt_lang_tag,
24624
        feature_variations,
24624
        feature_mask,
24624
        custom_features,
24624
        glyphs,
24624
        max_glyphs,
    )?;
24624
    strip_joiners(glyphs);
24624
    replace_missing_glyphs(glyphs, num_glyphs);
24624
    Ok(())
24624
}
24624
fn gsub_apply_custom_features(
24624
    gsub_cache: &LayoutCache<GSUB>,
24624
    gsub_table: &LayoutTable<GSUB>,
24624
    opt_gdef_table: Option<&GDEFTable>,
24624
    script_tag: u32,
24624
    opt_lang_tag: Option<u32>,
24624
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
24624
    feature_mask: FeatureMask,
24624
    custom_features: &[FeatureInfo],
24624
    glyphs: &mut Vec<RawGlyph<()>>,
24624
    max_glyphs: usize,
24624
) -> Result<(), ShapingError> {
24624
    if custom_features.is_empty() {
24624
        return Ok(());
    }
    // Resolve the script table. For Indic scripts, the shaper uses the v2
    // tag (dev2, bng2, etc.) so we must look up features there too.
    let script_table = match ScriptType::from(script_tag) {
        ScriptType::Indic => {
            let indic2 = scripts::indic::indic2_tag(script_tag);
            match gsub_table.find_script(indic2)? {
                Some(table) => Some(table),
                None => gsub_table.find_script_or_default(script_tag)?,
            }
        }
        _ => gsub_table.find_script_or_default(script_tag)?,
    };
    if let Some(script) = script_table {
        if let Some(langsys) = script.find_langsys_or_default(opt_lang_tag)? {
            let lookups = build_lookups_custom(
                gsub_table,
                langsys,
                custom_features,
                feature_mask,
                feature_variations,
            )?;
            for (lookup_index, feature_tag) in lookups {
                let alternate = find_alternate(custom_features, feature_tag);
                gsub_apply_lookup(
                    gsub_cache,
                    gsub_table,
                    opt_gdef_table,
                    lookup_index,
                    feature_tag,
                    alternate,
                    glyphs,
                    max_glyphs,
                    0,
                    glyphs.len(),
                    |_| true,
                )?;
            }
        }
    }
    Ok(())
24624
}
24624
fn gsub_apply_lookups(
24624
    gsub_cache: &LayoutCache<GSUB>,
24624
    gsub_table: &LayoutTable<GSUB>,
24624
    opt_gdef_table: Option<&GDEFTable>,
24624
    lookups: &[(usize, u32)],
24624
    glyphs: &mut Vec<RawGlyph<()>>,
24624
    max_glyphs: usize,
24624
) -> Result<(), ShapingError> {
24624
    gsub_apply_lookups_impl(
24624
        gsub_cache,
24624
        gsub_table,
24624
        opt_gdef_table,
24624
        lookups,
24624
        glyphs,
24624
        max_glyphs,
        0,
24624
        glyphs.len(),
    )?;
24624
    Ok(())
24624
}
24624
fn gsub_apply_lookups_impl(
24624
    gsub_cache: &LayoutCache<GSUB>,
24624
    gsub_table: &LayoutTable<GSUB>,
24624
    opt_gdef_table: Option<&GDEFTable>,
24624
    lookups: &[(usize, u32)],
24624
    glyphs: &mut Vec<RawGlyph<()>>,
24624
    max_glyphs: usize,
24624
    start: usize,
24624
    mut length: usize,
24624
) -> Result<usize, ShapingError> {
199746
    for (lookup_index, feature_tag) in lookups {
175122
        length = gsub_apply_lookup(
175122
            gsub_cache,
175122
            gsub_table,
175122
            opt_gdef_table,
175122
            *lookup_index,
175122
            *feature_tag,
175122
            None,
175122
            glyphs,
175122
            max_glyphs,
175122
            start,
175122
            length,
            |_| true,
        )?;
    }
24624
    Ok(length)
24624
}
fn gsub_apply_lookups_frac(
    gsub_cache: &LayoutCache<GSUB>,
    gsub_table: &LayoutTable<GSUB>,
    opt_gdef_table: Option<&GDEFTable>,
    lookups: &[(usize, u32)],
    lookups_frac: &[(usize, u32)],
    glyphs: &mut Vec<RawGlyph<()>>,
    max_glyphs: usize,
) -> Result<(), ShapingError> {
    let mut i = 0;
    while i < glyphs.len() {
        if let Some((start_pos, _slash_pos, end_pos)) = find_fraction(&glyphs[i..]) {
            if start_pos > 0 {
                i += gsub_apply_lookups_impl(
                    gsub_cache,
                    gsub_table,
                    opt_gdef_table,
                    lookups,
                    glyphs,
                    max_glyphs,
                    i,
                    start_pos,
                )?;
            }
            i += gsub_apply_lookups_impl(
                gsub_cache,
                gsub_table,
                opt_gdef_table,
                lookups_frac,
                glyphs,
                max_glyphs,
                i,
                end_pos - start_pos + 1,
            )?;
        } else {
            gsub_apply_lookups_impl(
                gsub_cache,
                gsub_table,
                opt_gdef_table,
                lookups,
                glyphs,
                max_glyphs,
                i,
                glyphs.len() - i,
            )?;
            break;
        }
    }
    Ok(())
}
fn find_fraction(glyphs: &[RawGlyph<()>]) -> Option<(usize, usize, usize)> {
    let slash_pos = glyphs
        .iter()
        .position(|g| g.glyph_origin == GlyphOrigin::Char('/'))?;
    let mut start_pos = slash_pos;
    while start_pos > 0 {
        match glyphs[start_pos - 1].glyph_origin {
            GlyphOrigin::Char(c) if c.is_ascii_digit() => {
                start_pos -= 1;
            }
            _ => break,
        }
    }
    let mut end_pos = slash_pos;
    while end_pos + 1 < glyphs.len() {
        match glyphs[end_pos + 1].glyph_origin {
            GlyphOrigin::Char(c) if c.is_ascii_digit() => {
                end_pos += 1;
            }
            _ => break,
        }
    }
    if start_pos < slash_pos && slash_pos < end_pos {
        Some((start_pos, slash_pos, end_pos))
    } else {
        None
    }
}
fn apply_rvrn(
    gsub_cache: &LayoutCache<GSUB>,
    opt_gdef_table: Option<&GDEFTable>,
    script_tag: u32,
    opt_lang_tag: Option<u32>,
    feature_variations: Option<&FeatureTableSubstitution<'_>>,
    glyphs: &mut Vec<RawGlyph<()>>,
    max_glyphs: usize,
) -> Result<(), ShapingError> {
    let gsub_table = &gsub_cache.layout_table;
    let index = get_lookups_cache_index(
        gsub_cache,
        script_tag,
        opt_lang_tag,
        feature_variations,
        Feature::RVRN.mask(),
    )?;
    let lookups = &gsub_cache.cached_lookups.lock().unwrap()[index];
    gsub_apply_lookups(
        gsub_cache,
        gsub_table,
        opt_gdef_table,
        lookups,
        glyphs,
        max_glyphs,
    )?;
    Ok(())
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        binary::read::ReadScope, font::MatchingPresentation, font_data::FontData,
        tests::read_fixture, Font,
    };
    #[test]
    fn feature_mask_iter() {
        let mask = FeatureMask::empty();
        assert_eq!(mask.features().count(), 0);
        let mask = FeatureMask::default_mask();
        let expected = &[
            FeatureInfo {
                feature_tag: tag::CALT,
                alternate: None,
            },
            FeatureInfo {
                feature_tag: tag::CCMP,
                alternate: None,
            },
            FeatureInfo {
                feature_tag: tag::CLIG,
                alternate: None,
            },
            FeatureInfo {
                feature_tag: tag::LIGA,
                alternate: None,
            },
            FeatureInfo {
                feature_tag: tag::LOCL,
                alternate: None,
            },
            FeatureInfo {
                feature_tag: tag::RLIG,
                alternate: None,
            },
        ];
        assert_eq!(&mask.features().collect::<Vec<_>>(), expected);
    }
    /// Verify that Feature::tag and Feature::from_tag stay in sync.
    ///
    /// Every Feature variant must round-trip through tag/from_tag.
    #[test]
    fn feature_from_tag_in_sync() {
        // Check that every Feature variant round-trips through tag/from_tag.
        for feature in FeatureMask::all() {
            let tag = feature.tag();
            let back = Feature::from_tag(tag);
            assert!(
                back == Some(feature),
                "from_tag(tag({:?})) = {:?}, expected Some({:?})",
                feature,
                back,
                feature,
            );
        }
    }
    #[test]
    fn billion_laughs() -> Result<(), Box<dyn std::error::Error>> {
        let data = read_fixture("tests/fonts/opentype/TestGSUBThree.ttf");
        let scope = ReadScope::new(&data);
        let font_file = scope.read::<FontData<'_>>()?;
        let provider = font_file.table_provider(0)?;
        let mut font = Font::new(provider)?;
        // Map text to glyphs and then apply font shaping
        let script = tag::LATN;
        let lang = tag!(b"ENG ");
        let glyphs = font.map_glyphs("lol", script, MatchingPresentation::NotRequired);
        let infos = font
            .shape(
                glyphs,
                script,
                Some(lang),
                FeatureMask::default_mask(),
                &[],
                None,
                true,
            )
            .map_err(|(err, _info)| err)?;
        assert_eq!(infos.len(), 759);
        Ok(())
    }
}