rustybuzz/hb/
ot_layout_gpos_table.rs

1#[cfg(not(feature = "std"))]
2use core_maths::CoreFloat;
3
4use super::buffer::*;
5use super::hb_font_t;
6use super::ot_layout::*;
7use super::ot_layout_common::{PositioningLookup, PositioningTable};
8use super::ot_layout_gsubgpos::{Apply, OT::hb_ot_apply_context_t};
9use super::ot_shape_plan::hb_ot_shape_plan_t;
10use crate::Direction;
11use ttf_parser::gpos::*;
12use ttf_parser::opentype_layout::LookupIndex;
13
14pub fn position(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
15    apply_layout_table(plan, face, buffer, face.gpos.as_ref());
16}
17
18pub(crate) trait ValueRecordExt {
19    fn is_empty(&self) -> bool;
20    fn apply(&self, ctx: &mut hb_ot_apply_context_t, idx: usize) -> bool;
21    fn apply_to_pos(&self, ctx: &mut hb_ot_apply_context_t, pos: &mut GlyphPosition) -> bool;
22}
23
24impl ValueRecordExt for ValueRecord<'_> {
25    fn is_empty(&self) -> bool {
26        self.x_placement == 0
27            && self.y_placement == 0
28            && self.x_advance == 0
29            && self.y_advance == 0
30            && self.x_placement_device.is_none()
31            && self.y_placement_device.is_none()
32            && self.x_advance_device.is_none()
33            && self.y_advance_device.is_none()
34    }
35
36    fn apply(&self, ctx: &mut hb_ot_apply_context_t, idx: usize) -> bool {
37        let mut pos = ctx.buffer.pos[idx];
38        let worked = self.apply_to_pos(ctx, &mut pos);
39        ctx.buffer.pos[idx] = pos;
40        worked
41    }
42
43    fn apply_to_pos(&self, ctx: &mut hb_ot_apply_context_t, pos: &mut GlyphPosition) -> bool {
44        let horizontal = ctx.buffer.direction.is_horizontal();
45        let mut worked = false;
46
47        if self.x_placement != 0 {
48            pos.x_offset += i32::from(self.x_placement);
49            worked = true;
50        }
51
52        if self.y_placement != 0 {
53            pos.y_offset += i32::from(self.y_placement);
54            worked = true;
55        }
56
57        if self.x_advance != 0 && horizontal {
58            pos.x_advance += i32::from(self.x_advance);
59            worked = true;
60        }
61
62        if self.y_advance != 0 && !horizontal {
63            // y_advance values grow downward but font-space grows upward, hence negation
64            pos.y_advance -= i32::from(self.y_advance);
65            worked = true;
66        }
67
68        {
69            let (ppem_x, ppem_y) = ctx.face.pixels_per_em().unwrap_or((0, 0));
70            let coords = ctx.face.ttfp_face.variation_coordinates().len();
71            let use_x_device = ppem_x != 0 || coords != 0;
72            let use_y_device = ppem_y != 0 || coords != 0;
73
74            if use_x_device {
75                if let Some(device) = self.x_placement_device {
76                    pos.x_offset += device.get_x_delta(ctx.face).unwrap_or(0);
77                    worked = true; // TODO: even when 0?
78                }
79            }
80
81            if use_y_device {
82                if let Some(device) = self.y_placement_device {
83                    pos.y_offset += device.get_y_delta(ctx.face).unwrap_or(0);
84                    worked = true;
85                }
86            }
87
88            if horizontal && use_x_device {
89                if let Some(device) = self.x_advance_device {
90                    pos.x_advance += device.get_x_delta(ctx.face).unwrap_or(0);
91                    worked = true;
92                }
93            }
94
95            if !horizontal && use_y_device {
96                if let Some(device) = self.y_advance_device {
97                    // y_advance values grow downward but face-space grows upward, hence negation
98                    pos.y_advance -= device.get_y_delta(ctx.face).unwrap_or(0);
99                    worked = true;
100                }
101            }
102        }
103
104        worked
105    }
106}
107
108pub(crate) trait AnchorExt {
109    fn get(&self, face: &hb_font_t) -> (i32, i32);
110}
111
112impl AnchorExt for Anchor<'_> {
113    fn get(&self, face: &hb_font_t) -> (i32, i32) {
114        let mut x = i32::from(self.x);
115        let mut y = i32::from(self.y);
116
117        if self.x_device.is_some() || self.y_device.is_some() {
118            let (ppem_x, ppem_y) = face.pixels_per_em().unwrap_or((0, 0));
119            let coords = face.ttfp_face.variation_coordinates().len();
120
121            if let Some(device) = self.x_device {
122                if ppem_x != 0 || coords != 0 {
123                    x += device.get_x_delta(face).unwrap_or(0);
124                }
125            }
126
127            if let Some(device) = self.y_device {
128                if ppem_y != 0 || coords != 0 {
129                    y += device.get_y_delta(face).unwrap_or(0);
130                }
131            }
132        }
133
134        (x, y)
135    }
136}
137
138impl<'a> LayoutTable for PositioningTable<'a> {
139    const INDEX: TableIndex = TableIndex::GPOS;
140    const IN_PLACE: bool = true;
141
142    type Lookup = PositioningLookup<'a>;
143
144    fn get_lookup(&self, index: LookupIndex) -> Option<&Self::Lookup> {
145        self.lookups.get(usize::from(index))
146    }
147}
148
149pub mod attach_type {
150    pub const MARK: u8 = 1;
151    pub const CURSIVE: u8 = 2;
152}
153
154/// Just like TryFrom<N>, but for numeric types not supported by the Rust's std.
155pub(crate) trait TryNumFrom<T>: Sized {
156    /// Casts between numeric types.
157    fn try_num_from(_: T) -> Option<Self>;
158}
159
160impl TryNumFrom<f32> for i32 {
161    #[inline]
162    fn try_num_from(v: f32) -> Option<Self> {
163        // Based on https://github.com/rust-num/num-traits/blob/master/src/cast.rs
164
165        // Float as int truncates toward zero, so we want to allow values
166        // in the exclusive range `(MIN-1, MAX+1)`.
167
168        // We can't represent `MIN-1` exactly, but there's no fractional part
169        // at this magnitude, so we can just use a `MIN` inclusive boundary.
170        const MIN: f32 = i32::MIN as f32;
171        // We can't represent `MAX` exactly, but it will round up to exactly
172        // `MAX+1` (a power of two) when we cast it.
173        const MAX_P1: f32 = i32::MAX as f32;
174        if v >= MIN && v < MAX_P1 {
175            Some(v as i32)
176        } else {
177            None
178        }
179    }
180}
181
182pub(crate) trait DeviceExt {
183    fn get_x_delta(&self, face: &hb_font_t) -> Option<i32>;
184    fn get_y_delta(&self, face: &hb_font_t) -> Option<i32>;
185}
186
187impl DeviceExt for Device<'_> {
188    fn get_x_delta(&self, face: &hb_font_t) -> Option<i32> {
189        match self {
190            Device::Hinting(hinting) => hinting.x_delta(face.units_per_em, face.pixels_per_em()),
191            Device::Variation(variation) => face
192                .tables()
193                .gdef?
194                .glyph_variation_delta(
195                    variation.outer_index,
196                    variation.inner_index,
197                    face.variation_coordinates(),
198                )
199                .and_then(|float| i32::try_num_from(float.round())),
200        }
201    }
202
203    fn get_y_delta(&self, face: &hb_font_t) -> Option<i32> {
204        match self {
205            Device::Hinting(hinting) => hinting.y_delta(face.units_per_em, face.pixels_per_em()),
206            Device::Variation(variation) => face
207                .tables()
208                .gdef?
209                .glyph_variation_delta(
210                    variation.outer_index,
211                    variation.inner_index,
212                    face.variation_coordinates(),
213                )
214                .and_then(|float| i32::try_num_from(float.round())),
215        }
216    }
217}
218
219impl Apply for PositioningSubtable<'_> {
220    fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> {
221        match self {
222            Self::Single(t) => t.apply(ctx),
223            Self::Pair(t) => t.apply(ctx),
224            Self::Cursive(t) => t.apply(ctx),
225            Self::MarkToBase(t) => t.apply(ctx),
226            Self::MarkToLigature(t) => t.apply(ctx),
227            Self::MarkToMark(t) => t.apply(ctx),
228            Self::Context(t) => t.apply(ctx),
229            Self::ChainContext(t) => t.apply(ctx),
230        }
231    }
232}
233
234fn propagate_attachment_offsets(
235    pos: &mut [GlyphPosition],
236    len: usize,
237    i: usize,
238    direction: Direction,
239) {
240    // Adjusts offsets of attached glyphs (both cursive and mark) to accumulate
241    // offset of glyph they are attached to.
242    let chain = pos[i].attach_chain();
243    let kind = pos[i].attach_type();
244    if chain == 0 {
245        return;
246    }
247
248    pos[i].set_attach_chain(0);
249
250    let j = (i as isize + isize::from(chain)) as _;
251    if j >= len {
252        return;
253    }
254
255    propagate_attachment_offsets(pos, len, j, direction);
256
257    match kind {
258        attach_type::MARK => {
259            pos[i].x_offset += pos[j].x_offset;
260            pos[i].y_offset += pos[j].y_offset;
261
262            assert!(j < i);
263            if direction.is_forward() {
264                for k in j..i {
265                    pos[i].x_offset -= pos[k].x_advance;
266                    pos[i].y_offset -= pos[k].y_advance;
267                }
268            } else {
269                for k in j + 1..i + 1 {
270                    pos[i].x_offset += pos[k].x_advance;
271                    pos[i].y_offset += pos[k].y_advance;
272                }
273            }
274        }
275        attach_type::CURSIVE => {
276            if direction.is_horizontal() {
277                pos[i].y_offset += pos[j].y_offset;
278            } else {
279                pos[i].x_offset += pos[j].x_offset;
280            }
281        }
282        _ => {}
283    }
284}
285
286pub mod GPOS {
287    use super::*;
288
289    pub fn position_start(_: &hb_font_t, buffer: &mut hb_buffer_t) {
290        let len = buffer.len;
291        for pos in &mut buffer.pos[..len] {
292            pos.set_attach_chain(0);
293            pos.set_attach_type(0);
294        }
295    }
296
297    pub fn position_finish_advances(_: &hb_font_t, _: &mut hb_buffer_t) {}
298
299    pub fn position_finish_offsets(_: &hb_font_t, buffer: &mut hb_buffer_t) {
300        let len = buffer.len;
301        let direction = buffer.direction;
302
303        // Handle attachments
304        if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT != 0 {
305            for i in 0..len {
306                propagate_attachment_offsets(&mut buffer.pos, len, i, direction);
307            }
308        }
309    }
310}