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 | let mut ctx = ApplyContext::new(TableIndex::GPOS, face, buffer); |
89 | ctx.lookup_mask = kern_mask; |
90 | ctx.lookup_props = u32::from(lookup_flags::IGNORE_MARKS); |
91 | |
92 | let horizontal = ctx.buffer.direction.is_horizontal(); |
93 | |
94 | let mut i = 0; |
95 | while i < ctx.buffer.len { |
96 | if (ctx.buffer.info[i].mask & kern_mask) == 0 { |
97 | i += 1; |
98 | continue; |
99 | } |
100 | |
101 | let mut iter = SkippyIter::new(&ctx, i, 1, false); |
102 | if !iter.next() { |
103 | i += 1; |
104 | continue; |
105 | } |
106 | |
107 | let j = iter.index(); |
108 | |
109 | let info = &ctx.buffer.info; |
110 | let kern = get_kerning(info[i].glyph_id, info[j].glyph_id); |
111 | |
112 | let pos = &mut ctx.buffer.pos; |
113 | if kern != 0 { |
114 | if horizontal { |
115 | if cross_stream { |
116 | pos[j].y_offset = kern; |
117 | ctx.buffer.scratch_flags |= BufferScratchFlags::HAS_GPOS_ATTACHMENT; |
118 | } else { |
119 | let kern1 = kern >> 1; |
120 | let kern2 = kern - kern1; |
121 | pos[i].x_advance += kern1; |
122 | pos[j].x_advance += kern2; |
123 | pos[j].x_offset += kern2; |
124 | } |
125 | } else { |
126 | if cross_stream { |
127 | pos[j].x_offset = kern; |
128 | ctx.buffer.scratch_flags |= BufferScratchFlags::HAS_GPOS_ATTACHMENT; |
129 | } else { |
130 | let kern1 = kern >> 1; |
131 | let kern2 = kern - kern1; |
132 | pos[i].y_advance += kern1; |
133 | pos[j].y_advance += kern2; |
134 | pos[j].y_offset += kern2; |
135 | } |
136 | } |
137 | |
138 | ctx.buffer.unsafe_to_break(i, j + 1) |
139 | } |
140 | |
141 | i = j; |
142 | } |
143 | } |
144 | |
145 | fn apply_simple_kerning( |
146 | subtable: &kern::Subtable, |
147 | face: &Face, |
148 | kern_mask: Mask, |
149 | buffer: &mut Buffer, |
150 | ) { |
151 | machine_kern( |
152 | face, |
153 | buffer, |
154 | kern_mask, |
155 | subtable.has_cross_stream, |
156 | |left: u32, right: u32| { |
157 | subtable |
158 | .glyphs_kerning(GlyphId(left as u16), GlyphId(right as u16)) |
159 | .map(i32::from) |
160 | .unwrap_or(default:0) |
161 | }, |
162 | ); |
163 | } |
164 | |
165 | struct StateMachineDriver { |
166 | stack: [usize; 8], |
167 | depth: usize, |
168 | } |
169 | |
170 | fn apply_state_machine_kerning(subtable: &kern::Subtable, kern_mask: Mask, buffer: &mut Buffer) { |
171 | let state_table = match subtable.format { |
172 | kern::Format::Format1(ref state_table) => state_table, |
173 | _ => return, |
174 | }; |
175 | |
176 | let mut driver = StateMachineDriver { |
177 | stack: [0; 8], |
178 | depth: 0, |
179 | }; |
180 | |
181 | let mut state = apple_layout::state::START_OF_TEXT; |
182 | buffer.idx = 0; |
183 | loop { |
184 | let class = if buffer.idx < buffer.len { |
185 | state_table |
186 | .class(buffer.info[buffer.idx].as_glyph()) |
187 | .unwrap_or(1) |
188 | } else { |
189 | apple_layout::class::END_OF_TEXT as u8 |
190 | }; |
191 | |
192 | let entry = match state_table.entry(state, class) { |
193 | Some(v) => v, |
194 | None => break, |
195 | }; |
196 | |
197 | // Unsafe-to-break before this if not in state 0, as things might |
198 | // go differently if we start from state 0 here. |
199 | if state != apple_layout::state::START_OF_TEXT |
200 | && buffer.backtrack_len() != 0 |
201 | && buffer.idx < buffer.len |
202 | { |
203 | // If there's no value and we're just epsilon-transitioning to state 0, safe to break. |
204 | if entry.has_offset() |
205 | || !(entry.new_state == apple_layout::state::START_OF_TEXT && !entry.has_advance()) |
206 | { |
207 | buffer.unsafe_to_break_from_outbuffer(buffer.backtrack_len() - 1, buffer.idx + 1); |
208 | } |
209 | } |
210 | |
211 | // Unsafe-to-break if end-of-text would kick in here. |
212 | if buffer.idx + 2 <= buffer.len { |
213 | let end_entry = match state_table.entry(state, apple_layout::class::END_OF_TEXT) { |
214 | Some(v) => v, |
215 | None => break, |
216 | }; |
217 | |
218 | if end_entry.has_offset() { |
219 | buffer.unsafe_to_break(buffer.idx, buffer.idx + 2); |
220 | } |
221 | } |
222 | |
223 | state_machine_transition( |
224 | entry, |
225 | subtable.has_cross_stream, |
226 | kern_mask, |
227 | state_table, |
228 | &mut driver, |
229 | buffer, |
230 | ); |
231 | |
232 | state = state_table.new_state(entry.new_state); |
233 | |
234 | if buffer.idx >= buffer.len { |
235 | break; |
236 | } |
237 | |
238 | buffer.max_ops -= 1; |
239 | if entry.has_advance() || buffer.max_ops <= 0 { |
240 | buffer.next_glyph(); |
241 | } |
242 | } |
243 | } |
244 | |
245 | fn state_machine_transition( |
246 | entry: apple_layout::StateEntry, |
247 | has_cross_stream: bool, |
248 | kern_mask: Mask, |
249 | state_table: &apple_layout::StateTable, |
250 | driver: &mut StateMachineDriver, |
251 | buffer: &mut Buffer, |
252 | ) { |
253 | if entry.has_push() { |
254 | if driver.depth < driver.stack.len() { |
255 | driver.stack[driver.depth] = buffer.idx; |
256 | driver.depth += 1; |
257 | } else { |
258 | driver.depth = 0; // Probably not what CoreText does, but better? |
259 | } |
260 | } |
261 | |
262 | if entry.has_offset() && driver.depth != 0 { |
263 | let mut value_offset = entry.value_offset(); |
264 | let mut value = match state_table.kerning(value_offset) { |
265 | Some(v) => v, |
266 | None => { |
267 | driver.depth = 0; |
268 | return; |
269 | } |
270 | }; |
271 | |
272 | // From Apple 'kern' spec: |
273 | // "Each pops one glyph from the kerning stack and applies the kerning value to it. |
274 | // The end of the list is marked by an odd value... |
275 | let mut last = false; |
276 | while !last && driver.depth != 0 { |
277 | driver.depth -= 1; |
278 | let idx = driver.stack[driver.depth]; |
279 | let mut v = value as i32; |
280 | value_offset = value_offset.next(); |
281 | value = state_table.kerning(value_offset).unwrap_or(0); |
282 | if idx >= buffer.len { |
283 | continue; |
284 | } |
285 | |
286 | // "The end of the list is marked by an odd value..." |
287 | last = v & 1 != 0; |
288 | v &= !1; |
289 | |
290 | // Testing shows that CoreText only applies kern (cross-stream or not) |
291 | // if none has been applied by previous subtables. That is, it does |
292 | // NOT seem to accumulate as otherwise implied by specs. |
293 | |
294 | let mut has_gpos_attachment = false; |
295 | let glyph_mask = buffer.info[idx].mask; |
296 | let pos = &mut buffer.pos[idx]; |
297 | |
298 | if buffer.direction.is_horizontal() { |
299 | if has_cross_stream { |
300 | // The following flag is undocumented in the spec, but described |
301 | // in the 'kern' table example. |
302 | if v == -0x8000 { |
303 | pos.set_attach_type(0); |
304 | pos.set_attach_chain(0); |
305 | pos.y_offset = 0; |
306 | } else if pos.attach_type() != 0 { |
307 | pos.y_offset += v; |
308 | has_gpos_attachment = true; |
309 | } |
310 | } else if glyph_mask & kern_mask != 0 { |
311 | pos.x_advance += v; |
312 | pos.x_offset += v; |
313 | } |
314 | } else { |
315 | if has_cross_stream { |
316 | // CoreText doesn't do crossStream kerning in vertical. We do. |
317 | if v == -0x8000 { |
318 | pos.set_attach_type(0); |
319 | pos.set_attach_chain(0); |
320 | pos.x_offset = 0; |
321 | } else if pos.attach_type() != 0 { |
322 | pos.x_offset += v; |
323 | has_gpos_attachment = true; |
324 | } |
325 | } else if glyph_mask & kern_mask != 0 { |
326 | if pos.y_offset == 0 { |
327 | pos.y_advance += v; |
328 | pos.y_offset += v; |
329 | } |
330 | } |
331 | } |
332 | |
333 | if has_gpos_attachment { |
334 | buffer.scratch_flags |= BufferScratchFlags::HAS_GPOS_ATTACHMENT; |
335 | } |
336 | } |
337 | } |
338 | } |
339 | |