Skip to main content

harfrust/hb/
ot_layout_gpos_table.rs

1use super::buffer::*;
2use super::font_funcs::FontFuncsDispatch;
3use super::hb_font_t;
4use super::ot_layout::*;
5use super::ot_shape_plan::hb_ot_shape_plan_t;
6use crate::Direction;
7
8pub fn position(
9    plan: &hb_ot_shape_plan_t,
10    face: &hb_font_t,
11    font_funcs: &mut FontFuncsDispatch,
12    buffer: &mut hb_buffer_t,
13) {
14    apply_layout_table(plan, face, font_funcs, buffer, face.ot_tables.gpos.as_ref());
15}
16
17pub mod attach_type {
18    pub const MARK: u8 = 1;
19    pub const CURSIVE: u8 = 2;
20}
21
22/// See <https://github.com/harfbuzz/harfbuzz/blob/7d936359a27abb2d7cb14ecc102463bb15c11843/src/OT/Layout/GPOS/GPOS.hh#L75>
23fn propagate_attachment_offsets(
24    pos: &mut [GlyphPosition],
25    len: usize,
26    i: usize,
27    direction: Direction,
28    nesting_level: usize,
29) {
30    // Adjusts offsets of attached glyphs (both cursive and mark) to accumulate
31    // offset of glyph they are attached to.
32    let chain = pos[i].attach_chain();
33    let kind = pos[i].attach_type();
34
35    pos[i].set_attach_chain(0);
36
37    let j = (i as isize + isize::from(chain)) as usize;
38    if j >= len {
39        return;
40    }
41
42    if nesting_level == 0 {
43        return;
44    }
45
46    if pos[j].attach_chain() != 0 {
47        propagate_attachment_offsets(pos, len, j, direction, nesting_level - 1);
48    }
49
50    match kind {
51        attach_type::MARK => {
52            pos[i].x_offset = pos[i].x_offset.saturating_add(pos[j].x_offset);
53            pos[i].y_offset = pos[i].y_offset.saturating_add(pos[j].y_offset);
54
55            // i is the position of the mark; j is the base.
56            if j < i {
57                // This is the common case: mark follows base.
58                // And currently the only way in OpenType.
59                if direction.is_forward() {
60                    for k in j..i {
61                        pos[i].x_offset = pos[i].x_offset.saturating_sub(pos[k].x_advance);
62                        pos[i].y_offset = pos[i].y_offset.saturating_sub(pos[k].y_advance);
63                    }
64                } else {
65                    for k in j + 1..i + 1 {
66                        pos[i].x_offset = pos[i].x_offset.saturating_add(pos[k].x_advance);
67                        pos[i].y_offset = pos[i].y_offset.saturating_add(pos[k].y_advance);
68                    }
69                }
70            } else {
71                // This can happen with `kerx`: a mark attaching
72                // to a base after it in the logical order.
73                if direction.is_forward() {
74                    for k in i..j {
75                        pos[i].x_offset = pos[i].x_offset.saturating_add(pos[k].x_advance);
76                        pos[i].y_offset = pos[i].y_offset.saturating_add(pos[k].y_advance);
77                    }
78                } else {
79                    for k in i + 1..j + 1 {
80                        pos[i].x_offset = pos[i].x_offset.saturating_sub(pos[k].x_advance);
81                        pos[i].y_offset = pos[i].y_offset.saturating_sub(pos[k].y_advance);
82                    }
83                }
84            }
85        }
86        attach_type::CURSIVE => {
87            if direction.is_horizontal() {
88                pos[i].y_offset = pos[i].y_offset.saturating_add(pos[j].y_offset);
89            } else {
90                pos[i].x_offset = pos[i].x_offset.saturating_add(pos[j].x_offset);
91            }
92        }
93        _ => {}
94    }
95}
96
97pub mod GPOS {
98    use super::*;
99
100    pub fn position_start(_: &hb_font_t, buffer: &mut hb_buffer_t) {
101        let len = buffer.len;
102        for pos in &mut buffer.pos[..len] {
103            pos.set_attach_chain(0);
104            pos.set_attach_type(0);
105        }
106    }
107
108    pub fn position_finish_advances(_: &hb_font_t, _: &mut hb_buffer_t) {
109        //buffer.assert_gsubgpos_vars();
110    }
111
112    pub fn position_finish_offsets(_: &hb_font_t, buffer: &mut hb_buffer_t) {
113        buffer.assert_gsubgpos_vars();
114
115        let len = buffer.len;
116        let direction = buffer.direction;
117
118        // Handle attachments
119        if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT != 0 {
120            // https://github.com/harfbuzz/harfbuzz/issues/5514
121            if buffer.direction.is_forward() {
122                for i in 0..len {
123                    if buffer.pos[i].attach_chain() != 0 {
124                        propagate_attachment_offsets(
125                            &mut buffer.pos,
126                            len,
127                            i,
128                            direction,
129                            MAX_NESTING_LEVEL,
130                        );
131                    }
132                }
133            } else {
134                for i in (0..len).rev() {
135                    if buffer.pos[i].attach_chain() != 0 {
136                        propagate_attachment_offsets(
137                            &mut buffer.pos,
138                            len,
139                            i,
140                            direction,
141                            MAX_NESTING_LEVEL,
142                        );
143                    }
144                }
145            }
146        }
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn propagate_attachment_offsets_saturates_deep_mark_chains() {
156        let len = MAX_NESTING_LEVEL + 2;
157        let mut pos = vec![GlyphPosition::default(); len];
158
159        for i in 1..len {
160            pos[i].x_offset = 40_000_000;
161            pos[i].y_offset = 40_000_000;
162            pos[i].set_attach_type(attach_type::MARK);
163            pos[i].set_attach_chain(-1);
164        }
165
166        propagate_attachment_offsets(
167            &mut pos,
168            len,
169            len - 1,
170            Direction::LeftToRight,
171            MAX_NESTING_LEVEL,
172        );
173
174        assert_eq!(pos[len - 1].x_offset, i32::MAX);
175        assert_eq!(pos[len - 1].y_offset, i32::MAX);
176    }
177}