1 | use ttf_parser::{apple_layout, kern, GlyphId}; |
2 | |
3 | use super::apply::ApplyContext; |
4 | use super::matching::SkippyIter; |
5 | use super::{lookup_flags, TableIndex}; |
6 | use crate::buffer::{Buffer, BufferScratchFlags}; |
7 | use crate::ot::attach_type; |
8 | use crate::plan::ShapePlan; |
9 | use crate::{Face, Mask}; |
10 | |
11 | pub fn has_kerning(face: &Face) -> bool { |
12 | face.tables().kern.is_some() |
13 | } |
14 | |
15 | pub fn has_machine_kerning(face: &Face) -> bool { |
16 | match face.tables().kern { |
17 | Some(ref kern: &Table<'_>) => kern.subtables.into_iter().any(|s: Subtable<'_>| s.has_state_machine), |
18 | None => false, |
19 | } |
20 | } |
21 | |
22 | pub fn has_cross_kerning(face: &Face) -> bool { |
23 | match face.tables().kern { |
24 | Some(ref kern: &Table<'_>) => kern.subtables.into_iter().any(|s: Subtable<'_>| s.has_cross_stream), |
25 | None => false, |
26 | } |
27 | } |
28 | |
29 | pub fn kern(plan: &ShapePlan, face: &Face, buffer: &mut Buffer) { |
30 | let subtables = match face.tables().kern { |
31 | Some(table) => table.subtables, |
32 | None => return, |
33 | }; |
34 | |
35 | let mut seen_cross_stream = false; |
36 | for subtable in subtables { |
37 | if subtable.variable { |
38 | continue; |
39 | } |
40 | |
41 | if buffer.direction.is_horizontal() != subtable.horizontal { |
42 | continue; |
43 | } |
44 | |
45 | let reverse = buffer.direction.is_backward(); |
46 | |
47 | if !seen_cross_stream && subtable.has_cross_stream { |
48 | seen_cross_stream = true; |
49 | |
50 | // Attach all glyphs into a chain. |
51 | for pos in &mut buffer.pos { |
52 | pos.set_attach_type(attach_type::CURSIVE); |
53 | pos.set_attach_chain(if buffer.direction.is_forward() { -1 } else { 1 }); |
54 | // We intentionally don't set BufferScratchFlags::HAS_GPOS_ATTACHMENT, |
55 | // since there needs to be a non-zero attachment for post-positioning to |
56 | // be needed. |
57 | } |
58 | } |
59 | |
60 | if reverse { |
61 | buffer.reverse(); |
62 | } |
63 | |
64 | if subtable.has_state_machine { |
65 | apply_state_machine_kerning(&subtable, plan.kern_mask, buffer); |
66 | } else { |
67 | if !plan.requested_kerning { |
68 | continue; |
69 | } |
70 | |
71 | apply_simple_kerning(&subtable, face, plan.kern_mask, buffer); |
72 | } |
73 | |
74 | if reverse { |
75 | buffer.reverse(); |
76 | } |
77 | } |
78 | } |
79 | |
80 | // TODO: remove |
81 | fn machine_kern( |
82 | face: &Face, |
83 | buffer: &mut Buffer, |
84 | kern_mask: Mask, |
85 | cross_stream: bool, |
86 | get_kerning: impl Fn(u32, u32) -> i32, |
87 | ) { |
88 | buffer.unsafe_to_concat(None, None); |
89 | let mut ctx = ApplyContext::new(TableIndex::GPOS, face, buffer); |
90 | ctx.lookup_mask = kern_mask; |
91 | ctx.lookup_props = u32::from(lookup_flags::IGNORE_MARKS); |
92 | |
93 | let horizontal = ctx.buffer.direction.is_horizontal(); |
94 | |
95 | let mut i = 0; |
96 | while i < ctx.buffer.len { |
97 | if (ctx.buffer.info[i].mask & kern_mask) == 0 { |
98 | i += 1; |
99 | continue; |
100 | } |
101 | |
102 | let mut iter = SkippyIter::new(&ctx, i, 1, false); |
103 | |
104 | let mut unsafe_to = 0; |
105 | if !iter.next(Some(&mut unsafe_to)) { |
106 | i += 1; |
107 | continue; |
108 | } |
109 | |
110 | let j = iter.index(); |
111 | |
112 | let info = &ctx.buffer.info; |
113 | let kern = get_kerning(info[i].glyph_id, info[j].glyph_id); |
114 | |
115 | let pos = &mut ctx.buffer.pos; |
116 | if kern != 0 { |
117 | if horizontal { |
118 | if cross_stream { |
119 | pos[j].y_offset = kern; |
120 | ctx.buffer.scratch_flags |= BufferScratchFlags::HAS_GPOS_ATTACHMENT; |
121 | } else { |
122 | let kern1 = kern >> 1; |
123 | let kern2 = kern - kern1; |
124 | pos[i].x_advance += kern1; |
125 | pos[j].x_advance += kern2; |
126 | pos[j].x_offset += kern2; |
127 | } |
128 | } else { |
129 | if cross_stream { |
130 | pos[j].x_offset = kern; |
131 | ctx.buffer.scratch_flags |= BufferScratchFlags::HAS_GPOS_ATTACHMENT; |
132 | } else { |
133 | let kern1 = kern >> 1; |
134 | let kern2 = kern - kern1; |
135 | pos[i].y_advance += kern1; |
136 | pos[j].y_advance += kern2; |
137 | pos[j].y_offset += kern2; |
138 | } |
139 | } |
140 | |
141 | ctx.buffer.unsafe_to_break(Some(i), Some(j + 1)) |
142 | } |
143 | |
144 | i = j; |
145 | } |
146 | } |
147 | |
148 | fn apply_simple_kerning( |
149 | subtable: &kern::Subtable, |
150 | face: &Face, |
151 | kern_mask: Mask, |
152 | buffer: &mut Buffer, |
153 | ) { |
154 | machine_kern( |
155 | face, |
156 | buffer, |
157 | kern_mask, |
158 | subtable.has_cross_stream, |
159 | |left: u32, right: u32| { |
160 | subtable |
161 | .glyphs_kerning(GlyphId(left as u16), GlyphId(right as u16)) |
162 | .map(i32::from) |
163 | .unwrap_or(default:0) |
164 | }, |
165 | ); |
166 | } |
167 | |
168 | struct StateMachineDriver { |
169 | stack: [usize; 8], |
170 | depth: usize, |
171 | } |
172 | |
173 | fn apply_state_machine_kerning(subtable: &kern::Subtable, kern_mask: Mask, buffer: &mut Buffer) { |
174 | let state_table = match subtable.format { |
175 | kern::Format::Format1(ref state_table) => state_table, |
176 | _ => return, |
177 | }; |
178 | |
179 | let mut driver = StateMachineDriver { |
180 | stack: [0; 8], |
181 | depth: 0, |
182 | }; |
183 | |
184 | let mut state = apple_layout::state::START_OF_TEXT; |
185 | buffer.idx = 0; |
186 | loop { |
187 | let class = if buffer.idx < buffer.len { |
188 | state_table |
189 | .class(buffer.info[buffer.idx].as_glyph()) |
190 | .unwrap_or(1) |
191 | } else { |
192 | apple_layout::class::END_OF_TEXT as u8 |
193 | }; |
194 | |
195 | let entry = match state_table.entry(state, class) { |
196 | Some(v) => v, |
197 | None => break, |
198 | }; |
199 | |
200 | // Unsafe-to-break before this if not in state 0, as things might |
201 | // go differently if we start from state 0 here. |
202 | if state != apple_layout::state::START_OF_TEXT |
203 | && buffer.backtrack_len() != 0 |
204 | && buffer.idx < buffer.len |
205 | { |
206 | // If there's no value and we're just epsilon-transitioning to state 0, safe to break. |
207 | if entry.has_offset() |
208 | || !(entry.new_state == apple_layout::state::START_OF_TEXT && !entry.has_advance()) |
209 | { |
210 | buffer.unsafe_to_break_from_outbuffer( |
211 | Some(buffer.backtrack_len() - 1), |
212 | Some(buffer.idx + 1), |
213 | ); |
214 | } |
215 | } |
216 | |
217 | // Unsafe-to-break if end-of-text would kick in here. |
218 | if buffer.idx + 2 <= buffer.len { |
219 | let end_entry = match state_table.entry(state, apple_layout::class::END_OF_TEXT) { |
220 | Some(v) => v, |
221 | None => break, |
222 | }; |
223 | |
224 | if end_entry.has_offset() { |
225 | buffer.unsafe_to_break(Some(buffer.idx), Some(buffer.idx + 2)); |
226 | } |
227 | } |
228 | |
229 | state_machine_transition( |
230 | entry, |
231 | subtable.has_cross_stream, |
232 | kern_mask, |
233 | state_table, |
234 | &mut driver, |
235 | buffer, |
236 | ); |
237 | |
238 | state = state_table.new_state(entry.new_state); |
239 | |
240 | if buffer.idx >= buffer.len { |
241 | break; |
242 | } |
243 | |
244 | buffer.max_ops -= 1; |
245 | if entry.has_advance() || buffer.max_ops <= 0 { |
246 | buffer.next_glyph(); |
247 | } |
248 | } |
249 | } |
250 | |
251 | fn state_machine_transition( |
252 | entry: apple_layout::StateEntry, |
253 | has_cross_stream: bool, |
254 | kern_mask: Mask, |
255 | state_table: &apple_layout::StateTable, |
256 | driver: &mut StateMachineDriver, |
257 | buffer: &mut Buffer, |
258 | ) { |
259 | if entry.has_push() { |
260 | if driver.depth < driver.stack.len() { |
261 | driver.stack[driver.depth] = buffer.idx; |
262 | driver.depth += 1; |
263 | } else { |
264 | driver.depth = 0; // Probably not what CoreText does, but better? |
265 | } |
266 | } |
267 | |
268 | if entry.has_offset() && driver.depth != 0 { |
269 | let mut value_offset = entry.value_offset(); |
270 | let mut value = match state_table.kerning(value_offset) { |
271 | Some(v) => v, |
272 | None => { |
273 | driver.depth = 0; |
274 | return; |
275 | } |
276 | }; |
277 | |
278 | // From Apple 'kern' spec: |
279 | // "Each pops one glyph from the kerning stack and applies the kerning value to it. |
280 | // The end of the list is marked by an odd value... |
281 | let mut last = false; |
282 | while !last && driver.depth != 0 { |
283 | driver.depth -= 1; |
284 | let idx = driver.stack[driver.depth]; |
285 | let mut v = value as i32; |
286 | value_offset = value_offset.next(); |
287 | value = state_table.kerning(value_offset).unwrap_or(0); |
288 | if idx >= buffer.len { |
289 | continue; |
290 | } |
291 | |
292 | // "The end of the list is marked by an odd value..." |
293 | last = v & 1 != 0; |
294 | v &= !1; |
295 | |
296 | // Testing shows that CoreText only applies kern (cross-stream or not) |
297 | // if none has been applied by previous subtables. That is, it does |
298 | // NOT seem to accumulate as otherwise implied by specs. |
299 | |
300 | let mut has_gpos_attachment = false; |
301 | let glyph_mask = buffer.info[idx].mask; |
302 | let pos = &mut buffer.pos[idx]; |
303 | |
304 | if buffer.direction.is_horizontal() { |
305 | if has_cross_stream { |
306 | // The following flag is undocumented in the spec, but described |
307 | // in the 'kern' table example. |
308 | if v == -0x8000 { |
309 | pos.set_attach_type(0); |
310 | pos.set_attach_chain(0); |
311 | pos.y_offset = 0; |
312 | } else if pos.attach_type() != 0 { |
313 | pos.y_offset += v; |
314 | has_gpos_attachment = true; |
315 | } |
316 | } else if glyph_mask & kern_mask != 0 { |
317 | pos.x_advance += v; |
318 | pos.x_offset += v; |
319 | } |
320 | } else { |
321 | if has_cross_stream { |
322 | // CoreText doesn't do crossStream kerning in vertical. We do. |
323 | if v == -0x8000 { |
324 | pos.set_attach_type(0); |
325 | pos.set_attach_chain(0); |
326 | pos.x_offset = 0; |
327 | } else if pos.attach_type() != 0 { |
328 | pos.x_offset += v; |
329 | has_gpos_attachment = true; |
330 | } |
331 | } else if glyph_mask & kern_mask != 0 { |
332 | if pos.y_offset == 0 { |
333 | pos.y_advance += v; |
334 | pos.y_offset += v; |
335 | } |
336 | } |
337 | } |
338 | |
339 | if has_gpos_attachment { |
340 | buffer.scratch_flags |= BufferScratchFlags::HAS_GPOS_ATTACHMENT; |
341 | } |
342 | } |
343 | } |
344 | } |
345 | |