1 | use ttf_parser::{apple_layout, morx, FromData, GlyphId, LazyArray32}; |
2 | |
3 | use crate::aat::feature_selector; |
4 | use crate::aat::{FeatureType, Map, MapBuilder}; |
5 | use crate::buffer::Buffer; |
6 | use crate::plan::ShapePlan; |
7 | use crate::{Face, GlyphInfo}; |
8 | |
9 | pub fn compile_flags(face: &Face, builder: &MapBuilder) -> Option<Map> { |
10 | let mut map = Map::default(); |
11 | |
12 | for chain in face.tables().morx.as_ref()?.chains { |
13 | let mut flags = chain.default_flags; |
14 | for feature in chain.features { |
15 | // Check whether this type/setting pair was requested in the map, |
16 | // and if so, apply its flags. |
17 | |
18 | if builder.has_feature(feature.kind, feature.setting) { |
19 | flags &= feature.disable_flags; |
20 | flags |= feature.enable_flags; |
21 | } else if feature.kind == FeatureType::LetterCase as u16 |
22 | && feature.setting == u16::from(feature_selector::SMALL_CAPS) |
23 | { |
24 | // Deprecated. https://github.com/harfbuzz/harfbuzz/issues/1342 |
25 | let ok = builder.has_feature( |
26 | FeatureType::LowerCase as u16, |
27 | u16::from(feature_selector::LOWER_CASE_SMALL_CAPS), |
28 | ); |
29 | if ok { |
30 | flags &= feature.disable_flags; |
31 | flags |= feature.enable_flags; |
32 | } |
33 | } |
34 | } |
35 | |
36 | map.chain_flags.push(flags); |
37 | } |
38 | |
39 | Some(map) |
40 | } |
41 | |
42 | pub fn apply(plan: &ShapePlan, face: &Face, buffer: &mut Buffer) -> Option<()> { |
43 | for (chain_idx, chain) in face.tables().morx.as_ref()?.chains.into_iter().enumerate() { |
44 | let flags = plan.aat_map.chain_flags[chain_idx]; |
45 | for subtable in chain.subtables { |
46 | if subtable.feature_flags & flags == 0 { |
47 | continue; |
48 | } |
49 | |
50 | if !subtable.coverage.is_all_directions() |
51 | && buffer.direction.is_vertical() != subtable.coverage.is_vertical() |
52 | { |
53 | continue; |
54 | } |
55 | |
56 | // Buffer contents is always in logical direction. Determine if |
57 | // we need to reverse before applying this subtable. We reverse |
58 | // back after if we did reverse indeed. |
59 | // |
60 | // Quoting the spec: |
61 | // """ |
62 | // Bits 28 and 30 of the coverage field control the order in which |
63 | // glyphs are processed when the subtable is run by the layout engine. |
64 | // Bit 28 is used to indicate if the glyph processing direction is |
65 | // the same as logical order or layout order. Bit 30 is used to |
66 | // indicate whether glyphs are processed forwards or backwards within |
67 | // that order. |
68 | // |
69 | // Bit 30 Bit 28 Interpretation for Horizontal Text |
70 | // 0 0 The subtable is processed in layout order |
71 | // (the same order as the glyphs, which is |
72 | // always left-to-right). |
73 | // 1 0 The subtable is processed in reverse layout order |
74 | // (the order opposite that of the glyphs, which is |
75 | // always right-to-left). |
76 | // 0 1 The subtable is processed in logical order |
77 | // (the same order as the characters, which may be |
78 | // left-to-right or right-to-left). |
79 | // 1 1 The subtable is processed in reverse logical order |
80 | // (the order opposite that of the characters, which |
81 | // may be right-to-left or left-to-right). |
82 | |
83 | let reverse = if subtable.coverage.is_logical() { |
84 | subtable.coverage.is_backwards() |
85 | } else { |
86 | subtable.coverage.is_backwards() != buffer.direction.is_backward() |
87 | }; |
88 | |
89 | if reverse { |
90 | buffer.reverse_graphemes(); |
91 | } |
92 | |
93 | apply_subtable(&subtable.kind, buffer, face); |
94 | |
95 | if reverse { |
96 | buffer.reverse_graphemes(); |
97 | } |
98 | } |
99 | } |
100 | |
101 | Some(()) |
102 | } |
103 | |
104 | trait Driver<T: FromData> { |
105 | fn in_place(&self) -> bool; |
106 | fn can_advance(&self, entry: &apple_layout::GenericStateEntry<T>) -> bool; |
107 | fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<T>, buffer: &Buffer) -> bool; |
108 | fn transition( |
109 | &mut self, |
110 | entry: &apple_layout::GenericStateEntry<T>, |
111 | buffer: &mut Buffer, |
112 | ) -> Option<()>; |
113 | } |
114 | |
115 | const START_OF_TEXT: u16 = 0; |
116 | |
117 | fn drive<T: FromData>( |
118 | machine: &apple_layout::ExtendedStateTable<T>, |
119 | c: &mut dyn Driver<T>, |
120 | buffer: &mut Buffer, |
121 | ) { |
122 | if !c.in_place() { |
123 | buffer.clear_output(); |
124 | } |
125 | |
126 | let mut state = START_OF_TEXT; |
127 | buffer.idx = 0; |
128 | loop { |
129 | let class = if buffer.idx < buffer.len { |
130 | machine |
131 | .class(buffer.info[buffer.idx].as_glyph()) |
132 | .unwrap_or(1) |
133 | } else { |
134 | u16::from(apple_layout::class::END_OF_TEXT) |
135 | }; |
136 | |
137 | let entry: apple_layout::GenericStateEntry<T> = match machine.entry(state, class) { |
138 | Some(v) => v, |
139 | None => break, |
140 | }; |
141 | |
142 | let next_state = entry.new_state; |
143 | |
144 | // Conditions under which it's guaranteed safe-to-break before current glyph: |
145 | // |
146 | // 1. There was no action in this transition; and |
147 | // |
148 | // 2. If we break before current glyph, the results will be the same. That |
149 | // is guaranteed if: |
150 | // |
151 | // 2a. We were already in start-of-text state; or |
152 | // |
153 | // 2b. We are epsilon-transitioning to start-of-text state; or |
154 | // |
155 | // 2c. Starting from start-of-text state seeing current glyph: |
156 | // |
157 | // 2c'. There won't be any actions; and |
158 | // |
159 | // 2c". We would end up in the same state that we were going to end up |
160 | // in now, including whether epsilon-transitioning. |
161 | // |
162 | // and |
163 | // |
164 | // 3. If we break before current glyph, there won't be any end-of-text action |
165 | // after previous glyph. |
166 | // |
167 | // This triples the transitions we need to look up, but is worth returning |
168 | // granular unsafe-to-break results. See eg.: |
169 | // |
170 | // https://github.com/harfbuzz/harfbuzz/issues/2860 |
171 | |
172 | let is_safe_to_break_extra = || { |
173 | // 2c |
174 | let wouldbe_entry = match machine.entry(START_OF_TEXT, class) { |
175 | Some(v) => v, |
176 | None => return false, |
177 | }; |
178 | |
179 | // 2c' |
180 | if c.is_actionable(&wouldbe_entry, &buffer) { |
181 | return false; |
182 | } |
183 | |
184 | // 2c" |
185 | return next_state == wouldbe_entry.new_state |
186 | && c.can_advance(&entry) == c.can_advance(&wouldbe_entry); |
187 | }; |
188 | |
189 | let is_safe_to_break = || { |
190 | // 1 |
191 | if c.is_actionable(&entry, &buffer) { |
192 | return false; |
193 | } |
194 | |
195 | // 2 |
196 | let ok = state == START_OF_TEXT |
197 | || (!c.can_advance(&entry) && next_state == START_OF_TEXT) |
198 | || is_safe_to_break_extra(); |
199 | if !ok { |
200 | return false; |
201 | } |
202 | |
203 | // 3 |
204 | let end_entry = match machine.entry(state, u16::from(apple_layout::class::END_OF_TEXT)) |
205 | { |
206 | Some(v) => v, |
207 | None => return false, |
208 | }; |
209 | return !c.is_actionable(&end_entry, &buffer); |
210 | }; |
211 | |
212 | if !is_safe_to_break() && buffer.backtrack_len() > 0 && buffer.idx < buffer.len { |
213 | buffer.unsafe_to_break_from_outbuffer( |
214 | Some(buffer.backtrack_len() - 1), |
215 | Some(buffer.idx + 1), |
216 | ); |
217 | } |
218 | |
219 | c.transition(&entry, buffer); |
220 | |
221 | state = next_state; |
222 | |
223 | if buffer.idx >= buffer.len || !buffer.successful { |
224 | break; |
225 | } |
226 | |
227 | if c.can_advance(&entry) { |
228 | buffer.next_glyph(); |
229 | } else { |
230 | if buffer.max_ops <= 0 { |
231 | buffer.next_glyph(); |
232 | } |
233 | buffer.max_ops -= 1; |
234 | } |
235 | } |
236 | |
237 | if !c.in_place() { |
238 | buffer.sync(); |
239 | } |
240 | } |
241 | |
242 | fn apply_subtable(kind: &morx::SubtableKind, buffer: &mut Buffer, face: &Face) { |
243 | match kind { |
244 | morx::SubtableKind::Rearrangement(ref table) => { |
245 | let mut c = RearrangementCtx { start: 0, end: 0 }; |
246 | |
247 | drive::<()>(table, &mut c, buffer); |
248 | } |
249 | morx::SubtableKind::Contextual(ref table) => { |
250 | let mut c = ContextualCtx { |
251 | mark_set: false, |
252 | face_if_has_glyph_classes: |
253 | matches!(face.tables().gdef, Some(gdef) if gdef.has_glyph_classes()) |
254 | .then_some(face), |
255 | mark: 0, |
256 | table, |
257 | }; |
258 | |
259 | drive::<morx::ContextualEntryData>(&table.state, &mut c, buffer); |
260 | } |
261 | morx::SubtableKind::Ligature(ref table) => { |
262 | let mut c = LigatureCtx { |
263 | table, |
264 | match_length: 0, |
265 | match_positions: [0; LIGATURE_MAX_MATCHES], |
266 | }; |
267 | |
268 | drive::<u16>(&table.state, &mut c, buffer); |
269 | } |
270 | morx::SubtableKind::NonContextual(ref lookup) => { |
271 | let face_if_has_glyph_classes = |
272 | matches!(face.tables().gdef, Some(gdef) if gdef.has_glyph_classes()) |
273 | .then_some(face); |
274 | for info in &mut buffer.info { |
275 | if let Some(replacement) = lookup.value(info.as_glyph()) { |
276 | info.glyph_id = u32::from(replacement); |
277 | if let Some(face) = face_if_has_glyph_classes { |
278 | info.set_glyph_props(face.glyph_props(GlyphId(replacement))); |
279 | } |
280 | } |
281 | } |
282 | } |
283 | morx::SubtableKind::Insertion(ref table) => { |
284 | let mut c = InsertionCtx { |
285 | mark: 0, |
286 | glyphs: table.glyphs, |
287 | }; |
288 | |
289 | drive::<morx::InsertionEntryData>(&table.state, &mut c, buffer); |
290 | } |
291 | } |
292 | } |
293 | |
294 | struct RearrangementCtx { |
295 | start: usize, |
296 | end: usize, |
297 | } |
298 | |
299 | impl RearrangementCtx { |
300 | const MARK_FIRST: u16 = 0x8000; |
301 | const DONT_ADVANCE: u16 = 0x4000; |
302 | const MARK_LAST: u16 = 0x2000; |
303 | const VERB: u16 = 0x000F; |
304 | } |
305 | |
306 | impl Driver<()> for RearrangementCtx { |
307 | fn in_place(&self) -> bool { |
308 | true |
309 | } |
310 | |
311 | fn can_advance(&self, entry: &apple_layout::GenericStateEntry<()>) -> bool { |
312 | entry.flags & Self::DONT_ADVANCE == 0 |
313 | } |
314 | |
315 | fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<()>, _: &Buffer) -> bool { |
316 | entry.flags & Self::VERB != 0 && self.start < self.end |
317 | } |
318 | |
319 | fn transition( |
320 | &mut self, |
321 | entry: &apple_layout::GenericStateEntry<()>, |
322 | buffer: &mut Buffer, |
323 | ) -> Option<()> { |
324 | let flags = entry.flags; |
325 | |
326 | if flags & Self::MARK_FIRST != 0 { |
327 | self.start = buffer.idx; |
328 | } |
329 | |
330 | if flags & Self::MARK_LAST != 0 { |
331 | self.end = (buffer.idx + 1).min(buffer.len); |
332 | } |
333 | |
334 | if flags & Self::VERB != 0 && self.start < self.end { |
335 | // The following map has two nibbles, for start-side |
336 | // and end-side. Values of 0,1,2 mean move that many |
337 | // to the other side. Value of 3 means move 2 and |
338 | // flip them. |
339 | const MAP: [u8; 16] = [ |
340 | 0x00, // 0 no change |
341 | 0x10, // 1 Ax => xA |
342 | 0x01, // 2 xD => Dx |
343 | 0x11, // 3 AxD => DxA |
344 | 0x20, // 4 ABx => xAB |
345 | 0x30, // 5 ABx => xBA |
346 | 0x02, // 6 xCD => CDx |
347 | 0x03, // 7 xCD => DCx |
348 | 0x12, // 8 AxCD => CDxA |
349 | 0x13, // 9 AxCD => DCxA |
350 | 0x21, // 10 ABxD => DxAB |
351 | 0x31, // 11 ABxD => DxBA |
352 | 0x22, // 12 ABxCD => CDxAB |
353 | 0x32, // 13 ABxCD => CDxBA |
354 | 0x23, // 14 ABxCD => DCxAB |
355 | 0x33, // 15 ABxCD => DCxBA |
356 | ]; |
357 | |
358 | let m = MAP[usize::from(flags & Self::VERB)]; |
359 | let l = 2.min(m >> 4) as usize; |
360 | let r = 2.min(m & 0x0F) as usize; |
361 | let reverse_l = 3 == (m >> 4); |
362 | let reverse_r = 3 == (m & 0x0F); |
363 | |
364 | if self.end - self.start >= l + r { |
365 | buffer.merge_clusters(self.start, (buffer.idx + 1).min(buffer.len)); |
366 | buffer.merge_clusters(self.start, self.end); |
367 | |
368 | let mut buf = [GlyphInfo::default(); 4]; |
369 | |
370 | for (i, glyph_info) in buf[..l].iter_mut().enumerate() { |
371 | *glyph_info = buffer.info[self.start + i]; |
372 | } |
373 | |
374 | for i in 0..r { |
375 | buf[i + 2] = buffer.info[self.end - r + i]; |
376 | } |
377 | |
378 | if l > r { |
379 | for i in 0..(self.end - self.start - l - r) { |
380 | buffer.info[self.start + r + i] = buffer.info[self.start + l + i]; |
381 | } |
382 | } else if l < r { |
383 | for i in (0..(self.end - self.start - l - r)).rev() { |
384 | buffer.info[self.start + r + i] = buffer.info[self.start + l + i]; |
385 | } |
386 | } |
387 | |
388 | for i in 0..r { |
389 | buffer.info[self.start + i] = buf[2 + i]; |
390 | } |
391 | |
392 | for i in 0..l { |
393 | buffer.info[self.end - l + i] = buf[i]; |
394 | } |
395 | |
396 | if reverse_l { |
397 | buffer.info.swap(self.end - 1, self.end - 2); |
398 | } |
399 | |
400 | if reverse_r { |
401 | buffer.info.swap(self.start, self.start + 1); |
402 | } |
403 | } |
404 | } |
405 | |
406 | Some(()) |
407 | } |
408 | } |
409 | |
410 | struct ContextualCtx<'a> { |
411 | mark_set: bool, |
412 | face_if_has_glyph_classes: Option<&'a Face<'a>>, |
413 | mark: usize, |
414 | table: &'a morx::ContextualSubtable<'a>, |
415 | } |
416 | |
417 | impl ContextualCtx<'_> { |
418 | const SET_MARK: u16 = 0x8000; |
419 | const DONT_ADVANCE: u16 = 0x4000; |
420 | } |
421 | |
422 | impl Driver<morx::ContextualEntryData> for ContextualCtx<'_> { |
423 | fn in_place(&self) -> bool { |
424 | true |
425 | } |
426 | |
427 | fn can_advance( |
428 | &self, |
429 | entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>, |
430 | ) -> bool { |
431 | entry.flags & Self::DONT_ADVANCE == 0 |
432 | } |
433 | |
434 | fn is_actionable( |
435 | &self, |
436 | entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>, |
437 | buffer: &Buffer, |
438 | ) -> bool { |
439 | if buffer.idx == buffer.len && !self.mark_set { |
440 | return false; |
441 | } |
442 | |
443 | entry.extra.mark_index != 0xFFFF || entry.extra.current_index != 0xFFFF |
444 | } |
445 | |
446 | fn transition( |
447 | &mut self, |
448 | entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>, |
449 | buffer: &mut Buffer, |
450 | ) -> Option<()> { |
451 | // Looks like CoreText applies neither mark nor current substitution for |
452 | // end-of-text if mark was not explicitly set. |
453 | if buffer.idx == buffer.len && !self.mark_set { |
454 | return Some(()); |
455 | } |
456 | |
457 | let mut replacement = None; |
458 | |
459 | if entry.extra.mark_index != 0xFFFF { |
460 | let lookup = self.table.lookup(u32::from(entry.extra.mark_index))?; |
461 | replacement = lookup.value(buffer.info[self.mark].as_glyph()); |
462 | } |
463 | |
464 | if let Some(replacement) = replacement { |
465 | buffer.unsafe_to_break(Some(self.mark), Some((buffer.idx + 1).min(buffer.len))); |
466 | buffer.info[self.mark].glyph_id = u32::from(replacement); |
467 | |
468 | if let Some(face) = self.face_if_has_glyph_classes { |
469 | buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement))); |
470 | } |
471 | } |
472 | |
473 | replacement = None; |
474 | let idx = buffer.idx.min(buffer.len - 1); |
475 | if entry.extra.current_index != 0xFFFF { |
476 | let lookup = self.table.lookup(u32::from(entry.extra.current_index))?; |
477 | replacement = lookup.value(buffer.info[idx].as_glyph()); |
478 | } |
479 | |
480 | if let Some(replacement) = replacement { |
481 | buffer.info[idx].glyph_id = u32::from(replacement); |
482 | |
483 | if let Some(face) = self.face_if_has_glyph_classes { |
484 | buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement))); |
485 | } |
486 | } |
487 | |
488 | if entry.flags & Self::SET_MARK != 0 { |
489 | self.mark_set = true; |
490 | self.mark = buffer.idx; |
491 | } |
492 | |
493 | Some(()) |
494 | } |
495 | } |
496 | |
497 | struct InsertionCtx<'a> { |
498 | mark: u32, |
499 | glyphs: LazyArray32<'a, GlyphId>, |
500 | } |
501 | |
502 | impl InsertionCtx<'_> { |
503 | const SET_MARK: u16 = 0x8000; |
504 | const DONT_ADVANCE: u16 = 0x4000; |
505 | const CURRENT_INSERT_BEFORE: u16 = 0x0800; |
506 | const MARKED_INSERT_BEFORE: u16 = 0x0400; |
507 | const CURRENT_INSERT_COUNT: u16 = 0x03E0; |
508 | const MARKED_INSERT_COUNT: u16 = 0x001F; |
509 | } |
510 | |
511 | impl Driver<morx::InsertionEntryData> for InsertionCtx<'_> { |
512 | fn in_place(&self) -> bool { |
513 | false |
514 | } |
515 | |
516 | fn can_advance( |
517 | &self, |
518 | entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>, |
519 | ) -> bool { |
520 | entry.flags & Self::DONT_ADVANCE == 0 |
521 | } |
522 | |
523 | fn is_actionable( |
524 | &self, |
525 | entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>, |
526 | _: &Buffer, |
527 | ) -> bool { |
528 | (entry.flags & (Self::CURRENT_INSERT_COUNT | Self::MARKED_INSERT_COUNT) != 0) |
529 | && (entry.extra.current_insert_index != 0xFFFF |
530 | || entry.extra.marked_insert_index != 0xFFFF) |
531 | } |
532 | |
533 | fn transition( |
534 | &mut self, |
535 | entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>, |
536 | buffer: &mut Buffer, |
537 | ) -> Option<()> { |
538 | let flags = entry.flags; |
539 | let mark_loc = buffer.out_len; |
540 | |
541 | if entry.extra.marked_insert_index != 0xFFFF { |
542 | let count = flags & Self::MARKED_INSERT_COUNT; |
543 | buffer.max_ops -= i32::from(count); |
544 | if buffer.max_ops <= 0 { |
545 | return Some(()); |
546 | } |
547 | |
548 | let start = entry.extra.marked_insert_index; |
549 | let before = flags & Self::MARKED_INSERT_BEFORE != 0; |
550 | |
551 | let end = buffer.out_len; |
552 | buffer.move_to(self.mark as usize); |
553 | |
554 | if buffer.idx < buffer.len && !before { |
555 | buffer.copy_glyph(); |
556 | } |
557 | |
558 | // TODO We ignore KashidaLike setting. |
559 | for i in 0..count { |
560 | let i = u32::from(start + i); |
561 | buffer.output_glyph(u32::from(self.glyphs.get(i)?.0)); |
562 | } |
563 | |
564 | if buffer.idx < buffer.len && !before { |
565 | buffer.skip_glyph(); |
566 | } |
567 | |
568 | buffer.move_to(end + usize::from(count)); |
569 | |
570 | buffer.unsafe_to_break_from_outbuffer( |
571 | Some(self.mark as usize), |
572 | Some((buffer.idx + 1).min(buffer.len)), |
573 | ); |
574 | } |
575 | |
576 | if flags & Self::SET_MARK != 0 { |
577 | self.mark = mark_loc as u32; |
578 | } |
579 | |
580 | if entry.extra.current_insert_index != 0xFFFF { |
581 | let count = (flags & Self::CURRENT_INSERT_COUNT) >> 5; |
582 | buffer.max_ops -= i32::from(count); |
583 | if buffer.max_ops < 0 { |
584 | return Some(()); |
585 | } |
586 | |
587 | let start = entry.extra.current_insert_index; |
588 | let before = flags & Self::CURRENT_INSERT_BEFORE != 0; |
589 | let end = buffer.out_len; |
590 | |
591 | if buffer.idx < buffer.len && !before { |
592 | buffer.copy_glyph(); |
593 | } |
594 | |
595 | // TODO We ignore KashidaLike setting. |
596 | for i in 0..count { |
597 | let i = u32::from(start + i); |
598 | buffer.output_glyph(u32::from(self.glyphs.get(i)?.0)); |
599 | } |
600 | |
601 | if buffer.idx < buffer.len && !before { |
602 | buffer.skip_glyph(); |
603 | } |
604 | |
605 | // Humm. Not sure where to move to. There's this wording under |
606 | // DontAdvance flag: |
607 | // |
608 | // "If set, don't update the glyph index before going to the new state. |
609 | // This does not mean that the glyph pointed to is the same one as |
610 | // before. If you've made insertions immediately downstream of the |
611 | // current glyph, the next glyph processed would in fact be the first |
612 | // one inserted." |
613 | // |
614 | // This suggests that if DontAdvance is NOT set, we should move to |
615 | // end+count. If it *was*, then move to end, such that newly inserted |
616 | // glyphs are now visible. |
617 | // |
618 | // https://github.com/harfbuzz/harfbuzz/issues/1224#issuecomment-427691417 |
619 | buffer.move_to(if flags & Self::DONT_ADVANCE != 0 { |
620 | end |
621 | } else { |
622 | end + usize::from(count) |
623 | }); |
624 | } |
625 | |
626 | Some(()) |
627 | } |
628 | } |
629 | |
630 | const LIGATURE_MAX_MATCHES: usize = 64; |
631 | |
632 | struct LigatureCtx<'a> { |
633 | table: &'a morx::LigatureSubtable<'a>, |
634 | match_length: usize, |
635 | match_positions: [usize; LIGATURE_MAX_MATCHES], |
636 | } |
637 | |
638 | impl LigatureCtx<'_> { |
639 | const SET_COMPONENT: u16 = 0x8000; |
640 | const DONT_ADVANCE: u16 = 0x4000; |
641 | const PERFORM_ACTION: u16 = 0x2000; |
642 | |
643 | const LIG_ACTION_LAST: u32 = 0x80000000; |
644 | const LIG_ACTION_STORE: u32 = 0x40000000; |
645 | const LIG_ACTION_OFFSET: u32 = 0x3FFFFFFF; |
646 | } |
647 | |
648 | impl Driver<u16> for LigatureCtx<'_> { |
649 | fn in_place(&self) -> bool { |
650 | false |
651 | } |
652 | |
653 | fn can_advance(&self, entry: &apple_layout::GenericStateEntry<u16>) -> bool { |
654 | entry.flags & Self::DONT_ADVANCE == 0 |
655 | } |
656 | |
657 | fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<u16>, _: &Buffer) -> bool { |
658 | entry.flags & Self::PERFORM_ACTION != 0 |
659 | } |
660 | |
661 | fn transition( |
662 | &mut self, |
663 | entry: &apple_layout::GenericStateEntry<u16>, |
664 | buffer: &mut Buffer, |
665 | ) -> Option<()> { |
666 | if entry.flags & Self::SET_COMPONENT != 0 { |
667 | // Never mark same index twice, in case DONT_ADVANCE was used... |
668 | if self.match_length != 0 |
669 | && self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES] |
670 | == buffer.out_len |
671 | { |
672 | self.match_length -= 1; |
673 | } |
674 | |
675 | self.match_positions[self.match_length % LIGATURE_MAX_MATCHES] = buffer.out_len; |
676 | self.match_length += 1; |
677 | } |
678 | |
679 | if entry.flags & Self::PERFORM_ACTION != 0 { |
680 | let end = buffer.out_len; |
681 | |
682 | if self.match_length == 0 { |
683 | return Some(()); |
684 | } |
685 | |
686 | if buffer.idx >= buffer.len { |
687 | return Some(()); // TODO: Work on previous instead? |
688 | } |
689 | |
690 | let mut cursor = self.match_length; |
691 | |
692 | let mut ligature_actions_index = entry.extra; |
693 | let mut ligature_idx = 0; |
694 | loop { |
695 | if cursor == 0 { |
696 | // Stack underflow. Clear the stack. |
697 | self.match_length = 0; |
698 | break; |
699 | } |
700 | |
701 | cursor -= 1; |
702 | buffer.move_to(self.match_positions[cursor % LIGATURE_MAX_MATCHES]); |
703 | |
704 | // We cannot use ? in this loop, because we must call |
705 | // buffer.move_to(end) in the end. |
706 | let action = match self |
707 | .table |
708 | .ligature_actions |
709 | .get(u32::from(ligature_actions_index)) |
710 | { |
711 | Some(v) => v, |
712 | None => break, |
713 | }; |
714 | |
715 | let mut uoffset = action & Self::LIG_ACTION_OFFSET; |
716 | if uoffset & 0x20000000 != 0 { |
717 | uoffset |= 0xC0000000; // Sign-extend. |
718 | } |
719 | |
720 | let offset = uoffset as i32; |
721 | let component_idx = (buffer.cur(0).glyph_id as i32 + offset) as u32; |
722 | ligature_idx += match self.table.components.get(component_idx) { |
723 | Some(v) => v, |
724 | None => break, |
725 | }; |
726 | |
727 | if (action & (Self::LIG_ACTION_STORE | Self::LIG_ACTION_LAST)) != 0 { |
728 | let lig = match self.table.ligatures.get(u32::from(ligature_idx)) { |
729 | Some(v) => v, |
730 | None => break, |
731 | }; |
732 | |
733 | buffer.replace_glyph(u32::from(lig.0)); |
734 | |
735 | let lig_end = |
736 | self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES] + 1; |
737 | // Now go and delete all subsequent components. |
738 | while self.match_length - 1 > cursor { |
739 | self.match_length -= 1; |
740 | buffer.move_to( |
741 | self.match_positions[self.match_length % LIGATURE_MAX_MATCHES], |
742 | ); |
743 | buffer.replace_glyph(0xFFFF); |
744 | } |
745 | |
746 | buffer.move_to(lig_end); |
747 | buffer.merge_out_clusters( |
748 | self.match_positions[cursor % LIGATURE_MAX_MATCHES], |
749 | buffer.out_len, |
750 | ); |
751 | } |
752 | |
753 | ligature_actions_index += 1; |
754 | |
755 | if action & Self::LIG_ACTION_LAST != 0 { |
756 | break; |
757 | } |
758 | } |
759 | |
760 | buffer.move_to(end); |
761 | } |
762 | |
763 | Some(()) |
764 | } |
765 | } |
766 | |