1use super::layout::*;
2use super::map::{AatMap, AatMapBuilder, RangeFlags};
3use crate::hb::aat::layout_common::{
4 get_class, AatApplyContext, ClassCache, TypedCollectGlyphs, START_OF_TEXT,
5};
6use crate::hb::ot_layout::MAX_CONTEXT_LENGTH;
7use crate::hb::{hb_font_t, GlyphInfo};
8use crate::U32Set;
9use alloc::vec;
10use read_fonts::tables::aat;
11use read_fonts::tables::aat::{ExtendedStateTable, NoPayload, StateEntry};
12use read_fonts::tables::morx::{
13 ContextualEntryData, ContextualSubtable, InsertionEntryData, LigatureSubtable, Subtable,
14 SubtableKind,
15};
16use read_fonts::types::{BigEndian, FixedSize, GlyphId16};
17
18pub fn compile_flags(face: &hb_font_t, builder: &AatMapBuilder, map: &mut AatMap) -> Option<()> {
20 let has_feature = |kind: u16, setting: u16| {
21 builder
22 .current_features
23 .binary_search_by(|probe| {
24 if probe.kind != kind {
25 probe.kind.cmp(&kind)
26 } else {
27 probe.setting.cmp(&setting)
28 }
29 })
30 .is_ok()
31 };
32
33 let chains = face.aat_tables.morx.as_ref()?.0.chains();
34 let chain_len = chains.iter().count();
35 map.chain_flags.resize(chain_len, vec![]);
36
37 for (chain, chain_flags) in chains.iter().zip(map.chain_flags.iter_mut()) {
38 let Ok(chain) = chain else {
39 continue;
40 };
41 let mut flags = chain.default_flags();
42 for feature in chain.features() {
43 if has_feature(feature.feature_type(), feature.feature_settings()) {
47 flags &= feature.disable_flags();
48 flags |= feature.enable_flags();
49 } else if feature.feature_type() == FEATURE_TYPE_LETTER_CASE as u16
50 && feature.feature_settings() == u16::from(FEATURE_SELECTOR_SMALL_CAPS)
51 {
52 let ok = has_feature(
54 FEATURE_TYPE_LOWER_CASE as u16,
55 u16::from(FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS),
56 );
57 if ok {
58 flags &= feature.disable_flags();
59 flags |= feature.enable_flags();
60 }
61 }
62 }
64
65 chain_flags.push(RangeFlags {
66 flags,
67 cluster_first: builder.range_first as u32,
68 cluster_last: builder.range_last as u32,
69 });
70 }
71
72 Some(())
73}
74
75pub fn apply<'a>(c: &mut AatApplyContext<'a>, map: &'a AatMap) -> Option<()> {
77 c.buffer.unsafe_to_concat(None, None);
78
79 c.setup_buffer_glyph_set();
80
81 let (morx, subtable_caches) = c.face.aat_tables.morx.as_ref()?;
82
83 let chains = morx.chains();
84
85 let mut subtable_idx = 0;
86
87 'outer: for (chain, chain_flags) in chains.iter().zip(map.chain_flags.iter()) {
88 let Ok(chain) = chain else {
89 continue;
90 };
91 c.range_flags = Some(chain_flags.as_slice());
92 for subtable in chain.subtables().iter() {
93 let Ok(subtable) = subtable else {
94 continue;
95 };
96
97 let subtable_cache = subtable_caches.get(subtable_idx);
98 let Some(subtable_cache) = subtable_cache.as_ref() else {
99 break 'outer;
100 };
101 subtable_idx += 1;
102
103 if let Some(range_flags) = c.range_flags.as_ref() {
104 if range_flags.len() == 1
105 && (subtable.sub_feature_flags() & range_flags[0].flags == 0)
106 {
107 continue;
108 }
109 }
110
111 if !subtable.is_all_directions()
112 && c.buffer.direction.is_vertical() != subtable.is_vertical()
113 {
114 continue;
115 }
116
117 c.subtable_flags = subtable.sub_feature_flags();
118 c.first_set = Some(&subtable_cache.glyph_set);
119 c.machine_class_cache = Some(&subtable_cache.class_cache);
120 c.start_end_safe_to_break = subtable_cache.start_end_safe_to_break;
121
122 if !c.buffer_intersects_machine() {
123 continue;
124 }
125
126 let reverse = if subtable.is_logical() {
154 subtable.is_backwards()
155 } else {
156 subtable.is_backwards() != c.buffer.direction.is_backward()
157 };
158
159 if reverse != c.buffer_is_reversed {
160 c.reverse_buffer();
161 }
162
163 if let Ok(kind) = subtable.kind() {
164 apply_subtable(kind, c);
165 }
166 }
167 if c.buffer_is_reversed {
168 c.reverse_buffer();
169 }
170 }
171
172 Some(())
173}
174
175fn collect_initial_glyphs<T, Ctx: DriverContext<T>>(
176 machine: &ExtendedStateTable<T>,
177 glyphs: &mut U32Set,
178 num_glyphs: u32,
179) where
180 T: FixedSize + bytemuck::AnyBitPattern,
181{
182 let mut classes = U32Set::default();
183
184 let class_table = &machine.class_table;
185 for i in 0..machine.n_classes {
186 if let Ok(entry) = machine.entry(START_OF_TEXT, i as u16) {
187 if entry.new_state == START_OF_TEXT
188 && !Ctx::is_action_initiable(&entry)
189 && !Ctx::is_actionable(&entry)
190 {
191 continue;
192 }
193 classes.insert(i as u32);
194 }
195 }
196
197 let filter = |class: u16| classes.contains(class as u32);
200
201 if filter(aat::class::DELETED_GLYPH as u16) {
202 glyphs.insert(DELETED_GLYPH);
203 }
204
205 class_table.collect_glyphs_filtered(glyphs, num_glyphs, filter);
206}
207
208fn collect_start_end_safe_to_break<T, Ctx: DriverContext<T>>(machine: &ExtendedStateTable<T>) -> u64
209where
210 T: FixedSize + bytemuck::AnyBitPattern,
211{
212 let mut result = 0u64;
213 for state in 0..64 {
214 let bit = if let Ok(entry) = machine.entry(state, aat::class::END_OF_TEXT as u16) {
215 !Ctx::is_actionable(&entry)
216 } else {
217 true
218 };
219 if bit {
220 result |= 1 << state;
221 }
222 }
223 result
224}
225
226pub(crate) trait DriverContext<T> {
227 fn in_place() -> bool;
228 fn can_advance(entry: &StateEntry<T>) -> bool;
229 fn is_action_initiable(entry: &StateEntry<T>) -> bool;
230 fn is_actionable(entry: &StateEntry<T>) -> bool;
231 fn transition(&mut self, entry: &StateEntry<T>, ac: &mut AatApplyContext) -> Option<()>;
232}
233
234fn drive<T: bytemuck::AnyBitPattern + FixedSize + core::fmt::Debug, Ctx: DriverContext<T>>(
235 machine: &ExtendedStateTable<'_, T>,
236 c: &mut Ctx,
237 ac: &mut AatApplyContext,
238) {
239 if !Ctx::in_place() {
240 ac.buffer.clear_output();
241 }
242
243 let mut state = START_OF_TEXT;
244 let mut last_range = ac.range_flags.as_ref().and_then(|rf| {
245 if rf.len() > 1 {
246 rf.first().map(|_| 0usize)
247 } else {
248 None
250 }
251 });
252 ac.buffer.idx = 0;
253 loop {
254 if let Some(range_flags) = ac.range_flags.as_ref() {
256 if let Some(last_range) = last_range.as_mut() {
257 let mut range = *last_range;
258 if ac.buffer.idx < ac.buffer.len {
259 let cluster = ac.buffer.cur(0).cluster;
260 while cluster < range_flags[range].cluster_first {
261 range -= 1;
262 }
263
264 while cluster > range_flags[range].cluster_last {
265 range += 1;
266 }
267
268 *last_range = range;
269 }
270
271 if range_flags[range].flags & ac.subtable_flags == 0 {
272 if ac.buffer.idx == ac.buffer.len || !ac.buffer.successful {
273 break;
274 }
275
276 state = START_OF_TEXT;
277
278 ac.buffer.next_glyph();
279 continue;
280 }
281 }
282 }
283
284 let class = if ac.buffer.idx < ac.buffer.len {
285 get_class(
286 machine,
287 ac.buffer.cur(0).as_glyph(),
288 ac.machine_class_cache.unwrap(),
289 )
290 } else {
291 u16::from(aat::class::END_OF_TEXT)
292 };
293
294 let Ok(entry) = machine.entry(state, class) else {
295 break;
296 };
297
298 let next_state = entry.new_state;
299
300 let is_safe_to_break =
329 !Ctx::is_actionable(&entry) &&
331
332 (
334 state == START_OF_TEXT
335 || (!Ctx::can_advance(&entry) && next_state == START_OF_TEXT)
336 ||
337 {
338 if let Ok(wouldbe_entry) = machine.entry(START_OF_TEXT, class) {
340 !Ctx::is_actionable(&wouldbe_entry) &&
342
343 (
345 next_state == wouldbe_entry.new_state &&
346 Ctx::can_advance(&entry) == Ctx::can_advance(&wouldbe_entry)
347 )
348 } else {
349 false
350 }
351 }
352 ) &&
353
354 (
356 if state < 64 {
357 (ac.start_end_safe_to_break & (1 << state)) != 0
358 } else {
359 if let Ok(end_entry) = machine.entry(state, u16::from(aat::class::END_OF_TEXT)) {
360 !Ctx::is_actionable(&end_entry)
361 } else {
362 false
363 }
364 }
365 )
366 ;
367
368 if !is_safe_to_break && ac.buffer.backtrack_len() > 0 && ac.buffer.idx < ac.buffer.len {
369 ac.buffer.unsafe_to_break_from_outbuffer(
370 Some(ac.buffer.backtrack_len() - 1),
371 Some(ac.buffer.idx + 1),
372 );
373 }
374
375 c.transition(&entry, ac);
376
377 state = next_state;
378
379 if ac.buffer.idx >= ac.buffer.len || !ac.buffer.successful {
380 break;
381 }
382
383 if Ctx::can_advance(&entry) {
384 ac.buffer.next_glyph();
385 } else {
386 if ac.buffer.max_ops <= 0 {
387 ac.buffer.next_glyph();
388 }
389 ac.buffer.max_ops -= 1;
390 }
391 }
392
393 if !Ctx::in_place() {
394 ac.buffer.sync();
395 }
396}
397
398fn apply_subtable<'a>(kind: SubtableKind<'a>, ac: &mut AatApplyContext<'a>) {
399 match kind {
400 SubtableKind::Rearrangement(table) => {
401 let mut c = RearrangementCtx { start: 0, end: 0 };
402 drive(&table, &mut c, ac);
403 }
404 SubtableKind::Contextual(table) => {
405 let mut c = ContextualCtx {
406 mark_set: false,
407 mark: 0,
408 table: table.clone(),
409 };
410 drive(&table.state_table, &mut c, ac);
411 }
412 SubtableKind::Ligature(table) => {
413 let mut c = LigatureCtx {
414 table: table.clone(),
415 match_length: 0,
416 match_positions: [0; LIGATURE_MAX_MATCHES],
417 };
418 drive(&table.state_table, &mut c, ac);
419 }
420 SubtableKind::NonContextual(ref lookup) => {
421 let mut last_range = ac.range_flags.as_ref().and_then(|rf| {
422 if rf.len() > 1 {
423 rf.first().map(|_| 0usize)
424 } else {
425 None
427 }
428 });
429
430 for i in 0..ac.buffer.len {
431 if let Some(range_flags) = ac.range_flags.as_ref() {
433 if let Some(last_range) = last_range.as_mut() {
434 let mut range = *last_range;
435 if ac.buffer.idx < ac.buffer.len {
436 let cluster = ac.buffer.cur(0).cluster;
438 while cluster < range_flags[range].cluster_first {
439 range -= 1;
440 }
441
442 while cluster > range_flags[range].cluster_last {
443 range += 1;
444 }
445
446 *last_range = range;
447 }
448
449 if range_flags[range].flags & ac.subtable_flags == 0 {
450 continue;
451 }
452 }
453 }
454
455 if let Some(glyph) = ac.buffer.info[i].as_gid16() {
456 if let Ok(replacement) = lookup.value(glyph.to_u16()) {
457 ac.replace_glyph_inplace(i, replacement.into());
458 }
459 }
460 }
461 }
462 SubtableKind::Insertion(table) => {
463 let mut c = InsertionCtx {
464 mark: 0,
465 glyphs: table.glyphs,
466 };
467 drive(&table.state_table, &mut c, ac);
468 }
469 }
470}
471
472struct RearrangementCtx {
473 start: usize,
474 end: usize,
475}
476
477impl RearrangementCtx {
478 const MARK_FIRST: u16 = 0x8000;
479 const DONT_ADVANCE: u16 = 0x4000;
480 const MARK_LAST: u16 = 0x2000;
481 const VERB: u16 = 0x000F;
482}
483
484impl DriverContext<NoPayload> for RearrangementCtx {
485 fn in_place() -> bool {
486 true
487 }
488
489 fn can_advance(entry: &StateEntry) -> bool {
490 entry.flags & Self::DONT_ADVANCE == 0
491 }
492
493 fn is_action_initiable(entry: &StateEntry) -> bool {
494 entry.flags & Self::MARK_FIRST != 0
495 }
496
497 fn is_actionable(entry: &StateEntry) -> bool {
498 entry.flags & Self::VERB != 0
499 }
500
501 #[inline(always)]
502 fn transition(&mut self, entry: &StateEntry, ac: &mut AatApplyContext) -> Option<()> {
503 let buffer = &mut *ac.buffer;
504 let flags = entry.flags;
505
506 if flags & Self::MARK_FIRST != 0 {
507 self.start = buffer.idx;
508 }
509
510 if flags & Self::MARK_LAST != 0 {
511 self.end = (buffer.idx + 1).min(buffer.len);
512 }
513
514 if flags & Self::VERB != 0 && self.start < self.end {
515 static MAP: [u8; 16] = [
520 0x00, 0x10, 0x01, 0x11, 0x20, 0x30, 0x02, 0x03, 0x12, 0x13, 0x21, 0x31, 0x22, 0x32, 0x23, 0x33, ];
537
538 let m = MAP[usize::from(flags & Self::VERB)];
539 let l = 2.min(m >> 4) as usize;
540 let r = 2.min(m & 0x0F) as usize;
541 let reverse_l = 3 == (m >> 4);
542 let reverse_r = 3 == (m & 0x0F);
543
544 if (self.end - self.start >= l + r) && (self.end - self.start <= MAX_CONTEXT_LENGTH) {
545 buffer.merge_clusters(self.start, (buffer.idx + 1).min(buffer.len));
546 buffer.merge_clusters(self.start, self.end);
547
548 let mut buf = [GlyphInfo::default(); 4];
549
550 for (i, glyph_info) in buf[..l].iter_mut().enumerate() {
551 *glyph_info = buffer.info[self.start + i];
552 }
553
554 for i in 0..r {
555 buf[i + 2] = buffer.info[self.end - r + i];
556 }
557
558 if l > r {
559 for i in 0..(self.end - self.start - l - r) {
560 buffer.info[self.start + r + i] = buffer.info[self.start + l + i];
561 }
562 } else if l < r {
563 for i in (0..(self.end - self.start - l - r)).rev() {
564 buffer.info[self.start + r + i] = buffer.info[self.start + l + i];
565 }
566 }
567
568 for i in 0..r {
569 buffer.info[self.start + i] = buf[2 + i];
570 }
571
572 for i in 0..l {
573 buffer.info[self.end - l + i] = buf[i];
574 }
575
576 if reverse_l {
577 buffer.info.swap(self.end - 1, self.end - 2);
578 }
579
580 if reverse_r {
581 buffer.info.swap(self.start, self.start + 1);
582 }
583 }
584 }
585
586 Some(())
587 }
588}
589
590struct ContextualCtx<'a> {
591 mark_set: bool,
592 mark: usize,
593 table: ContextualSubtable<'a>,
594}
595
596impl ContextualCtx<'_> {
597 const SET_MARK: u16 = 0x8000;
598 const DONT_ADVANCE: u16 = 0x4000;
599}
600
601impl DriverContext<ContextualEntryData> for ContextualCtx<'_> {
602 fn in_place() -> bool {
603 true
604 }
605
606 fn can_advance(entry: &StateEntry<ContextualEntryData>) -> bool {
607 entry.flags & Self::DONT_ADVANCE == 0
608 }
609
610 fn is_action_initiable(entry: &StateEntry<ContextualEntryData>) -> bool {
611 entry.flags & Self::SET_MARK != 0
612 }
613
614 fn is_actionable(entry: &StateEntry<ContextualEntryData>) -> bool {
615 entry.payload.mark_index.get() != 0xFFFF || entry.payload.current_index.get() != 0xFFFF
616 }
617
618 #[inline(always)]
619 fn transition(
620 &mut self,
621 entry: &StateEntry<ContextualEntryData>,
622 ac: &mut AatApplyContext,
623 ) -> Option<()> {
624 if ac.buffer.idx == ac.buffer.len && !self.mark_set {
627 return Some(());
628 }
629
630 let mut replacement = None;
631
632 if entry.payload.mark_index.get() != 0xFFFF {
633 let lookup = self
634 .table
635 .lookups
636 .get(usize::from(entry.payload.mark_index.get()))
637 .ok()?;
638 if let Some(gid) = ac.buffer.info[self.mark].as_gid16() {
639 replacement = lookup.value(gid.to_u16()).ok();
640 }
641 }
642
643 if let Some(replacement) = replacement {
644 ac.buffer.unsafe_to_break(
645 Some(self.mark),
646 Some((ac.buffer.idx + 1).min(ac.buffer.len)),
647 );
648 ac.replace_glyph_inplace(self.mark, replacement.into());
649 }
650
651 replacement = None;
652 let idx = ac.buffer.idx.min(ac.buffer.len - 1);
653 if entry.payload.current_index.get() != 0xFFFF {
654 let lookup = self
655 .table
656 .lookups
657 .get(usize::from(entry.payload.current_index.get()))
658 .ok()?;
659 if let Some(gid) = ac.buffer.info[idx].as_gid16() {
660 replacement = lookup.value(gid.to_u16()).ok();
661 }
662 }
663
664 if let Some(replacement) = replacement {
665 ac.replace_glyph_inplace(idx, replacement.into());
666 }
667
668 if entry.flags & Self::SET_MARK != 0 {
669 self.mark_set = true;
670 self.mark = ac.buffer.idx;
671 }
672
673 Some(())
674 }
675}
676
677struct InsertionCtx<'a> {
678 mark: u32,
679 glyphs: &'a [BigEndian<GlyphId16>],
680}
681
682impl InsertionCtx<'_> {
683 const SET_MARK: u16 = 0x8000;
684 const DONT_ADVANCE: u16 = 0x4000;
685 const CURRENT_INSERT_BEFORE: u16 = 0x0800;
686 const MARKED_INSERT_BEFORE: u16 = 0x0400;
687 const CURRENT_INSERT_COUNT: u16 = 0x03E0;
688 const MARKED_INSERT_COUNT: u16 = 0x001F;
689}
690
691impl DriverContext<InsertionEntryData> for InsertionCtx<'_> {
692 fn in_place() -> bool {
693 false
694 }
695
696 fn can_advance(entry: &StateEntry<InsertionEntryData>) -> bool {
697 entry.flags & Self::DONT_ADVANCE == 0
698 }
699
700 fn is_action_initiable(entry: &StateEntry<InsertionEntryData>) -> bool {
701 entry.flags & Self::SET_MARK != 0
702 }
703
704 fn is_actionable(entry: &StateEntry<InsertionEntryData>) -> bool {
705 (entry.flags & (Self::CURRENT_INSERT_COUNT | Self::MARKED_INSERT_COUNT) != 0)
706 && (entry.payload.current_insert_index.get() != 0xFFFF
707 || entry.payload.marked_insert_index.get() != 0xFFFF)
708 }
709
710 #[inline(always)]
711 fn transition(
712 &mut self,
713 entry: &StateEntry<InsertionEntryData>,
714 ac: &mut AatApplyContext,
715 ) -> Option<()> {
716 let flags = entry.flags;
717 let mark_loc = ac.buffer.out_len;
718
719 if entry.payload.marked_insert_index.get() != 0xFFFF {
720 let count = flags & Self::MARKED_INSERT_COUNT;
721 ac.buffer.max_ops -= i32::from(count);
722 if ac.buffer.max_ops <= 0 {
723 return Some(());
724 }
725
726 let start = entry.payload.marked_insert_index.get();
727 let before = flags & Self::MARKED_INSERT_BEFORE != 0;
728
729 let end = ac.buffer.out_len;
730 if !ac.buffer.move_to(self.mark as usize) {
731 return Some(());
732 }
733
734 if ac.buffer.idx < ac.buffer.len && !before {
735 ac.buffer.copy_glyph();
736 }
737
738 for i in 0..count {
740 let i = usize::from(start + i);
741 ac.output_glyph(u32::from(self.glyphs.get(i)?.get().to_u16()));
742 }
743
744 if ac.buffer.idx < ac.buffer.len && !before {
745 ac.buffer.skip_glyph();
746 }
747
748 if !ac.buffer.move_to(end + usize::from(count)) {
749 return Some(());
750 }
751
752 ac.buffer.unsafe_to_break_from_outbuffer(
753 Some(self.mark as usize),
754 Some((ac.buffer.idx + 1).min(ac.buffer.len)),
755 );
756 }
757
758 if flags & Self::SET_MARK != 0 {
759 self.mark = mark_loc as u32;
760 }
761
762 if entry.payload.current_insert_index.get() != 0xFFFF {
763 let count = (flags & Self::CURRENT_INSERT_COUNT) >> 5;
764 ac.buffer.max_ops -= i32::from(count);
765 if ac.buffer.max_ops < 0 {
766 return Some(());
767 }
768
769 let start = entry.payload.current_insert_index.get();
770 let before = flags & Self::CURRENT_INSERT_BEFORE != 0;
771 let end = ac.buffer.out_len;
772
773 if ac.buffer.idx < ac.buffer.len && !before {
774 ac.buffer.copy_glyph();
775 }
776
777 for i in 0..count {
779 let i = usize::from(start + i);
780 ac.output_glyph(u32::from(self.glyphs.get(i)?.get().to_u16()));
781 }
782
783 if ac.buffer.idx < ac.buffer.len && !before {
784 ac.buffer.skip_glyph();
785 }
786
787 if !ac.buffer.move_to(if flags & Self::DONT_ADVANCE != 0 {
802 end
803 } else {
804 end + usize::from(count)
805 }) {
806 return Some(());
807 }
808 }
809
810 Some(())
811 }
812}
813
814const LIGATURE_MAX_MATCHES: usize = 64;
815
816struct LigatureCtx<'a> {
817 table: LigatureSubtable<'a>,
818 match_length: usize,
819 match_positions: [usize; LIGATURE_MAX_MATCHES],
820}
821
822impl LigatureCtx<'_> {
823 const SET_COMPONENT: u16 = 0x8000;
824 const DONT_ADVANCE: u16 = 0x4000;
825 const PERFORM_ACTION: u16 = 0x2000;
826
827 const LIG_ACTION_LAST: u32 = 0x8000_0000;
828 const LIG_ACTION_STORE: u32 = 0x4000_0000;
829 const LIG_ACTION_OFFSET: u32 = 0x3FFF_FFFF;
830}
831
832impl DriverContext<BigEndian<u16>> for LigatureCtx<'_> {
833 fn in_place() -> bool {
834 false
835 }
836
837 fn can_advance(entry: &StateEntry<BigEndian<u16>>) -> bool {
838 entry.flags & Self::DONT_ADVANCE == 0
839 }
840
841 fn is_action_initiable(entry: &StateEntry<BigEndian<u16>>) -> bool {
842 entry.flags & Self::SET_COMPONENT != 0
843 }
844
845 fn is_actionable(entry: &StateEntry<BigEndian<u16>>) -> bool {
846 entry.flags & Self::PERFORM_ACTION != 0
847 }
848
849 #[inline(always)]
850 fn transition(
851 &mut self,
852 entry: &StateEntry<BigEndian<u16>>,
853 ac: &mut AatApplyContext,
854 ) -> Option<()> {
855 if entry.flags & Self::SET_COMPONENT != 0 {
856 if self.match_length != 0
858 && self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES]
859 == ac.buffer.out_len
860 {
861 self.match_length -= 1;
862 }
863
864 self.match_positions[self.match_length % LIGATURE_MAX_MATCHES] = ac.buffer.out_len;
865 self.match_length += 1;
866 }
867
868 if entry.flags & Self::PERFORM_ACTION != 0 {
869 let end = ac.buffer.out_len;
870
871 if self.match_length == 0 {
872 return Some(());
873 }
874
875 if ac.buffer.idx >= ac.buffer.len {
876 return Some(()); }
878
879 let mut cursor = self.match_length;
880
881 let mut ligature_actions_index = entry.payload.get();
882 let mut ligature_idx = 0;
883 loop {
884 if cursor == 0 {
885 self.match_length = 0;
887 break;
888 }
889
890 cursor -= 1;
891 if !ac
892 .buffer
893 .move_to(self.match_positions[cursor % LIGATURE_MAX_MATCHES])
894 {
895 return Some(());
896 }
897
898 let action = match self
901 .table
902 .ligature_actions
903 .get(usize::from(ligature_actions_index))
904 {
905 Some(v) => v.get(),
906 None => break,
907 };
908
909 let mut uoffset = action & Self::LIG_ACTION_OFFSET;
910 if uoffset & 0x2000_0000 != 0 {
911 uoffset |= 0xC000_0000; }
913
914 let offset = uoffset as i32;
915 let component_idx = (ac.buffer.cur(0).glyph_id as i32 + offset) as usize;
916 ligature_idx += match self.table.components.get(component_idx) {
917 Some(v) => v.get(),
918 None => break,
919 };
920
921 if (action & (Self::LIG_ACTION_STORE | Self::LIG_ACTION_LAST)) != 0 {
922 let lig = match self.table.ligatures.get(usize::from(ligature_idx)) {
923 Some(v) => v.get(),
924 None => break,
925 };
926
927 ac.replace_glyph(u32::from(lig.to_u16()));
928
929 let lig_end =
930 self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES] + 1;
931 while self.match_length - 1 > cursor {
933 self.match_length -= 1;
934 if !ac
935 .buffer
936 .move_to(self.match_positions[self.match_length % LIGATURE_MAX_MATCHES])
937 {
938 return Some(());
939 }
940 ac.delete_glyph();
941 }
942
943 if !ac.buffer.move_to(lig_end) {
944 return Some(());
945 }
946 ac.buffer.merge_out_clusters(
947 self.match_positions[cursor % LIGATURE_MAX_MATCHES],
948 ac.buffer.out_len,
949 );
950 }
951
952 ligature_actions_index += 1;
953
954 if action & Self::LIG_ACTION_LAST != 0 {
955 break;
956 }
957 }
958
959 if !ac.buffer.move_to(end) {
960 return Some(());
961 }
962 }
963
964 Some(())
965 }
966}
967
968pub(crate) struct MorxSubtableCache {
969 start_end_safe_to_break: u64,
970 glyph_set: U32Set,
971 class_cache: ClassCache,
972}
973
974impl MorxSubtableCache {
975 pub(crate) fn new(subtable: &Subtable, num_glyphs: u32) -> Self {
976 let mut start_end_safe_to_break = 0u64;
977 let mut glyph_set = U32Set::default();
978 if let Ok(kind) = subtable.kind() {
979 match &kind {
980 SubtableKind::Rearrangement(table) => {
981 start_end_safe_to_break =
982 collect_start_end_safe_to_break::<_, RearrangementCtx>(table);
983 collect_initial_glyphs::<_, RearrangementCtx>(
984 table,
985 &mut glyph_set,
986 num_glyphs,
987 );
988 }
989 SubtableKind::Contextual(table) => {
990 start_end_safe_to_break =
991 collect_start_end_safe_to_break::<_, ContextualCtx>(&table.state_table);
992 collect_initial_glyphs::<_, ContextualCtx>(
993 &table.state_table,
994 &mut glyph_set,
995 num_glyphs,
996 );
997 }
998 SubtableKind::Ligature(table) => {
999 start_end_safe_to_break =
1000 collect_start_end_safe_to_break::<_, LigatureCtx>(&table.state_table);
1001 collect_initial_glyphs::<_, LigatureCtx>(
1002 &table.state_table,
1003 &mut glyph_set,
1004 num_glyphs,
1005 );
1006 }
1007 SubtableKind::NonContextual(ref lookup) => {
1008 lookup.collect_glyphs(&mut glyph_set, num_glyphs);
1009 }
1010 SubtableKind::Insertion(table) => {
1011 start_end_safe_to_break =
1012 collect_start_end_safe_to_break::<_, InsertionCtx>(&table.state_table);
1013 collect_initial_glyphs::<_, InsertionCtx>(
1014 &table.state_table,
1015 &mut glyph_set,
1016 num_glyphs,
1017 );
1018 }
1019 }
1020 }
1021 MorxSubtableCache {
1022 start_end_safe_to_break,
1023 glyph_set,
1024 class_cache: ClassCache::new(),
1025 }
1026 }
1027}