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, table: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(index: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: &SingleAdjustment<'_>) => t.apply(ctx),
223 Self::Pair(t: &PairAdjustment<'_>) => t.apply(ctx),
224 Self::Cursive(t: &CursiveAdjustment<'_>) => t.apply(ctx),
225 Self::MarkToBase(t: &MarkToBaseAdjustment<'_>) => t.apply(ctx),
226 Self::MarkToLigature(t: &MarkToLigatureAdjustment<'_>) => t.apply(ctx),
227 Self::MarkToMark(t: &MarkToMarkAdjustment<'_>) => t.apply(ctx),
228 Self::Context(t: &ContextLookup<'_>) => t.apply(ctx),
229 Self::ChainContext(t: &ChainedContextLookup<'_>) => 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}
311