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