1use ttf_parser::gpos::*;
2use ttf_parser::opentype_layout::LookupIndex;
3use ttf_parser::GlyphId;
4
5use crate::buffer::{Buffer, BufferScratchFlags, GlyphPosition};
6use crate::plan::ShapePlan;
7use crate::{Direction, Face};
8
9use super::apply::{Apply, ApplyContext};
10use super::matching::SkippyIter;
11use super::{
12 lookup_flags, LayoutLookup, LayoutTable, PositioningLookup, PositioningTable, TableIndex,
13};
14
15pub fn position_start(_: &Face, buffer: &mut Buffer) {
16 let len: usize = buffer.len;
17 for pos: &mut GlyphPosition in &mut buffer.pos[..len] {
18 pos.set_attach_chain(0);
19 pos.set_attach_type(0);
20 }
21}
22
23pub fn position(plan: &ShapePlan, face: &Face, buffer: &mut Buffer) {
24 super::apply_layout_table(plan, face, buffer, table:face.gpos.as_ref());
25}
26
27pub fn position_finish_advances(_: &Face, _: &mut Buffer) {}
28
29pub fn position_finish_offsets(_: &Face, buffer: &mut Buffer) {
30 let len: usize = buffer.len;
31 let direction: Direction = buffer.direction;
32
33 // Handle attachments
34 if bufferBufferScratchFlags
35 .scratch_flags
36 .contains(BufferScratchFlags::HAS_GPOS_ATTACHMENT)
37 {
38 for i: usize in 0..len {
39 propagate_attachment_offsets(&mut buffer.pos, len, i, direction);
40 }
41 }
42}
43
44fn propagate_attachment_offsets(
45 pos: &mut [GlyphPosition],
46 len: usize,
47 i: usize,
48 direction: Direction,
49) {
50 // Adjusts offsets of attached glyphs (both cursive and mark) to accumulate
51 // offset of glyph they are attached to.
52 let chain = pos[i].attach_chain();
53 let kind = pos[i].attach_type();
54 if chain == 0 {
55 return;
56 }
57
58 pos[i].set_attach_chain(0);
59
60 let j = (i as isize + isize::from(chain)) as _;
61 if j >= len {
62 return;
63 }
64
65 propagate_attachment_offsets(pos, len, j, direction);
66
67 match kind {
68 attach_type::MARK => {
69 pos[i].x_offset += pos[j].x_offset;
70 pos[i].y_offset += pos[j].y_offset;
71
72 assert!(j < i);
73 if direction.is_forward() {
74 for k in j..i {
75 pos[i].x_offset -= pos[k].x_advance;
76 pos[i].y_offset -= pos[k].y_advance;
77 }
78 } else {
79 for k in j + 1..i + 1 {
80 pos[i].x_offset += pos[k].x_advance;
81 pos[i].y_offset += pos[k].y_advance;
82 }
83 }
84 }
85 attach_type::CURSIVE => {
86 if direction.is_horizontal() {
87 pos[i].y_offset += pos[j].y_offset;
88 } else {
89 pos[i].x_offset += pos[j].x_offset;
90 }
91 }
92 _ => {}
93 }
94}
95
96impl<'a> LayoutTable for PositioningTable<'a> {
97 const INDEX: TableIndex = TableIndex::GPOS;
98 const IN_PLACE: bool = true;
99
100 type Lookup = PositioningLookup<'a>;
101
102 fn get_lookup(&self, index: LookupIndex) -> Option<&Self::Lookup> {
103 self.lookups.get(index:usize::from(index))
104 }
105}
106
107impl LayoutLookup for PositioningLookup<'_> {
108 fn props(&self) -> u32 {
109 self.props
110 }
111
112 fn is_reverse(&self) -> bool {
113 false
114 }
115
116 fn covers(&self, glyph: GlyphId) -> bool {
117 self.coverage.contains(glyph)
118 }
119}
120
121impl Apply for PositioningLookup<'_> {
122 fn apply(&self, ctx: &mut ApplyContext) -> Option<()> {
123 if self.covers(ctx.buffer.cur(0).as_glyph()) {
124 for subtable: &PositioningSubtable<'_> in &self.subtables {
125 if subtable.apply(ctx).is_some() {
126 return Some(());
127 }
128 }
129 }
130
131 None
132 }
133}
134
135impl Apply for PositioningSubtable<'_> {
136 fn apply(&self, ctx: &mut ApplyContext) -> Option<()> {
137 match self {
138 Self::Single(t: &SingleAdjustment<'_>) => t.apply(ctx),
139 Self::Pair(t: &PairAdjustment<'_>) => t.apply(ctx),
140 Self::Cursive(t: &CursiveAdjustment<'_>) => t.apply(ctx),
141 Self::MarkToBase(t: &MarkToBaseAdjustment<'_>) => t.apply(ctx),
142 Self::MarkToLigature(t: &MarkToLigatureAdjustment<'_>) => t.apply(ctx),
143 Self::MarkToMark(t: &MarkToMarkAdjustment<'_>) => t.apply(ctx),
144 Self::Context(t: &ContextLookup<'_>) => t.apply(ctx),
145 Self::ChainContext(t: &ChainedContextLookup<'_>) => t.apply(ctx),
146 }
147 }
148}
149
150impl Apply for SingleAdjustment<'_> {
151 fn apply(&self, ctx: &mut ApplyContext) -> Option<()> {
152 let glyph: GlyphId = ctx.buffer.cur(0).as_glyph();
153 let record: ValueRecord<'_> = match self {
154 Self::Format1 { coverage: &Coverage<'_>, value: &ValueRecord<'_> } => {
155 coverage.get(glyph)?;
156 *value
157 }
158 Self::Format2 { coverage: &Coverage<'_>, values: &ValueRecordsArray<'_> } => {
159 let index: u16 = coverage.get(glyph)?;
160 values.get(index)?
161 }
162 };
163 record.apply(ctx, ctx.buffer.idx);
164 ctx.buffer.idx += 1;
165 Some(())
166 }
167}
168
169impl Apply for PairAdjustment<'_> {
170 fn apply(&self, ctx: &mut ApplyContext) -> Option<()> {
171 let first = ctx.buffer.cur(0).as_glyph();
172 let index = self.coverage().get(first)?;
173
174 let mut iter = SkippyIter::new(ctx, ctx.buffer.idx, 1, false);
175 if !iter.next() {
176 return None;
177 }
178
179 let pos = iter.index();
180 let second = ctx.buffer.info[pos].as_glyph();
181
182 let records = match self {
183 Self::Format1 { sets, .. } => sets.get(index)?.get(second),
184 Self::Format2 {
185 classes, matrix, ..
186 } => {
187 let classes = (classes.0.get(first), classes.1.get(second));
188 matrix.get(classes)
189 }
190 }?;
191
192 let flag1 = records.0.apply(ctx, ctx.buffer.idx);
193 let flag2 = records.1.apply(ctx, pos);
194 // Note the intentional use of "|" instead of short-circuit "||".
195 if flag1 | flag2 {
196 ctx.buffer.unsafe_to_break(ctx.buffer.idx, pos + 1);
197 }
198
199 ctx.buffer.idx = pos + usize::from(flag2);
200 Some(())
201 }
202}
203
204impl Apply for CursiveAdjustment<'_> {
205 fn apply(&self, ctx: &mut ApplyContext) -> Option<()> {
206 let this = ctx.buffer.cur(0).as_glyph();
207
208 let index_this = self.coverage.get(this)?;
209 let entry_this = self.sets.entry(index_this)?;
210
211 let mut iter = SkippyIter::new(ctx, ctx.buffer.idx, 1, false);
212 if !iter.prev() {
213 return None;
214 }
215
216 let i = iter.index();
217 let prev = ctx.buffer.info[i].as_glyph();
218 let index_prev = self.coverage.get(prev)?;
219 let exit_prev = self.sets.exit(index_prev)?;
220
221 let (exit_x, exit_y) = exit_prev.get(ctx.face);
222 let (entry_x, entry_y) = entry_this.get(ctx.face);
223
224 let direction = ctx.buffer.direction;
225 let j = ctx.buffer.idx;
226 ctx.buffer.unsafe_to_break(i, j);
227
228 let pos = &mut ctx.buffer.pos;
229 match direction {
230 Direction::LeftToRight => {
231 pos[i].x_advance = exit_x + pos[i].x_offset;
232 let d = entry_x + pos[j].x_offset;
233 pos[j].x_advance -= d;
234 pos[j].x_offset -= d;
235 }
236 Direction::RightToLeft => {
237 let d = exit_x + pos[i].x_offset;
238 pos[i].x_advance -= d;
239 pos[i].x_offset -= d;
240 pos[j].x_advance = entry_x + pos[j].x_offset;
241 }
242 Direction::TopToBottom => {
243 pos[i].y_advance = exit_y + pos[i].y_offset;
244 let d = entry_y + pos[j].y_offset;
245 pos[j].y_advance -= d;
246 pos[j].y_offset -= d;
247 }
248 Direction::BottomToTop => {
249 let d = exit_y + pos[i].y_offset;
250 pos[i].y_advance -= d;
251 pos[i].y_offset -= d;
252 pos[j].y_advance = entry_y;
253 }
254 Direction::Invalid => {}
255 }
256
257 // Cross-direction adjustment
258
259 // We attach child to parent (think graph theory and rooted trees whereas
260 // the root stays on baseline and each node aligns itself against its
261 // parent.
262 //
263 // Optimize things for the case of RightToLeft, as that's most common in
264 // Arabic.
265 let mut child = i;
266 let mut parent = j;
267 let mut x_offset = entry_x - exit_x;
268 let mut y_offset = entry_y - exit_y;
269
270 // Low bits are lookup flags, so we want to truncate.
271 if ctx.lookup_props as u16 & lookup_flags::RIGHT_TO_LEFT == 0 {
272 core::mem::swap(&mut child, &mut parent);
273 x_offset = -x_offset;
274 y_offset = -y_offset;
275 }
276
277 // If child was already connected to someone else, walk through its old
278 // chain and reverse the link direction, such that the whole tree of its
279 // previous connection now attaches to new parent. Watch out for case
280 // where new parent is on the path from old chain...
281 reverse_cursive_minor_offset(pos, child, direction, parent);
282
283 pos[child].set_attach_type(attach_type::CURSIVE);
284 pos[child].set_attach_chain((parent as isize - child as isize) as i16);
285
286 ctx.buffer.scratch_flags |= BufferScratchFlags::HAS_GPOS_ATTACHMENT;
287 if direction.is_horizontal() {
288 pos[child].y_offset = y_offset;
289 } else {
290 pos[child].x_offset = x_offset;
291 }
292
293 // If parent was attached to child, separate them.
294 // https://github.com/harfbuzz/harfbuzz/issues/2469
295 if pos[parent].attach_chain() == -pos[child].attach_chain() {
296 pos[parent].set_attach_chain(0);
297 }
298
299 ctx.buffer.idx += 1;
300 Some(())
301 }
302}
303
304fn reverse_cursive_minor_offset(
305 pos: &mut [GlyphPosition],
306 i: usize,
307 direction: Direction,
308 new_parent: usize,
309) {
310 let chain = pos[i].attach_chain();
311 let attach_type = pos[i].attach_type();
312 if chain == 0 || attach_type & attach_type::CURSIVE == 0 {
313 return;
314 }
315
316 pos[i].set_attach_chain(0);
317
318 // Stop if we see new parent in the chain.
319 let j = (i as isize + isize::from(chain)) as _;
320 if j == new_parent {
321 return;
322 }
323
324 reverse_cursive_minor_offset(pos, j, direction, new_parent);
325
326 if direction.is_horizontal() {
327 pos[j].y_offset = -pos[i].y_offset;
328 } else {
329 pos[j].x_offset = -pos[i].x_offset;
330 }
331
332 pos[j].set_attach_chain(-chain);
333 pos[j].set_attach_type(attach_type);
334}
335
336impl Apply for MarkToBaseAdjustment<'_> {
337 fn apply(&self, ctx: &mut ApplyContext) -> Option<()> {
338 let buffer = &ctx.buffer;
339 let mark_glyph = ctx.buffer.cur(0).as_glyph();
340 let mark_index = self.mark_coverage.get(mark_glyph)?;
341
342 // Now we search backwards for a non-mark glyph
343 let mut iter = SkippyIter::new(ctx, buffer.idx, 1, false);
344 iter.set_lookup_props(u32::from(lookup_flags::IGNORE_MARKS));
345
346 let info = &buffer.info;
347 loop {
348 if !iter.prev() {
349 return None;
350 }
351
352 // We only want to attach to the first of a MultipleSubst sequence.
353 // https://github.com/harfbuzz/harfbuzz/issues/740
354 // Reject others...
355 // ...but stop if we find a mark in the MultipleSubst sequence:
356 // https://github.com/harfbuzz/harfbuzz/issues/1020
357 let idx = iter.index();
358 if !info[idx].is_multiplied()
359 || info[idx].lig_comp() == 0
360 || idx == 0
361 || info[idx - 1].is_mark()
362 || info[idx].lig_id() != info[idx - 1].lig_id()
363 || info[idx].lig_comp() != info[idx - 1].lig_comp() + 1
364 {
365 break;
366 }
367 iter.reject();
368 }
369
370 // Checking that matched glyph is actually a base glyph by GDEF is too strong; disabled
371
372 let idx = iter.index();
373 let base_glyph = info[idx].as_glyph();
374 let base_index = self.base_coverage.get(base_glyph)?;
375
376 self.marks
377 .apply(ctx, self.anchors, mark_index, base_index, idx)
378 }
379}
380
381impl Apply for MarkToLigatureAdjustment<'_> {
382 fn apply(&self, ctx: &mut ApplyContext) -> Option<()> {
383 let buffer = &ctx.buffer;
384 let mark_glyph = ctx.buffer.cur(0).as_glyph();
385 let mark_index = self.mark_coverage.get(mark_glyph)?;
386
387 // Now we search backwards for a non-mark glyph
388 let mut iter = SkippyIter::new(ctx, buffer.idx, 1, false);
389 iter.set_lookup_props(u32::from(lookup_flags::IGNORE_MARKS));
390 if !iter.prev() {
391 return None;
392 }
393
394 // Checking that matched glyph is actually a ligature by GDEF is too strong; disabled
395
396 let idx = iter.index();
397 let lig_glyph = buffer.info[idx].as_glyph();
398 let lig_index = self.ligature_coverage.get(lig_glyph)?;
399 let lig_attach = self.ligature_array.get(lig_index)?;
400
401 // Find component to attach to
402 let comp_count = lig_attach.rows;
403 if comp_count == 0 {
404 return None;
405 }
406
407 // We must now check whether the ligature ID of the current mark glyph
408 // is identical to the ligature ID of the found ligature. If yes, we
409 // can directly use the component index. If not, we attach the mark
410 // glyph to the last component of the ligature.
411 let lig_id = buffer.info[idx].lig_id();
412 let mark_id = buffer.cur(0).lig_id();
413 let mark_comp = u16::from(buffer.cur(0).lig_comp());
414 let matches = lig_id != 0 && lig_id == mark_id && mark_comp > 0;
415 let comp_index = if matches {
416 mark_comp.min(comp_count)
417 } else {
418 comp_count
419 } - 1;
420
421 self.marks
422 .apply(ctx, lig_attach, mark_index, comp_index, idx)
423 }
424}
425
426impl Apply for MarkToMarkAdjustment<'_> {
427 fn apply(&self, ctx: &mut ApplyContext) -> Option<()> {
428 let buffer = &ctx.buffer;
429 let mark1_glyph = ctx.buffer.cur(0).as_glyph();
430 let mark1_index = self.mark1_coverage.get(mark1_glyph)?;
431
432 // Now we search backwards for a suitable mark glyph until a non-mark glyph
433 let mut iter = SkippyIter::new(ctx, buffer.idx, 1, false);
434 iter.set_lookup_props(ctx.lookup_props & !u32::from(lookup_flags::IGNORE_FLAGS));
435 if !iter.prev() {
436 return None;
437 }
438
439 let idx = iter.index();
440 if !buffer.info[idx].is_mark() {
441 return None;
442 }
443
444 let id1 = buffer.cur(0).lig_id();
445 let id2 = buffer.info[idx].lig_id();
446 let comp1 = buffer.cur(0).lig_comp();
447 let comp2 = buffer.info[idx].lig_comp();
448
449 let matches = if id1 == id2 {
450 // Marks belonging to the same base
451 // or marks belonging to the same ligature component.
452 id1 == 0 || comp1 == comp2
453 } else {
454 // If ligature ids don't match, it may be the case that one of the marks
455 // itself is a ligature. In which case match.
456 (id1 > 0 && comp1 == 0) || (id2 > 0 && comp2 == 0)
457 };
458
459 if !matches {
460 return None;
461 }
462
463 let mark2_glyph = buffer.info[idx].as_glyph();
464 let mark2_index = self.mark2_coverage.get(mark2_glyph)?;
465
466 self.marks
467 .apply(ctx, self.mark2_matrix, mark1_index, mark2_index, idx)
468 }
469}
470
471trait ValueRecordExt {
472 fn apply(&self, ctx: &mut ApplyContext, idx: usize) -> bool;
473}
474
475impl ValueRecordExt for ValueRecord<'_> {
476 fn apply(&self, ctx: &mut ApplyContext, idx: usize) -> bool {
477 let horizontal = ctx.buffer.direction.is_horizontal();
478 let mut pos = ctx.buffer.pos[idx];
479 let mut worked = false;
480
481 if self.x_placement != 0 {
482 pos.x_offset += i32::from(self.x_placement);
483 worked = true;
484 }
485
486 if self.y_placement != 0 {
487 pos.y_offset += i32::from(self.y_placement);
488 worked = true;
489 }
490
491 if self.x_advance != 0 && horizontal {
492 pos.x_advance += i32::from(self.x_advance);
493 worked = true;
494 }
495
496 if self.y_advance != 0 && !horizontal {
497 // y_advance values grow downward but font-space grows upward, hence negation
498 pos.y_advance -= i32::from(self.y_advance);
499 worked = true;
500 }
501
502 {
503 let (ppem_x, ppem_y) = ctx.face.pixels_per_em().unwrap_or((0, 0));
504 let coords = ctx.face.ttfp_face.variation_coordinates().len();
505 let use_x_device = ppem_x != 0 || coords != 0;
506 let use_y_device = ppem_y != 0 || coords != 0;
507
508 if use_x_device {
509 if let Some(device) = self.x_placement_device {
510 pos.x_offset += device.get_x_delta(ctx.face).unwrap_or(0);
511 worked = true; // TODO: even when 0?
512 }
513 }
514
515 if use_y_device {
516 if let Some(device) = self.y_placement_device {
517 pos.y_offset += device.get_y_delta(ctx.face).unwrap_or(0);
518 worked = true;
519 }
520 }
521
522 if horizontal && use_x_device {
523 if let Some(device) = self.x_advance_device {
524 pos.x_advance += device.get_x_delta(ctx.face).unwrap_or(0);
525 worked = true;
526 }
527 }
528
529 if !horizontal && use_y_device {
530 if let Some(device) = self.y_advance_device {
531 // y_advance values grow downward but face-space grows upward, hence negation
532 pos.y_advance -= device.get_y_delta(ctx.face).unwrap_or(0);
533 worked = true;
534 }
535 }
536 }
537
538 ctx.buffer.pos[idx] = pos;
539 worked
540 }
541}
542
543trait MarkArrayExt {
544 fn apply(
545 &self,
546 ctx: &mut ApplyContext,
547 anchors: AnchorMatrix,
548 mark_index: u16,
549 glyph_index: u16,
550 glyph_pos: usize,
551 ) -> Option<()>;
552}
553
554impl MarkArrayExt for MarkArray<'_> {
555 fn apply(
556 &self,
557 ctx: &mut ApplyContext,
558 anchors: AnchorMatrix,
559 mark_index: u16,
560 glyph_index: u16,
561 glyph_pos: usize,
562 ) -> Option<()> {
563 // If this subtable doesn't have an anchor for this base and this class
564 // return `None` such that the subsequent subtables have a chance at it.
565 let (mark_class, mark_anchor) = self.get(mark_index)?;
566 let base_anchor = anchors.get(glyph_index, mark_class)?;
567
568 let (mark_x, mark_y) = mark_anchor.get(ctx.face);
569 let (base_x, base_y) = base_anchor.get(ctx.face);
570
571 ctx.buffer.unsafe_to_break(glyph_pos, ctx.buffer.idx);
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 |= BufferScratchFlags::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
592/// Just like TryFrom<N>, but for numeric types not supported by the Rust's std.
593pub trait TryNumFrom<T>: Sized {
594 /// Casts between numeric types.
595 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 // Based on https://github.com/rust-num/num-traits/blob/master/src/cast.rs
602
603 // Float as int truncates toward zero, so we want to allow values
604 // in the exclusive range `(MIN-1, MAX+1)`.
605
606 // We can't represent `MIN-1` exactly, but there's no fractional part
607 // at this magnitude, so we can just use a `MIN` inclusive boundary.
608 const MIN: f32 = core::i32::MIN as f32;
609 // We can't represent `MAX` exactly, but it will round up to exactly
610 // `MAX+1` (a power of two) when we cast it.
611 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: &Face) -> Option<i32>;
622 fn get_y_delta(&self, face: &Face) -> Option<i32>;
623}
624
625impl DeviceExt for Device<'_> {
626 fn get_x_delta(&self, face: &Face) -> 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(crate::round(float))),
638 }
639 }
640
641 fn get_y_delta(&self, face: &Face) -> 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(crate::round(float))),
653 }
654 }
655}
656
657trait AnchorExt {
658 fn get(&self, face: &Face) -> (i32, i32);
659}
660
661impl AnchorExt for Anchor<'_> {
662 fn get(&self, face: &Face) -> (i32, i32) {
663 let mut x = i32::from(self.x);
664 let mut y = i32::from(self.y);
665
666 if self.x_device.is_some() || self.y_device.is_some() {
667 let (ppem_x, ppem_y) = face.pixels_per_em().unwrap_or((0, 0));
668 let coords = face.ttfp_face.variation_coordinates().len();
669
670 if let Some(device) = self.x_device {
671 if ppem_x != 0 || coords != 0 {
672 x += device.get_x_delta(face).unwrap_or(0);
673 }
674 }
675
676 if let Some(device) = self.y_device {
677 if ppem_y != 0 || coords != 0 {
678 y += device.get_y_delta(face).unwrap_or(0);
679 }
680 }
681 }
682
683 (x, y)
684 }
685}
686