1 | use core::convert::TryFrom; |
2 | |
3 | use ttf_parser::gsub::*; |
4 | use ttf_parser::GlyphId; |
5 | |
6 | use crate::buffer::{Buffer, GlyphPropsFlags}; |
7 | use crate::plan::ShapePlan; |
8 | use crate::unicode::GeneralCategory; |
9 | use crate::Face; |
10 | |
11 | use super::apply::{Apply, ApplyContext, WouldApply, WouldApplyContext}; |
12 | use super::matching::{match_backtrack, match_glyph, match_input, match_lookahead, Matched}; |
13 | use super::{ |
14 | LayoutLookup, LayoutTable, Map, SubstLookup, SubstitutionTable, TableIndex, MAX_NESTING_LEVEL, |
15 | }; |
16 | use ttf_parser::opentype_layout::LookupIndex; |
17 | |
18 | /// Called before substitution lookups are performed, to ensure that glyph |
19 | /// class and other properties are set on the glyphs in the buffer. |
20 | pub fn substitute_start(face: &Face, buffer: &mut Buffer) { |
21 | set_glyph_props(face, buffer) |
22 | } |
23 | |
24 | pub fn substitute(plan: &ShapePlan, face: &Face, buffer: &mut Buffer) { |
25 | super::apply_layout_table(plan, face, buffer, table:face.gsub.as_ref()); |
26 | } |
27 | |
28 | fn set_glyph_props(face: &Face, buffer: &mut Buffer) { |
29 | let len: usize = buffer.len; |
30 | for info: &mut GlyphInfo in &mut buffer.info[..len] { |
31 | info.set_glyph_props(face.glyph_props(info.as_glyph())); |
32 | info.set_lig_props(0); |
33 | info.set_syllable(0); |
34 | } |
35 | } |
36 | |
37 | impl<'a> LayoutTable for SubstitutionTable<'a> { |
38 | const INDEX: TableIndex = TableIndex::GSUB; |
39 | const IN_PLACE: bool = false; |
40 | |
41 | type Lookup = SubstLookup<'a>; |
42 | |
43 | fn get_lookup(&self, index: LookupIndex) -> Option<&Self::Lookup> { |
44 | self.lookups.get(index:usize::from(index)) |
45 | } |
46 | } |
47 | |
48 | impl LayoutLookup for SubstLookup<'_> { |
49 | fn props(&self) -> u32 { |
50 | self.props |
51 | } |
52 | |
53 | fn is_reverse(&self) -> bool { |
54 | self.reverse |
55 | } |
56 | |
57 | fn covers(&self, glyph: GlyphId) -> bool { |
58 | self.coverage.contains(glyph) |
59 | } |
60 | } |
61 | |
62 | impl WouldApply for SubstLookup<'_> { |
63 | fn would_apply(&self, ctx: &WouldApplyContext) -> bool { |
64 | self.covers(glyph:ctx.glyphs[0]) |
65 | && self |
66 | .subtables |
67 | .iter() |
68 | .any(|subtable: &SubstitutionSubtable<'_>| subtable.would_apply(ctx)) |
69 | } |
70 | } |
71 | |
72 | impl Apply for SubstLookup<'_> { |
73 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
74 | if self.covers(ctx.buffer.cur(0).as_glyph()) { |
75 | for subtable: &SubstitutionSubtable<'_> in &self.subtables { |
76 | if subtable.apply(ctx).is_some() { |
77 | return Some(()); |
78 | } |
79 | } |
80 | } |
81 | |
82 | None |
83 | } |
84 | } |
85 | |
86 | impl WouldApply for SubstitutionSubtable<'_> { |
87 | fn would_apply(&self, ctx: &WouldApplyContext) -> bool { |
88 | match self { |
89 | Self::Single(t: &SingleSubstitution<'_>) => t.would_apply(ctx), |
90 | Self::Multiple(t: &MultipleSubstitution<'_>) => t.would_apply(ctx), |
91 | Self::Alternate(t: &AlternateSubstitution<'_>) => t.would_apply(ctx), |
92 | Self::Ligature(t: &LigatureSubstitution<'_>) => t.would_apply(ctx), |
93 | Self::Context(t: &ContextLookup<'_>) => t.would_apply(ctx), |
94 | Self::ChainContext(t: &ChainedContextLookup<'_>) => t.would_apply(ctx), |
95 | Self::ReverseChainSingle(t: &ReverseChainSingleSubstitution<'_>) => t.would_apply(ctx), |
96 | } |
97 | } |
98 | } |
99 | |
100 | impl Apply for SubstitutionSubtable<'_> { |
101 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
102 | match self { |
103 | Self::Single(t: &SingleSubstitution<'_>) => t.apply(ctx), |
104 | Self::Multiple(t: &MultipleSubstitution<'_>) => t.apply(ctx), |
105 | Self::Alternate(t: &AlternateSubstitution<'_>) => t.apply(ctx), |
106 | Self::Ligature(t: &LigatureSubstitution<'_>) => t.apply(ctx), |
107 | Self::Context(t: &ContextLookup<'_>) => t.apply(ctx), |
108 | Self::ChainContext(t: &ChainedContextLookup<'_>) => t.apply(ctx), |
109 | Self::ReverseChainSingle(t: &ReverseChainSingleSubstitution<'_>) => t.apply(ctx), |
110 | } |
111 | } |
112 | } |
113 | |
114 | impl WouldApply for SingleSubstitution<'_> { |
115 | fn would_apply(&self, ctx: &WouldApplyContext) -> bool { |
116 | ctx.glyphs.len() == 1 && self.coverage().get(glyph:ctx.glyphs[0]).is_some() |
117 | } |
118 | } |
119 | |
120 | impl Apply for SingleSubstitution<'_> { |
121 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
122 | let glyph: GlyphId = ctx.buffer.cur(0).as_glyph(); |
123 | let subst: GlyphId = match *self { |
124 | Self::Format1 { coverage: Coverage<'_>, delta: i16 } => { |
125 | coverage.get(glyph)?; |
126 | // According to the Adobe Annotated OpenType Suite, result is always |
127 | // limited to 16bit, so we explicitly want to truncate. |
128 | GlyphId((i32::from(glyph.0) + i32::from(delta)) as u16) |
129 | } |
130 | Self::Format2 { |
131 | coverage: Coverage<'_>, |
132 | substitutes: LazyArray16<'_, GlyphId>, |
133 | } => { |
134 | let index: u16 = coverage.get(glyph)?; |
135 | substitutes.get(index)? |
136 | } |
137 | }; |
138 | |
139 | ctx.replace_glyph(glyph_id:subst); |
140 | Some(()) |
141 | } |
142 | } |
143 | |
144 | impl WouldApply for MultipleSubstitution<'_> { |
145 | fn would_apply(&self, ctx: &WouldApplyContext) -> bool { |
146 | ctx.glyphs.len() == 1 && self.coverage.get(glyph:ctx.glyphs[0]).is_some() |
147 | } |
148 | } |
149 | |
150 | impl Apply for MultipleSubstitution<'_> { |
151 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
152 | let glyph: GlyphId = ctx.buffer.cur(0).as_glyph(); |
153 | let index: u16 = self.coverage.get(glyph)?; |
154 | let seq: Sequence<'_> = self.sequences.get(index)?; |
155 | seq.apply(ctx) |
156 | } |
157 | } |
158 | |
159 | impl Apply for Sequence<'_> { |
160 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
161 | match self.substitutes.len() { |
162 | // Spec disallows this, but Uniscribe allows it. |
163 | // https://github.com/harfbuzz/harfbuzz/issues/253 |
164 | 0 => ctx.buffer.delete_glyph(), |
165 | |
166 | // Special-case to make it in-place and not consider this |
167 | // as a "multiplied" substitution. |
168 | 1 => ctx.replace_glyph(self.substitutes.get(0)?), |
169 | |
170 | _ => { |
171 | let class = if ctx.buffer.cur(0).is_ligature() { |
172 | GlyphPropsFlags::BASE_GLYPH |
173 | } else { |
174 | GlyphPropsFlags::empty() |
175 | }; |
176 | let lig_id = ctx.buffer.cur(0).lig_id(); |
177 | |
178 | for (i, subst) in self.substitutes.into_iter().enumerate() { |
179 | // If is attached to a ligature, don't disturb that. |
180 | // https://github.com/harfbuzz/harfbuzz/issues/3069 |
181 | if lig_id == 0 { |
182 | // Index is truncated to 4 bits anway, so we can safely cast to u8. |
183 | ctx.buffer.cur_mut(0).set_lig_props_for_component(i as u8); |
184 | } |
185 | ctx.output_glyph_for_component(subst, class); |
186 | } |
187 | |
188 | ctx.buffer.skip_glyph(); |
189 | } |
190 | } |
191 | Some(()) |
192 | } |
193 | } |
194 | |
195 | impl WouldApply for AlternateSubstitution<'_> { |
196 | fn would_apply(&self, ctx: &WouldApplyContext) -> bool { |
197 | ctx.glyphs.len() == 1 && self.coverage.get(glyph:ctx.glyphs[0]).is_some() |
198 | } |
199 | } |
200 | |
201 | impl Apply for AlternateSubstitution<'_> { |
202 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
203 | let glyph: GlyphId = ctx.buffer.cur(0).as_glyph(); |
204 | let index: u16 = self.coverage.get(glyph)?; |
205 | let set: AlternateSet<'_> = self.alternate_sets.get(index)?; |
206 | set.apply(ctx) |
207 | } |
208 | } |
209 | |
210 | impl Apply for AlternateSet<'_> { |
211 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
212 | let len = self.alternates.len(); |
213 | if len == 0 { |
214 | return None; |
215 | } |
216 | |
217 | let glyph_mask = ctx.buffer.cur(0).mask; |
218 | |
219 | // Note: This breaks badly if two features enabled this lookup together. |
220 | let shift = ctx.lookup_mask.trailing_zeros(); |
221 | let mut alt_index = (ctx.lookup_mask & glyph_mask) >> shift; |
222 | |
223 | // If alt_index is MAX_VALUE, randomize feature if it is the rand feature. |
224 | if alt_index == Map::MAX_VALUE && ctx.random { |
225 | // Maybe we can do better than unsafe-to-break all; but since we are |
226 | // changing random state, it would be hard to track that. Good 'nough. |
227 | ctx.buffer.unsafe_to_break(0, ctx.buffer.len); |
228 | alt_index = ctx.random_number() % u32::from(len) + 1; |
229 | } |
230 | |
231 | let idx = u16::try_from(alt_index).ok()?.checked_sub(1)?; |
232 | ctx.replace_glyph(self.alternates.get(idx)?); |
233 | |
234 | Some(()) |
235 | } |
236 | } |
237 | |
238 | impl WouldApply for LigatureSubstitution<'_> { |
239 | fn would_apply(&self, ctx: &WouldApplyContext) -> bool { |
240 | self.coverage |
241 | .get(ctx.glyphs[0]) |
242 | .and_then(|index| self.ligature_sets.get(index)) |
243 | .map_or(default:false, |set: LazyOffsetArray16<'_, Ligature<'_>>| set.would_apply(ctx)) |
244 | } |
245 | } |
246 | |
247 | impl Apply for LigatureSubstitution<'_> { |
248 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
249 | let glyph: GlyphId = ctx.buffer.cur(0).as_glyph(); |
250 | self.coverage |
251 | .get(glyph) |
252 | .and_then(|index: u16| self.ligature_sets.get(index)) |
253 | .and_then(|set: LazyOffsetArray16<'_, Ligature<'_>>| set.apply(ctx)) |
254 | } |
255 | } |
256 | |
257 | impl WouldApply for LigatureSet<'_> { |
258 | fn would_apply(&self, ctx: &WouldApplyContext) -> bool { |
259 | self.into_iter().any(|lig: Ligature<'_>| lig.would_apply(ctx)) |
260 | } |
261 | } |
262 | |
263 | impl Apply for LigatureSet<'_> { |
264 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
265 | for lig: Ligature<'_> in self.into_iter() { |
266 | if lig.apply(ctx).is_some() { |
267 | return Some(()); |
268 | } |
269 | } |
270 | None |
271 | } |
272 | } |
273 | |
274 | impl WouldApply for Ligature<'_> { |
275 | fn would_apply(&self, ctx: &WouldApplyContext) -> bool { |
276 | ctx.glyphs.len() == usize::from(self.components.len()) + 1 |
277 | && self |
278 | .components |
279 | .into_iter() |
280 | .enumerate() |
281 | .all(|(i: usize, comp: GlyphId)| ctx.glyphs[i + 1] == comp) |
282 | } |
283 | } |
284 | |
285 | impl Apply for Ligature<'_> { |
286 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
287 | // Special-case to make it in-place and not consider this |
288 | // as a "ligated" substitution. |
289 | if self.components.is_empty() { |
290 | ctx.replace_glyph(self.glyph); |
291 | Some(()) |
292 | } else { |
293 | let f: impl Fn(GlyphId, u16) -> … = |glyph: GlyphId, num_items: u16| { |
294 | let index: u16 = self.components.len() - num_items; |
295 | let value: GlyphId = self.components.get(index).unwrap(); |
296 | match_glyph(glyph, value:value.0) |
297 | }; |
298 | |
299 | match_input(ctx, self.components.len(), &f).map(|matched: Matched| { |
300 | let count: usize = usize::from(self.components.len()) + 1; |
301 | ligate(ctx, count, matched, self.glyph); |
302 | }) |
303 | } |
304 | } |
305 | } |
306 | |
307 | fn ligate(ctx: &mut ApplyContext, count: usize, matched: Matched, lig_glyph: GlyphId) { |
308 | // - If a base and one or more marks ligate, consider that as a base, NOT |
309 | // ligature, such that all following marks can still attach to it. |
310 | // https://github.com/harfbuzz/harfbuzz/issues/1109 |
311 | // |
312 | // - If all components of the ligature were marks, we call this a mark ligature. |
313 | // If it *is* a mark ligature, we don't allocate a new ligature id, and leave |
314 | // the ligature to keep its old ligature id. This will allow it to attach to |
315 | // a base ligature in GPOS. Eg. if the sequence is: LAM,LAM,SHADDA,FATHA,HEH, |
316 | // and LAM,LAM,HEH for a ligature, they will leave SHADDA and FATHA with a |
317 | // ligature id and component value of 2. Then if SHADDA,FATHA form a ligature |
318 | // later, we don't want them to lose their ligature id/component, otherwise |
319 | // GPOS will fail to correctly position the mark ligature on top of the |
320 | // LAM,LAM,HEH ligature. See: |
321 | // https://bugzilla.gnome.org/show_bug.cgi?id=676343 |
322 | // |
323 | // - If a ligature is formed of components that some of which are also ligatures |
324 | // themselves, and those ligature components had marks attached to *their* |
325 | // components, we have to attach the marks to the new ligature component |
326 | // positions! Now *that*'s tricky! And these marks may be following the |
327 | // last component of the whole sequence, so we should loop forward looking |
328 | // for them and update them. |
329 | // |
330 | // Eg. the sequence is LAM,LAM,SHADDA,FATHA,HEH, and the font first forms a |
331 | // 'calt' ligature of LAM,HEH, leaving the SHADDA and FATHA with a ligature |
332 | // id and component == 1. Now, during 'liga', the LAM and the LAM-HEH ligature |
333 | // form a LAM-LAM-HEH ligature. We need to reassign the SHADDA and FATHA to |
334 | // the new ligature with a component value of 2. |
335 | // |
336 | // This in fact happened to a font... See: |
337 | // https://bugzilla.gnome.org/show_bug.cgi?id=437633 |
338 | // |
339 | |
340 | let mut buffer = &mut ctx.buffer; |
341 | buffer.merge_clusters(buffer.idx, buffer.idx + matched.len); |
342 | |
343 | let mut is_base_ligature = buffer.info[matched.positions[0]].is_base_glyph(); |
344 | let mut is_mark_ligature = buffer.info[matched.positions[0]].is_mark(); |
345 | for i in 1..count { |
346 | if !buffer.info[matched.positions[i]].is_mark() { |
347 | is_base_ligature = false; |
348 | is_mark_ligature = false; |
349 | } |
350 | } |
351 | |
352 | let is_ligature = !is_base_ligature && !is_mark_ligature; |
353 | let class = if is_ligature { |
354 | GlyphPropsFlags::LIGATURE |
355 | } else { |
356 | GlyphPropsFlags::empty() |
357 | }; |
358 | let lig_id = if is_ligature { |
359 | buffer.allocate_lig_id() |
360 | } else { |
361 | 0 |
362 | }; |
363 | let first = buffer.cur_mut(0); |
364 | let mut last_lig_id = first.lig_id(); |
365 | let mut last_num_comps = first.lig_num_comps(); |
366 | let mut comps_so_far = last_num_comps; |
367 | |
368 | if is_ligature { |
369 | first.set_lig_props_for_ligature(lig_id, matched.total_component_count); |
370 | if first.general_category() == GeneralCategory::NonspacingMark { |
371 | first.set_general_category(GeneralCategory::OtherLetter); |
372 | } |
373 | } |
374 | |
375 | ctx.replace_glyph_with_ligature(lig_glyph, class); |
376 | buffer = &mut ctx.buffer; |
377 | |
378 | for i in 1..count { |
379 | while buffer.idx < matched.positions[i] && buffer.successful { |
380 | if is_ligature { |
381 | let cur = buffer.cur_mut(0); |
382 | let mut this_comp = cur.lig_comp(); |
383 | if this_comp == 0 { |
384 | this_comp = last_num_comps; |
385 | } |
386 | let new_lig_comp = comps_so_far - last_num_comps + this_comp.min(last_num_comps); |
387 | cur.set_lig_props_for_mark(lig_id, new_lig_comp); |
388 | } |
389 | buffer.next_glyph(); |
390 | } |
391 | |
392 | let cur = buffer.cur(0); |
393 | last_lig_id = cur.lig_id(); |
394 | last_num_comps = cur.lig_num_comps(); |
395 | comps_so_far += last_num_comps; |
396 | |
397 | // Skip the base glyph. |
398 | buffer.idx += 1; |
399 | } |
400 | |
401 | if !is_mark_ligature && last_lig_id != 0 { |
402 | // Re-adjust components for any marks following. |
403 | for i in buffer.idx..buffer.len { |
404 | let info = &mut buffer.info[i]; |
405 | if last_lig_id != info.lig_id() { |
406 | break; |
407 | } |
408 | |
409 | let this_comp = info.lig_comp(); |
410 | if this_comp == 0 { |
411 | break; |
412 | } |
413 | |
414 | let new_lig_comp = comps_so_far - last_num_comps + this_comp.min(last_num_comps); |
415 | info.set_lig_props_for_mark(lig_id, new_lig_comp) |
416 | } |
417 | } |
418 | } |
419 | |
420 | impl WouldApply for ReverseChainSingleSubstitution<'_> { |
421 | fn would_apply(&self, ctx: &WouldApplyContext) -> bool { |
422 | ctx.glyphs.len() == 1 && self.coverage.get(glyph:ctx.glyphs[0]).is_some() |
423 | } |
424 | } |
425 | |
426 | impl Apply for ReverseChainSingleSubstitution<'_> { |
427 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
428 | // No chaining to this type. |
429 | if ctx.nesting_level_left != MAX_NESTING_LEVEL { |
430 | return None; |
431 | } |
432 | |
433 | let glyph = ctx.buffer.cur(0).as_glyph(); |
434 | let index = self.coverage.get(glyph)?; |
435 | if index >= self.substitutes.len() { |
436 | return None; |
437 | } |
438 | |
439 | let subst = self.substitutes.get(index)?; |
440 | |
441 | let f1 = |glyph, num_items| { |
442 | let index = self.backtrack_coverages.len() - num_items; |
443 | let value = self.backtrack_coverages.get(index).unwrap(); |
444 | value.contains(glyph) |
445 | }; |
446 | |
447 | let f2 = |glyph, num_items| { |
448 | let index = self.lookahead_coverages.len() - num_items; |
449 | let value = self.lookahead_coverages.get(index).unwrap(); |
450 | value.contains(glyph) |
451 | }; |
452 | |
453 | if let Some(start_idx) = match_backtrack(ctx, self.backtrack_coverages.len(), &f1) { |
454 | if let Some(end_idx) = match_lookahead(ctx, self.lookahead_coverages.len(), &f2, 1) { |
455 | ctx.buffer |
456 | .unsafe_to_break_from_outbuffer(start_idx, end_idx); |
457 | ctx.replace_glyph_inplace(subst); |
458 | |
459 | // Note: We DON'T decrease buffer.idx. The main loop does it |
460 | // for us. This is useful for preventing surprises if someone |
461 | // calls us through a Context lookup. |
462 | return Some(()); |
463 | } |
464 | } |
465 | |
466 | None |
467 | } |
468 | } |
469 | |