1use super::aat_layout::*;
2use super::aat_map::{hb_aat_map_builder_t, hb_aat_map_t, range_flags_t};
3use super::buffer::{hb_buffer_t, UnicodeProps};
4use super::{hb_font_t, hb_glyph_info_t};
5use crate::hb::aat_layout_common::hb_aat_apply_context_t;
6use crate::hb::ot_layout::MAX_CONTEXT_LENGTH;
7use alloc::vec;
8use ttf_parser::{apple_layout, morx, FromData, GlyphId, LazyArray32};
9
10pub fn compile_flags(
14 face: &hb_font_t,
15 builder: &hb_aat_map_builder_t,
16 map: &mut hb_aat_map_t,
17) -> Option<()> {
18 let has_feature = |kind: u16, setting: u16| {
19 builder
20 .current_features
21 .binary_search_by(|probe| {
22 if probe.kind != kind {
23 probe.kind.cmp(&kind)
24 } else {
25 probe.setting.cmp(&setting)
26 }
27 })
28 .is_ok()
29 };
30
31 let chains = face.tables().morx.as_ref()?.chains;
32 let chain_len = chains.into_iter().count();
33 map.chain_flags.resize(chain_len, vec![]);
34
35 for (chain, chain_flags) in chains.into_iter().zip(map.chain_flags.iter_mut()) {
36 let mut flags = chain.default_flags;
37 for feature in chain.features {
38 if has_feature(feature.kind, feature.setting) {
42 flags &= feature.disable_flags;
43 flags |= feature.enable_flags;
44 } else if feature.kind == HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE as u16
45 && feature.setting == u16::from(HB_AAT_LAYOUT_FEATURE_SELECTOR_SMALL_CAPS)
46 {
47 let ok = has_feature(
49 HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE as u16,
50 u16::from(HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS),
51 );
52 if ok {
53 flags &= feature.disable_flags;
54 flags |= feature.enable_flags;
55 }
56 }
57 }
59
60 chain_flags.push(range_flags_t {
61 flags,
62 cluster_first: builder.range_first as u32,
63 cluster_last: builder.range_last as u32,
64 });
65 }
66
67 Some(())
68}
69
70pub fn apply<'a>(c: &mut hb_aat_apply_context_t<'a>, map: &'a mut hb_aat_map_t) -> Option<()> {
72 c.buffer.unsafe_to_concat(None, None);
73
74 let chains = c.face.tables().morx.as_ref()?.chains;
75 let chain_len = chains.into_iter().count();
76 map.chain_flags.resize(chain_len, vec![]);
77
78 for (chain, chain_flags) in chains.into_iter().zip(map.chain_flags.iter_mut()) {
79 c.range_flags = Some(chain_flags.as_mut_slice());
80 for subtable in chain.subtables {
81 if let Some(range_flags) = c.range_flags.as_ref() {
82 if range_flags.len() == 1 && (subtable.feature_flags & range_flags[0].flags == 0) {
83 continue;
84 }
85 }
86
87 c.subtable_flags = subtable.feature_flags;
88
89 if !subtable.coverage.is_all_directions()
90 && c.buffer.direction.is_vertical() != subtable.coverage.is_vertical()
91 {
92 continue;
93 }
94
95 let reverse = if subtable.coverage.is_logical() {
123 subtable.coverage.is_backwards()
124 } else {
125 subtable.coverage.is_backwards() != c.buffer.direction.is_backward()
126 };
127
128 if reverse {
129 c.buffer.reverse();
130 }
131
132 apply_subtable(&subtable.kind, c);
133
134 if reverse {
135 c.buffer.reverse();
136 }
137 }
138 }
139
140 Some(())
141}
142
143trait driver_context_t<T: FromData> {
144 fn in_place(&self) -> bool;
145 fn can_advance(&self, entry: &apple_layout::GenericStateEntry<T>) -> bool;
146 fn is_actionable(
147 &self,
148 entry: &apple_layout::GenericStateEntry<T>,
149 buffer: &hb_buffer_t,
150 ) -> bool;
151 fn transition(
152 &mut self,
153 entry: &apple_layout::GenericStateEntry<T>,
154 buffer: &mut hb_buffer_t,
155 ) -> Option<()>;
156}
157
158const START_OF_TEXT: u16 = 0;
159
160fn drive<T: FromData>(
161 machine: &apple_layout::ExtendedStateTable<T>,
162 c: &mut dyn driver_context_t<T>,
163 ac: &mut hb_aat_apply_context_t,
164) {
165 if !c.in_place() {
166 ac.buffer.clear_output();
167 }
168
169 let mut state = START_OF_TEXT;
170 let mut last_range = ac.range_flags.as_ref().and_then(|rf| {
171 if rf.len() > 1 {
172 rf.first().map(|_| 0usize)
173 } else {
174 None
176 }
177 });
178 ac.buffer.idx = 0;
179 loop {
180 if let Some(range_flags) = ac.range_flags.as_ref() {
182 if let Some(last_range) = last_range.as_mut() {
183 let mut range = *last_range;
184 if ac.buffer.idx < ac.buffer.len {
185 let cluster = ac.buffer.cur(0).cluster;
186 while cluster < range_flags[range].cluster_first {
187 range -= 1;
188 }
189
190 while cluster > range_flags[range].cluster_last {
191 range += 1;
192 }
193
194 *last_range = range;
195 }
196
197 if range_flags[range].flags & ac.subtable_flags == 0 {
198 if ac.buffer.idx == ac.buffer.len || !ac.buffer.successful {
199 break;
200 }
201
202 state = START_OF_TEXT;
203
204 ac.buffer.next_glyph();
205 continue;
206 }
207 }
208 }
209
210 let class = if ac.buffer.idx < ac.buffer.len {
211 machine.class(ac.buffer.cur(0).as_glyph()).unwrap_or(1)
212 } else {
213 u16::from(apple_layout::class::END_OF_TEXT)
214 };
215
216 let entry: apple_layout::GenericStateEntry<T> = match machine.entry(state, class) {
217 Some(v) => v,
218 None => break,
219 };
220
221 let next_state = entry.new_state;
222
223 let is_safe_to_break_extra = || {
252 let wouldbe_entry = match machine.entry(START_OF_TEXT, class) {
254 Some(v) => v,
255 None => return false,
256 };
257
258 if c.is_actionable(&wouldbe_entry, ac.buffer) {
260 return false;
261 }
262
263 next_state == wouldbe_entry.new_state
265 && c.can_advance(&entry) == c.can_advance(&wouldbe_entry)
266 };
267
268 let is_safe_to_break = || {
269 if c.is_actionable(&entry, ac.buffer) {
271 return false;
272 }
273
274 let ok = state == START_OF_TEXT
276 || (!c.can_advance(&entry) && next_state == START_OF_TEXT)
277 || is_safe_to_break_extra();
278 if !ok {
279 return false;
280 }
281
282 let end_entry = match machine.entry(state, u16::from(apple_layout::class::END_OF_TEXT))
284 {
285 Some(v) => v,
286 None => return false,
287 };
288 !c.is_actionable(&end_entry, ac.buffer)
289 };
290
291 if !is_safe_to_break() && ac.buffer.backtrack_len() > 0 && ac.buffer.idx < ac.buffer.len {
292 ac.buffer.unsafe_to_break_from_outbuffer(
293 Some(ac.buffer.backtrack_len() - 1),
294 Some(ac.buffer.idx + 1),
295 );
296 }
297
298 c.transition(&entry, ac.buffer);
299
300 state = next_state;
301
302 if ac.buffer.idx >= ac.buffer.len || !ac.buffer.successful {
303 break;
304 }
305
306 if c.can_advance(&entry) {
307 ac.buffer.next_glyph();
308 } else {
309 if ac.buffer.max_ops <= 0 {
310 ac.buffer.next_glyph();
311 }
312 ac.buffer.max_ops -= 1;
313 }
314 }
315
316 if !c.in_place() {
317 ac.buffer.sync();
318 }
319}
320
321fn apply_subtable(kind: &morx::SubtableKind, ac: &mut hb_aat_apply_context_t) {
322 match kind {
323 morx::SubtableKind::Rearrangement(ref table) => {
324 let mut c = RearrangementCtx { start: 0, end: 0 };
325
326 drive::<()>(table, &mut c, ac);
327 }
328 morx::SubtableKind::Contextual(ref table) => {
329 let mut c = ContextualCtx {
330 mark_set: false,
331 face_if_has_glyph_classes:
332 matches!(ac.face.tables().gdef, Some(gdef) if gdef.has_glyph_classes())
333 .then_some(ac.face),
334 mark: 0,
335 table,
336 };
337
338 drive::<morx::ContextualEntryData>(&table.state, &mut c, ac);
339 }
340 morx::SubtableKind::Ligature(ref table) => {
341 let mut c = LigatureCtx {
342 table,
343 match_length: 0,
344 match_positions: [0; LIGATURE_MAX_MATCHES],
345 };
346
347 drive::<u16>(&table.state, &mut c, ac);
348 }
349 morx::SubtableKind::NonContextual(ref lookup) => {
350 let face_if_has_glyph_classes =
351 matches!(ac.face.tables().gdef, Some(gdef) if gdef.has_glyph_classes())
352 .then_some(ac.face);
353
354 let mut last_range = ac.range_flags.as_ref().and_then(|rf| {
355 if rf.len() > 1 {
356 rf.first().map(|_| 0usize)
357 } else {
358 None
360 }
361 });
362
363 for info in 0..ac.buffer.len {
364 if let Some(range_flags) = ac.range_flags.as_ref() {
366 if let Some(last_range) = last_range.as_mut() {
367 let mut range = *last_range;
368 if ac.buffer.idx < ac.buffer.len {
369 let cluster = ac.buffer.cur(0).cluster;
371 while cluster < range_flags[range].cluster_first {
372 range -= 1;
373 }
374
375 while cluster > range_flags[range].cluster_last {
376 range += 1;
377 }
378
379 *last_range = range;
380 }
381
382 if range_flags[range].flags & ac.subtable_flags == 0 {
383 continue;
384 }
385 }
386 }
387
388 let info = &mut ac.buffer.info[info];
389 if let Some(replacement) = lookup.value(info.as_glyph()) {
390 info.glyph_id = u32::from(replacement);
391 if let Some(face) = face_if_has_glyph_classes {
392 info.set_glyph_props(face.glyph_props(GlyphId(replacement)));
393 }
394 }
395 }
396 }
397 morx::SubtableKind::Insertion(ref table) => {
398 let mut c = InsertionCtx {
399 mark: 0,
400 glyphs: table.glyphs,
401 };
402
403 drive::<morx::InsertionEntryData>(&table.state, &mut c, ac);
404 }
405 }
406}
407
408struct RearrangementCtx {
409 start: usize,
410 end: usize,
411}
412
413impl RearrangementCtx {
414 const MARK_FIRST: u16 = 0x8000;
415 const DONT_ADVANCE: u16 = 0x4000;
416 const MARK_LAST: u16 = 0x2000;
417 const VERB: u16 = 0x000F;
418}
419
420impl driver_context_t<()> for RearrangementCtx {
421 fn in_place(&self) -> bool {
422 true
423 }
424
425 fn can_advance(&self, entry: &apple_layout::GenericStateEntry<()>) -> bool {
426 entry.flags & Self::DONT_ADVANCE == 0
427 }
428
429 fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<()>, _: &hb_buffer_t) -> bool {
430 entry.flags & Self::VERB != 0 && self.start < self.end
431 }
432
433 fn transition(
434 &mut self,
435 entry: &apple_layout::GenericStateEntry<()>,
436 buffer: &mut hb_buffer_t,
437 ) -> Option<()> {
438 let flags = entry.flags;
439
440 if flags & Self::MARK_FIRST != 0 {
441 self.start = buffer.idx;
442 }
443
444 if flags & Self::MARK_LAST != 0 {
445 self.end = (buffer.idx + 1).min(buffer.len);
446 }
447
448 if flags & Self::VERB != 0 && self.start < self.end {
449 const MAP: [u8; 16] = [
454 0x00, 0x10, 0x01, 0x11, 0x20, 0x30, 0x02, 0x03, 0x12, 0x13, 0x21, 0x31, 0x22, 0x32, 0x23, 0x33, ];
471
472 let m = MAP[usize::from(flags & Self::VERB)];
473 let l = 2.min(m >> 4) as usize;
474 let r = 2.min(m & 0x0F) as usize;
475 let reverse_l = 3 == (m >> 4);
476 let reverse_r = 3 == (m & 0x0F);
477
478 if (self.end - self.start >= l + r) && (self.end - self.start <= MAX_CONTEXT_LENGTH) {
479 buffer.merge_clusters(self.start, (buffer.idx + 1).min(buffer.len));
480 buffer.merge_clusters(self.start, self.end);
481
482 let mut buf = [hb_glyph_info_t::default(); 4];
483
484 for (i, glyph_info) in buf[..l].iter_mut().enumerate() {
485 *glyph_info = buffer.info[self.start + i];
486 }
487
488 for i in 0..r {
489 buf[i + 2] = buffer.info[self.end - r + i];
490 }
491
492 if l > r {
493 for i in 0..(self.end - self.start - l - r) {
494 buffer.info[self.start + r + i] = buffer.info[self.start + l + i];
495 }
496 } else if l < r {
497 for i in (0..(self.end - self.start - l - r)).rev() {
498 buffer.info[self.start + r + i] = buffer.info[self.start + l + i];
499 }
500 }
501
502 for i in 0..r {
503 buffer.info[self.start + i] = buf[2 + i];
504 }
505
506 for i in 0..l {
507 buffer.info[self.end - l + i] = buf[i];
508 }
509
510 if reverse_l {
511 buffer.info.swap(self.end - 1, self.end - 2);
512 }
513
514 if reverse_r {
515 buffer.info.swap(self.start, self.start + 1);
516 }
517 }
518 }
519
520 Some(())
521 }
522}
523
524struct ContextualCtx<'a> {
525 mark_set: bool,
526 face_if_has_glyph_classes: Option<&'a hb_font_t<'a>>,
527 mark: usize,
528 table: &'a morx::ContextualSubtable<'a>,
529}
530
531impl ContextualCtx<'_> {
532 const SET_MARK: u16 = 0x8000;
533 const DONT_ADVANCE: u16 = 0x4000;
534}
535
536impl driver_context_t<morx::ContextualEntryData> for ContextualCtx<'_> {
537 fn in_place(&self) -> bool {
538 true
539 }
540
541 fn can_advance(
542 &self,
543 entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>,
544 ) -> bool {
545 entry.flags & Self::DONT_ADVANCE == 0
546 }
547
548 fn is_actionable(
549 &self,
550 entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>,
551 buffer: &hb_buffer_t,
552 ) -> bool {
553 if buffer.idx == buffer.len && !self.mark_set {
554 return false;
555 }
556
557 entry.extra.mark_index != 0xFFFF || entry.extra.current_index != 0xFFFF
558 }
559
560 fn transition(
561 &mut self,
562 entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>,
563 buffer: &mut hb_buffer_t,
564 ) -> Option<()> {
565 if buffer.idx == buffer.len && !self.mark_set {
568 return Some(());
569 }
570
571 let mut replacement = None;
572
573 if entry.extra.mark_index != 0xFFFF {
574 let lookup = self.table.lookup(u32::from(entry.extra.mark_index))?;
575 replacement = lookup.value(buffer.info[self.mark].as_glyph());
576 }
577
578 if let Some(replacement) = replacement {
579 buffer.unsafe_to_break(Some(self.mark), Some((buffer.idx + 1).min(buffer.len)));
580 buffer.info[self.mark].glyph_id = u32::from(replacement);
581
582 if let Some(face) = self.face_if_has_glyph_classes {
583 buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement)));
584 }
585 }
586
587 replacement = None;
588 let idx = buffer.idx.min(buffer.len - 1);
589 if entry.extra.current_index != 0xFFFF {
590 let lookup = self.table.lookup(u32::from(entry.extra.current_index))?;
591 replacement = lookup.value(buffer.info[idx].as_glyph());
592 }
593
594 if let Some(replacement) = replacement {
595 buffer.info[idx].glyph_id = u32::from(replacement);
596
597 if let Some(face) = self.face_if_has_glyph_classes {
598 buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement)));
599 }
600 }
601
602 if entry.flags & Self::SET_MARK != 0 {
603 self.mark_set = true;
604 self.mark = buffer.idx;
605 }
606
607 Some(())
608 }
609}
610
611struct InsertionCtx<'a> {
612 mark: u32,
613 glyphs: LazyArray32<'a, GlyphId>,
614}
615
616impl InsertionCtx<'_> {
617 const SET_MARK: u16 = 0x8000;
618 const DONT_ADVANCE: u16 = 0x4000;
619 const CURRENT_INSERT_BEFORE: u16 = 0x0800;
620 const MARKED_INSERT_BEFORE: u16 = 0x0400;
621 const CURRENT_INSERT_COUNT: u16 = 0x03E0;
622 const MARKED_INSERT_COUNT: u16 = 0x001F;
623}
624
625impl driver_context_t<morx::InsertionEntryData> for InsertionCtx<'_> {
626 fn in_place(&self) -> bool {
627 false
628 }
629
630 fn can_advance(
631 &self,
632 entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>,
633 ) -> bool {
634 entry.flags & Self::DONT_ADVANCE == 0
635 }
636
637 fn is_actionable(
638 &self,
639 entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>,
640 _: &hb_buffer_t,
641 ) -> bool {
642 (entry.flags & (Self::CURRENT_INSERT_COUNT | Self::MARKED_INSERT_COUNT) != 0)
643 && (entry.extra.current_insert_index != 0xFFFF
644 || entry.extra.marked_insert_index != 0xFFFF)
645 }
646
647 fn transition(
648 &mut self,
649 entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>,
650 buffer: &mut hb_buffer_t,
651 ) -> Option<()> {
652 let flags = entry.flags;
653 let mark_loc = buffer.out_len;
654
655 if entry.extra.marked_insert_index != 0xFFFF {
656 let count = flags & Self::MARKED_INSERT_COUNT;
657 buffer.max_ops -= i32::from(count);
658 if buffer.max_ops <= 0 {
659 return Some(());
660 }
661
662 let start = entry.extra.marked_insert_index;
663 let before = flags & Self::MARKED_INSERT_BEFORE != 0;
664
665 let end = buffer.out_len;
666 buffer.move_to(self.mark as usize);
667
668 if buffer.idx < buffer.len && !before {
669 buffer.copy_glyph();
670 }
671
672 for i in 0..count {
674 let i = u32::from(start + i);
675 buffer.output_glyph(u32::from(self.glyphs.get(i)?.0));
676 }
677
678 if buffer.idx < buffer.len && !before {
679 buffer.skip_glyph();
680 }
681
682 buffer.move_to(end + usize::from(count));
683
684 buffer.unsafe_to_break_from_outbuffer(
685 Some(self.mark as usize),
686 Some((buffer.idx + 1).min(buffer.len)),
687 );
688 }
689
690 if flags & Self::SET_MARK != 0 {
691 self.mark = mark_loc as u32;
692 }
693
694 if entry.extra.current_insert_index != 0xFFFF {
695 let count = (flags & Self::CURRENT_INSERT_COUNT) >> 5;
696 buffer.max_ops -= i32::from(count);
697 if buffer.max_ops < 0 {
698 return Some(());
699 }
700
701 let start = entry.extra.current_insert_index;
702 let before = flags & Self::CURRENT_INSERT_BEFORE != 0;
703 let end = buffer.out_len;
704
705 if buffer.idx < buffer.len && !before {
706 buffer.copy_glyph();
707 }
708
709 for i in 0..count {
711 let i = u32::from(start + i);
712 buffer.output_glyph(u32::from(self.glyphs.get(i)?.0));
713 }
714
715 if buffer.idx < buffer.len && !before {
716 buffer.skip_glyph();
717 }
718
719 buffer.move_to(if flags & Self::DONT_ADVANCE != 0 {
734 end
735 } else {
736 end + usize::from(count)
737 });
738 }
739
740 Some(())
741 }
742}
743
744const LIGATURE_MAX_MATCHES: usize = 64;
745
746struct LigatureCtx<'a> {
747 table: &'a morx::LigatureSubtable<'a>,
748 match_length: usize,
749 match_positions: [usize; LIGATURE_MAX_MATCHES],
750}
751
752impl LigatureCtx<'_> {
753 const SET_COMPONENT: u16 = 0x8000;
754 const DONT_ADVANCE: u16 = 0x4000;
755 const PERFORM_ACTION: u16 = 0x2000;
756
757 const LIG_ACTION_LAST: u32 = 0x80000000;
758 const LIG_ACTION_STORE: u32 = 0x40000000;
759 const LIG_ACTION_OFFSET: u32 = 0x3FFFFFFF;
760}
761
762impl driver_context_t<u16> for LigatureCtx<'_> {
763 fn in_place(&self) -> bool {
764 false
765 }
766
767 fn can_advance(&self, entry: &apple_layout::GenericStateEntry<u16>) -> bool {
768 entry.flags & Self::DONT_ADVANCE == 0
769 }
770
771 fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<u16>, _: &hb_buffer_t) -> bool {
772 entry.flags & Self::PERFORM_ACTION != 0
773 }
774
775 fn transition(
776 &mut self,
777 entry: &apple_layout::GenericStateEntry<u16>,
778 buffer: &mut hb_buffer_t,
779 ) -> Option<()> {
780 if entry.flags & Self::SET_COMPONENT != 0 {
781 if self.match_length != 0
783 && self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES]
784 == buffer.out_len
785 {
786 self.match_length -= 1;
787 }
788
789 self.match_positions[self.match_length % LIGATURE_MAX_MATCHES] = buffer.out_len;
790 self.match_length += 1;
791 }
792
793 if entry.flags & Self::PERFORM_ACTION != 0 {
794 let end = buffer.out_len;
795
796 if self.match_length == 0 {
797 return Some(());
798 }
799
800 if buffer.idx >= buffer.len {
801 return Some(()); }
803
804 let mut cursor = self.match_length;
805
806 let mut ligature_actions_index = entry.extra;
807 let mut ligature_idx = 0;
808 loop {
809 if cursor == 0 {
810 self.match_length = 0;
812 break;
813 }
814
815 cursor -= 1;
816 buffer.move_to(self.match_positions[cursor % LIGATURE_MAX_MATCHES]);
817
818 let action = match self
821 .table
822 .ligature_actions
823 .get(u32::from(ligature_actions_index))
824 {
825 Some(v) => v,
826 None => break,
827 };
828
829 let mut uoffset = action & Self::LIG_ACTION_OFFSET;
830 if uoffset & 0x20000000 != 0 {
831 uoffset |= 0xC0000000; }
833
834 let offset = uoffset as i32;
835 let component_idx = (buffer.cur(0).glyph_id as i32 + offset) as u32;
836 ligature_idx += match self.table.components.get(component_idx) {
837 Some(v) => v,
838 None => break,
839 };
840
841 if (action & (Self::LIG_ACTION_STORE | Self::LIG_ACTION_LAST)) != 0 {
842 let lig = match self.table.ligatures.get(u32::from(ligature_idx)) {
843 Some(v) => v,
844 None => break,
845 };
846
847 buffer.replace_glyph(u32::from(lig.0));
848
849 let lig_end =
850 self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES] + 1;
851 while self.match_length - 1 > cursor {
853 self.match_length -= 1;
854 buffer.move_to(
855 self.match_positions[self.match_length % LIGATURE_MAX_MATCHES],
856 );
857 let cur_unicode = buffer.cur(0).unicode_props();
858 buffer
859 .cur_mut(0)
860 .set_unicode_props(cur_unicode | UnicodeProps::IGNORABLE.bits());
861 buffer.replace_glyph(0xFFFF);
862 }
863
864 buffer.move_to(lig_end);
865 buffer.merge_out_clusters(
866 self.match_positions[cursor % LIGATURE_MAX_MATCHES],
867 buffer.out_len,
868 );
869 }
870
871 ligature_actions_index += 1;
872
873 if action & Self::LIG_ACTION_LAST != 0 {
874 break;
875 }
876 }
877
878 buffer.move_to(end);
879 }
880
881 Some(())
882 }
883}