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::opentype_layout::{ChainedContextLookup, ContextLookup, Coverage, LookupSubtable};
8use crate::parser::{FromSlice, LazyArray16, LazyOffsetArray16, Stream};
9use 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)]
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 {
39 coverage,
40 substitutes,
41 })
42 }
43 _ => None,
44 }
45 }
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)]
60pub struct Sequence<'a> {
61 /// A list of substitute glyphs.
62 pub substitutes: LazyArray16<'a, GlyphId>,
63}
64
65impl<'a> FromSlice<'a> for Sequence<'a> {
66 fn parse(data: &'a [u8]) -> Option<Self> {
67 let mut s: Stream<'_> = Stream::new(data);
68 let count: u16 = s.read::<u16>()?;
69 let substitutes: LazyArray16<'_, GlyphId> = s.read_array16(count)?;
70 Some(Self { substitutes })
71 }
72}
73
74/// A list of [`Sequence`] tables.
75pub 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)]
80pub struct MultipleSubstitution<'a> {
81 pub coverage: Coverage<'a>,
82 pub sequences: SequenceList<'a>,
83}
84
85impl<'a> MultipleSubstitution<'a> {
86 fn parse(data: &'a [u8]) -> Option<Self> {
87 let mut s: Stream<'_> = Stream::new(data);
88 match s.read::<u16>()? {
89 1 => {
90 let coverage: Coverage<'_> = Coverage::parse(data:s.read_at_offset16(data)?)?;
91 let count: u16 = s.read::<u16>()?;
92 let offsets: LazyArray16<'_, Option> = s.read_array16(count)?;
93 Some(Self {
94 coverage,
95 sequences: SequenceList::new(data, offsets),
96 })
97 }
98 _ => None,
99 }
100 }
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)]
106pub struct AlternateSet<'a> {
107 /// Array of alternate glyph IDs, in arbitrary order.
108 pub alternates: LazyArray16<'a, GlyphId>,
109}
110
111impl<'a> FromSlice<'a> for AlternateSet<'a> {
112 fn parse(data: &'a [u8]) -> Option<Self> {
113 let mut s: Stream<'_> = Stream::new(data);
114 let count: u16 = s.read::<u16>()?;
115 let alternates: LazyArray16<'_, GlyphId> = s.read_array16(count)?;
116 Some(Self { alternates })
117 }
118}
119
120/// A set of [`AlternateSet`].
121pub 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)]
126pub struct AlternateSubstitution<'a> {
127 pub coverage: Coverage<'a>,
128 pub alternate_sets: AlternateSets<'a>,
129}
130
131impl<'a> AlternateSubstitution<'a> {
132 fn parse(data: &'a [u8]) -> Option<Self> {
133 let mut s: Stream<'_> = Stream::new(data);
134 match s.read::<u16>()? {
135 1 => {
136 let coverage: Coverage<'_> = Coverage::parse(data:s.read_at_offset16(data)?)?;
137 let count: u16 = s.read::<u16>()?;
138 let offsets: LazyArray16<'_, Option> = s.read_array16(count)?;
139 Some(Self {
140 coverage,
141 alternate_sets: AlternateSets::new(data, offsets),
142 })
143 }
144 _ => None,
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<'_> = Stream::new(data);
161 let glyph: GlyphId = s.read::<GlyphId>()?;
162 let count: u16 = s.read::<u16>()?;
163 let components: LazyArray16<'_, GlyphId> = s.read_array16(count: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<'_> = Stream::new(data);
191 match s.read::<u16>()? {
192 1 => {
193 let coverage: Coverage<'_> = Coverage::parse(data:s.read_at_offset16(data)?)?;
194 let count: u16 = s.read::<u16>()?;
195 let offsets: LazyArray16<'_, Option> = 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/// 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)]
210pub 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
217impl<'a> ReverseChainSingleSubstitution<'a> {
218 fn parse(data: &'a [u8]) -> Option<Self> {
219 let mut s: Stream<'_> = Stream::new(data);
220 match s.read::<u16>()? {
221 1 => {
222 let coverage: Coverage<'_> = Coverage::parse(data:s.read_at_offset16(data)?)?;
223 let backtrack_count: u16 = s.read::<u16>()?;
224 let backtrack_coverages: LazyArray16<'_, Option> = s.read_array16(backtrack_count)?;
225 let lookahead_count: u16 = s.read::<u16>()?;
226 let lookahead_coverages: LazyArray16<'_, Option> = s.read_array16(lookahead_count)?;
227 let substitute_count: u16 = s.read::<u16>()?;
228 let substitutes: LazyArray16<'_, GlyphId> = s.read_array16(substitute_count)?;
229 Some(Self {
230 coverage,
231 backtrack_coverages: LazyOffsetArray16::new(data, offsets:backtrack_coverages),
232 lookahead_coverages: LazyOffsetArray16::new(data, offsets:lookahead_coverages),
233 substitutes,
234 })
235 }
236 _ => None,
237 }
238 }
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)]
246pub 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
256impl<'a> LookupSubtable<'a> for SubstitutionSubtable<'a> {
257 fn parse(data: &'a [u8], kind: u16) -> Option<Self> {
258 match kind {
259 1 => SingleSubstitution::parse(data).map(Self::Single),
260 2 => MultipleSubstitution::parse(data).map(Self::Multiple),
261 3 => AlternateSubstitution::parse(data).map(Self::Alternate),
262 4 => LigatureSubstitution::parse(data).map(Self::Ligature),
263 5 => ContextLookup::parse(data).map(Self::Context),
264 6 => ChainedContextLookup::parse(data).map(Self::ChainContext),
265 7 => crate::ggg::parse_extension_lookup(data, Self::parse),
266 8 => ReverseChainSingleSubstitution::parse(data).map(Self::ReverseChainSingle),
267 _ => None,
268 }
269 }
270}
271
272impl<'a> SubstitutionSubtable<'a> {
273 /// Returns the subtable coverage.
274 #[inline]
275 pub fn coverage(&self) -> Coverage<'a> {
276 match self {
277 Self::Single(t: &SingleSubstitution<'_>) => t.coverage(),
278 Self::Multiple(t: &MultipleSubstitution<'_>) => t.coverage,
279 Self::Alternate(t: &AlternateSubstitution<'_>) => t.coverage,
280 Self::Ligature(t: &LigatureSubstitution<'_>) => t.coverage,
281 Self::Context(t: &ContextLookup<'_>) => t.coverage(),
282 Self::ChainContext(t: &ChainedContextLookup<'_>) => t.coverage(),
283 Self::ReverseChainSingle(t: &ReverseChainSingleSubstitution<'_>) => 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}
293