1use super::layout::DELETED_GLYPH;
2use crate::hb::aat::layout_common::{
3 get_class, AatApplyContext, ClassCache, TypedCollectGlyphs, START_OF_TEXT,
4};
5use crate::hb::{
6 buffer::*,
7 ot_layout::TableIndex,
8 ot_layout_common::lookup_flags,
9 ot_layout_gpos_table::attach_type,
10 ot_layout_gsubgpos::{skipping_iterator_t, OT::hb_ot_apply_context_t},
11};
12use crate::U32Set;
13use alloc::boxed::Box;
14use core::convert::TryFrom;
15use read_fonts::{
16 tables::{
17 aat,
18 ankr::Ankr,
19 kerx::{
20 Subtable, Subtable0, Subtable1, Subtable2, Subtable4, Subtable4Actions, Subtable6,
21 SubtableKind,
22 },
23 },
24 types::{BigEndian, FixedSize, GlyphId},
25};
26
27pub(crate) fn apply(c: &mut AatApplyContext) -> Option<()> {
28 c.buffer.unsafe_to_concat(None, None);
29
30 c.setup_buffer_glyph_set();
31
32 let (kerx, subtable_caches) = c.face.aat_tables.kerx.as_ref()?;
33
34 let mut subtable_idx = 0;
35
36 let mut seen_cross_stream = false;
37 for subtable in kerx.subtables().iter() {
38 let Ok(subtable) = subtable else {
39 continue;
40 };
41
42 let subtable_cache = subtable_caches.get(subtable_idx);
43 let Some(subtable_cache) = subtable_cache.as_ref() else {
44 break;
45 };
46 subtable_idx += 1;
47
48 if subtable.is_variable() {
50 continue;
51 }
52
53 if c.buffer.direction.is_horizontal() != subtable.is_horizontal() {
54 continue;
55 }
56
57 c.first_set = Some(&subtable_cache.first_set);
58 c.second_set = Some(&subtable_cache.second_set);
59 c.machine_class_cache = Some(&subtable_cache.class_cache);
60 c.start_end_safe_to_break = subtable_cache.start_end_safe_to_break;
61
62 if !c.buffer_intersects_machine() {
63 continue;
64 }
65
66 let reverse = c.buffer.direction.is_backward();
67
68 if !seen_cross_stream && subtable.is_cross_stream() {
69 seen_cross_stream = true;
70
71 for pos in &mut c.buffer.pos {
73 pos.set_attach_type(attach_type::CURSIVE);
74 pos.set_attach_chain(if c.buffer.direction.is_forward() {
75 -1
76 } else {
77 1
78 });
79 }
83 }
84
85 let Ok(kind) = subtable.kind() else {
86 continue;
87 };
88
89 if reverse != c.buffer_is_reversed {
90 c.reverse_buffer();
91 }
92
93 match &kind {
94 SubtableKind::Format0(format0) => {
95 if !c.plan.requested_kerning {
96 continue;
97 }
98 apply_simple_kerning(c, &subtable, format0);
99 }
100 SubtableKind::Format1(format1) => {
101 let mut driver = Driver1 {
102 stack: [0; 8],
103 depth: 0,
104 };
105 apply_state_machine_kerning(
106 c,
107 &subtable,
108 format1,
109 &format1.state_table,
110 &mut driver,
111 );
112 }
113 SubtableKind::Format2(format2) => {
114 if !c.plan.requested_kerning {
115 continue;
116 }
117 apply_simple_kerning(c, &subtable, format2);
118 }
119 SubtableKind::Format4(format4) => {
120 let mut driver = Driver4 {
121 mark_set: false,
122 mark: 0,
123 ankr_table: c.face.aat_tables.ankr.clone(),
124 };
125 apply_state_machine_kerning(
126 c,
127 &subtable,
128 format4,
129 &format4.state_table,
130 &mut driver,
131 );
132 }
133 SubtableKind::Format6(format6) => {
134 if !c.plan.requested_kerning {
135 continue;
136 }
137 apply_simple_kerning(c, &subtable, format6);
138 }
139 }
140 }
141 if c.buffer_is_reversed {
142 c.reverse_buffer();
143 }
144
145 Some(())
146}
147
148pub trait SimpleKerning {
149 fn simple_kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32>;
150 fn collect_glyphs(&self, _first_set: &mut U32Set, _second_set: &mut U32Set, _num_glyphs: u32);
151}
152
153impl SimpleKerning for Subtable0<'_> {
154 fn simple_kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
155 self.kerning(left, right)
156 }
157 fn collect_glyphs(&self, first_set: &mut U32Set, second_set: &mut U32Set, _num_glyphs: u32) {
158 for &pair in self.pairs() {
159 first_set.insert(pair.left.get().to_u32());
160 second_set.insert(pair.right.get().to_u32());
161 }
162 }
163}
164
165impl SimpleKerning for Subtable2<'_> {
166 fn simple_kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
167 self.kerning(left, right)
168 }
169 fn collect_glyphs(&self, first_set: &mut U32Set, second_set: &mut U32Set, num_glyphs: u32) {
170 let left_classes = &self.left_offset_table;
171 let right_classes = &self.right_offset_table;
172
173 left_classes.collect_glyphs(first_set, num_glyphs);
174 right_classes.collect_glyphs(second_set, num_glyphs);
175 }
176}
177
178impl SimpleKerning for Subtable6<'_> {
179 fn simple_kerning(&self, left: GlyphId, right: GlyphId) -> Option<i32> {
180 self.kerning(left, right)
181 }
182 fn collect_glyphs(&self, first_set: &mut U32Set, second_set: &mut U32Set, num_glyphs: u32) {
183 match &self {
184 Self::ShortValues(rows, columns, ..) => {
185 rows.collect_glyphs(first_set, num_glyphs);
186 columns.collect_glyphs(second_set, num_glyphs);
187 }
188 Self::LongValues(rows, columns, ..) => {
189 rows.collect_glyphs(first_set, num_glyphs);
190 columns.collect_glyphs(second_set, num_glyphs);
191 }
192 }
193 }
194}
195
196fn apply_simple_kerning<T: SimpleKerning>(c: &mut AatApplyContext, subtable: &Subtable, kind: &T) {
197 let scale = c.scale;
198 let mut ctx = hb_ot_apply_context_t::new(TableIndex::GPOS, c.face, c.scale, c.buffer);
199 ctx.set_lookup_mask(c.plan.kern_mask);
200 ctx.lookup_props = u32::from(lookup_flags::IGNORE_MARKS);
201 ctx.update_matchers();
202
203 let horizontal = ctx.buffer.direction.is_horizontal();
204 let cross_stream = subtable.is_cross_stream();
205 let use_x_scale = horizontal ^ cross_stream;
206
207 let first_set = c.first_set.as_ref().unwrap();
208 let second_set = c.second_set.as_ref().unwrap();
209
210 let mut i = 0;
211 let mut iter = skipping_iterator_t::new(&mut ctx, false);
212 while i < iter.buffer.len {
213 if (iter.buffer.info[i].mask & c.plan.kern_mask) == 0 {
214 i += 1;
215 continue;
216 }
217
218 iter.reset_fast(i);
219
220 let mut unsafe_to = 0;
221 if !iter.next(Some(&mut unsafe_to)) {
222 iter.buffer.unsafe_to_concat(Some(i), Some(unsafe_to));
223 i += 1;
224 continue;
225 }
226
227 let j = iter.index();
228
229 let info = &iter.buffer.info;
230 let a = info[i].as_glyph();
231 let b = info[j].as_glyph();
232 let kern = if !first_set.contains(a.to_u32()) || !second_set.contains(b.to_u32()) {
233 0
234 } else {
235 kind.simple_kerning(a, b).unwrap_or(0)
236 };
237 let kern = if use_x_scale {
238 scale.scale_x(kern)
239 } else {
240 scale.scale_y(kern)
241 };
242
243 let pos = &mut iter.buffer.pos;
244 if kern != 0 {
245 if horizontal {
246 if cross_stream {
247 pos[j].y_offset = kern;
248 iter.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
249 } else {
250 let kern1 = kern >> 1;
251 let kern2 = kern - kern1;
252 pos[i].x_advance = pos[i].x_advance.saturating_add(kern1);
253 pos[j].x_advance = pos[j].x_advance.saturating_add(kern2);
254 pos[j].x_offset = pos[j].x_offset.saturating_add(kern2);
255 }
256 } else {
257 if cross_stream {
258 pos[j].x_offset = kern;
259 iter.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
260 } else {
261 let kern1 = kern >> 1;
262 let kern2 = kern - kern1;
263 pos[i].y_advance = pos[i].y_advance.saturating_add(kern1);
264 pos[j].y_advance = pos[j].y_advance.saturating_add(kern2);
265 pos[j].y_offset = pos[j].y_offset.saturating_add(kern2);
266 }
267 }
268
269 iter.buffer.unsafe_to_break(Some(i), Some(j + 1));
270 }
271
272 i = j;
273 }
274}
275
276pub(crate) trait KerxStateEntryExt {
277 fn flags(&self) -> u16;
278 fn action_index(&self) -> u16;
279
280 fn is_action_initiable(&self) -> bool {
281 self.flags() & 0x8000 != 0
282 }
283
284 fn is_actionable(&self) -> bool {
285 self.action_index() != 0xFFFF
286 }
287
288 fn has_advance(&self) -> bool {
289 self.flags() & 0x4000 == 0
290 }
291
292 fn has_reset(&self) -> bool {
293 self.flags() & 0x2000 != 0
294 }
295
296 fn has_push(&self) -> bool {
297 self.flags() & 0x8000 != 0
298 }
299
300 fn has_mark(&self) -> bool {
301 self.flags() & 0x8000 != 0
302 }
303}
304
305impl KerxStateEntryExt for aat::StateEntry<BigEndian<u16>> {
306 fn flags(&self) -> u16 {
307 self.flags
308 }
309
310 fn action_index(&self) -> u16 {
311 self.payload.get()
312 }
313}
314
315fn collect_initial_glyphs<T>(
316 machine: &aat::ExtendedStateTable<T>,
317 glyphs: &mut U32Set,
318 num_glyphs: u32,
319) where
320 T: FixedSize + bytemuck::AnyBitPattern,
321 aat::StateEntry<T>: KerxStateEntryExt,
322{
323 let mut classes = U32Set::default();
324
325 let class_table = &machine.class_table;
326 for i in 0..machine.n_classes {
327 if let Ok(entry) = machine.entry(START_OF_TEXT, i as u16) {
328 if entry.new_state == START_OF_TEXT
329 && !entry.is_action_initiable()
330 && !entry.is_actionable()
331 {
332 continue;
333 }
334 classes.insert(i as u32);
335 }
336 }
337
338 let filter = |class: u16| classes.contains(class as u32);
341
342 if filter(aat::class::DELETED_GLYPH as u16) {
343 glyphs.insert(DELETED_GLYPH);
344 }
345
346 class_table.collect_glyphs_filtered(glyphs, num_glyphs, filter);
347}
348
349fn collect_start_end_safe_to_break<T>(machine: &aat::ExtendedStateTable<T>) -> u64
350where
351 T: FixedSize + bytemuck::AnyBitPattern,
352 aat::StateEntry<T>: KerxStateEntryExt,
353{
354 let mut result = 0u64;
355 for state in 0..64 {
356 let bit = if let Ok(entry) = machine.entry(state, aat::class::END_OF_TEXT as u16) {
357 !entry.is_actionable()
358 } else {
359 true
360 };
361 if bit {
362 result |= 1 << state;
363 }
364 }
365 result
366}
367
368fn apply_state_machine_kerning<T, E, Driver: StateTableDriver<T, E>>(
369 c: &mut AatApplyContext,
370 subtable: &Subtable,
371 kind: &T,
372 state_table: &aat::ExtendedStateTable<E>,
373 driver: &mut Driver,
374) where
375 E: FixedSize + bytemuck::AnyBitPattern,
376 aat::StateEntry<E>: KerxStateEntryExt,
377{
378 let mut state = START_OF_TEXT;
379 c.buffer.idx = 0;
380 loop {
381 let class = if c.buffer.idx < c.buffer.len {
382 get_class(
383 state_table,
384 c.buffer.cur(0).as_glyph(),
385 c.machine_class_cache.unwrap(),
386 )
387 } else {
388 u16::from(aat::class::END_OF_TEXT)
389 };
390
391 let Ok(entry) = state_table.entry(state, class) else {
392 break;
393 };
394
395 let next_state = entry.new_state;
396
397 let is_safe_to_break =
426 !entry.is_actionable() &&
428
429 (
431 state == START_OF_TEXT
432 || (!entry.has_advance() && next_state == START_OF_TEXT)
433 ||
434 {
435 if let Ok(wouldbe_entry) = state_table.entry(START_OF_TEXT, class) {
437 !wouldbe_entry.is_actionable() &&
439
440 (
442 next_state == wouldbe_entry.new_state &&
443 entry.has_advance() == wouldbe_entry.has_advance()
444 )
445 } else {
446 false
447 }
448 }
449 ) &&
450
451 (
453 if state < 64 {
454 (c.start_end_safe_to_break & (1 << state)) != 0
455 } else {
456 if let Ok(end_entry) = state_table.entry(state, u16::from(aat::class::END_OF_TEXT)) {
457 !end_entry.is_actionable()
458 } else {
459 false
460 }
461 }
462 )
463 ;
464
465 if !is_safe_to_break && c.buffer.backtrack_len() > 0 && c.buffer.idx < c.buffer.len {
466 c.buffer.unsafe_to_break_from_outbuffer(
467 Some(c.buffer.backtrack_len() - 1),
468 Some(c.buffer.idx + 1),
469 );
470 }
471
472 let _ = driver.transition(
473 kind,
474 &entry,
475 subtable.is_cross_stream(),
476 subtable.tuple_count(),
477 c,
478 );
479
480 state = next_state;
481
482 if c.buffer.idx >= c.buffer.len {
483 break;
484 }
485
486 if entry.has_advance() || c.buffer.max_ops <= 0 {
487 c.buffer.next_glyph();
488 }
489 c.buffer.max_ops -= 1;
490 }
491}
492
493trait StateTableDriver<T, E> {
494 fn transition(
495 &mut self,
496 aat: &T,
497 entry: &aat::StateEntry<E>,
498 has_cross_stream: bool,
499 tuple_count: u32,
500 c: &mut AatApplyContext,
501 ) -> Option<()>;
502}
503
504struct Driver1 {
505 stack: [usize; 8],
506 depth: usize,
507}
508
509impl StateTableDriver<Subtable1<'_>, BigEndian<u16>> for Driver1 {
510 #[inline(always)]
511 fn transition(
512 &mut self,
513 aat: &Subtable1,
514 entry: &aat::StateEntry<BigEndian<u16>>,
515 has_cross_stream: bool,
516 tuple_count: u32,
517 c: &mut AatApplyContext,
518 ) -> Option<()> {
519 if entry.has_reset() {
520 self.depth = 0;
521 }
522
523 if entry.has_push() {
524 if self.depth < self.stack.len() {
525 self.stack[self.depth] = c.buffer.idx;
526 self.depth += 1;
527 } else {
528 self.depth = 0; }
530 }
531
532 if entry.is_actionable() && self.depth != 0 {
533 let tuple_count = u16::try_from(tuple_count.max(1)).ok()?;
534
535 let mut action_index = entry.action_index();
536
537 let mut last = false;
541 let use_x_scale = c.buffer.direction.is_horizontal() ^ has_cross_stream;
542 while !last && self.depth != 0 {
543 self.depth -= 1;
544 let idx = self.stack[self.depth];
545 let mut v = aat.values.get(action_index as usize)?.get() as i32;
546 action_index = action_index.checked_add(tuple_count)?;
547 if idx >= c.buffer.len {
548 continue;
549 }
550
551 last = v & 1 != 0;
553 v &= !1;
554 let scaled_v = if use_x_scale {
555 c.scale_x(v)
556 } else {
557 c.scale_y(v)
558 };
559 let mut has_gpos_attachment = false;
564 let glyph_mask = c.buffer.info[idx].mask;
565 let pos = &mut c.buffer.pos[idx];
566
567 if c.buffer.direction.is_horizontal() {
568 if has_cross_stream {
569 if v == -0x8000 {
572 pos.set_attach_type(0);
573 pos.set_attach_chain(0);
574 pos.y_offset = 0;
575 } else if pos.attach_type() != 0 {
576 pos.y_offset = pos.y_offset.saturating_add(scaled_v);
577 has_gpos_attachment = true;
578 }
579 } else if glyph_mask & c.plan.kern_mask != 0 {
580 pos.x_advance = pos.x_advance.saturating_add(scaled_v);
581 pos.x_offset = pos.x_offset.saturating_add(scaled_v);
582 }
583 } else {
584 if has_cross_stream {
585 if v == -0x8000 {
587 pos.set_attach_type(0);
588 pos.set_attach_chain(0);
589 pos.x_offset = 0;
590 } else if pos.attach_type() != 0 {
591 pos.x_offset = pos.x_offset.saturating_add(scaled_v);
592 has_gpos_attachment = true;
593 }
594 } else if glyph_mask & c.plan.kern_mask != 0 {
595 if pos.y_offset == 0 {
596 pos.y_advance = pos.y_advance.saturating_add(scaled_v);
597 pos.y_offset = pos.y_offset.saturating_add(scaled_v);
598 }
599 }
600 }
601
602 if has_gpos_attachment {
603 c.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
604 }
605 }
606 }
607
608 Some(())
609 }
610}
611struct Driver4<'a> {
612 mark_set: bool,
613 mark: usize,
614 ankr_table: Option<Ankr<'a>>,
615}
616
617impl StateTableDriver<Subtable4<'_>, BigEndian<u16>> for Driver4<'_> {
618 #[inline(always)]
619 fn transition(
620 &mut self,
621 aat: &Subtable4,
622 entry: &aat::StateEntry<BigEndian<u16>>,
623 _has_cross_stream: bool,
624 _tuple_count: u32,
625 c: &mut AatApplyContext,
626 ) -> Option<()> {
627 if self.mark_set && entry.is_actionable() && c.buffer.idx < c.buffer.len {
628 match (self.ankr_table.as_ref(), &aat.actions) {
629 (Some(ankr_table), Subtable4Actions::AnchorPoints(ankr_data)) => {
630 let action_idx = entry.action_index() as usize * 2;
631 let mark_action_idx = ankr_data.get(action_idx)?.get() as usize;
632 let curr_action_idx = ankr_data.get(action_idx + 1)?.get() as usize;
633 let mark_idx = c.buffer.info[self.mark].as_glyph();
634 let mark_anchor = ankr_table
635 .anchor_points(mark_idx)
636 .ok()
637 .and_then(|list| list.get(mark_action_idx))
638 .map(|point| (point.x(), point.y()))
639 .unwrap_or_default();
640
641 let curr_idx = c.buffer.cur(0).as_glyph();
642 let curr_anchor = ankr_table
643 .anchor_points(curr_idx)
644 .ok()
645 .and_then(|list| list.get(curr_action_idx))
646 .map(|point| (point.x(), point.y()))
647 .unwrap_or_default();
648
649 let x_offset =
650 c.scale_x(i32::from(mark_anchor.0)) - c.scale_x(i32::from(curr_anchor.0));
651 let y_offset =
652 c.scale_y(i32::from(mark_anchor.1)) - c.scale_y(i32::from(curr_anchor.1));
653 let pos = c.buffer.cur_pos_mut();
654 pos.x_offset = x_offset;
655 pos.y_offset = y_offset;
656 }
657 (_, Subtable4Actions::ControlPointCoords(coords)) => {
658 let action_idx = entry.action_index() as usize * 4;
659 let mark_x = coords.get(action_idx)?.get() as i32;
660 let mark_y = coords.get(action_idx + 1)?.get() as i32;
661 let curr_x = coords.get(action_idx + 2)?.get() as i32;
662 let curr_y = coords.get(action_idx + 3)?.get() as i32;
663 let x_offset = c.scale_x(mark_x) - c.scale_x(curr_x);
664 let y_offset = c.scale_y(mark_y) - c.scale_y(curr_y);
665 let pos = c.buffer.cur_pos_mut();
666 pos.x_offset = x_offset;
667 pos.y_offset = y_offset;
668 }
669 _ => {}
670 }
671
672 c.buffer.cur_pos_mut().set_attach_type(attach_type::MARK);
673 let idx = c.buffer.idx;
674 let mut attach_chain = self.mark as i16 - idx as i16;
675 if c.buffer_is_reversed {
676 attach_chain = -attach_chain;
677 }
678 c.buffer.cur_pos_mut().set_attach_chain(attach_chain);
679 c.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
680 }
681
682 if entry.has_mark() {
683 self.mark_set = true;
684 self.mark = c.buffer.idx;
685 }
686
687 Some(())
688 }
689}
690
691pub(crate) struct KerxSubtableCache {
692 start_end_safe_to_break: u64,
693 first_set: U32Set,
694 second_set: U32Set,
695 class_cache: Box<ClassCache>,
696}
697
698impl KerxSubtableCache {
699 pub(crate) fn new(subtable: &Subtable, num_glyphs: u32) -> Self {
700 let mut start_end_safe_to_break = 0u64;
701 let mut first_set = U32Set::default();
702 let mut second_set = U32Set::default();
703 if let Ok(kind) = subtable.kind() {
704 match &kind {
705 SubtableKind::Format0(format0) => {
706 format0.collect_glyphs(&mut first_set, &mut second_set, num_glyphs);
707 }
708 SubtableKind::Format1(format1) => {
709 start_end_safe_to_break = collect_start_end_safe_to_break(&format1.state_table);
710 collect_initial_glyphs(&format1.state_table, &mut first_set, num_glyphs);
711 }
712 SubtableKind::Format2(format2) => {
713 format2.collect_glyphs(&mut first_set, &mut second_set, num_glyphs);
714 }
715 SubtableKind::Format4(format4) => {
716 start_end_safe_to_break = collect_start_end_safe_to_break(&format4.state_table);
717 collect_initial_glyphs(&format4.state_table, &mut first_set, num_glyphs);
718 }
719 SubtableKind::Format6(format6) => {
720 format6.collect_glyphs(&mut first_set, &mut second_set, num_glyphs);
721 }
722 }
723 }
724 KerxSubtableCache {
725 start_end_safe_to_break,
726 first_set,
727 second_set,
728 class_cache: Box::new(ClassCache::new()),
729 }
730 }
731}