Skip to main content

harfrust/hb/
kerning.rs

1use super::aat::layout::DELETED_GLYPH;
2use alloc::boxed::Box;
3use read_fonts::{
4    tables::{
5        aat,
6        kern::{Subtable, Subtable0, Subtable2, Subtable3, SubtableKind},
7    },
8    types::{GlyphId, GlyphId16},
9};
10
11use super::aat::layout_common::{AatApplyContext, ClassCache, START_OF_TEXT};
12use super::aat::layout_kerx_table::SimpleKerning;
13use super::buffer::*;
14use super::face::Scale;
15use super::ot_layout::TableIndex;
16use super::ot_layout_common::lookup_flags;
17use super::ot_layout_gpos_table::attach_type;
18use super::ot_layout_gsubgpos::{skipping_iterator_t, OT::hb_ot_apply_context_t};
19use super::ot_shape_plan::hb_ot_shape_plan_t;
20use super::{hb_font_t, hb_mask_t};
21use crate::U32Set;
22
23pub(crate) fn get_class(machine: &aat::StateTable, glyph_id: GlyphId, cache: &ClassCache) -> u8 {
24    if let Some(klass) = cache.get(glyph_id.to_u32()) {
25        return klass as u8;
26    }
27    let klass = machine
28        .class(GlyphId16::new(glyph_id.to_u32() as u16))
29        .unwrap_or(aat::class::OUT_OF_BOUNDS);
30    cache.set(glyph_id.to_u32(), klass as u32);
31    klass
32}
33
34pub fn hb_ot_layout_kern(
35    plan: &hb_ot_shape_plan_t,
36    face: &hb_font_t,
37    scale: Scale,
38    buffer: &mut hb_buffer_t,
39) -> Option<()> {
40    let mut c = AatApplyContext::new(plan, face, scale, buffer);
41
42    let (kern, subtable_caches) = c.face.aat_tables.kern.as_ref()?;
43
44    let mut subtable_idx = 0;
45
46    let mut seen_cross_stream = false;
47    for subtable in kern.subtables() {
48        let Ok(subtable) = subtable else { continue };
49
50        let subtable_cache = subtable_caches.get(subtable_idx);
51        let Some(subtable_cache) = subtable_cache.as_ref() else {
52            break;
53        };
54        subtable_idx += 1;
55
56        if subtable.is_variable() {
57            continue;
58        }
59
60        if c.buffer.direction.is_horizontal() != subtable.is_horizontal() {
61            continue;
62        }
63
64        c.first_set = Some(&subtable_cache.first_set);
65        c.second_set = Some(&subtable_cache.second_set);
66        c.machine_class_cache = Some(&subtable_cache.class_cache);
67        c.start_end_safe_to_break = subtable_cache.start_end_safe_to_break;
68
69        if !c.buffer_intersects_machine() {
70            continue;
71        }
72
73        let reverse = c.buffer.direction.is_backward();
74        let is_cross_stream = subtable.is_cross_stream();
75
76        if !seen_cross_stream && is_cross_stream {
77            seen_cross_stream = true;
78
79            // Attach all glyphs into a chain.
80            for pos in &mut c.buffer.pos {
81                pos.set_attach_type(attach_type::CURSIVE);
82                pos.set_attach_chain(if c.buffer.direction.is_forward() {
83                    -1
84                } else {
85                    1
86                });
87                // We intentionally don't set BufferScratchFlags::HAS_GPOS_ATTACHMENT,
88                // since there needs to be a non-zero attachment for post-positioning to
89                // be needed.
90            }
91        }
92
93        let Ok(kind) = subtable.kind() else {
94            continue;
95        };
96
97        if reverse != c.buffer_is_reversed {
98            c.reverse_buffer();
99        }
100
101        match kind {
102            SubtableKind::Format0(format0) if plan.requested_kerning => {
103                apply_simple_kerning(&mut c, &format0, is_cross_stream);
104            }
105            SubtableKind::Format1(format1) => {
106                apply_state_machine_kerning(&mut c, &format1, is_cross_stream);
107            }
108            SubtableKind::Format2(format2) if plan.requested_kerning => {
109                apply_simple_kerning(&mut c, &format2, is_cross_stream);
110            }
111            SubtableKind::Format3(format3) if plan.requested_kerning => {
112                apply_simple_kerning(&mut c, &format3, is_cross_stream);
113            }
114            _ => {}
115        }
116    }
117    if c.buffer_is_reversed {
118        c.reverse_buffer();
119    }
120    Some(())
121}
122
123fn machine_kern<F>(
124    face: &hb_font_t,
125    scale: Scale,
126    buffer: &mut hb_buffer_t,
127    kern_mask: hb_mask_t,
128    cross_stream: bool,
129    get_kerning: F,
130) where
131    F: Fn(u32, u32) -> i32,
132{
133    buffer.unsafe_to_concat(None, None);
134    let mut ctx = hb_ot_apply_context_t::new(TableIndex::GPOS, face, scale, buffer);
135    ctx.set_lookup_mask(kern_mask);
136    ctx.lookup_props = u32::from(lookup_flags::IGNORE_MARKS);
137    ctx.update_matchers();
138
139    let horizontal = ctx.buffer.direction.is_horizontal();
140    let use_x_scale = horizontal ^ cross_stream;
141    let mut i = 0;
142    let mut iter = skipping_iterator_t::new(&mut ctx, false);
143    while i < iter.buffer.len {
144        if (iter.buffer.info[i].mask & kern_mask) == 0 {
145            i += 1;
146            continue;
147        }
148
149        iter.reset_fast(i);
150
151        let mut unsafe_to = 0;
152        if !iter.next(Some(&mut unsafe_to)) {
153            i += 1;
154            continue;
155        }
156
157        let j = iter.index();
158
159        let info = &iter.buffer.info;
160        let kern = get_kerning(info[i].glyph_id, info[j].glyph_id);
161        let kern = if use_x_scale {
162            scale.scale_x(kern)
163        } else {
164            scale.scale_y(kern)
165        };
166        let pos = &mut iter.buffer.pos;
167        if kern != 0 {
168            if horizontal {
169                if cross_stream {
170                    pos[j].y_offset = kern;
171                    iter.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
172                } else {
173                    let kern1 = kern >> 1;
174                    let kern2 = kern - kern1;
175                    pos[i].x_advance = pos[i].x_advance.saturating_add(kern1);
176                    pos[j].x_advance = pos[j].x_advance.saturating_add(kern2);
177                    pos[j].x_offset = pos[j].x_offset.saturating_add(kern2);
178                }
179            } else {
180                if cross_stream {
181                    pos[j].x_offset = kern;
182                    iter.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
183                } else {
184                    let kern1 = kern >> 1;
185                    let kern2 = kern - kern1;
186                    pos[i].y_advance = pos[i].y_advance.saturating_add(kern1);
187                    pos[j].y_advance = pos[j].y_advance.saturating_add(kern2);
188                    pos[j].y_offset = pos[j].y_offset.saturating_add(kern2);
189                }
190            }
191
192            iter.buffer.unsafe_to_break(Some(i), Some(j + 1));
193        }
194
195        i = j;
196    }
197}
198
199fn apply_simple_kerning<T: SimpleKerning>(
200    c: &mut AatApplyContext,
201    subtable: &T,
202    is_cross_stream: bool,
203) {
204    let first_set = c.first_set.as_ref().unwrap();
205    let second_set = c.second_set.as_ref().unwrap();
206
207    machine_kern(
208        c.face,
209        c.scale,
210        c.buffer,
211        c.plan.kern_mask,
212        is_cross_stream,
213        |left, right| {
214            if !first_set.contains(left) || !second_set.contains(right) {
215                0
216            } else {
217                subtable
218                    .simple_kerning(left.into(), right.into())
219                    .unwrap_or(0)
220            }
221        },
222    );
223}
224
225struct StateMachineDriver {
226    stack: [usize; 8],
227    depth: usize,
228}
229
230pub trait CollectGlyphs {
231    /// For each valid index, read the value of type `T`.
232    /// If `filter(&value)` returns true, insert the index into `set`.
233    fn collect_glyphs_filtered<F>(&self, _set: &mut U32Set, _num_glyphs: u32, _filter: F)
234    where
235        F: Fn(u8) -> bool;
236}
237
238impl CollectGlyphs for aat::ClassSubtable<'_> {
239    fn collect_glyphs_filtered<F>(&self, set: &mut U32Set, _num_glyphs: u32, filter: F)
240    where
241        F: Fn(u8) -> bool,
242    {
243        let first_glyph = self.first_glyph() as u32;
244        let class_array = self.class_array();
245        for (i, class) in class_array.iter().enumerate() {
246            let gid = first_glyph + i as u32;
247            if filter(*class) {
248                set.insert(gid);
249            }
250        }
251    }
252}
253
254fn collect_initial_glyphs(machine: &aat::StateTable, glyphs: &mut U32Set, num_glyphs: u32) {
255    let mut classes = U32Set::default();
256
257    let class_table = machine.header.class_table().ok();
258    let Some(class_table) = class_table else {
259        return;
260    };
261
262    let n_classes = machine.header.state_size();
263    for i in 0..n_classes {
264        if let Ok(entry) = machine.entry(START_OF_TEXT, i as u8) {
265            if entry.new_state == START_OF_TEXT
266                && !entry.is_action_initiable()
267                && !entry.is_actionable()
268            {
269                continue;
270            }
271            classes.insert(i as u32);
272        }
273    }
274
275    // And glyphs in those classes.
276
277    let filter = |class: u8| classes.contains(class as u32);
278
279    if filter(aat::class::DELETED_GLYPH) {
280        glyphs.insert(DELETED_GLYPH);
281    }
282
283    class_table.collect_glyphs_filtered(glyphs, num_glyphs, filter);
284}
285
286fn collect_start_end_safe_to_break(machine: &aat::StateTable) -> u64 {
287    let mut result = 0u64;
288    for state in 0..64 {
289        let bit = if let Ok(entry) = machine.entry(state, aat::class::END_OF_TEXT) {
290            !entry.is_actionable()
291        } else {
292            true
293        };
294        if bit {
295            result |= 1 << state;
296        }
297    }
298    result
299}
300
301fn apply_state_machine_kerning(
302    c: &mut AatApplyContext,
303    subtable: &aat::StateTable,
304    is_cross_stream: bool,
305) {
306    let mut driver = StateMachineDriver {
307        stack: [0; 8],
308        depth: 0,
309    };
310
311    let mut state = START_OF_TEXT;
312    c.buffer.idx = 0;
313    loop {
314        let class = if c.buffer.idx < c.buffer.len {
315            get_class(
316                subtable,
317                c.buffer.cur(0).as_glyph(),
318                c.machine_class_cache.unwrap(),
319            )
320        } else {
321            aat::class::END_OF_TEXT
322        };
323
324        let Ok(entry) = subtable.entry(state, class) else {
325            break;
326        };
327
328        let next_state = entry.new_state;
329
330        // Conditions under which it's guaranteed safe-to-break before current glyph:
331        //
332        // 1. There was no action in this transition; and
333        //
334        // 2. If we break before current glyph, the results will be the same. That
335        //    is guaranteed if:
336        //
337        //    2a. We were already in start-of-text state; or
338        //
339        //    2b. We are epsilon-transitioning to start-of-text state; or
340        //
341        //    2c. Starting from start-of-text state seeing current glyph:
342        //
343        //        2c'. There won't be any actions; and
344        //
345        //        2c". We would end up in the same state that we were going to end up
346        //             in now, including whether epsilon-transitioning.
347        //
348        //    and
349        //
350        // 3. If we break before current glyph, there won't be any end-of-text action
351        //    after previous glyph.
352        //
353        // This triples the transitions we need to look up, but is worth returning
354        // granular unsafe-to-break results. See eg.:
355        //
356        //   https://github.com/harfbuzz/harfbuzz/issues/2860
357
358        let is_safe_to_break =
359            // 1
360            !entry.is_actionable() &&
361
362            // 2
363            (
364                state == START_OF_TEXT
365                || (!entry.has_advance() && next_state == START_OF_TEXT)
366                ||
367                {
368                    // 2c
369                    if let Ok(wouldbe_entry) = subtable.entry(START_OF_TEXT, class) {
370                        // 2c'
371                        !wouldbe_entry.is_actionable() &&
372
373                        // 2c"
374                        (
375                            next_state == wouldbe_entry.new_state &&
376                            entry.has_advance() == wouldbe_entry.has_advance()
377                        )
378                    } else {
379                        false
380                    }
381                }
382            ) &&
383
384            // 3
385            (
386                if state < 64 {
387                    (c.start_end_safe_to_break & (1 << state)) != 0
388                } else {
389                    if let Ok(end_entry) = subtable.entry(state, aat::class::END_OF_TEXT) {
390                        !end_entry.is_actionable()
391                    } else {
392                        false
393                    }
394                }
395            )
396        ;
397
398        if !is_safe_to_break && c.buffer.backtrack_len() > 0 && c.buffer.idx < c.buffer.len {
399            c.buffer.unsafe_to_break_from_outbuffer(
400                Some(c.buffer.backtrack_len() - 1),
401                Some(c.buffer.idx + 1),
402            );
403        }
404
405        state_machine_transition(c, subtable, &entry, is_cross_stream, &mut driver);
406
407        state = next_state;
408
409        if c.buffer.idx >= c.buffer.len {
410            break;
411        }
412
413        c.buffer.max_ops -= 1;
414        if entry.has_advance() || c.buffer.max_ops <= 0 {
415            c.buffer.next_glyph();
416        }
417    }
418}
419
420#[inline(always)]
421fn state_machine_transition(
422    c: &mut AatApplyContext,
423    subtable: &aat::StateTable,
424    entry: &aat::StateEntry,
425    is_cross_stream: bool,
426    driver: &mut StateMachineDriver,
427) {
428    let scale = c.scale;
429    let use_x_scale = c.buffer.direction.is_horizontal() ^ is_cross_stream;
430    let buffer = &mut *c.buffer;
431    let kern_mask = c.plan.kern_mask;
432
433    if entry.has_push() {
434        if driver.depth < driver.stack.len() {
435            driver.stack[driver.depth] = buffer.idx;
436            driver.depth += 1;
437        } else {
438            driver.depth = 0; // Probably not what CoreText does, but better?
439        }
440    }
441
442    if entry.has_offset() && driver.depth != 0 {
443        let mut value_offset = entry.value_offset();
444        let Ok(mut value) = subtable.read_value::<i16>(value_offset as usize) else {
445            driver.depth = 0;
446            return;
447        };
448
449        // From Apple 'kern' spec:
450        // "Each pops one glyph from the kerning stack and applies the kerning value to it.
451        // The end of the list is marked by an odd value...
452        let mut last = false;
453        while !last && driver.depth != 0 {
454            driver.depth -= 1;
455            let idx = driver.stack[driver.depth];
456            let mut v = value as i32;
457            value_offset = value_offset.wrapping_add(2);
458            value = subtable
459                .read_value::<i16>(value_offset as usize)
460                .unwrap_or(0);
461            if idx >= buffer.len {
462                continue;
463            }
464
465            // "The end of the list is marked by an odd value..."
466            last = v & 1 != 0;
467            v &= !1;
468            let scaled_v = if use_x_scale {
469                scale.scale_x(v)
470            } else {
471                scale.scale_y(v)
472            };
473
474            // Testing shows that CoreText only applies kern (cross-stream or not)
475            // if none has been applied by previous subtables. That is, it does
476            // NOT seem to accumulate as otherwise implied by specs.
477
478            let mut has_gpos_attachment = false;
479            let glyph_mask = buffer.info[idx].mask;
480            let pos = &mut buffer.pos[idx];
481
482            if buffer.direction.is_horizontal() {
483                if is_cross_stream {
484                    // The following flag is undocumented in the spec, but described
485                    // in the 'kern' table example.
486                    if v == -0x8000 {
487                        pos.set_attach_type(0);
488                        pos.set_attach_chain(0);
489                        pos.y_offset = 0;
490                    } else if pos.attach_type() != 0 {
491                        pos.y_offset = pos.y_offset.saturating_add(scaled_v);
492                        has_gpos_attachment = true;
493                    }
494                } else if glyph_mask & kern_mask != 0 {
495                    pos.x_advance = pos.x_advance.saturating_add(scaled_v);
496                    pos.x_offset = pos.x_offset.saturating_add(scaled_v);
497                }
498            } else {
499                if is_cross_stream {
500                    // CoreText doesn't do crossStream kerning in vertical. We do.
501                    if v == -0x8000 {
502                        pos.set_attach_type(0);
503                        pos.set_attach_chain(0);
504                        pos.x_offset = 0;
505                    } else if pos.attach_type() != 0 {
506                        pos.x_offset = pos.x_offset.saturating_add(scaled_v);
507                        has_gpos_attachment = true;
508                    }
509                } else if glyph_mask & kern_mask != 0 {
510                    if pos.y_offset == 0 {
511                        pos.y_advance = pos.y_advance.saturating_add(scaled_v);
512                        pos.y_offset = pos.y_offset.saturating_add(scaled_v);
513                    }
514                }
515            }
516
517            if has_gpos_attachment {
518                buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
519            }
520        }
521    }
522}
523
524trait KernStateEntryExt {
525    fn flags(&self) -> u16;
526
527    fn is_action_initiable(&self) -> bool {
528        self.flags() & 0x8000 != 0
529    }
530
531    fn is_actionable(&self) -> bool {
532        self.flags() & 0x3FFF != 0
533    }
534
535    fn has_offset(&self) -> bool {
536        self.flags() & 0x3FFF != 0
537    }
538
539    fn value_offset(&self) -> u16 {
540        self.flags() & 0x3FFF
541    }
542
543    fn has_advance(&self) -> bool {
544        self.flags() & 0x4000 == 0
545    }
546
547    fn has_push(&self) -> bool {
548        self.flags() & 0x8000 != 0
549    }
550}
551
552impl<T> KernStateEntryExt for aat::StateEntry<T> {
553    fn flags(&self) -> u16 {
554        self.flags
555    }
556}
557
558impl SimpleKerning for Subtable0<'_> {
559    fn simple_kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
560        self.kerning(left, right)
561    }
562    fn collect_glyphs(&self, first_set: &mut U32Set, second_set: &mut U32Set, _num_glyphs: u32) {
563        for &pair in self.pairs() {
564            first_set.insert(pair.left.get().to_u32());
565            second_set.insert(pair.right.get().to_u32());
566        }
567    }
568}
569
570impl SimpleKerning for Subtable2<'_> {
571    fn simple_kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
572        self.kerning(left, right)
573    }
574    fn collect_glyphs(&self, first_set: &mut U32Set, second_set: &mut U32Set, _num_glyphs: u32) {
575        let left_classes = &self.left_offset_table;
576        let right_classes = &self.right_offset_table;
577
578        let first_glyph = left_classes.first_glyph().to_u32();
579        let last_glyphs = first_glyph + left_classes.n_glyphs().saturating_sub(1) as u32;
580        first_set.insert_range(first_glyph..=last_glyphs);
581
582        let first_glyph = right_classes.first_glyph().to_u32();
583        let last_glyphs = first_glyph + right_classes.n_glyphs().saturating_sub(1) as u32;
584        second_set.insert_range(first_glyph..=last_glyphs);
585    }
586}
587
588impl SimpleKerning for Subtable3<'_> {
589    fn simple_kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
590        self.kerning(left, right)
591    }
592    fn collect_glyphs(&self, first_set: &mut U32Set, second_set: &mut U32Set, _num_glyphs: u32) {
593        first_set.insert_range(0..=self.glyph_count().saturating_sub(1) as u32);
594        second_set.insert_range(0..=self.glyph_count().saturating_sub(1) as u32);
595    }
596}
597
598pub(crate) struct KernSubtableCache {
599    start_end_safe_to_break: u64,
600    first_set: U32Set,
601    second_set: U32Set,
602    class_cache: Box<ClassCache>,
603}
604
605impl KernSubtableCache {
606    pub(crate) fn new(subtable: &Subtable, num_glyphs: u32) -> Self {
607        let mut start_end_safe_to_break = 0u64;
608        let mut first_set = U32Set::default();
609        let mut second_set = U32Set::default();
610        if let Ok(kind) = subtable.kind() {
611            match &kind {
612                SubtableKind::Format0(format0) => {
613                    format0.collect_glyphs(&mut first_set, &mut second_set, num_glyphs);
614                }
615                SubtableKind::Format1(format1) => {
616                    start_end_safe_to_break = collect_start_end_safe_to_break(format1);
617                    collect_initial_glyphs(format1, &mut first_set, num_glyphs);
618                }
619                SubtableKind::Format2(format2) => {
620                    format2.collect_glyphs(&mut first_set, &mut second_set, num_glyphs);
621                }
622                SubtableKind::Format3(format3) => {
623                    format3.collect_glyphs(&mut first_set, &mut second_set, num_glyphs);
624                }
625            }
626        }
627        KernSubtableCache {
628            start_end_safe_to_break,
629            first_set,
630            second_set,
631            class_cache: Box::new(ClassCache::new()),
632        }
633    }
634}