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 | |
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 | 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)] |
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 | 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. |
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 | 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)] |
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 | 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`]. |
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 | 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)] |
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 | 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. |
169 | pub type LigatureSet<'a> = LazyOffsetArray16<'a, Ligature<'a>>; |
170 | |
171 | impl<'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. |
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 | 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)] |
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 | 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)] |
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 | 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 | |
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: &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 | |