/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 | | } |