rustybuzz/hb/
ot_layout_gpos_table.rs

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