1use ttf_parser::gpos::*;
2use ttf_parser::opentype_layout::LookupIndex;
3use ttf_parser::GlyphId;
4
5use crate::buffer::{Buffer, BufferScratchFlags, GlyphPosition};
6use crate::plan::ShapePlan;
7use crate::{Direction, Face};
8
9use super::apply::{Apply, ApplyContext};
10use super::matching::SkippyIter;
11use super::{
12 lookup_flags, LayoutLookup, LayoutTable, PositioningLookup, PositioningTable, TableIndex,
13};
14
15pub 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
23pub fn position(plan: &ShapePlan, face: &Face, buffer: &mut Buffer) {
24 super::apply_layout_table(plan, face, buffer, table:face.gpos.as_ref());
25}
26
27pub fn position_finish_advances(_: &Face, _: &mut Buffer) {}
28
29pub 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
44fn 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
96impl<'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
107impl 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
121impl 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
135impl 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
150impl 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
169impl 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
246impl 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
354fn 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
386impl 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
438impl 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
493impl 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
546trait 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
552impl 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
636trait 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
647impl 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
681pub 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.
687pub trait TryNumFrom<T>: Sized {
688 /// Casts between numeric types.
689 fn try_num_from(_: T) -> Option<Self>;
690}
691
692impl 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
714trait DeviceExt {
715 fn get_x_delta(&self, face: &Face) -> Option<i32>;
716 fn get_y_delta(&self, face: &Face) -> Option<i32>;
717}
718
719impl 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
751trait AnchorExt {
752 fn get(&self, face: &Face) -> (i32, i32);
753}
754
755impl 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