1 | use ttf_parser::gpos::*; |
2 | use ttf_parser::opentype_layout::LookupIndex; |
3 | use ttf_parser::GlyphId; |
4 | |
5 | use crate::buffer::{Buffer, BufferScratchFlags, GlyphPosition}; |
6 | use crate::plan::ShapePlan; |
7 | use crate::{Direction, Face}; |
8 | |
9 | use super::apply::{Apply, ApplyContext}; |
10 | use super::matching::SkippyIter; |
11 | use super::{ |
12 | lookup_flags, LayoutLookup, LayoutTable, PositioningLookup, PositioningTable, TableIndex, |
13 | }; |
14 | |
15 | pub fn position_start(_: &Face, buffer: &mut Buffer) { |
16 | let len: usize = buffer.len; |
17 | for pos: &mut GlyphPosition in &mut buffer.pos[..len] { |
18 | pos.set_attach_chain(0); |
19 | pos.set_attach_type(0); |
20 | } |
21 | } |
22 | |
23 | pub fn position(plan: &ShapePlan, face: &Face, buffer: &mut Buffer) { |
24 | super::apply_layout_table(plan, face, buffer, table:face.gpos.as_ref()); |
25 | } |
26 | |
27 | pub fn position_finish_advances(_: &Face, _: &mut Buffer) {} |
28 | |
29 | pub fn position_finish_offsets(_: &Face, buffer: &mut Buffer) { |
30 | let len: usize = buffer.len; |
31 | let direction: Direction = buffer.direction; |
32 | |
33 | // Handle attachments |
34 | if bufferBufferScratchFlags |
35 | .scratch_flags |
36 | .contains(BufferScratchFlags::HAS_GPOS_ATTACHMENT) |
37 | { |
38 | for i: usize in 0..len { |
39 | propagate_attachment_offsets(&mut buffer.pos, len, i, direction); |
40 | } |
41 | } |
42 | } |
43 | |
44 | fn propagate_attachment_offsets( |
45 | pos: &mut [GlyphPosition], |
46 | len: usize, |
47 | i: usize, |
48 | direction: Direction, |
49 | ) { |
50 | // Adjusts offsets of attached glyphs (both cursive and mark) to accumulate |
51 | // offset of glyph they are attached to. |
52 | let chain = pos[i].attach_chain(); |
53 | let kind = pos[i].attach_type(); |
54 | if chain == 0 { |
55 | return; |
56 | } |
57 | |
58 | pos[i].set_attach_chain(0); |
59 | |
60 | let j = (i as isize + isize::from(chain)) as _; |
61 | if j >= len { |
62 | return; |
63 | } |
64 | |
65 | propagate_attachment_offsets(pos, len, j, direction); |
66 | |
67 | match kind { |
68 | attach_type::MARK => { |
69 | pos[i].x_offset += pos[j].x_offset; |
70 | pos[i].y_offset += pos[j].y_offset; |
71 | |
72 | assert!(j < i); |
73 | if direction.is_forward() { |
74 | for k in j..i { |
75 | pos[i].x_offset -= pos[k].x_advance; |
76 | pos[i].y_offset -= pos[k].y_advance; |
77 | } |
78 | } else { |
79 | for k in j + 1..i + 1 { |
80 | pos[i].x_offset += pos[k].x_advance; |
81 | pos[i].y_offset += pos[k].y_advance; |
82 | } |
83 | } |
84 | } |
85 | attach_type::CURSIVE => { |
86 | if direction.is_horizontal() { |
87 | pos[i].y_offset += pos[j].y_offset; |
88 | } else { |
89 | pos[i].x_offset += pos[j].x_offset; |
90 | } |
91 | } |
92 | _ => {} |
93 | } |
94 | } |
95 | |
96 | impl<'a> LayoutTable for PositioningTable<'a> { |
97 | const INDEX: TableIndex = TableIndex::GPOS; |
98 | const IN_PLACE: bool = true; |
99 | |
100 | type Lookup = PositioningLookup<'a>; |
101 | |
102 | fn get_lookup(&self, index: LookupIndex) -> Option<&Self::Lookup> { |
103 | self.lookups.get(index:usize::from(index)) |
104 | } |
105 | } |
106 | |
107 | impl LayoutLookup for PositioningLookup<'_> { |
108 | fn props(&self) -> u32 { |
109 | self.props |
110 | } |
111 | |
112 | fn is_reverse(&self) -> bool { |
113 | false |
114 | } |
115 | |
116 | fn covers(&self, glyph: GlyphId) -> bool { |
117 | self.coverage.contains(glyph) |
118 | } |
119 | } |
120 | |
121 | impl Apply for PositioningLookup<'_> { |
122 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
123 | if self.covers(ctx.buffer.cur(0).as_glyph()) { |
124 | for subtable: &PositioningSubtable<'_> in &self.subtables { |
125 | if subtable.apply(ctx).is_some() { |
126 | return Some(()); |
127 | } |
128 | } |
129 | } |
130 | |
131 | None |
132 | } |
133 | } |
134 | |
135 | impl Apply for PositioningSubtable<'_> { |
136 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
137 | match self { |
138 | Self::Single(t: &SingleAdjustment<'_>) => t.apply(ctx), |
139 | Self::Pair(t: &PairAdjustment<'_>) => t.apply(ctx), |
140 | Self::Cursive(t: &CursiveAdjustment<'_>) => t.apply(ctx), |
141 | Self::MarkToBase(t: &MarkToBaseAdjustment<'_>) => t.apply(ctx), |
142 | Self::MarkToLigature(t: &MarkToLigatureAdjustment<'_>) => t.apply(ctx), |
143 | Self::MarkToMark(t: &MarkToMarkAdjustment<'_>) => t.apply(ctx), |
144 | Self::Context(t: &ContextLookup<'_>) => t.apply(ctx), |
145 | Self::ChainContext(t: &ChainedContextLookup<'_>) => t.apply(ctx), |
146 | } |
147 | } |
148 | } |
149 | |
150 | impl Apply for SingleAdjustment<'_> { |
151 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
152 | let glyph: GlyphId = ctx.buffer.cur(0).as_glyph(); |
153 | let record: ValueRecord<'_> = match self { |
154 | Self::Format1 { coverage: &Coverage<'_>, value: &ValueRecord<'_> } => { |
155 | coverage.get(glyph)?; |
156 | *value |
157 | } |
158 | Self::Format2 { coverage: &Coverage<'_>, values: &ValueRecordsArray<'_> } => { |
159 | let index: u16 = coverage.get(glyph)?; |
160 | values.get(index)? |
161 | } |
162 | }; |
163 | record.apply(ctx, ctx.buffer.idx); |
164 | ctx.buffer.idx += 1; |
165 | Some(()) |
166 | } |
167 | } |
168 | |
169 | impl Apply for PairAdjustment<'_> { |
170 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
171 | let first_glyph = ctx.buffer.cur(0).as_glyph(); |
172 | let first_glyph_coverage_index = self.coverage().get(first_glyph)?; |
173 | |
174 | let mut iter = SkippyIter::new(ctx, ctx.buffer.idx, 1, false); |
175 | |
176 | let mut unsafe_to = 0; |
177 | if !iter.next(Some(&mut unsafe_to)) { |
178 | ctx.buffer |
179 | .unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to)); |
180 | return None; |
181 | } |
182 | |
183 | let second_glyph_index = iter.index(); |
184 | let second_glyph = ctx.buffer.info[second_glyph_index].as_glyph(); |
185 | |
186 | let finish = |ctx: &mut ApplyContext, has_record2| { |
187 | ctx.buffer.idx = second_glyph_index; |
188 | |
189 | if has_record2 { |
190 | ctx.buffer.idx += 1; |
191 | } |
192 | |
193 | Some(()) |
194 | }; |
195 | |
196 | let boring = |ctx: &mut ApplyContext, has_record2| { |
197 | ctx.buffer |
198 | .unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1)); |
199 | finish(ctx, has_record2) |
200 | }; |
201 | |
202 | let success = |ctx: &mut ApplyContext, flag1, flag2, has_record2| { |
203 | if flag1 || flag2 { |
204 | ctx.buffer |
205 | .unsafe_to_break(Some(ctx.buffer.idx), Some(second_glyph_index + 1)); |
206 | finish(ctx, has_record2) |
207 | } else { |
208 | boring(ctx, has_record2) |
209 | } |
210 | }; |
211 | |
212 | let bail = |ctx: &mut ApplyContext, records: (ValueRecord, ValueRecord)| { |
213 | let flag1 = records.0.apply(ctx, ctx.buffer.idx); |
214 | let flag2 = records.1.apply(ctx, second_glyph_index); |
215 | |
216 | let has_record2 = !records.1.is_empty(); |
217 | success(ctx, flag1, flag2, has_record2) |
218 | }; |
219 | |
220 | let records = match self { |
221 | Self::Format1 { sets, .. } => { |
222 | sets.get(first_glyph_coverage_index)?.get(second_glyph)? |
223 | } |
224 | Self::Format2 { |
225 | classes, matrix, .. |
226 | } => { |
227 | let classes = (classes.0.get(first_glyph), classes.1.get(second_glyph)); |
228 | |
229 | let records = match matrix.get(classes) { |
230 | Some(v) => v, |
231 | None => { |
232 | ctx.buffer |
233 | .unsafe_to_concat(Some(ctx.buffer.idx), Some(iter.index() + 1)); |
234 | return None; |
235 | } |
236 | }; |
237 | |
238 | return bail(ctx, records); |
239 | } |
240 | }; |
241 | |
242 | bail(ctx, records) |
243 | } |
244 | } |
245 | |
246 | impl Apply for CursiveAdjustment<'_> { |
247 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
248 | let this = ctx.buffer.cur(0).as_glyph(); |
249 | |
250 | let index_this = self.coverage.get(this)?; |
251 | let entry_this = self.sets.entry(index_this)?; |
252 | |
253 | let mut iter = SkippyIter::new(ctx, ctx.buffer.idx, 1, false); |
254 | |
255 | let mut unsafe_from = 0; |
256 | if !iter.prev(Some(&mut unsafe_from)) { |
257 | ctx.buffer |
258 | .unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1)); |
259 | return None; |
260 | } |
261 | |
262 | let i = iter.index(); |
263 | let prev = ctx.buffer.info[i].as_glyph(); |
264 | let index_prev = self.coverage.get(prev)?; |
265 | let Some(exit_prev) = self.sets.exit(index_prev) else { |
266 | ctx.buffer |
267 | .unsafe_to_concat_from_outbuffer(Some(iter.index()), Some(ctx.buffer.idx + 1)); |
268 | return None; |
269 | }; |
270 | |
271 | let (exit_x, exit_y) = exit_prev.get(ctx.face); |
272 | let (entry_x, entry_y) = entry_this.get(ctx.face); |
273 | |
274 | let direction = ctx.buffer.direction; |
275 | let j = ctx.buffer.idx; |
276 | ctx.buffer.unsafe_to_break(Some(i), Some(j)); |
277 | |
278 | let pos = &mut ctx.buffer.pos; |
279 | match direction { |
280 | Direction::LeftToRight => { |
281 | pos[i].x_advance = exit_x + pos[i].x_offset; |
282 | let d = entry_x + pos[j].x_offset; |
283 | pos[j].x_advance -= d; |
284 | pos[j].x_offset -= d; |
285 | } |
286 | Direction::RightToLeft => { |
287 | let d = exit_x + pos[i].x_offset; |
288 | pos[i].x_advance -= d; |
289 | pos[i].x_offset -= d; |
290 | pos[j].x_advance = entry_x + pos[j].x_offset; |
291 | } |
292 | Direction::TopToBottom => { |
293 | pos[i].y_advance = exit_y + pos[i].y_offset; |
294 | let d = entry_y + pos[j].y_offset; |
295 | pos[j].y_advance -= d; |
296 | pos[j].y_offset -= d; |
297 | } |
298 | Direction::BottomToTop => { |
299 | let d = exit_y + pos[i].y_offset; |
300 | pos[i].y_advance -= d; |
301 | pos[i].y_offset -= d; |
302 | pos[j].y_advance = entry_y; |
303 | } |
304 | Direction::Invalid => {} |
305 | } |
306 | |
307 | // Cross-direction adjustment |
308 | |
309 | // We attach child to parent (think graph theory and rooted trees whereas |
310 | // the root stays on baseline and each node aligns itself against its |
311 | // parent. |
312 | // |
313 | // Optimize things for the case of RightToLeft, as that's most common in |
314 | // Arabic. |
315 | let mut child = i; |
316 | let mut parent = j; |
317 | let mut x_offset = entry_x - exit_x; |
318 | let mut y_offset = entry_y - exit_y; |
319 | |
320 | // Low bits are lookup flags, so we want to truncate. |
321 | if ctx.lookup_props as u16 & lookup_flags::RIGHT_TO_LEFT == 0 { |
322 | core::mem::swap(&mut child, &mut parent); |
323 | x_offset = -x_offset; |
324 | y_offset = -y_offset; |
325 | } |
326 | |
327 | // If child was already connected to someone else, walk through its old |
328 | // chain and reverse the link direction, such that the whole tree of its |
329 | // previous connection now attaches to new parent. Watch out for case |
330 | // where new parent is on the path from old chain... |
331 | reverse_cursive_minor_offset(pos, child, direction, parent); |
332 | |
333 | pos[child].set_attach_type(attach_type::CURSIVE); |
334 | pos[child].set_attach_chain((parent as isize - child as isize) as i16); |
335 | |
336 | ctx.buffer.scratch_flags |= BufferScratchFlags::HAS_GPOS_ATTACHMENT; |
337 | if direction.is_horizontal() { |
338 | pos[child].y_offset = y_offset; |
339 | } else { |
340 | pos[child].x_offset = x_offset; |
341 | } |
342 | |
343 | // If parent was attached to child, separate them. |
344 | // https://github.com/harfbuzz/harfbuzz/issues/2469 |
345 | if pos[parent].attach_chain() == -pos[child].attach_chain() { |
346 | pos[parent].set_attach_chain(0); |
347 | } |
348 | |
349 | ctx.buffer.idx += 1; |
350 | Some(()) |
351 | } |
352 | } |
353 | |
354 | fn reverse_cursive_minor_offset( |
355 | pos: &mut [GlyphPosition], |
356 | i: usize, |
357 | direction: Direction, |
358 | new_parent: usize, |
359 | ) { |
360 | let chain = pos[i].attach_chain(); |
361 | let attach_type = pos[i].attach_type(); |
362 | if chain == 0 || attach_type & attach_type::CURSIVE == 0 { |
363 | return; |
364 | } |
365 | |
366 | pos[i].set_attach_chain(0); |
367 | |
368 | // Stop if we see new parent in the chain. |
369 | let j = (i as isize + isize::from(chain)) as _; |
370 | if j == new_parent { |
371 | return; |
372 | } |
373 | |
374 | reverse_cursive_minor_offset(pos, j, direction, new_parent); |
375 | |
376 | if direction.is_horizontal() { |
377 | pos[j].y_offset = -pos[i].y_offset; |
378 | } else { |
379 | pos[j].x_offset = -pos[i].x_offset; |
380 | } |
381 | |
382 | pos[j].set_attach_chain(-chain); |
383 | pos[j].set_attach_type(attach_type); |
384 | } |
385 | |
386 | impl Apply for MarkToBaseAdjustment<'_> { |
387 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
388 | let buffer = &ctx.buffer; |
389 | let mark_glyph = ctx.buffer.cur(0).as_glyph(); |
390 | let mark_index = self.mark_coverage.get(mark_glyph)?; |
391 | |
392 | // Now we search backwards for a non-mark glyph |
393 | let mut iter = SkippyIter::new(ctx, buffer.idx, 1, false); |
394 | iter.set_lookup_props(u32::from(lookup_flags::IGNORE_MARKS)); |
395 | |
396 | let info = &buffer.info; |
397 | loop { |
398 | let mut unsafe_from = 0; |
399 | if !iter.prev(Some(&mut unsafe_from)) { |
400 | ctx.buffer |
401 | .unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1)); |
402 | return None; |
403 | } |
404 | |
405 | // We only want to attach to the first of a MultipleSubst sequence. |
406 | // https://github.com/harfbuzz/harfbuzz/issues/740 |
407 | // Reject others... |
408 | // ...but stop if we find a mark in the MultipleSubst sequence: |
409 | // https://github.com/harfbuzz/harfbuzz/issues/1020 |
410 | let idx = iter.index(); |
411 | if !info[idx].is_multiplied() |
412 | || info[idx].lig_comp() == 0 |
413 | || idx == 0 |
414 | || info[idx - 1].is_mark() |
415 | || info[idx].lig_id() != info[idx - 1].lig_id() |
416 | || info[idx].lig_comp() != info[idx - 1].lig_comp() + 1 |
417 | { |
418 | break; |
419 | } |
420 | iter.reject(); |
421 | } |
422 | |
423 | // Checking that matched glyph is actually a base glyph by GDEF is too strong; disabled |
424 | |
425 | let iter_idx = iter.index(); |
426 | let base_glyph = info[iter_idx].as_glyph(); |
427 | let Some(base_index) = self.base_coverage.get(base_glyph) else { |
428 | ctx.buffer |
429 | .unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1)); |
430 | return None; |
431 | }; |
432 | |
433 | self.marks |
434 | .apply(ctx, self.anchors, mark_index, base_index, iter_idx) |
435 | } |
436 | } |
437 | |
438 | impl Apply for MarkToLigatureAdjustment<'_> { |
439 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
440 | let buffer = &ctx.buffer; |
441 | let mark_glyph = ctx.buffer.cur(0).as_glyph(); |
442 | let mark_index = self.mark_coverage.get(mark_glyph)?; |
443 | |
444 | // Now we search backwards for a non-mark glyph |
445 | let mut iter = SkippyIter::new(ctx, buffer.idx, 1, false); |
446 | iter.set_lookup_props(u32::from(lookup_flags::IGNORE_MARKS)); |
447 | |
448 | let mut unsafe_from = 0; |
449 | if !iter.prev(Some(&mut unsafe_from)) { |
450 | ctx.buffer |
451 | .unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1)); |
452 | return None; |
453 | } |
454 | |
455 | // Checking that matched glyph is actually a ligature by GDEF is too strong; disabled |
456 | |
457 | let iter_idx = iter.index(); |
458 | let lig_glyph = buffer.info[iter_idx].as_glyph(); |
459 | let Some(lig_index) = self.ligature_coverage.get(lig_glyph) else { |
460 | ctx.buffer |
461 | .unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1)); |
462 | return None; |
463 | }; |
464 | let lig_attach = self.ligature_array.get(lig_index)?; |
465 | |
466 | // Find component to attach to |
467 | let comp_count = lig_attach.rows; |
468 | if comp_count == 0 { |
469 | ctx.buffer |
470 | .unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1)); |
471 | return None; |
472 | } |
473 | |
474 | // We must now check whether the ligature ID of the current mark glyph |
475 | // is identical to the ligature ID of the found ligature. If yes, we |
476 | // can directly use the component index. If not, we attach the mark |
477 | // glyph to the last component of the ligature. |
478 | let lig_id = buffer.info[iter_idx].lig_id(); |
479 | let mark_id = buffer.cur(0).lig_id(); |
480 | let mark_comp = u16::from(buffer.cur(0).lig_comp()); |
481 | let matches = lig_id != 0 && lig_id == mark_id && mark_comp > 0; |
482 | let comp_index = if matches { |
483 | mark_comp.min(comp_count) |
484 | } else { |
485 | comp_count |
486 | } - 1; |
487 | |
488 | self.marks |
489 | .apply(ctx, lig_attach, mark_index, comp_index, iter_idx) |
490 | } |
491 | } |
492 | |
493 | impl Apply for MarkToMarkAdjustment<'_> { |
494 | fn apply(&self, ctx: &mut ApplyContext) -> Option<()> { |
495 | let buffer = &ctx.buffer; |
496 | let mark1_glyph = ctx.buffer.cur(0).as_glyph(); |
497 | let mark1_index = self.mark1_coverage.get(mark1_glyph)?; |
498 | |
499 | // Now we search backwards for a suitable mark glyph until a non-mark glyph |
500 | let mut iter = SkippyIter::new(ctx, buffer.idx, 1, false); |
501 | iter.set_lookup_props(ctx.lookup_props & !u32::from(lookup_flags::IGNORE_FLAGS)); |
502 | |
503 | let mut unsafe_from = 0; |
504 | if !iter.prev(Some(&mut unsafe_from)) { |
505 | ctx.buffer |
506 | .unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1)); |
507 | return None; |
508 | } |
509 | |
510 | let iter_idx = iter.index(); |
511 | if !buffer.info[iter_idx].is_mark() { |
512 | ctx.buffer |
513 | .unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1)); |
514 | return None; |
515 | } |
516 | |
517 | let id1 = buffer.cur(0).lig_id(); |
518 | let id2 = buffer.info[iter_idx].lig_id(); |
519 | let comp1 = buffer.cur(0).lig_comp(); |
520 | let comp2 = buffer.info[iter_idx].lig_comp(); |
521 | |
522 | let matches = if id1 == id2 { |
523 | // Marks belonging to the same base |
524 | // or marks belonging to the same ligature component. |
525 | id1 == 0 || comp1 == comp2 |
526 | } else { |
527 | // If ligature ids don't match, it may be the case that one of the marks |
528 | // itself is a ligature. In which case match. |
529 | (id1 > 0 && comp1 == 0) || (id2 > 0 && comp2 == 0) |
530 | }; |
531 | |
532 | if !matches { |
533 | ctx.buffer |
534 | .unsafe_to_concat_from_outbuffer(Some(iter_idx), Some(buffer.idx + 1)); |
535 | return None; |
536 | } |
537 | |
538 | let mark2_glyph = buffer.info[iter_idx].as_glyph(); |
539 | let mark2_index = self.mark2_coverage.get(mark2_glyph)?; |
540 | |
541 | self.marks |
542 | .apply(ctx, self.mark2_matrix, mark1_index, mark2_index, iter_idx) |
543 | } |
544 | } |
545 | |
546 | trait ValueRecordExt { |
547 | fn is_empty(&self) -> bool; |
548 | fn apply(&self, ctx: &mut ApplyContext, idx: usize) -> bool; |
549 | fn apply_to_pos(&self, ctx: &mut ApplyContext, pos: &mut GlyphPosition) -> bool; |
550 | } |
551 | |
552 | impl ValueRecordExt for ValueRecord<'_> { |
553 | fn is_empty(&self) -> bool { |
554 | self.x_placement == 0 |
555 | && self.y_placement == 0 |
556 | && self.x_advance == 0 |
557 | && self.y_advance == 0 |
558 | && self.x_placement_device.is_none() |
559 | && self.y_placement_device.is_none() |
560 | && self.x_advance_device.is_none() |
561 | && self.y_advance_device.is_none() |
562 | } |
563 | |
564 | fn apply(&self, ctx: &mut ApplyContext, idx: usize) -> bool { |
565 | let mut pos = ctx.buffer.pos[idx]; |
566 | let worked = self.apply_to_pos(ctx, &mut pos); |
567 | ctx.buffer.pos[idx] = pos; |
568 | worked |
569 | } |
570 | |
571 | fn apply_to_pos(&self, ctx: &mut ApplyContext, pos: &mut GlyphPosition) -> bool { |
572 | let horizontal = ctx.buffer.direction.is_horizontal(); |
573 | let mut worked = false; |
574 | |
575 | if self.x_placement != 0 { |
576 | pos.x_offset += i32::from(self.x_placement); |
577 | worked = true; |
578 | } |
579 | |
580 | if self.y_placement != 0 { |
581 | pos.y_offset += i32::from(self.y_placement); |
582 | worked = true; |
583 | } |
584 | |
585 | if self.x_advance != 0 && horizontal { |
586 | pos.x_advance += i32::from(self.x_advance); |
587 | worked = true; |
588 | } |
589 | |
590 | if self.y_advance != 0 && !horizontal { |
591 | // y_advance values grow downward but font-space grows upward, hence negation |
592 | pos.y_advance -= i32::from(self.y_advance); |
593 | worked = true; |
594 | } |
595 | |
596 | { |
597 | let (ppem_x, ppem_y) = ctx.face.pixels_per_em().unwrap_or((0, 0)); |
598 | let coords = ctx.face.ttfp_face.variation_coordinates().len(); |
599 | let use_x_device = ppem_x != 0 || coords != 0; |
600 | let use_y_device = ppem_y != 0 || coords != 0; |
601 | |
602 | if use_x_device { |
603 | if let Some(device) = self.x_placement_device { |
604 | pos.x_offset += device.get_x_delta(ctx.face).unwrap_or(0); |
605 | worked = true; // TODO: even when 0? |
606 | } |
607 | } |
608 | |
609 | if use_y_device { |
610 | if let Some(device) = self.y_placement_device { |
611 | pos.y_offset += device.get_y_delta(ctx.face).unwrap_or(0); |
612 | worked = true; |
613 | } |
614 | } |
615 | |
616 | if horizontal && use_x_device { |
617 | if let Some(device) = self.x_advance_device { |
618 | pos.x_advance += device.get_x_delta(ctx.face).unwrap_or(0); |
619 | worked = true; |
620 | } |
621 | } |
622 | |
623 | if !horizontal && use_y_device { |
624 | if let Some(device) = self.y_advance_device { |
625 | // y_advance values grow downward but face-space grows upward, hence negation |
626 | pos.y_advance -= device.get_y_delta(ctx.face).unwrap_or(0); |
627 | worked = true; |
628 | } |
629 | } |
630 | } |
631 | |
632 | worked |
633 | } |
634 | } |
635 | |
636 | trait MarkArrayExt { |
637 | fn apply( |
638 | &self, |
639 | ctx: &mut ApplyContext, |
640 | anchors: AnchorMatrix, |
641 | mark_index: u16, |
642 | glyph_index: u16, |
643 | glyph_pos: usize, |
644 | ) -> Option<()>; |
645 | } |
646 | |
647 | impl MarkArrayExt for MarkArray<'_> { |
648 | fn apply( |
649 | &self, |
650 | ctx: &mut ApplyContext, |
651 | anchors: AnchorMatrix, |
652 | mark_index: u16, |
653 | glyph_index: u16, |
654 | glyph_pos: usize, |
655 | ) -> Option<()> { |
656 | // If this subtable doesn't have an anchor for this base and this class |
657 | // return `None` such that the subsequent subtables have a chance at it. |
658 | let (mark_class, mark_anchor) = self.get(mark_index)?; |
659 | let base_anchor = anchors.get(glyph_index, mark_class)?; |
660 | |
661 | let (mark_x, mark_y) = mark_anchor.get(ctx.face); |
662 | let (base_x, base_y) = base_anchor.get(ctx.face); |
663 | |
664 | ctx.buffer |
665 | .unsafe_to_break(Some(glyph_pos), Some(ctx.buffer.idx + 1)); |
666 | |
667 | let idx = ctx.buffer.idx; |
668 | let pos = ctx.buffer.cur_pos_mut(); |
669 | pos.x_offset = base_x - mark_x; |
670 | pos.y_offset = base_y - mark_y; |
671 | pos.set_attach_type(attach_type::MARK); |
672 | pos.set_attach_chain((glyph_pos as isize - idx as isize) as i16); |
673 | |
674 | ctx.buffer.scratch_flags |= BufferScratchFlags::HAS_GPOS_ATTACHMENT; |
675 | ctx.buffer.idx += 1; |
676 | |
677 | Some(()) |
678 | } |
679 | } |
680 | |
681 | pub mod attach_type { |
682 | pub const MARK: u8 = 1; |
683 | pub const CURSIVE: u8 = 2; |
684 | } |
685 | |
686 | /// Just like TryFrom<N>, but for numeric types not supported by the Rust's std. |
687 | pub trait TryNumFrom<T>: Sized { |
688 | /// Casts between numeric types. |
689 | fn try_num_from(_: T) -> Option<Self>; |
690 | } |
691 | |
692 | impl TryNumFrom<f32> for i32 { |
693 | #[inline ] |
694 | fn try_num_from(v: f32) -> Option<Self> { |
695 | // Based on https://github.com/rust-num/num-traits/blob/master/src/cast.rs |
696 | |
697 | // Float as int truncates toward zero, so we want to allow values |
698 | // in the exclusive range `(MIN-1, MAX+1)`. |
699 | |
700 | // We can't represent `MIN-1` exactly, but there's no fractional part |
701 | // at this magnitude, so we can just use a `MIN` inclusive boundary. |
702 | const MIN: f32 = core::i32::MIN as f32; |
703 | // We can't represent `MAX` exactly, but it will round up to exactly |
704 | // `MAX+1` (a power of two) when we cast it. |
705 | const MAX_P1: f32 = core::i32::MAX as f32; |
706 | if v >= MIN && v < MAX_P1 { |
707 | Some(v as i32) |
708 | } else { |
709 | None |
710 | } |
711 | } |
712 | } |
713 | |
714 | trait DeviceExt { |
715 | fn get_x_delta(&self, face: &Face) -> Option<i32>; |
716 | fn get_y_delta(&self, face: &Face) -> Option<i32>; |
717 | } |
718 | |
719 | impl DeviceExt for Device<'_> { |
720 | fn get_x_delta(&self, face: &Face) -> Option<i32> { |
721 | match self { |
722 | Device::Hinting(hinting) => hinting.x_delta(face.units_per_em, face.pixels_per_em()), |
723 | Device::Variation(variation) => face |
724 | .tables() |
725 | .gdef? |
726 | .glyph_variation_delta( |
727 | variation.outer_index, |
728 | variation.inner_index, |
729 | face.variation_coordinates(), |
730 | ) |
731 | .and_then(|float| i32::try_num_from(crate::round(float))), |
732 | } |
733 | } |
734 | |
735 | fn get_y_delta(&self, face: &Face) -> Option<i32> { |
736 | match self { |
737 | Device::Hinting(hinting) => hinting.y_delta(face.units_per_em, face.pixels_per_em()), |
738 | Device::Variation(variation) => face |
739 | .tables() |
740 | .gdef? |
741 | .glyph_variation_delta( |
742 | variation.outer_index, |
743 | variation.inner_index, |
744 | face.variation_coordinates(), |
745 | ) |
746 | .and_then(|float| i32::try_num_from(crate::round(float))), |
747 | } |
748 | } |
749 | } |
750 | |
751 | trait AnchorExt { |
752 | fn get(&self, face: &Face) -> (i32, i32); |
753 | } |
754 | |
755 | impl AnchorExt for Anchor<'_> { |
756 | fn get(&self, face: &Face) -> (i32, i32) { |
757 | let mut x = i32::from(self.x); |
758 | let mut y = i32::from(self.y); |
759 | |
760 | if self.x_device.is_some() || self.y_device.is_some() { |
761 | let (ppem_x, ppem_y) = face.pixels_per_em().unwrap_or((0, 0)); |
762 | let coords = face.ttfp_face.variation_coordinates().len(); |
763 | |
764 | if let Some(device) = self.x_device { |
765 | if ppem_x != 0 || coords != 0 { |
766 | x += device.get_x_delta(face).unwrap_or(0); |
767 | } |
768 | } |
769 | |
770 | if let Some(device) = self.y_device { |
771 | if ppem_y != 0 || coords != 0 { |
772 | y += device.get_y_delta(face).unwrap_or(0); |
773 | } |
774 | } |
775 | } |
776 | |
777 | (x, y) |
778 | } |
779 | } |
780 | |