rustybuzz/hb/
kerning.rs
1use ttf_parser::{apple_layout, kern, GlyphId};
2
3use super::buffer::*;
4use super::ot_layout::TableIndex;
5use super::ot_layout_common::lookup_flags;
6use super::ot_layout_gpos_table::attach_type;
7use super::ot_layout_gsubgpos::{skipping_iterator_t, OT::hb_ot_apply_context_t};
8use super::ot_shape_plan::hb_ot_shape_plan_t;
9use super::{hb_font_t, hb_mask_t};
10
11pub fn kern(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
12 let subtables = match face.tables().kern {
13 Some(table) => table.subtables,
14 None => return,
15 };
16
17 let mut seen_cross_stream = false;
18 for subtable in subtables {
19 if subtable.variable {
20 continue;
21 }
22
23 if buffer.direction.is_horizontal() != subtable.horizontal {
24 continue;
25 }
26
27 let reverse = buffer.direction.is_backward();
28
29 if !seen_cross_stream && subtable.has_cross_stream {
30 seen_cross_stream = true;
31
32 for pos in &mut buffer.pos {
34 pos.set_attach_type(attach_type::CURSIVE);
35 pos.set_attach_chain(if buffer.direction.is_forward() { -1 } else { 1 });
36 }
40 }
41
42 if reverse {
43 buffer.reverse();
44 }
45
46 if subtable.has_state_machine {
47 apply_state_machine_kerning(&subtable, plan.kern_mask, buffer);
48 } else {
49 if !plan.requested_kerning {
50 continue;
51 }
52
53 apply_simple_kerning(&subtable, face, plan.kern_mask, buffer);
54 }
55
56 if reverse {
57 buffer.reverse();
58 }
59 }
60}
61
62fn machine_kern(
64 face: &hb_font_t,
65 buffer: &mut hb_buffer_t,
66 kern_mask: hb_mask_t,
67 cross_stream: bool,
68 get_kerning: impl Fn(u32, u32) -> i32,
69) {
70 buffer.unsafe_to_concat(None, None);
71 let mut ctx = hb_ot_apply_context_t::new(TableIndex::GPOS, face, buffer);
72 ctx.lookup_mask = kern_mask;
73 ctx.lookup_props = u32::from(lookup_flags::IGNORE_MARKS);
74
75 let horizontal = ctx.buffer.direction.is_horizontal();
76
77 let mut i = 0;
78 while i < ctx.buffer.len {
79 if (ctx.buffer.info[i].mask & kern_mask) == 0 {
80 i += 1;
81 continue;
82 }
83
84 let mut iter = skipping_iterator_t::new(&ctx, i, 1, false);
85
86 let mut unsafe_to = 0;
87 if !iter.next(Some(&mut unsafe_to)) {
88 i += 1;
89 continue;
90 }
91
92 let j = iter.index();
93
94 let info = &ctx.buffer.info;
95 let kern = get_kerning(info[i].glyph_id, info[j].glyph_id);
96
97 let pos = &mut ctx.buffer.pos;
98 if kern != 0 {
99 if horizontal {
100 if cross_stream {
101 pos[j].y_offset = kern;
102 ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
103 } else {
104 let kern1 = kern >> 1;
105 let kern2 = kern - kern1;
106 pos[i].x_advance += kern1;
107 pos[j].x_advance += kern2;
108 pos[j].x_offset += kern2;
109 }
110 } else {
111 if cross_stream {
112 pos[j].x_offset = kern;
113 ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
114 } else {
115 let kern1 = kern >> 1;
116 let kern2 = kern - kern1;
117 pos[i].y_advance += kern1;
118 pos[j].y_advance += kern2;
119 pos[j].y_offset += kern2;
120 }
121 }
122
123 ctx.buffer.unsafe_to_break(Some(i), Some(j + 1))
124 }
125
126 i = j;
127 }
128}
129
130fn apply_simple_kerning(
131 subtable: &kern::Subtable,
132 face: &hb_font_t,
133 kern_mask: hb_mask_t,
134 buffer: &mut hb_buffer_t,
135) {
136 machine_kern(
137 face,
138 buffer,
139 kern_mask,
140 subtable.has_cross_stream,
141 |left, right| {
142 subtable
143 .glyphs_kerning(GlyphId(left as u16), GlyphId(right as u16))
144 .map(i32::from)
145 .unwrap_or(0)
146 },
147 );
148}
149
150struct StateMachineDriver {
151 stack: [usize; 8],
152 depth: usize,
153}
154
155fn apply_state_machine_kerning(
156 subtable: &kern::Subtable,
157 kern_mask: hb_mask_t,
158 buffer: &mut hb_buffer_t,
159) {
160 let state_table = match subtable.format {
161 kern::Format::Format1(ref state_table) => state_table,
162 _ => return,
163 };
164
165 let mut driver = StateMachineDriver {
166 stack: [0; 8],
167 depth: 0,
168 };
169
170 let mut state = apple_layout::state::START_OF_TEXT;
171 buffer.idx = 0;
172 loop {
173 let class = if buffer.idx < buffer.len {
174 state_table
175 .class(buffer.info[buffer.idx].as_glyph())
176 .unwrap_or(1)
177 } else {
178 apple_layout::class::END_OF_TEXT as u8
179 };
180
181 let entry = match state_table.entry(state, class) {
182 Some(v) => v,
183 None => break,
184 };
185
186 if state != apple_layout::state::START_OF_TEXT
189 && buffer.backtrack_len() != 0
190 && buffer.idx < buffer.len
191 {
192 if entry.has_offset()
194 || !(entry.new_state == apple_layout::state::START_OF_TEXT && !entry.has_advance())
195 {
196 buffer.unsafe_to_break_from_outbuffer(
197 Some(buffer.backtrack_len() - 1),
198 Some(buffer.idx + 1),
199 );
200 }
201 }
202
203 if buffer.idx + 2 <= buffer.len {
205 let end_entry = match state_table.entry(state, apple_layout::class::END_OF_TEXT) {
206 Some(v) => v,
207 None => break,
208 };
209
210 if end_entry.has_offset() {
211 buffer.unsafe_to_break(Some(buffer.idx), Some(buffer.idx + 2));
212 }
213 }
214
215 state_machine_transition(
216 entry,
217 subtable.has_cross_stream,
218 kern_mask,
219 state_table,
220 &mut driver,
221 buffer,
222 );
223
224 state = state_table.new_state(entry.new_state);
225
226 if buffer.idx >= buffer.len {
227 break;
228 }
229
230 buffer.max_ops -= 1;
231 if entry.has_advance() || buffer.max_ops <= 0 {
232 buffer.next_glyph();
233 }
234 }
235}
236
237fn state_machine_transition(
238 entry: apple_layout::StateEntry,
239 has_cross_stream: bool,
240 kern_mask: hb_mask_t,
241 state_table: &apple_layout::StateTable,
242 driver: &mut StateMachineDriver,
243 buffer: &mut hb_buffer_t,
244) {
245 if entry.has_push() {
246 if driver.depth < driver.stack.len() {
247 driver.stack[driver.depth] = buffer.idx;
248 driver.depth += 1;
249 } else {
250 driver.depth = 0; }
252 }
253
254 if entry.has_offset() && driver.depth != 0 {
255 let mut value_offset = entry.value_offset();
256 let mut value = match state_table.kerning(value_offset) {
257 Some(v) => v,
258 None => {
259 driver.depth = 0;
260 return;
261 }
262 };
263
264 let mut last = false;
268 while !last && driver.depth != 0 {
269 driver.depth -= 1;
270 let idx = driver.stack[driver.depth];
271 let mut v = value as i32;
272 value_offset = value_offset.next();
273 value = state_table.kerning(value_offset).unwrap_or(0);
274 if idx >= buffer.len {
275 continue;
276 }
277
278 last = v & 1 != 0;
280 v &= !1;
281
282 let mut has_gpos_attachment = false;
287 let glyph_mask = buffer.info[idx].mask;
288 let pos = &mut buffer.pos[idx];
289
290 if buffer.direction.is_horizontal() {
291 if has_cross_stream {
292 if v == -0x8000 {
295 pos.set_attach_type(0);
296 pos.set_attach_chain(0);
297 pos.y_offset = 0;
298 } else if pos.attach_type() != 0 {
299 pos.y_offset += v;
300 has_gpos_attachment = true;
301 }
302 } else if glyph_mask & kern_mask != 0 {
303 pos.x_advance += v;
304 pos.x_offset += v;
305 }
306 } else {
307 if has_cross_stream {
308 if v == -0x8000 {
310 pos.set_attach_type(0);
311 pos.set_attach_chain(0);
312 pos.x_offset = 0;
313 } else if pos.attach_type() != 0 {
314 pos.x_offset += v;
315 has_gpos_attachment = true;
316 }
317 } else if glyph_mask & kern_mask != 0 {
318 if pos.y_offset == 0 {
319 pos.y_advance += v;
320 pos.y_offset += v;
321 }
322 }
323 }
324
325 if has_gpos_attachment {
326 buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
327 }
328 }
329 }
330}