rustybuzz/hb/ot/layout/GPOS/
cursive_pos.rs

1use crate::hb::buffer::HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
2use crate::hb::ot_layout_common::lookup_flags;
3use crate::hb::ot_layout_gpos_table::attach_type;
4use crate::hb::ot_layout_gpos_table::AnchorExt;
5use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t;
6use crate::hb::ot_layout_gsubgpos::{skipping_iterator_t, Apply};
7use crate::{Direction, GlyphPosition};
8use ttf_parser::gpos::CursiveAdjustment;
9
10impl Apply for CursiveAdjustment<'_> {
11    fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
12        let this = ctx.buffer.cur(0).as_glyph();
13
14        let index_this = self.coverage.get(this)?;
15        let entry_this = self.sets.entry(index_this)?;
16
17        let mut iter = skipping_iterator_t::new(ctx, ctx.buffer.idx, false);
18
19        let mut unsafe_from = 0;
20        if !iter.prev(Some(&mut unsafe_from)) {
21            ctx.buffer
22                .unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1));
23            return None;
24        }
25
26        let i = iter.index();
27        let prev = ctx.buffer.info[i].as_glyph();
28        let index_prev = self.coverage.get(prev)?;
29        let Some(exit_prev) = self.sets.exit(index_prev) else {
30            ctx.buffer
31                .unsafe_to_concat_from_outbuffer(Some(iter.index()), Some(ctx.buffer.idx + 1));
32            return None;
33        };
34
35        let (exit_x, exit_y) = exit_prev.get(ctx.face);
36        let (entry_x, entry_y) = entry_this.get(ctx.face);
37
38        let direction = ctx.buffer.direction;
39        let j = ctx.buffer.idx;
40        ctx.buffer.unsafe_to_break(Some(i), Some(j + 1));
41
42        let pos = &mut ctx.buffer.pos;
43        match direction {
44            Direction::LeftToRight => {
45                pos[i].x_advance = exit_x + pos[i].x_offset;
46                let d = entry_x + pos[j].x_offset;
47                pos[j].x_advance -= d;
48                pos[j].x_offset -= d;
49            }
50            Direction::RightToLeft => {
51                let d = exit_x + pos[i].x_offset;
52                pos[i].x_advance -= d;
53                pos[i].x_offset -= d;
54                pos[j].x_advance = entry_x + pos[j].x_offset;
55            }
56            Direction::TopToBottom => {
57                pos[i].y_advance = exit_y + pos[i].y_offset;
58                let d = entry_y + pos[j].y_offset;
59                pos[j].y_advance -= d;
60                pos[j].y_offset -= d;
61            }
62            Direction::BottomToTop => {
63                let d = exit_y + pos[i].y_offset;
64                pos[i].y_advance -= d;
65                pos[i].y_offset -= d;
66                pos[j].y_advance = entry_y;
67            }
68            Direction::Invalid => {}
69        }
70
71        // Cross-direction adjustment
72
73        // We attach child to parent (think graph theory and rooted trees whereas
74        // the root stays on baseline and each node aligns itself against its
75        // parent.
76        //
77        // Optimize things for the case of RightToLeft, as that's most common in
78        // Arabic.
79        let mut child = i;
80        let mut parent = j;
81        let mut x_offset = entry_x - exit_x;
82        let mut y_offset = entry_y - exit_y;
83
84        // Low bits are lookup flags, so we want to truncate.
85        if ctx.lookup_props as u16 & lookup_flags::RIGHT_TO_LEFT == 0 {
86            core::mem::swap(&mut child, &mut parent);
87            x_offset = -x_offset;
88            y_offset = -y_offset;
89        }
90
91        // If child was already connected to someone else, walk through its old
92        // chain and reverse the link direction, such that the whole tree of its
93        // previous connection now attaches to new parent.  Watch out for case
94        // where new parent is on the path from old chain...
95        reverse_cursive_minor_offset(pos, child, direction, parent);
96
97        pos[child].set_attach_type(attach_type::CURSIVE);
98        pos[child].set_attach_chain((parent as isize - child as isize) as i16);
99
100        ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
101        if direction.is_horizontal() {
102            pos[child].y_offset = y_offset;
103        } else {
104            pos[child].x_offset = x_offset;
105        }
106
107        // If parent was attached to child, separate them.
108        // https://github.com/harfbuzz/harfbuzz/issues/2469
109        if pos[parent].attach_chain() == -pos[child].attach_chain() {
110            pos[parent].set_attach_chain(0);
111
112            if direction.is_horizontal() {
113                pos[parent].y_offset = 0;
114            } else {
115                pos[parent].x_offset = 0;
116            }
117        }
118
119        ctx.buffer.idx += 1;
120        Some(())
121    }
122}
123
124fn reverse_cursive_minor_offset(
125    pos: &mut [GlyphPosition],
126    i: usize,
127    direction: Direction,
128    new_parent: usize,
129) {
130    let chain = pos[i].attach_chain();
131    let attach_type = pos[i].attach_type();
132    if chain == 0 || attach_type & attach_type::CURSIVE == 0 {
133        return;
134    }
135
136    pos[i].set_attach_chain(0);
137
138    // Stop if we see new parent in the chain.
139    let j = (i as isize + isize::from(chain)) as _;
140    if j == new_parent {
141        return;
142    }
143
144    reverse_cursive_minor_offset(pos, j, direction, new_parent);
145
146    if direction.is_horizontal() {
147        pos[j].y_offset = -pos[i].y_offset;
148    } else {
149        pos[j].x_offset = -pos[i].x_offset;
150    }
151
152    pos[j].set_attach_chain(-chain);
153    pos[j].set_attach_type(attach_type);
154}