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 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; }
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 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 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 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 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 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 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 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 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 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 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 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 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 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 id1 == 0 || comp1 == comp2
522 } else {
523 (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 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
592pub trait TryNumFrom<T>: Sized {
594 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 const MIN: f32 = core::i32::MIN as f32;
609 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 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 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 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}