1use core::convert::TryFrom;
2
3use ttf_parser::{ankr, apple_layout, kerx, FromData, GlyphId};
4
5use super::buffer::*;
6use super::hb_font_t;
7use super::ot_layout::TableIndex;
8use super::ot_layout_common::lookup_flags;
9use super::ot_layout_gpos_table::attach_type;
10use super::ot_layout_gsubgpos::{skipping_iterator_t, OT::hb_ot_apply_context_t};
11use super::ot_shape_plan::hb_ot_shape_plan_t;
12
13trait ExtendedStateTableExt<T: FromData + Copy> {
16 fn class(&self, glyph_id: GlyphId) -> Option<u16>;
17 fn entry(&self, state: u16, class: u16) -> Option<apple_layout::GenericStateEntry<T>>;
18}
19
20impl ExtendedStateTableExt<kerx::EntryData> for kerx::Subtable1<'_> {
21 fn class(&self, glyph_id: GlyphId) -> Option<u16> {
22 self.state_table.class(glyph_id)
23 }
24
25 fn entry(
26 &self,
27 state: u16,
28 class: u16,
29 ) -> Option<apple_layout::GenericStateEntry<kerx::EntryData>> {
30 self.state_table.entry(state, class)
31 }
32}
33
34impl ExtendedStateTableExt<kerx::EntryData> for kerx::Subtable4<'_> {
35 fn class(&self, glyph_id: GlyphId) -> Option<u16> {
36 self.state_table.class(glyph_id)
37 }
38
39 fn entry(
40 &self,
41 state: u16,
42 class: u16,
43 ) -> Option<apple_layout::GenericStateEntry<kerx::EntryData>> {
44 self.state_table.entry(state, class)
45 }
46}
47
48pub(crate) fn apply(
49 plan: &hb_ot_shape_plan_t,
50 face: &hb_font_t,
51 buffer: &mut hb_buffer_t,
52) -> Option<()> {
53 buffer.unsafe_to_concat(None, None);
54
55 let mut seen_cross_stream = false;
56 for subtable in face.tables().kerx?.subtables {
57 if subtable.variable {
58 continue;
59 }
60
61 if buffer.direction.is_horizontal() != subtable.horizontal {
62 continue;
63 }
64
65 let reverse = buffer.direction.is_backward();
66
67 if !seen_cross_stream && subtable.has_cross_stream {
68 seen_cross_stream = true;
69
70 for pos in &mut buffer.pos {
72 pos.set_attach_type(attach_type::CURSIVE);
73 pos.set_attach_chain(if buffer.direction.is_forward() { -1 } else { 1 });
74 }
78 }
79
80 if reverse {
81 buffer.reverse();
82 }
83
84 match subtable.format {
85 kerx::Format::Format0(_) => {
86 if !plan.requested_kerning {
87 continue;
88 }
89
90 apply_simple_kerning(&subtable, plan, face, buffer);
91 }
92 kerx::Format::Format1(ref sub) => {
93 let mut driver = Driver1 {
94 stack: [0; 8],
95 depth: 0,
96 };
97
98 apply_state_machine_kerning(&subtable, sub, &mut driver, plan, buffer);
99 }
100 kerx::Format::Format2(_) => {
101 if !plan.requested_kerning {
102 continue;
103 }
104
105 buffer.unsafe_to_concat(None, None);
106
107 apply_simple_kerning(&subtable, plan, face, buffer);
108 }
109 kerx::Format::Format4(ref sub) => {
110 let mut driver = Driver4 {
111 mark_set: false,
112 mark: 0,
113 ankr_table: face.tables().ankr.clone(),
114 };
115
116 apply_state_machine_kerning(&subtable, sub, &mut driver, plan, buffer);
117 }
118 kerx::Format::Format6(_) => {
119 if !plan.requested_kerning {
120 continue;
121 }
122
123 apply_simple_kerning(&subtable, plan, face, buffer);
124 }
125 }
126
127 if reverse {
128 buffer.reverse();
129 }
130 }
131
132 Some(())
133}
134
135fn apply_simple_kerning(
136 subtable: &kerx::Subtable,
137 plan: &hb_ot_shape_plan_t,
138 face: &hb_font_t,
139 buffer: &mut hb_buffer_t,
140) {
141 let mut ctx = hb_ot_apply_context_t::new(TableIndex::GPOS, face, buffer);
142 ctx.set_lookup_mask(plan.kern_mask);
143 ctx.lookup_props = u32::from(lookup_flags::IGNORE_FLAGS);
144
145 let horizontal = ctx.buffer.direction.is_horizontal();
146
147 let mut i = 0;
148 while i < ctx.buffer.len {
149 if (ctx.buffer.info[i].mask & plan.kern_mask) == 0 {
150 i += 1;
151 continue;
152 }
153
154 let mut iter = skipping_iterator_t::new(&ctx, i, false);
155
156 let mut unsafe_to = 0;
157 if !iter.next(Some(&mut unsafe_to)) {
158 ctx.buffer.unsafe_to_concat(Some(i), Some(unsafe_to));
159 i += 1;
160 continue;
161 }
162
163 let j = iter.index();
164
165 let info = &ctx.buffer.info;
166 let kern = subtable
167 .glyphs_kerning(info[i].as_glyph(), info[j].as_glyph())
168 .unwrap_or(0);
169 let kern = i32::from(kern);
170
171 let pos = &mut ctx.buffer.pos;
172 if kern != 0 {
173 if horizontal {
174 if subtable.has_cross_stream {
175 pos[j].y_offset = kern;
176 ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
177 } else {
178 let kern1 = kern >> 1;
179 let kern2 = kern - kern1;
180 pos[i].x_advance += kern1;
181 pos[j].x_advance += kern2;
182 pos[j].x_offset += kern2;
183 }
184 } else {
185 if subtable.has_cross_stream {
186 pos[j].x_offset = kern;
187 ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
188 } else {
189 let kern1 = kern >> 1;
190 let kern2 = kern - kern1;
191 pos[i].y_advance += kern1;
192 pos[j].y_advance += kern2;
193 pos[j].y_offset += kern2;
194 }
195 }
196
197 ctx.buffer.unsafe_to_break(Some(i), Some(j + 1))
198 }
199
200 i = j;
201 }
202}
203
204const START_OF_TEXT: u16 = 0;
205
206trait KerxEntryDataExt {
207 fn action_index(self) -> u16;
208 fn is_actionable(&self) -> bool;
209}
210
211impl KerxEntryDataExt for apple_layout::GenericStateEntry<kerx::EntryData> {
212 fn action_index(self) -> u16 {
213 self.extra.action_index
214 }
215 fn is_actionable(&self) -> bool {
216 self.extra.action_index != 0xFFFF
217 }
218}
219
220fn apply_state_machine_kerning<T, E>(
221 subtable: &kerx::Subtable,
222 state_table: &T,
223 driver: &mut dyn StateTableDriver<T, E>,
224 plan: &hb_ot_shape_plan_t,
225 buffer: &mut hb_buffer_t,
226) where
227 T: ExtendedStateTableExt<E>,
228 E: FromData + Copy,
229 apple_layout::GenericStateEntry<E>: KerxEntryDataExt,
230{
231 let mut state = START_OF_TEXT;
232 buffer.idx = 0;
233 loop {
234 let class = if buffer.idx < buffer.len {
235 state_table
236 .class(buffer.info[buffer.idx].as_glyph())
237 .unwrap_or(1)
238 } else {
239 u16::from(apple_layout::class::END_OF_TEXT)
240 };
241
242 let entry: apple_layout::GenericStateEntry<E> = match state_table.entry(state, class) {
243 Some(v) => v,
244 None => break,
245 };
246
247 if state != START_OF_TEXT && buffer.backtrack_len() != 0 && buffer.idx < buffer.len {
250 if entry.is_actionable() || entry.new_state != START_OF_TEXT || entry.has_advance()
252 {
253 buffer.unsafe_to_break_from_outbuffer(
254 Some(buffer.backtrack_len() - 1),
255 Some(buffer.idx + 1),
256 );
257 }
258 }
259
260 if buffer.idx + 2 <= buffer.len {
262 let end_entry: Option<apple_layout::GenericStateEntry<E>> =
263 state_table.entry(state, u16::from(apple_layout::class::END_OF_TEXT));
264 let end_entry = match end_entry {
265 Some(v) => v,
266 None => break,
267 };
268
269 if end_entry.is_actionable() {
270 buffer.unsafe_to_break(Some(buffer.idx), Some(buffer.idx + 2));
271 }
272 }
273
274 let _ = driver.transition(
275 state_table,
276 entry,
277 subtable.has_cross_stream,
278 subtable.tuple_count,
279 plan,
280 buffer,
281 );
282
283 state = entry.new_state;
284
285 if buffer.idx >= buffer.len {
286 break;
287 }
288
289 if entry.has_advance() || buffer.max_ops <= 0 {
290 buffer.next_glyph();
291 }
292 buffer.max_ops -= 1;
293 }
294}
295
296trait StateTableDriver<Table, E: FromData> {
297 fn transition(
298 &mut self,
299 aat: &Table,
300 entry: apple_layout::GenericStateEntry<E>,
301 has_cross_stream: bool,
302 tuple_count: u32,
303 plan: &hb_ot_shape_plan_t,
304 buffer: &mut hb_buffer_t,
305 ) -> Option<()>;
306}
307
308struct Driver1 {
309 stack: [usize; 8],
310 depth: usize,
311}
312
313impl StateTableDriver<kerx::Subtable1<'_>, kerx::EntryData> for Driver1 {
314 fn transition(
315 &mut self,
316 aat: &kerx::Subtable1,
317 entry: apple_layout::GenericStateEntry<kerx::EntryData>,
318 has_cross_stream: bool,
319 tuple_count: u32,
320 plan: &hb_ot_shape_plan_t,
321 buffer: &mut hb_buffer_t,
322 ) -> Option<()> {
323 if entry.has_reset() {
324 self.depth = 0;
325 }
326
327 if entry.has_push() {
328 if self.depth < self.stack.len() {
329 self.stack[self.depth] = buffer.idx;
330 self.depth += 1;
331 } else {
332 self.depth = 0; }
334 }
335
336 if entry.is_actionable() && self.depth != 0 {
337 let tuple_count = u16::try_from(tuple_count.max(1)).ok()?;
338
339 let mut action_index = entry.action_index();
340
341 let mut last = false;
345 while !last && self.depth != 0 {
346 self.depth -= 1;
347 let idx = self.stack[self.depth];
348 let mut v = aat.glyphs_kerning(action_index)? as i32;
349 action_index = action_index.checked_add(tuple_count)?;
350 if idx >= buffer.len {
351 continue;
352 }
353
354 last = v & 1 != 0;
356 v &= !1;
357
358 let mut has_gpos_attachment = false;
363 let glyph_mask = buffer.info[idx].mask;
364 let pos = &mut buffer.pos[idx];
365
366 if buffer.direction.is_horizontal() {
367 if has_cross_stream {
368 if v == -0x8000 {
371 pos.set_attach_type(0);
372 pos.set_attach_chain(0);
373 pos.y_offset = 0;
374 } else if pos.attach_type() != 0 {
375 pos.y_offset += v;
376 has_gpos_attachment = true;
377 }
378 } else if glyph_mask & plan.kern_mask != 0 {
379 pos.x_advance += v;
380 pos.x_offset += v;
381 }
382 } else {
383 if has_cross_stream {
384 if v == -0x8000 {
386 pos.set_attach_type(0);
387 pos.set_attach_chain(0);
388 pos.x_offset = 0;
389 } else if pos.attach_type() != 0 {
390 pos.x_offset += v;
391 has_gpos_attachment = true;
392 }
393 } else if glyph_mask & plan.kern_mask != 0 {
394 if pos.y_offset == 0 {
395 pos.y_advance += v;
396 pos.y_offset += v;
397 }
398 }
399 }
400
401 if has_gpos_attachment {
402 buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
403 }
404 }
405 }
406
407 Some(())
408 }
409}
410
411struct Driver4<'a> {
412 mark_set: bool,
413 mark: usize,
414 ankr_table: Option<ankr::Table<'a>>,
415}
416
417impl StateTableDriver<kerx::Subtable4<'_>, kerx::EntryData> for Driver4<'_> {
418 fn transition(
419 &mut self,
420 aat: &kerx::Subtable4,
421 entry: apple_layout::GenericStateEntry<kerx::EntryData>,
422 _has_cross_stream: bool,
423 _tuple_count: u32,
424 _opt: &hb_ot_shape_plan_t,
425 buffer: &mut hb_buffer_t,
426 ) -> Option<()> {
427 if self.mark_set && entry.is_actionable() && buffer.idx < buffer.len {
428 if let Some(ref ankr_table) = self.ankr_table {
429 let point = aat.anchor_points.get(entry.action_index())?;
430
431 let mark_idx = buffer.info[self.mark].as_glyph();
432 let mark_anchor = ankr_table
433 .points(mark_idx)
434 .and_then(|list| list.get(u32::from(point.0)))
435 .unwrap_or_default();
436
437 let curr_idx = buffer.cur(0).as_glyph();
438 let curr_anchor = ankr_table
439 .points(curr_idx)
440 .and_then(|list| list.get(u32::from(point.1)))
441 .unwrap_or_default();
442
443 let pos = buffer.cur_pos_mut();
444 pos.x_offset = i32::from(mark_anchor.x - curr_anchor.x);
445 pos.y_offset = i32::from(mark_anchor.y - curr_anchor.y);
446 }
447
448 buffer.cur_pos_mut().set_attach_type(attach_type::MARK);
449 let idx = buffer.idx;
450 buffer
451 .cur_pos_mut()
452 .set_attach_chain(self.mark as i16 - idx as i16);
453 buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
454 }
455
456 if entry.has_mark() {
457 self.mark_set = true;
458 self.mark = buffer.idx;
459 }
460
461 Some(())
462 }
463}