1
//! Glyph positioning (`gpos`) implementation.
2
//!
3
//! > The Glyph Positioning table (GPOS) provides precise control over glyph placement for
4
//! > sophisticated text layout and rendering in each script and language system that a font
5
//! > supports.
6
//!
7
//! — <https://docs.microsoft.com/en-us/typography/opentype/spec/gpos>
8

            
9
use tinyvec::tiny_vec;
10
use unicode_general_category::GeneralCategory;
11

            
12
use crate::context::{ContextLookupHelper, Glyph, LookupFlag, MatchType};
13
use crate::error::ParseError;
14
use crate::gdef::gdef_is_mark;
15
use crate::gsub::{FeatureInfo, FeatureMask, FeatureMaskExt, RawGlyph};
16
use crate::layout::{
17
    chain_context_lookup_info, context_lookup_info, Adjust, Anchor, ChainContextLookup,
18
    ContextLookup, CursivePos, GDEFTable, LangSys, LayoutCache, LayoutTable, LookupList,
19
    MarkBasePos, MarkLigPos, PairPos, PosLookup, SinglePos, ValueRecord, VariationIndex, GPOS,
20
};
21
use crate::scripts;
22
use crate::scripts::ScriptType;
23
use crate::tables::kern::{self, KernTable};
24
use crate::tables::variable_fonts::fvar::Tuple;
25
use crate::tables::variable_fonts::owned;
26
use crate::tag;
27

            
28
type PosContext<'a> = ContextLookupHelper<'a, GPOS>;
29

            
30
/// Apply glyph positioning rules to glyph `Info`.
31
24624
pub fn apply(
32
24624
    gpos_cache: &LayoutCache<GPOS>,
33
24624
    opt_gdef_table: Option<&GDEFTable>,
34
24624
    kern_table: Option<KernTable<'_>>,
35
24624
    kerning: bool,
36
24624
    feature_mask: FeatureMask,
37
24624
    custom_features: &[FeatureInfo],
38
24624
    tuple: Option<Tuple<'_>>,
39
24624
    script_tag: u32,
40
24624
    opt_lang_tag: Option<u32>,
41
24624
    infos: &mut [Info],
42
24624
) -> Result<(), ParseError> {
43
24624
    let gpos_table = &gpos_cache.layout_table;
44
24624
    let script_type = ScriptType::from(script_tag);
45

            
46
24624
    let script = match script_type {
47
        ScriptType::Indic => {
48
            let indic2_tag = scripts::indic::indic2_tag(script_tag);
49
            match gpos_table.find_script(indic2_tag)? {
50
                Some(script) => script,
51
                None => match gpos_table.find_script_or_default(script_tag)? {
52
                    Some(script) => script,
53
                    None => return Ok(()),
54
                },
55
            }
56
        }
57
24624
        _ => match gpos_table.find_script_or_default(script_tag)? {
58
23274
            Some(script) => script,
59
1350
            None => return Ok(()),
60
        },
61
    };
62

            
63
23274
    let langsys = match script.find_langsys_or_default(opt_lang_tag)? {
64
23274
        Some(langsys) => langsys,
65
        None => return Ok(()),
66
    };
67

            
68
23274
    let base_features: &[u32] = match script_type {
69
        ScriptType::Arabic => &[tag::CURS, tag::KERN, tag::MARK, tag::MKMK],
70
        ScriptType::Mongolian => &[tag::CURS, tag::KERN, tag::MARK, tag::MKMK],
71
        ScriptType::Indic => &[
72
            tag::ABVM,
73
            tag::BLWM,
74
            tag::DIST,
75
            tag::KERN,
76
            tag::MARK,
77
            tag::MKMK,
78
        ],
79
        ScriptType::Khmer => &[tag::ABVM, tag::BLWM, tag::DIST, tag::MARK, tag::MKMK],
80
        // opentype-shaping-docs: kern is not mandatory for shaping Myanmar text and may be disabled by user preference.
81
        ScriptType::Myanmar if kerning => &[
82
            tag::DIST,
83
            tag::ABVM,
84
            tag::BLWM,
85
            tag::MARK,
86
            tag::MKMK,
87
            tag::KERN,
88
        ],
89
        ScriptType::Myanmar => &[tag::DIST, tag::ABVM, tag::BLWM, tag::MARK, tag::MKMK],
90
        ScriptType::Syriac => &[tag::CURS, tag::KERN, tag::MARK, tag::MKMK],
91
        ScriptType::Tibetan => &[tag::KERN, tag::ABVM, tag::BLWM, tag::MARK, tag::MKMK],
92
        ScriptType::ThaiLao => &[tag::KERN, tag::MARK, tag::MKMK],
93
23274
        ScriptType::Default if kerning => &[tag::DIST, tag::KERN, tag::MARK, tag::MKMK],
94
        ScriptType::Default => &[tag::DIST, tag::MARK, tag::MKMK],
95
    };
96

            
97
23274
    apply_features(
98
23274
        gpos_cache,
99
23274
        gpos_table,
100
23274
        opt_gdef_table,
101
23274
        kern_table,
102
23274
        langsys,
103
23274
        base_features.iter().map(|&feature_tag| FeatureInfo {
104
93096
            feature_tag,
105
93096
            alternate: None,
106
93096
        }),
107
23274
        tuple,
108
23274
        script_tag,
109
23274
        infos,
110
    )?;
111
23274
    apply_features(
112
23274
        gpos_cache,
113
23274
        gpos_table,
114
23274
        opt_gdef_table,
115
23274
        kern_table,
116
23274
        langsys,
117
23274
        feature_mask.features(),
118
23274
        tuple,
119
23274
        script_tag,
120
23274
        infos,
121
    )?;
122
23274
    if !custom_features.is_empty() {
123
        apply_features(
124
            gpos_cache,
125
            gpos_table,
126
            opt_gdef_table,
127
            kern_table,
128
            langsys,
129
            custom_features.iter().copied(),
130
            tuple,
131
            script_tag,
132
            infos,
133
        )?;
134
23274
    }
135
23274
    Ok(())
136
24624
}
137

            
138
/// Apply glyph positioning using specified OpenType features.
139
///
140
/// Generally prefer to use [apply], which will enable features based on script and language.
141
/// Use this method if you need more low-level control over the enabled features.
142
46548
pub fn apply_features(
143
46548
    gpos_cache: &LayoutCache<GPOS>,
144
46548
    gpos_table: &LayoutTable<GPOS>,
145
46548
    opt_gdef_table: Option<&GDEFTable>,
146
46548
    kern_table: Option<KernTable<'_>>,
147
46548
    langsys: &LangSys,
148
46548
    features: impl Iterator<Item = FeatureInfo>,
149
46548
    tuple: Option<Tuple<'_>>,
150
46548
    script_tag: u32,
151
46548
    infos: &mut [Info],
152
46548
) -> Result<(), ParseError> {
153
46548
    let mut lookup_indices = tiny_vec!([u16; 128]);
154
46548
    let feature_variations = gpos_table.feature_variations(tuple)?;
155

            
156
    // Collect the lookup indices in order across all features
157
46548
    let mut should_apply_kern = None;
158
139644
    for feature in features {
159
93096
        let feature_table = gpos_table.find_langsys_feature(
160
93096
            langsys,
161
93096
            feature.feature_tag,
162
93096
            feature_variations.as_ref(),
163
        )?;
164

            
165
58374
        match feature_table {
166
34722
            Some(feature_table) => {
167
34722
                lookup_indices.extend_from_slice(&feature_table.lookup_indices);
168
34722
            }
169
            // Apply kerning from kern table if `kern` feature was requested, but there is no `kern`
170
            // feature table in `GPOS`.
171
58374
            None if feature.feature_tag == tag::KERN && kern_table.is_some() => {
172
                // NOTE(unwrap): Safe due to `is_some` call above
173
                should_apply_kern = Some(kern_table.unwrap());
174
            }
175
58374
            None => {}
176
        }
177
    }
178
46548
    lookup_indices.sort_unstable();
179

            
180
    // Apply kerning from kern table if there is no kern feature table
181
46548
    if let Some(kern) = should_apply_kern {
182
        kern::apply(&kern, script_tag, infos)?;
183
46548
    }
184

            
185
    // Equivalent to `Itertools::dedup` — skip runs of equal consecutive
186
    // values. The slice is sorted just above, so this collapses duplicates.
187
46548
    let mut last: Option<u16> = None;
188
81216
    for lookup_index in lookup_indices.iter().copied().filter(|&i| {
189
81216
        let new = last != Some(i);
190
81216
        last = Some(i);
191
81216
        new
192
81216
    }) {
193
81216
        gpos_apply_lookup(
194
81216
            gpos_cache,
195
81216
            gpos_table,
196
81216
            opt_gdef_table,
197
81216
            usize::from(lookup_index),
198
81216
            tuple,
199
81216
            infos,
200
        )?;
201
    }
202
46548
    Ok(())
203
46548
}
204

            
205
/// Apply `kern` and basic mark processing when there is no `GPOS` table available.
206
///
207
/// Call this method when there is no `LayoutCache<GPOS>` available for this font.
208
8370
pub fn apply_fallback(
209
8370
    kern_table: Option<KernTable<'_>>,
210
8370
    script_tag: u32,
211
8370
    infos: &mut [Info],
212
8370
) -> Result<(), ParseError> {
213
    // Apply kerning from `kern` table if present
214
8370
    if let Some(kern) = kern_table {
215
8316
        kern::apply(&kern, script_tag, infos)?;
216
54
    }
217
8370
    apply_fallback_mark_positioning(infos);
218
8370
    Ok(())
219
8370
}
220

            
221
/// Apply fallback mark positioning.
222
///
223
/// Call this function if a font lacks a mechanism for positioning marks.
224
8370
pub fn apply_fallback_mark_positioning(infos: &mut [Info]) {
225
44334
    for info in infos.iter_mut() {
226
44334
        if !info.is_mark && unicodes_are_marks(&info.glyph.unicodes) {
227
270
            info.is_mark = true;
228
44064
        }
229
    }
230
8370
    let mut base_index = 0;
231
35964
    for (i, info) in infos.iter_mut().enumerate().skip(1) {
232
35964
        if info.is_mark {
233
270
            info.placement = Placement::MarkOverprint(base_index);
234
35694
        } else {
235
35694
            base_index = i;
236
35694
        }
237
    }
238
8370
}
239

            
240
44334
fn unicodes_are_marks(unicodes: &[char]) -> bool {
241
44334
    unicodes
242
44334
        .iter()
243
44334
        .copied()
244
44334
        .map(unicode_general_category::get_general_category)
245
44334
        .all(|cat| cat == GeneralCategory::NonspacingMark)
246
44334
}
247

            
248
81216
fn gpos_apply_lookup(
249
81216
    gpos_cache: &LayoutCache<GPOS>,
250
81216
    gpos_table: &LayoutTable<GPOS>,
251
81216
    opt_gdef_table: Option<&GDEFTable>,
252
81216
    lookup_index: usize,
253
81216
    tuple: Option<Tuple<'_>>,
254
81216
    infos: &mut [Info],
255
81216
) -> Result<(), ParseError> {
256
81216
    if let Some(ref lookup_list) = gpos_table.opt_lookup_list {
257
81216
        let lookup = lookup_list.lookup_cache_gpos(gpos_cache, lookup_index)?;
258
81216
        let match_type = MatchType::from_lookup_flag(lookup.lookup_flag, lookup.mark_filtering_set);
259
81216
        match lookup.lookup_subtables {
260
            PosLookup::SinglePos(ref subtables) => {
261
                forall_glyphs_match(match_type, opt_gdef_table, infos, |i, infos| {
262
                    singlepos(subtables, tuple, opt_gdef_table, &mut infos[i])
263
                })
264
            }
265
64098
            PosLookup::PairPos(ref subtables) => {
266
                // Spec suggests that the lookup will only be applied to the second glyph if it was
267
                // not repositioned, ie. if the value_format is zero, but applying the lookup
268
                // regardless does not break any test cases.
269
440478
                forall_glyph_pairs_match(match_type, opt_gdef_table, infos, |i1, i2, infos| {
270
440478
                    pairpos(subtables, tuple, opt_gdef_table, i1, i2, infos)
271
440478
                })
272
            }
273
            PosLookup::CursivePos(ref subtables) => forall_glyph_pairs_match(
274
                MatchType::ignore_marks(),
275
                opt_gdef_table,
276
                infos,
277
                |i1, i2, infos| cursivepos(subtables, i1, i2, lookup.lookup_flag, infos),
278
            ),
279
5724
            PosLookup::MarkBasePos(ref subtables) => {
280
5724
                forall_base_mark_glyph_pairs(infos, |i1, i2, infos| {
281
324
                    markbasepos(subtables, i1, i2, infos)
282
324
                })
283
            }
284
5670
            PosLookup::MarkLigPos(ref subtables) => {
285
5670
                forall_base_mark_glyph_pairs(infos, |i1, i2, infos| {
286
                    markligpos(subtables, i1, i2, infos)
287
                })
288
            }
289
5724
            PosLookup::MarkMarkPos(ref subtables) => {
290
5724
                forall_mark_mark_glyph_pairs(infos, |i1, i2, infos| {
291
                    markmarkpos(subtables, i1, i2, infos)
292
                })
293
            }
294
            PosLookup::ContextPos(ref subtables) => {
295
                forall_glyphs_match(match_type, opt_gdef_table, infos, |i, infos| {
296
                    contextpos(
297
                        gpos_cache,
298
                        lookup_list,
299
                        opt_gdef_table,
300
                        tuple,
301
                        match_type,
302
                        subtables,
303
                        i,
304
                        infos,
305
                    )
306
                })
307
            }
308
            PosLookup::ChainContextPos(ref subtables) => {
309
                forall_glyphs_chain_match(match_type, opt_gdef_table, infos, |i, infos| {
310
                    chaincontextpos(
311
                        gpos_cache,
312
                        lookup_list,
313
                        opt_gdef_table,
314
                        tuple,
315
                        match_type,
316
                        subtables,
317
                        i,
318
                        infos,
319
                    )
320
                })
321
            }
322
        }
323
    } else {
324
        Ok(())
325
    }
326
81216
}
327

            
328
fn gpos_lookup_singlepos(
329
    subtables: &[SinglePos],
330
    glyph_index: u16,
331
) -> Result<ValueRecord, ParseError> {
332
    for singlepos in subtables {
333
        if let Some(val) = singlepos.apply(glyph_index)? {
334
            return Ok(Some(val));
335
        }
336
    }
337
    Ok(None)
338
}
339

            
340
440478
fn gpos_lookup_pairpos(
341
440478
    subtables: &[PairPos],
342
440478
    glyph_index1: u16,
343
440478
    glyph_index2: u16,
344
440478
) -> Result<Option<(ValueRecord, ValueRecord)>, ParseError> {
345
3196746
    for pairpos in subtables {
346
2931552
        if let Some((val1, val2)) = pairpos.apply(glyph_index1, glyph_index2)? {
347
175284
            return Ok(Some((val1, val2)));
348
2756268
        }
349
    }
350
265194
    Ok(None)
351
440478
}
352

            
353
fn gpos_lookup_cursivepos(
354
    subtables: &[CursivePos],
355
    glyph_index1: u16,
356
    glyph_index2: u16,
357
) -> Result<Option<(Anchor, Anchor)>, ParseError> {
358
    for cursivepos in subtables {
359
        if let Some((an1, an2)) = cursivepos.apply(glyph_index1, glyph_index2)? {
360
            return Ok(Some((an1, an2)));
361
        }
362
    }
363
    Ok(None)
364
}
365

            
366
324
fn gpos_lookup_markbasepos(
367
324
    subtables: &[MarkBasePos],
368
324
    glyph_index1: u16,
369
324
    glyph_index2: u16,
370
324
) -> Result<Option<(Anchor, Anchor)>, ParseError> {
371
2268
    for markbasepos in subtables {
372
1944
        if let Some((an1, an2)) = markbasepos.apply(glyph_index1, glyph_index2)? {
373
            return Ok(Some((an1, an2)));
374
1944
        }
375
    }
376
324
    Ok(None)
377
324
}
378

            
379
fn gpos_lookup_markligpos(
380
    subtables: &[MarkLigPos],
381
    glyph_index1: u16,
382
    glyph_index2: u16,
383
    liga_component_index: u16,
384
) -> Result<Option<(Anchor, Anchor)>, ParseError> {
385
    for markligpos in subtables {
386
        if let Some((an1, an2)) = markligpos.apply(
387
            glyph_index1,
388
            glyph_index2,
389
            usize::from(liga_component_index),
390
        )? {
391
            return Ok(Some((an1, an2)));
392
        }
393
    }
394
    Ok(None)
395
}
396

            
397
fn gpos_lookup_markmarkpos(
398
    subtables: &[MarkBasePos],
399
    glyph_index1: u16,
400
    glyph_index2: u16,
401
) -> Result<Option<(Anchor, Anchor)>, ParseError> {
402
    for markmarkpos in subtables {
403
        if let Some((an1, an2)) = markmarkpos.apply(glyph_index1, glyph_index2)? {
404
            return Ok(Some((an1, an2)));
405
        }
406
    }
407
    Ok(None)
408
}
409

            
410
fn gpos_lookup_contextpos<'a>(
411
    opt_gdef_table: Option<&GDEFTable>,
412
    match_type: MatchType,
413
    subtables: &'a [ContextLookup<GPOS>],
414
    glyph_index: u16,
415
    i: usize,
416
    infos: &mut [Info],
417
) -> Result<Option<Box<PosContext<'a>>>, ParseError> {
418
    for context_lookup in subtables {
419
        if let Some(context) = context_lookup_info(context_lookup, glyph_index, |context| {
420
            context.matches(opt_gdef_table, match_type, infos, i)
421
        })? {
422
            return Ok(Some(context));
423
        }
424
    }
425
    Ok(None)
426
}
427

            
428
fn gpos_lookup_chaincontextpos<'a>(
429
    opt_gdef_table: Option<&GDEFTable>,
430
    match_type: MatchType,
431
    subtables: &'a [ChainContextLookup<GPOS>],
432
    glyph_index: u16,
433
    i: usize,
434
    infos: &mut [Info],
435
) -> Result<Option<Box<PosContext<'a>>>, ParseError> {
436
    for chain_context_lookup in subtables {
437
        if let Some(context) =
438
            chain_context_lookup_info(chain_context_lookup, glyph_index, |context| {
439
                context.matches(opt_gdef_table, match_type, infos, i)
440
            })?
441
        {
442
            return Ok(Some(context));
443
        }
444
    }
445
    Ok(None)
446
}
447

            
448
/// Adjustment to the placement of a glyph as a result of kerning and
449
/// placement of an attachment relative to a base glyph.
450
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
451
pub enum Placement {
452
    None,
453
    /// Placement offset by distance delta.
454
    ///
455
    /// Fields
456
    /// (delta x, delta y)
457
    Distance(i32, i32),
458
    /// An anchored mark.
459
    ///
460
    /// This is a mark where its anchor is aligned with the base glyph anchor.
461
    ///
462
    /// Fields:
463
    /// (base glyph index in `Vec<Info>`, base glyph anchor, mark anchor)
464
    MarkAnchor(usize, Anchor, Anchor),
465
    /// An overprint mark.
466
    ///
467
    /// This mark is shown at the same position as the base glyph.
468
    ///
469
    /// Fields:
470
    /// (base glyph index in `Vec<Info>`)
471
    MarkOverprint(usize),
472
    /// Cursive anchored placement.
473
    ///
474
    /// Fields:
475
    /// * exit glyph index in `Vec<Info>`,
476
    /// * [RIGHT_TO_LEFT flag from lookup table](https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#lookupFlags_1),
477
    /// * exit glyph anchor,
478
    /// * entry glyph anchor
479
    ///
480
    /// <https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-3-cursive-attachment-positioning-subtable>
481
    CursiveAnchor(usize, bool, Anchor, Anchor),
482
}
483

            
484
impl Placement {
485
    pub(super) fn combine_distance(&mut self, x2: i32, y2: i32) {
486
        use Placement::*;
487

            
488
        *self = match *self {
489
            None | MarkOverprint(_) => Distance(x2, y2),
490
            // FIXME HarfBuzz also updates cursive anchors
491
            // but we haven't found any fonts that test this codepath yet.
492
            CursiveAnchor(..) => Distance(x2, y2),
493
            Distance(x1, y1) => Distance(x1 + x2, y1 + y2),
494
            MarkAnchor(i, an1, an2) => {
495
                let x = an1.x + (x2 as i16);
496
                let y = an1.y + (y2 as i16);
497
                MarkAnchor(i, Anchor { x, y }, an2)
498
            }
499
        }
500
    }
501
}
502

            
503
/// A positioned glyph.
504
///
505
/// This struct is the output of applying glyph positioning (`gpos`). It contains the glyph
506
/// and information about how it should be positioned.
507
///
508
/// For more information about glyph placement refer to the OpenType documentation:
509
/// <https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#positioning-glyphs-with-opentype>
510
#[derive(Clone, Debug)]
511
pub struct Info {
512
    /// The glyph.
513
    pub glyph: RawGlyph<()>,
514
    /// An offset from the horizontal glyph advance position for this glyph.
515
    pub kerning: i16,
516
    /// When not `Placement::None` indicates that this glyph should be placed according to
517
    /// the variant.
518
    pub placement: Placement,
519
    /// Indicates that cross-stream kerning values (i.e., kerning values perpendicular to the flow
520
    /// of text) should be reset to zero, and should no longer be accumulated to.
521
    pub reset_cross_stream: bool,
522
    is_mark: bool,
523
}
524

            
525
impl Glyph for Info {
526
71928
    fn get_glyph_index(&self) -> u16 {
527
71928
        self.glyph.glyph_index
528
71928
    }
529
}
530

            
531
impl Info {
532
32994
    pub fn init_from_glyphs(
533
32994
        opt_gdef_table: Option<&GDEFTable>,
534
32994
        glyphs: Vec<RawGlyph<()>>,
535
32994
    ) -> Vec<Info> {
536
32994
        let mut infos = Vec::with_capacity(glyphs.len());
537
248886
        for glyph in glyphs {
538
215892
            let is_mark = gdef_is_mark(opt_gdef_table, glyph.glyph_index);
539
215892
            let info = Info {
540
215892
                glyph,
541
215892
                kerning: 0,
542
215892
                placement: Placement::None,
543
215892
                reset_cross_stream: false,
544
215892
                is_mark,
545
215892
            };
546
215892
            infos.push(info);
547
215892
        }
548
32994
        infos
549
32994
    }
550
}
551

            
552
impl Adjust {
553
175284
    fn apply(&self, tuple: Option<Tuple<'_>>, opt_gdef_table: Option<&GDEFTable>, info: &mut Info) {
554
175284
        let variation_store =
555
175284
            opt_gdef_table.and_then(|gdef| gdef.opt_item_variation_store.as_ref());
556
175284
        if self.x_placement == 0 && self.y_placement == 0 {
557
175284
            if self.x_advance != 0 && self.y_advance == 0 {
558
22842
                info.kerning +=
559
22842
                    self.x_advance + self.x_advance_delta(tuple, variation_store).round() as i16;
560
152442
            } else if self.y_advance != 0 {
561
                // error: y_advance non-zero
562
152442
            } else {
563
152442
                // both zero, but delta could still be present
564
152442
                let x_advance_delta = self.x_advance_delta(tuple, variation_store).round() as i16;
565
152442
                info.kerning += x_advance_delta;
566
152442
            }
567
        } else if self.y_advance == 0 {
568
            let x_placement = i32::from(self.x_placement)
569
                + self.x_placement_delta(tuple, variation_store).round() as i32;
570
            let y_placement = i32::from(self.y_placement)
571
                + self.y_placement_delta(tuple, variation_store).round() as i32;
572
            info.placement.combine_distance(x_placement, y_placement);
573

            
574
            let x_advance =
575
                self.x_advance + self.x_advance_delta(tuple, variation_store).round() as i16;
576
            info.kerning += x_advance;
577
        } else {
578
            // error: y_advance non-zero
579
        }
580
175284
    }
581

            
582
175284
    pub fn x_advance_delta(
583
175284
        &self,
584
175284
        tuple: Option<Tuple<'_>>,
585
175284
        variation_store: Option<&owned::ItemVariationStore>,
586
175284
    ) -> f32 {
587
175284
        Self::delta(self.x_advance_variation.as_ref(), tuple, variation_store)
588
175284
    }
589

            
590
    pub fn y_advance_delta(
591
        &self,
592
        tuple: Option<Tuple<'_>>,
593
        variation_store: Option<&owned::ItemVariationStore>,
594
    ) -> f32 {
595
        Self::delta(self.y_advance_variation.as_ref(), tuple, variation_store)
596
    }
597

            
598
    pub fn x_placement_delta(
599
        &self,
600
        tuple: Option<Tuple<'_>>,
601
        variation_store: Option<&owned::ItemVariationStore>,
602
    ) -> f32 {
603
        Self::delta(self.x_placement_variation.as_ref(), tuple, variation_store)
604
    }
605

            
606
    pub fn y_placement_delta(
607
        &self,
608
        tuple: Option<Tuple<'_>>,
609
        variation_store: Option<&owned::ItemVariationStore>,
610
    ) -> f32 {
611
        Self::delta(self.y_placement_variation.as_ref(), tuple, variation_store)
612
    }
613

            
614
175284
    fn delta(
615
175284
        variation: Option<&VariationIndex>,
616
175284
        tuple: Option<Tuple<'_>>,
617
175284
        variation_store: Option<&owned::ItemVariationStore>,
618
175284
    ) -> f32 {
619
175284
        match (tuple, variation_store, variation) {
620
            (Some(tuple), Some(store), Some(placement_variation)) => {
621
                store.adjustment(*placement_variation, tuple).unwrap_or(0.0)
622
            }
623
175284
            _ => 0.0,
624
        }
625
175284
    }
626
}
627

            
628
fn forall_glyphs_match(
629
    match_type: MatchType,
630
    opt_gdef_table: Option<&GDEFTable>,
631
    infos: &mut [Info],
632
    f: impl Fn(usize, &mut [Info]) -> Result<(), ParseError>,
633
) -> Result<(), ParseError> {
634
    for i in 0..infos.len() {
635
        if match_type.match_glyph(opt_gdef_table, &infos[i]) {
636
            f(i, infos)?;
637
        }
638
    }
639
    Ok(())
640
}
641

            
642
fn forall_glyphs_chain_match(
643
    match_type: MatchType,
644
    opt_gdef_table: Option<&GDEFTable>,
645
    infos: &mut [Info],
646
    f: impl Fn(usize, &mut [Info]) -> Result<usize, ParseError>,
647
) -> Result<(), ParseError> {
648
    let mut i = 0;
649
    while i < infos.len() {
650
        // `f` returns how many glyphs were matched
651
        let inc = if match_type.match_glyph(opt_gdef_table, &infos[i]) {
652
            // We always want to increment by at least one glyph to avoid getting stuck.
653
            f(i, infos)?.max(1)
654
        } else {
655
            1
656
        };
657
        i += inc;
658
    }
659
    Ok(())
660
}
661

            
662
64098
fn forall_glyph_pairs_match(
663
64098
    match_type: MatchType,
664
64098
    opt_gdef_table: Option<&GDEFTable>,
665
64098
    infos: &mut [Info],
666
64098
    f: impl Fn(usize, usize, &mut [Info]) -> Result<(), ParseError>,
667
64098
) -> Result<(), ParseError> {
668
64098
    if let Some(mut i1) = match_type.find_first(opt_gdef_table, infos) {
669
504576
        while let Some(i2) = match_type.find_next(opt_gdef_table, infos, i1) {
670
440478
            f(i1, i2, infos)?;
671
440478
            i1 = i2;
672
        }
673
    }
674
64098
    Ok(())
675
64098
}
676

            
677
11394
fn forall_base_mark_glyph_pairs(
678
11394
    infos: &mut [Info],
679
11394
    f: impl Fn(usize, usize, &mut [Info]) -> Result<(), ParseError>,
680
11394
) -> Result<(), ParseError> {
681
11394
    let mut i = 0;
682
11718
    'outer: while i + 1 < infos.len() {
683
324
        if !infos[i].is_mark {
684
324
            for j in i + 1..infos.len() {
685
324
                f(i, j, infos)?;
686
324
                if !infos[j].is_mark {
687
324
                    i = j;
688
324
                    continue 'outer;
689
                }
690
            }
691
        }
692
        i += 1;
693
    }
694
11394
    Ok(())
695
11394
}
696

            
697
5724
fn forall_mark_mark_glyph_pairs(
698
5724
    infos: &mut [Info],
699
5724
    f: impl Fn(usize, usize, &mut [Info]) -> Result<(), ParseError>,
700
5724
) -> Result<(), ParseError> {
701
5724
    let mut start = 0;
702
    'outer: loop {
703
5724
        let mut i = start;
704
6048
        while i + 1 < infos.len() {
705
324
            if infos[i].is_mark {
706
                // infos[i] is the base mark. Scan forward looking for attaching marks
707
                for j in i + 1..infos.len() {
708
                    if !infos[j].is_mark {
709
                        start = i + 1;
710
                        continue 'outer;
711
                    }
712

            
713
                    // infos[j] is a candidate attaching mark
714
                    if infos[i].glyph.liga_component_pos == infos[j].glyph.liga_component_pos {
715
                        f(i, j, infos)?;
716
                    } else if infos[i].glyph.ligature() || infos[j].glyph.ligature() {
717
                        f(i, j, infos)?;
718
                    }
719
                }
720
324
            }
721
324
            i += 1;
722
        }
723
5724
        break;
724
    }
725
5724
    Ok(())
726
5724
}
727

            
728
fn singlepos(
729
    subtables: &[SinglePos],
730
    tuple: Option<Tuple<'_>>,
731
    opt_gdef_table: Option<&GDEFTable>,
732
    i: &mut Info,
733
) -> Result<(), ParseError> {
734
    let glyph_index = i.glyph.glyph_index;
735
    if let Some(adj) = gpos_lookup_singlepos(subtables, glyph_index)? {
736
        adj.apply(tuple, opt_gdef_table, i);
737
    }
738
    Ok(())
739
}
740

            
741
440478
fn pairpos(
742
440478
    subtables: &[PairPos],
743
440478
    tuple: Option<Tuple<'_>>,
744
440478
    opt_gdef_table: Option<&GDEFTable>,
745
440478
    i1: usize,
746
440478
    i2: usize,
747
440478
    infos: &mut [Info],
748
440478
) -> Result<(), ParseError> {
749
440478
    match gpos_lookup_pairpos(
750
440478
        subtables,
751
440478
        infos[i1].glyph.glyph_index,
752
440478
        infos[i2].glyph.glyph_index,
753
    )? {
754
175284
        Some((opt_adj1, opt_adj2)) => {
755
175284
            if let Some(adj1) = opt_adj1 {
756
175284
                adj1.apply(tuple, opt_gdef_table, &mut infos[i1]);
757
175284
            }
758
175284
            if let Some(adj2) = opt_adj2 {
759
                adj2.apply(tuple, opt_gdef_table, &mut infos[i2]);
760
175284
            }
761
175284
            Ok(())
762
        }
763
265194
        None => Ok(()),
764
    }
765
440478
}
766

            
767
fn cursivepos(
768
    subtables: &[CursivePos],
769
    i1: usize,
770
    i2: usize,
771
    lookup_flag: LookupFlag,
772
    infos: &mut [Info],
773
) -> Result<(), ParseError> {
774
    match gpos_lookup_cursivepos(
775
        subtables,
776
        infos[i1].glyph.glyph_index,
777
        infos[i2].glyph.glyph_index,
778
    )? {
779
        Some((anchor1, anchor2)) => {
780
            infos[i1].placement =
781
                Placement::CursiveAnchor(i2, lookup_flag.get_rtl(), anchor2, anchor1);
782
            Ok(())
783
        }
784
        None => Ok(()),
785
    }
786
}
787

            
788
324
fn markbasepos(
789
324
    subtables: &[MarkBasePos],
790
324
    i1: usize,
791
324
    i2: usize,
792
324
    infos: &mut [Info],
793
324
) -> Result<(), ParseError> {
794
324
    match gpos_lookup_markbasepos(
795
324
        subtables,
796
324
        infos[i1].glyph.glyph_index,
797
324
        infos[i2].glyph.glyph_index,
798
    )? {
799
        Some((anchor1, anchor2)) => {
800
            infos[i2].placement = Placement::MarkAnchor(i1, anchor1, anchor2);
801
            infos[i2].is_mark = true;
802
            Ok(())
803
        }
804
324
        None => Ok(()),
805
    }
806
324
}
807

            
808
fn markligpos(
809
    subtables: &[MarkLigPos],
810
    i1: usize,
811
    i2: usize,
812
    infos: &mut [Info],
813
) -> Result<(), ParseError> {
814
    match gpos_lookup_markligpos(
815
        subtables,
816
        infos[i1].glyph.glyph_index,
817
        infos[i2].glyph.glyph_index,
818
        infos[i2].glyph.liga_component_pos,
819
    )? {
820
        Some((anchor1, anchor2)) => {
821
            infos[i2].placement = Placement::MarkAnchor(i1, anchor1, anchor2);
822
            infos[i2].is_mark = true;
823
            Ok(())
824
        }
825
        None => Ok(()),
826
    }
827
}
828

            
829
fn markmarkpos(
830
    subtables: &[MarkBasePos],
831
    i1: usize,
832
    i2: usize,
833
    infos: &mut [Info],
834
) -> Result<(), ParseError> {
835
    match gpos_lookup_markmarkpos(
836
        subtables,
837
        infos[i1].glyph.glyph_index,
838
        infos[i2].glyph.glyph_index,
839
    )? {
840
        Some((anchor1, anchor2)) => {
841
            infos[i2].placement = Placement::MarkAnchor(i1, anchor1, anchor2);
842
            infos[i2].is_mark = true;
843
            Ok(())
844
        }
845
        None => Ok(()),
846
    }
847
}
848

            
849
fn contextpos(
850
    gpos_cache: &LayoutCache<GPOS>,
851
    lookup_list: &LookupList<GPOS>,
852
    opt_gdef_table: Option<&GDEFTable>,
853
    tuple: Option<Tuple<'_>>,
854
    match_type: MatchType,
855
    subtables: &[ContextLookup<GPOS>],
856
    i: usize,
857
    infos: &mut [Info],
858
) -> Result<(), ParseError> {
859
    let glyph_index = infos[i].glyph.glyph_index;
860
    match gpos_lookup_contextpos(opt_gdef_table, match_type, subtables, glyph_index, i, infos)? {
861
        Some(pos) => apply_pos_context(
862
            gpos_cache,
863
            lookup_list,
864
            opt_gdef_table,
865
            tuple,
866
            match_type,
867
            &pos,
868
            i,
869
            infos,
870
        ),
871
        None => Ok(()),
872
    }
873
}
874

            
875
fn chaincontextpos(
876
    gpos_cache: &LayoutCache<GPOS>,
877
    lookup_list: &LookupList<GPOS>,
878
    opt_gdef_table: Option<&GDEFTable>,
879
    tuple: Option<Tuple<'_>>,
880
    match_type: MatchType,
881
    subtables: &[ChainContextLookup<GPOS>],
882
    i: usize,
883
    infos: &mut [Info],
884
) -> Result<usize, ParseError> {
885
    let glyph_index = infos[i].glyph.glyph_index;
886
    match gpos_lookup_chaincontextpos(opt_gdef_table, match_type, subtables, glyph_index, i, infos)?
887
    {
888
        Some(pos) => {
889
            apply_pos_context(
890
                gpos_cache,
891
                lookup_list,
892
                opt_gdef_table,
893
                tuple,
894
                match_type,
895
                &pos,
896
                i,
897
                infos,
898
            )?;
899

            
900
            // RangeInclusive<usize> does not implement ExactSizeIterator, so it has no len
901
            // method (because len returns a usize and if the range was 0..usize::MAX len
902
            // is not representable). However, in our case, we can only ever match up to u16::MAX
903
            // glyphs, so this isn't an issue.
904
            let len = pos.input_seq.end() - pos.input_seq.start() + 1;
905
            Ok(len)
906
        }
907
        // Just skip this glyph
908
        None => Ok(1),
909
    }
910
}
911

            
912
fn apply_pos_context(
913
    gpos_cache: &LayoutCache<GPOS>,
914
    lookup_list: &LookupList<GPOS>,
915
    opt_gdef_table: Option<&GDEFTable>,
916
    tuple: Option<Tuple<'_>>,
917
    _match_type: MatchType,
918
    pos: &PosContext<'_>,
919
    i: usize,
920
    infos: &mut [Info],
921
) -> Result<(), ParseError> {
922
    for (pos_index, pos_lookup_index) in pos.lookup_array {
923
        apply_pos(
924
            gpos_cache,
925
            lookup_list,
926
            opt_gdef_table,
927
            tuple,
928
            usize::from(*pos_index),
929
            usize::from(*pos_lookup_index),
930
            infos,
931
            i,
932
        )?;
933
    }
934
    Ok(())
935
}
936

            
937
fn apply_pos(
938
    gpos_cache: &LayoutCache<GPOS>,
939
    lookup_list: &LookupList<GPOS>,
940
    opt_gdef_table: Option<&GDEFTable>,
941
    tuple: Option<Tuple<'_>>,
942
    pos_index: usize,
943
    lookup_index: usize,
944
    infos: &mut [Info],
945
    index: usize,
946
) -> Result<(), ParseError> {
947
    let lookup = lookup_list.lookup_cache_gpos(gpos_cache, lookup_index)?;
948
    let match_type = MatchType::from_lookup_flag(lookup.lookup_flag, lookup.mark_filtering_set);
949
    let i1 = match match_type.find_nth(opt_gdef_table, infos, index, pos_index) {
950
        Some(index1) => index1,
951
        None => return Ok(()),
952
    };
953
    match lookup.lookup_subtables {
954
        PosLookup::SinglePos(ref subtables) => {
955
            singlepos(subtables, tuple, opt_gdef_table, &mut infos[i1])
956
        }
957
        PosLookup::PairPos(ref subtables) => {
958
            if let Some(i2) = match_type.find_next(opt_gdef_table, infos, i1) {
959
                pairpos(subtables, tuple, opt_gdef_table, i1, i2, infos)
960
            } else {
961
                Ok(())
962
            }
963
        }
964
        PosLookup::CursivePos(ref subtables) => {
965
            if let Some(i2) = match_type.find_next(opt_gdef_table, infos, i1) {
966
                cursivepos(subtables, i1, i2, lookup.lookup_flag, infos)
967
            } else {
968
                Ok(())
969
            }
970
        }
971
        PosLookup::MarkBasePos(ref subtables) => {
972
            // FIXME is this correct?
973
            if let Some(base_index) = MatchType::ignore_marks().find_prev(opt_gdef_table, infos, i1)
974
            {
975
                markbasepos(subtables, base_index, i1, infos)
976
            } else {
977
                Ok(())
978
            }
979
        }
980
        PosLookup::MarkLigPos(ref subtables) => {
981
            // FIXME is this correct?
982
            if let Some(base_index) = MatchType::ignore_marks().find_prev(opt_gdef_table, infos, i1)
983
            {
984
                markligpos(subtables, base_index, i1, infos)
985
            } else {
986
                Ok(())
987
            }
988
        }
989
        PosLookup::MarkMarkPos(ref subtables) => {
990
            // FIXME is this correct?
991
            if let Some(base_index) = match_type.find_prev(opt_gdef_table, infos, i1) {
992
                markmarkpos(subtables, base_index, i1, infos)
993
            } else {
994
                Ok(())
995
            }
996
        }
997
        PosLookup::ContextPos(ref _subtables) => Ok(()),
998
        PosLookup::ChainContextPos(ref _subtables) => Ok(()),
999
    }
}