1 | use crate::hb::buffer::HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT; |
2 | use crate::hb::ot_layout_common::lookup_flags; |
3 | use crate::hb::ot_layout_gpos_table::attach_type; |
4 | use crate::hb::ot_layout_gpos_table::AnchorExt; |
5 | use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t; |
6 | use crate::hb::ot_layout_gsubgpos::{skipping_iterator_t, Apply}; |
7 | use crate::{Direction, GlyphPosition}; |
8 | use ttf_parser::gpos::CursiveAdjustment; |
9 | |
10 | impl Apply for CursiveAdjustment<'_> { |
11 | fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { |
12 | let this = ctx.buffer.cur(0).as_glyph(); |
13 | |
14 | let index_this = self.coverage.get(this)?; |
15 | let entry_this = self.sets.entry(index_this)?; |
16 | |
17 | let mut iter = skipping_iterator_t::new(ctx, ctx.buffer.idx, false); |
18 | |
19 | let mut unsafe_from = 0; |
20 | if !iter.prev(Some(&mut unsafe_from)) { |
21 | ctx.buffer |
22 | .unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1)); |
23 | return None; |
24 | } |
25 | |
26 | let i = iter.index(); |
27 | let prev = ctx.buffer.info[i].as_glyph(); |
28 | let index_prev = self.coverage.get(prev)?; |
29 | let Some(exit_prev) = self.sets.exit(index_prev) else { |
30 | ctx.buffer |
31 | .unsafe_to_concat_from_outbuffer(Some(iter.index()), Some(ctx.buffer.idx + 1)); |
32 | return None; |
33 | }; |
34 | |
35 | let (exit_x, exit_y) = exit_prev.get(ctx.face); |
36 | let (entry_x, entry_y) = entry_this.get(ctx.face); |
37 | |
38 | let direction = ctx.buffer.direction; |
39 | let j = ctx.buffer.idx; |
40 | ctx.buffer.unsafe_to_break(Some(i), Some(j + 1)); |
41 | |
42 | let pos = &mut ctx.buffer.pos; |
43 | match direction { |
44 | Direction::LeftToRight => { |
45 | pos[i].x_advance = exit_x + pos[i].x_offset; |
46 | let d = entry_x + pos[j].x_offset; |
47 | pos[j].x_advance -= d; |
48 | pos[j].x_offset -= d; |
49 | } |
50 | Direction::RightToLeft => { |
51 | let d = exit_x + pos[i].x_offset; |
52 | pos[i].x_advance -= d; |
53 | pos[i].x_offset -= d; |
54 | pos[j].x_advance = entry_x + pos[j].x_offset; |
55 | } |
56 | Direction::TopToBottom => { |
57 | pos[i].y_advance = exit_y + pos[i].y_offset; |
58 | let d = entry_y + pos[j].y_offset; |
59 | pos[j].y_advance -= d; |
60 | pos[j].y_offset -= d; |
61 | } |
62 | Direction::BottomToTop => { |
63 | let d = exit_y + pos[i].y_offset; |
64 | pos[i].y_advance -= d; |
65 | pos[i].y_offset -= d; |
66 | pos[j].y_advance = entry_y; |
67 | } |
68 | Direction::Invalid => {} |
69 | } |
70 | |
71 | // Cross-direction adjustment |
72 | |
73 | // We attach child to parent (think graph theory and rooted trees whereas |
74 | // the root stays on baseline and each node aligns itself against its |
75 | // parent. |
76 | // |
77 | // Optimize things for the case of RightToLeft, as that's most common in |
78 | // Arabic. |
79 | let mut child = i; |
80 | let mut parent = j; |
81 | let mut x_offset = entry_x - exit_x; |
82 | let mut y_offset = entry_y - exit_y; |
83 | |
84 | // Low bits are lookup flags, so we want to truncate. |
85 | if ctx.lookup_props as u16 & lookup_flags::RIGHT_TO_LEFT == 0 { |
86 | core::mem::swap(&mut child, &mut parent); |
87 | x_offset = -x_offset; |
88 | y_offset = -y_offset; |
89 | } |
90 | |
91 | // If child was already connected to someone else, walk through its old |
92 | // chain and reverse the link direction, such that the whole tree of its |
93 | // previous connection now attaches to new parent. Watch out for case |
94 | // where new parent is on the path from old chain... |
95 | reverse_cursive_minor_offset(pos, child, direction, parent); |
96 | |
97 | pos[child].set_attach_type(attach_type::CURSIVE); |
98 | pos[child].set_attach_chain((parent as isize - child as isize) as i16); |
99 | |
100 | ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT; |
101 | if direction.is_horizontal() { |
102 | pos[child].y_offset = y_offset; |
103 | } else { |
104 | pos[child].x_offset = x_offset; |
105 | } |
106 | |
107 | // If parent was attached to child, separate them. |
108 | // https://github.com/harfbuzz/harfbuzz/issues/2469 |
109 | if pos[parent].attach_chain() == -pos[child].attach_chain() { |
110 | pos[parent].set_attach_chain(0); |
111 | |
112 | if direction.is_horizontal() { |
113 | pos[parent].y_offset = 0; |
114 | } else { |
115 | pos[parent].x_offset = 0; |
116 | } |
117 | } |
118 | |
119 | ctx.buffer.idx += 1; |
120 | Some(()) |
121 | } |
122 | } |
123 | |
124 | fn reverse_cursive_minor_offset( |
125 | pos: &mut [GlyphPosition], |
126 | i: usize, |
127 | direction: Direction, |
128 | new_parent: usize, |
129 | ) { |
130 | let chain = pos[i].attach_chain(); |
131 | let attach_type = pos[i].attach_type(); |
132 | if chain == 0 || attach_type & attach_type::CURSIVE == 0 { |
133 | return; |
134 | } |
135 | |
136 | pos[i].set_attach_chain(0); |
137 | |
138 | // Stop if we see new parent in the chain. |
139 | let j = (i as isize + isize::from(chain)) as _; |
140 | if j == new_parent { |
141 | return; |
142 | } |
143 | |
144 | reverse_cursive_minor_offset(pos, j, direction, new_parent); |
145 | |
146 | if direction.is_horizontal() { |
147 | pos[j].y_offset = -pos[i].y_offset; |
148 | } else { |
149 | pos[j].x_offset = -pos[i].x_offset; |
150 | } |
151 | |
152 | pos[j].set_attach_chain(-chain); |
153 | pos[j].set_attach_type(attach_type); |
154 | } |
155 | |