Skip to main content

harfrust/hb/aat/
layout_morx_table.rs

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
18// Chain::compile_flags in harfbuzz
19pub 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            // Check whether this type/setting pair was requested in the map,
44            // and if so, apply its flags.
45
46            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                // Deprecated. https://github.com/harfbuzz/harfbuzz/issues/1342
53                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            // TODO: Port the following commit: https://github.com/harfbuzz/harfbuzz/commit/2124ad890
63        }
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
75// Chain::apply in harfbuzz
76pub 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            // Buffer contents is always in logical direction.  Determine if
127            // we need to reverse before applying this subtable.  We reverse
128            // back after if we did reverse indeed.
129            //
130            // Quoting the spec:
131            // """
132            // Bits 28 and 30 of the coverage field control the order in which
133            // glyphs are processed when the subtable is run by the layout engine.
134            // Bit 28 is used to indicate if the glyph processing direction is
135            // the same as logical order or layout order. Bit 30 is used to
136            // indicate whether glyphs are processed forwards or backwards within
137            // that order.
138            //
139            // Bit 30   Bit 28   Interpretation for Horizontal Text
140            //      0        0   The subtable is processed in layout order
141            //                   (the same order as the glyphs, which is
142            //                   always left-to-right).
143            //      1        0   The subtable is processed in reverse layout order
144            //                   (the order opposite that of the glyphs, which is
145            //                   always right-to-left).
146            //      0        1   The subtable is processed in logical order
147            //                   (the same order as the characters, which may be
148            //                   left-to-right or right-to-left).
149            //      1        1   The subtable is processed in reverse logical order
150            //                   (the order opposite that of the characters, which
151            //                   may be right-to-left or left-to-right).
152
153            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    // And glyphs in those classes.
198
199    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            // If there's only one range, we already checked the flag.
249            None
250        }
251    });
252    ac.buffer.idx = 0;
253    loop {
254        // This block copied from NoncontextualSubtable::apply. Keep in sync.
255        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        // Conditions under which it's guaranteed safe-to-break before current glyph:
301        //
302        // 1. There was no action in this transition; and
303        //
304        // 2. If we break before current glyph, the results will be the same. That
305        //    is guaranteed if:
306        //
307        //    2a. We were already in start-of-text state; or
308        //
309        //    2b. We are epsilon-transitioning to start-of-text state; or
310        //
311        //    2c. Starting from start-of-text state seeing current glyph:
312        //
313        //        2c'. There won't be any actions; and
314        //
315        //        2c". We would end up in the same state that we were going to end up
316        //             in now, including whether epsilon-transitioning.
317        //
318        //    and
319        //
320        // 3. If we break before current glyph, there won't be any end-of-text action
321        //    after previous glyph.
322        //
323        // This triples the transitions we need to look up, but is worth returning
324        // granular unsafe-to-break results. See eg.:
325        //
326        //   https://github.com/harfbuzz/harfbuzz/issues/2860
327
328        let is_safe_to_break =
329            // 1
330            !Ctx::is_actionable(&entry) &&
331
332            // 2
333            (
334                state == START_OF_TEXT
335                || (!Ctx::can_advance(&entry) && next_state == START_OF_TEXT)
336                ||
337                {
338                    // 2c
339                    if let Ok(wouldbe_entry) = machine.entry(START_OF_TEXT, class) {
340                        // 2c'
341                        !Ctx::is_actionable(&wouldbe_entry) &&
342
343                        // 2c"
344                        (
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            // 3
355            (
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                    // If there's only one range, we already checked the flag.
426                    None
427                }
428            });
429
430            for i in 0..ac.buffer.len {
431                // This block copied from StateTableDriver::drive. Keep in sync.
432                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                            // We need to access info
437                            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            // The following map has two nibbles, for start-side
516            // and end-side. Values of 0,1,2 mean move that many
517            // to the other side. Value of 3 means move 2 and
518            // flip them.
519            static MAP: [u8; 16] = [
520                0x00, // 0  no change
521                0x10, // 1  Ax => xA
522                0x01, // 2  xD => Dx
523                0x11, // 3  AxD => DxA
524                0x20, // 4  ABx => xAB
525                0x30, // 5  ABx => xBA
526                0x02, // 6  xCD => CDx
527                0x03, // 7  xCD => DCx
528                0x12, // 8  AxCD => CDxA
529                0x13, // 9  AxCD => DCxA
530                0x21, // 10 ABxD => DxAB
531                0x31, // 11 ABxD => DxBA
532                0x22, // 12 ABxCD => CDxAB
533                0x32, // 13 ABxCD => CDxBA
534                0x23, // 14 ABxCD => DCxAB
535                0x33, // 15 ABxCD => DCxBA
536            ];
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        // Looks like CoreText applies neither mark nor current substitution for
625        // end-of-text if mark was not explicitly set.
626        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            // TODO We ignore KashidaLike setting.
739            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            // TODO We ignore KashidaLike setting.
778            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            // Humm. Not sure where to move to. There's this wording under
788            // DontAdvance flag:
789            //
790            // "If set, don't update the glyph index before going to the new state.
791            // This does not mean that the glyph pointed to is the same one as
792            // before. If you've made insertions immediately downstream of the
793            // current glyph, the next glyph processed would in fact be the first
794            // one inserted."
795            //
796            // This suggests that if DontAdvance is NOT set, we should move to
797            // end+count. If it *was*, then move to end, such that newly inserted
798            // glyphs are now visible.
799            //
800            // https://github.com/harfbuzz/harfbuzz/issues/1224#issuecomment-427691417
801            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            // Never mark same index twice, in case DONT_ADVANCE was used...
857            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(()); // TODO: Work on previous instead?
877            }
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                    // Stack underflow. Clear the stack.
886                    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                // We cannot use ? in this loop, because we must call
899                // ac.buffer.move_to(end) in the end.
900                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; // Sign-extend.
912                }
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                    // Now go and delete all subsequent components.
932                    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}