rustybuzz/hb/
aat_layout_morx_table.rs

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
10// TODO: Use set_digest, similarly to how it's used in harfbuzz.
11
12// Chain::compile_flags in harfbuzz
13pub 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            // Check whether this type/setting pair was requested in the map,
39            // and if so, apply its flags.
40
41            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                // Deprecated. https://github.com/harfbuzz/harfbuzz/issues/1342
48                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            // TODO: Port the following commit: https://github.com/harfbuzz/harfbuzz/commit/2124ad890
58        }
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
70// Chain::apply in harfbuzz
71pub 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            // Buffer contents is always in logical direction.  Determine if
96            // we need to reverse before applying this subtable.  We reverse
97            // back after if we did reverse indeed.
98            //
99            // Quoting the spec:
100            // """
101            // Bits 28 and 30 of the coverage field control the order in which
102            // glyphs are processed when the subtable is run by the layout engine.
103            // Bit 28 is used to indicate if the glyph processing direction is
104            // the same as logical order or layout order. Bit 30 is used to
105            // indicate whether glyphs are processed forwards or backwards within
106            // that order.
107            //
108            // Bit 30   Bit 28   Interpretation for Horizontal Text
109            //      0        0   The subtable is processed in layout order
110            //                   (the same order as the glyphs, which is
111            //                   always left-to-right).
112            //      1        0   The subtable is processed in reverse layout order
113            //                   (the order opposite that of the glyphs, which is
114            //                   always right-to-left).
115            //      0        1   The subtable is processed in logical order
116            //                   (the same order as the characters, which may be
117            //                   left-to-right or right-to-left).
118            //      1        1   The subtable is processed in reverse logical order
119            //                   (the order opposite that of the characters, which
120            //                   may be right-to-left or left-to-right).
121
122            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            // If there's only one range, we already checked the flag.
175            None
176        }
177    });
178    ac.buffer.idx = 0;
179    loop {
180        // This block copied from NoncontextualSubtable::apply. Keep in sync.
181        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        // Conditions under which it's guaranteed safe-to-break before current glyph:
224        //
225        // 1. There was no action in this transition; and
226        //
227        // 2. If we break before current glyph, the results will be the same. That
228        //    is guaranteed if:
229        //
230        //    2a. We were already in start-of-text state; or
231        //
232        //    2b. We are epsilon-transitioning to start-of-text state; or
233        //
234        //    2c. Starting from start-of-text state seeing current glyph:
235        //
236        //        2c'. There won't be any actions; and
237        //
238        //        2c". We would end up in the same state that we were going to end up
239        //             in now, including whether epsilon-transitioning.
240        //
241        //    and
242        //
243        // 3. If we break before current glyph, there won't be any end-of-text action
244        //    after previous glyph.
245        //
246        // This triples the transitions we need to look up, but is worth returning
247        // granular unsafe-to-break results. See eg.:
248        //
249        //   https://github.com/harfbuzz/harfbuzz/issues/2860
250
251        let is_safe_to_break_extra = || {
252            // 2c
253            let wouldbe_entry = match machine.entry(START_OF_TEXT, class) {
254                Some(v) => v,
255                None => return false,
256            };
257
258            // 2c'
259            if c.is_actionable(&wouldbe_entry, ac.buffer) {
260                return false;
261            }
262
263            // 2c"
264            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            // 1
270            if c.is_actionable(&entry, ac.buffer) {
271                return false;
272            }
273
274            // 2
275            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            // 3
283            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                    // If there's only one range, we already checked the flag.
359                    None
360                }
361            });
362
363            for info in 0..ac.buffer.len {
364                // This block copied from StateTableDriver::drive. Keep in sync.
365                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                            // We need to access info
370                            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            // The following map has two nibbles, for start-side
450            // and end-side. Values of 0,1,2 mean move that many
451            // to the other side. Value of 3 means move 2 and
452            // flip them.
453            const MAP: [u8; 16] = [
454                0x00, // 0  no change
455                0x10, // 1  Ax => xA
456                0x01, // 2  xD => Dx
457                0x11, // 3  AxD => DxA
458                0x20, // 4  ABx => xAB
459                0x30, // 5  ABx => xBA
460                0x02, // 6  xCD => CDx
461                0x03, // 7  xCD => DCx
462                0x12, // 8  AxCD => CDxA
463                0x13, // 9  AxCD => DCxA
464                0x21, // 10 ABxD => DxAB
465                0x31, // 11 ABxD => DxBA
466                0x22, // 12 ABxCD => CDxAB
467                0x32, // 13 ABxCD => CDxBA
468                0x23, // 14 ABxCD => DCxAB
469                0x33, // 15 ABxCD => DCxBA
470            ];
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        // Looks like CoreText applies neither mark nor current substitution for
566        // end-of-text if mark was not explicitly set.
567        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            // TODO We ignore KashidaLike setting.
673            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            // TODO We ignore KashidaLike setting.
710            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            // Humm. Not sure where to move to. There's this wording under
720            // DontAdvance flag:
721            //
722            // "If set, don't update the glyph index before going to the new state.
723            // This does not mean that the glyph pointed to is the same one as
724            // before. If you've made insertions immediately downstream of the
725            // current glyph, the next glyph processed would in fact be the first
726            // one inserted."
727            //
728            // This suggests that if DontAdvance is NOT set, we should move to
729            // end+count. If it *was*, then move to end, such that newly inserted
730            // glyphs are now visible.
731            //
732            // https://github.com/harfbuzz/harfbuzz/issues/1224#issuecomment-427691417
733            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            // Never mark same index twice, in case DONT_ADVANCE was used...
782            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(()); // TODO: Work on previous instead?
802            }
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                    // Stack underflow. Clear the stack.
811                    self.match_length = 0;
812                    break;
813                }
814
815                cursor -= 1;
816                buffer.move_to(self.match_positions[cursor % LIGATURE_MAX_MATCHES]);
817
818                // We cannot use ? in this loop, because we must call
819                // buffer.move_to(end) in the end.
820                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; // Sign-extend.
832                }
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                    // Now go and delete all subsequent components.
852                    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}