ttf_parser/tables/
gsub.rs

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