Coverage Report

Created: 2026-02-26 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ttf-parser/src/tables/gsub.rs
Line
Count
Source
1
//! A [Glyph Substitution Table](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub)
2
//! implementation.
3
4
// A heavily modified port of https://github.com/harfbuzz/rustybuzz implementation
5
// originally written by https://github.com/laurmaedje
6
7
use crate::opentype_layout::{ChainedContextLookup, ContextLookup, Coverage, LookupSubtable};
8
use crate::parser::{FromSlice, LazyArray16, LazyOffsetArray16, Stream};
9
use crate::GlyphId;
10
11
/// A [Single Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#SS).
12
#[allow(missing_docs)]
13
#[derive(Clone, Copy, Debug)]
14
pub enum SingleSubstitution<'a> {
15
    Format1 {
16
        coverage: Coverage<'a>,
17
        delta: i16,
18
    },
19
    Format2 {
20
        coverage: Coverage<'a>,
21
        substitutes: LazyArray16<'a, GlyphId>,
22
    },
23
}
24
25
impl<'a> SingleSubstitution<'a> {
26
0
    fn parse(data: &'a [u8]) -> Option<Self> {
27
0
        let mut s = Stream::new(data);
28
0
        match s.read::<u16>()? {
29
            1 => {
30
0
                let coverage = Coverage::parse(s.read_at_offset16(data)?)?;
31
0
                let delta = s.read::<i16>()?;
32
0
                Some(Self::Format1 { coverage, delta })
33
            }
34
            2 => {
35
0
                let coverage = Coverage::parse(s.read_at_offset16(data)?)?;
36
0
                let count = s.read::<u16>()?;
37
0
                let substitutes = s.read_array16(count)?;
38
0
                Some(Self::Format2 {
39
0
                    coverage,
40
0
                    substitutes,
41
0
                })
42
            }
43
0
            _ => None,
44
        }
45
0
    }
46
47
    /// Returns the subtable coverage.
48
    #[inline]
49
    pub fn coverage(&self) -> Coverage<'a> {
50
        match self {
51
            Self::Format1 { coverage, .. } => *coverage,
52
            Self::Format2 { coverage, .. } => *coverage,
53
        }
54
    }
55
}
56
57
/// A sequence of glyphs for
58
/// [Multiple Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#MS).
59
#[derive(Clone, Copy, Debug)]
60
pub struct Sequence<'a> {
61
    /// A list of substitute glyphs.
62
    pub substitutes: LazyArray16<'a, GlyphId>,
63
}
64
65
impl<'a> FromSlice<'a> for Sequence<'a> {
66
0
    fn parse(data: &'a [u8]) -> Option<Self> {
67
0
        let mut s = Stream::new(data);
68
0
        let count = s.read::<u16>()?;
69
0
        let substitutes = s.read_array16(count)?;
70
0
        Some(Self { substitutes })
71
0
    }
72
}
73
74
/// A list of [`Sequence`] tables.
75
pub type SequenceList<'a> = LazyOffsetArray16<'a, Sequence<'a>>;
76
77
/// A [Multiple Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#MS).
78
#[allow(missing_docs)]
79
#[derive(Clone, Copy, Debug)]
80
pub struct MultipleSubstitution<'a> {
81
    pub coverage: Coverage<'a>,
82
    pub sequences: SequenceList<'a>,
83
}
84
85
impl<'a> MultipleSubstitution<'a> {
86
0
    fn parse(data: &'a [u8]) -> Option<Self> {
87
0
        let mut s = Stream::new(data);
88
0
        match s.read::<u16>()? {
89
            1 => {
90
0
                let coverage = Coverage::parse(s.read_at_offset16(data)?)?;
91
0
                let count = s.read::<u16>()?;
92
0
                let offsets = s.read_array16(count)?;
93
0
                Some(Self {
94
0
                    coverage,
95
0
                    sequences: SequenceList::new(data, offsets),
96
0
                })
97
            }
98
0
            _ => None,
99
        }
100
0
    }
101
}
102
103
/// A list of glyphs for
104
/// [Alternate Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#AS).
105
#[derive(Clone, Copy, Debug)]
106
pub struct AlternateSet<'a> {
107
    /// Array of alternate glyph IDs, in arbitrary order.
108
    pub alternates: LazyArray16<'a, GlyphId>,
109
}
110
111
impl<'a> FromSlice<'a> for AlternateSet<'a> {
112
0
    fn parse(data: &'a [u8]) -> Option<Self> {
113
0
        let mut s = Stream::new(data);
114
0
        let count = s.read::<u16>()?;
115
0
        let alternates = s.read_array16(count)?;
116
0
        Some(Self { alternates })
117
0
    }
118
}
119
120
/// A set of [`AlternateSet`].
121
pub type AlternateSets<'a> = LazyOffsetArray16<'a, AlternateSet<'a>>;
122
123
/// A [Alternate Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#AS).
124
#[allow(missing_docs)]
125
#[derive(Clone, Copy, Debug)]
126
pub struct AlternateSubstitution<'a> {
127
    pub coverage: Coverage<'a>,
128
    pub alternate_sets: AlternateSets<'a>,
129
}
130
131
impl<'a> AlternateSubstitution<'a> {
132
0
    fn parse(data: &'a [u8]) -> Option<Self> {
133
0
        let mut s = Stream::new(data);
134
0
        match s.read::<u16>()? {
135
            1 => {
136
0
                let coverage = Coverage::parse(s.read_at_offset16(data)?)?;
137
0
                let count = s.read::<u16>()?;
138
0
                let offsets = s.read_array16(count)?;
139
0
                Some(Self {
140
0
                    coverage,
141
0
                    alternate_sets: AlternateSets::new(data, offsets),
142
0
                })
143
            }
144
0
            _ => None,
145
        }
146
0
    }
147
}
148
149
/// Glyph components for one ligature.
150
#[derive(Clone, Copy, Debug)]
151
pub struct Ligature<'a> {
152
    /// Ligature to substitute.
153
    pub glyph: GlyphId,
154
    /// Glyph components for one ligature.
155
    pub components: LazyArray16<'a, GlyphId>,
156
}
157
158
impl<'a> FromSlice<'a> for Ligature<'a> {
159
0
    fn parse(data: &'a [u8]) -> Option<Self> {
160
0
        let mut s = Stream::new(data);
161
0
        let glyph = s.read::<GlyphId>()?;
162
0
        let count = s.read::<u16>()?;
163
0
        let components = s.read_array16(count.checked_sub(1)?)?;
164
0
        Some(Self { glyph, components })
165
0
    }
166
}
167
168
/// A [`Ligature`] set.
169
pub type LigatureSet<'a> = LazyOffsetArray16<'a, Ligature<'a>>;
170
171
impl<'a> FromSlice<'a> for LigatureSet<'a> {
172
0
    fn parse(data: &'a [u8]) -> Option<Self> {
173
0
        Self::parse(data)
174
0
    }
175
}
176
177
/// A list of [`Ligature`] sets.
178
pub type LigatureSets<'a> = LazyOffsetArray16<'a, LigatureSet<'a>>;
179
180
/// A [Ligature Substitution Subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#LS).
181
#[allow(missing_docs)]
182
#[derive(Clone, Copy, Debug)]
183
pub struct LigatureSubstitution<'a> {
184
    pub coverage: Coverage<'a>,
185
    pub ligature_sets: LigatureSets<'a>,
186
}
187
188
impl<'a> LigatureSubstitution<'a> {
189
0
    fn parse(data: &'a [u8]) -> Option<Self> {
190
0
        let mut s = Stream::new(data);
191
0
        match s.read::<u16>()? {
192
            1 => {
193
0
                let coverage = Coverage::parse(s.read_at_offset16(data)?)?;
194
0
                let count = s.read::<u16>()?;
195
0
                let offsets = s.read_array16(count)?;
196
0
                Some(Self {
197
0
                    coverage,
198
0
                    ligature_sets: LigatureSets::new(data, offsets),
199
0
                })
200
            }
201
0
            _ => None,
202
        }
203
0
    }
204
}
205
206
/// A [Reverse Chaining Contextual Single Substitution Subtable](
207
/// https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#RCCS).
208
#[allow(missing_docs)]
209
#[derive(Clone, Copy, Debug)]
210
pub struct ReverseChainSingleSubstitution<'a> {
211
    pub coverage: Coverage<'a>,
212
    pub backtrack_coverages: LazyOffsetArray16<'a, Coverage<'a>>,
213
    pub lookahead_coverages: LazyOffsetArray16<'a, Coverage<'a>>,
214
    pub substitutes: LazyArray16<'a, GlyphId>,
215
}
216
217
impl<'a> ReverseChainSingleSubstitution<'a> {
218
0
    fn parse(data: &'a [u8]) -> Option<Self> {
219
0
        let mut s = Stream::new(data);
220
0
        match s.read::<u16>()? {
221
            1 => {
222
0
                let coverage = Coverage::parse(s.read_at_offset16(data)?)?;
223
0
                let backtrack_count = s.read::<u16>()?;
224
0
                let backtrack_coverages = s.read_array16(backtrack_count)?;
225
0
                let lookahead_count = s.read::<u16>()?;
226
0
                let lookahead_coverages = s.read_array16(lookahead_count)?;
227
0
                let substitute_count = s.read::<u16>()?;
228
0
                let substitutes = s.read_array16(substitute_count)?;
229
0
                Some(Self {
230
0
                    coverage,
231
0
                    backtrack_coverages: LazyOffsetArray16::new(data, backtrack_coverages),
232
0
                    lookahead_coverages: LazyOffsetArray16::new(data, lookahead_coverages),
233
0
                    substitutes,
234
0
                })
235
            }
236
0
            _ => None,
237
        }
238
0
    }
239
}
240
241
/// A glyph substitution
242
/// [lookup subtable](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#table-organization)
243
/// enumeration.
244
#[allow(missing_docs)]
245
#[derive(Clone, Copy, Debug)]
246
pub enum SubstitutionSubtable<'a> {
247
    Single(SingleSubstitution<'a>),
248
    Multiple(MultipleSubstitution<'a>),
249
    Alternate(AlternateSubstitution<'a>),
250
    Ligature(LigatureSubstitution<'a>),
251
    Context(ContextLookup<'a>),
252
    ChainContext(ChainedContextLookup<'a>),
253
    ReverseChainSingle(ReverseChainSingleSubstitution<'a>),
254
}
255
256
impl<'a> LookupSubtable<'a> for SubstitutionSubtable<'a> {
257
0
    fn parse(data: &'a [u8], kind: u16) -> Option<Self> {
258
0
        match kind {
259
0
            1 => SingleSubstitution::parse(data).map(Self::Single),
260
0
            2 => MultipleSubstitution::parse(data).map(Self::Multiple),
261
0
            3 => AlternateSubstitution::parse(data).map(Self::Alternate),
262
0
            4 => LigatureSubstitution::parse(data).map(Self::Ligature),
263
0
            5 => ContextLookup::parse(data).map(Self::Context),
264
0
            6 => ChainedContextLookup::parse(data).map(Self::ChainContext),
265
0
            7 => crate::ggg::parse_extension_lookup(data, Self::parse),
266
0
            8 => ReverseChainSingleSubstitution::parse(data).map(Self::ReverseChainSingle),
267
0
            _ => None,
268
        }
269
0
    }
270
}
271
272
impl<'a> SubstitutionSubtable<'a> {
273
    /// Returns the subtable coverage.
274
    #[inline]
275
    pub fn coverage(&self) -> Coverage<'a> {
276
        match self {
277
            Self::Single(t) => t.coverage(),
278
            Self::Multiple(t) => t.coverage,
279
            Self::Alternate(t) => t.coverage,
280
            Self::Ligature(t) => t.coverage,
281
            Self::Context(t) => t.coverage(),
282
            Self::ChainContext(t) => t.coverage(),
283
            Self::ReverseChainSingle(t) => t.coverage,
284
        }
285
    }
286
287
    /// Checks that the current subtable is *Reverse Chaining Contextual Single*.
288
    #[inline]
289
    pub fn is_reverse(&self) -> bool {
290
        matches!(self, Self::ReverseChainSingle(_))
291
    }
292
}