| 1 | use crate::hb::buffer::HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT; |
| 2 | use crate::hb::ot_layout_common::lookup_flags; |
| 3 | use crate::hb::ot_layout_gpos_table::attach_type; |
| 4 | use crate::hb::ot_layout_gpos_table::AnchorExt; |
| 5 | use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t; |
| 6 | use crate::hb::ot_layout_gsubgpos::{skipping_iterator_t, Apply}; |
| 7 | use crate::{Direction, GlyphPosition}; |
| 8 | use ttf_parser::gpos::CursiveAdjustment; |
| 9 | |
| 10 | impl Apply for CursiveAdjustment<'_> { |
| 11 | fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { |
| 12 | let this = ctx.buffer.cur(0).as_glyph(); |
| 13 | |
| 14 | let index_this = self.coverage.get(this)?; |
| 15 | let entry_this = self.sets.entry(index_this)?; |
| 16 | |
| 17 | let mut iter = skipping_iterator_t::new(ctx, ctx.buffer.idx, false); |
| 18 | |
| 19 | let mut unsafe_from = 0; |
| 20 | if !iter.prev(Some(&mut unsafe_from)) { |
| 21 | ctx.buffer |
| 22 | .unsafe_to_concat_from_outbuffer(Some(unsafe_from), Some(ctx.buffer.idx + 1)); |
| 23 | return None; |
| 24 | } |
| 25 | |
| 26 | let i = iter.index(); |
| 27 | let prev = ctx.buffer.info[i].as_glyph(); |
| 28 | let index_prev = self.coverage.get(prev)?; |
| 29 | let Some(exit_prev) = self.sets.exit(index_prev) else { |
| 30 | ctx.buffer |
| 31 | .unsafe_to_concat_from_outbuffer(Some(iter.index()), Some(ctx.buffer.idx + 1)); |
| 32 | return None; |
| 33 | }; |
| 34 | |
| 35 | let (exit_x, exit_y) = exit_prev.get(ctx.face); |
| 36 | let (entry_x, entry_y) = entry_this.get(ctx.face); |
| 37 | |
| 38 | let direction = ctx.buffer.direction; |
| 39 | let j = ctx.buffer.idx; |
| 40 | ctx.buffer.unsafe_to_break(Some(i), Some(j + 1)); |
| 41 | |
| 42 | let pos = &mut ctx.buffer.pos; |
| 43 | match direction { |
| 44 | Direction::LeftToRight => { |
| 45 | pos[i].x_advance = exit_x + pos[i].x_offset; |
| 46 | let d = entry_x + pos[j].x_offset; |
| 47 | pos[j].x_advance -= d; |
| 48 | pos[j].x_offset -= d; |
| 49 | } |
| 50 | Direction::RightToLeft => { |
| 51 | let d = exit_x + pos[i].x_offset; |
| 52 | pos[i].x_advance -= d; |
| 53 | pos[i].x_offset -= d; |
| 54 | pos[j].x_advance = entry_x + pos[j].x_offset; |
| 55 | } |
| 56 | Direction::TopToBottom => { |
| 57 | pos[i].y_advance = exit_y + pos[i].y_offset; |
| 58 | let d = entry_y + pos[j].y_offset; |
| 59 | pos[j].y_advance -= d; |
| 60 | pos[j].y_offset -= d; |
| 61 | } |
| 62 | Direction::BottomToTop => { |
| 63 | let d = exit_y + pos[i].y_offset; |
| 64 | pos[i].y_advance -= d; |
| 65 | pos[i].y_offset -= d; |
| 66 | pos[j].y_advance = entry_y; |
| 67 | } |
| 68 | Direction::Invalid => {} |
| 69 | } |
| 70 | |
| 71 | // Cross-direction adjustment |
| 72 | |
| 73 | // We attach child to parent (think graph theory and rooted trees whereas |
| 74 | // the root stays on baseline and each node aligns itself against its |
| 75 | // parent. |
| 76 | // |
| 77 | // Optimize things for the case of RightToLeft, as that's most common in |
| 78 | // Arabic. |
| 79 | let mut child = i; |
| 80 | let mut parent = j; |
| 81 | let mut x_offset = entry_x - exit_x; |
| 82 | let mut y_offset = entry_y - exit_y; |
| 83 | |
| 84 | // Low bits are lookup flags, so we want to truncate. |
| 85 | if ctx.lookup_props as u16 & lookup_flags::RIGHT_TO_LEFT == 0 { |
| 86 | core::mem::swap(&mut child, &mut parent); |
| 87 | x_offset = -x_offset; |
| 88 | y_offset = -y_offset; |
| 89 | } |
| 90 | |
| 91 | // If child was already connected to someone else, walk through its old |
| 92 | // chain and reverse the link direction, such that the whole tree of its |
| 93 | // previous connection now attaches to new parent. Watch out for case |
| 94 | // where new parent is on the path from old chain... |
| 95 | reverse_cursive_minor_offset(pos, child, direction, parent); |
| 96 | |
| 97 | pos[child].set_attach_type(attach_type::CURSIVE); |
| 98 | pos[child].set_attach_chain((parent as isize - child as isize) as i16); |
| 99 | |
| 100 | ctx.buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT; |
| 101 | if direction.is_horizontal() { |
| 102 | pos[child].y_offset = y_offset; |
| 103 | } else { |
| 104 | pos[child].x_offset = x_offset; |
| 105 | } |
| 106 | |
| 107 | // If parent was attached to child, separate them. |
| 108 | // https://github.com/harfbuzz/harfbuzz/issues/2469 |
| 109 | if pos[parent].attach_chain() == -pos[child].attach_chain() { |
| 110 | pos[parent].set_attach_chain(0); |
| 111 | |
| 112 | if direction.is_horizontal() { |
| 113 | pos[parent].y_offset = 0; |
| 114 | } else { |
| 115 | pos[parent].x_offset = 0; |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | ctx.buffer.idx += 1; |
| 120 | Some(()) |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | fn reverse_cursive_minor_offset( |
| 125 | pos: &mut [GlyphPosition], |
| 126 | i: usize, |
| 127 | direction: Direction, |
| 128 | new_parent: usize, |
| 129 | ) { |
| 130 | let chain = pos[i].attach_chain(); |
| 131 | let attach_type = pos[i].attach_type(); |
| 132 | if chain == 0 || attach_type & attach_type::CURSIVE == 0 { |
| 133 | return; |
| 134 | } |
| 135 | |
| 136 | pos[i].set_attach_chain(0); |
| 137 | |
| 138 | // Stop if we see new parent in the chain. |
| 139 | let j = (i as isize + isize::from(chain)) as _; |
| 140 | if j == new_parent { |
| 141 | return; |
| 142 | } |
| 143 | |
| 144 | reverse_cursive_minor_offset(pos, j, direction, new_parent); |
| 145 | |
| 146 | if direction.is_horizontal() { |
| 147 | pos[j].y_offset = -pos[i].y_offset; |
| 148 | } else { |
| 149 | pos[j].x_offset = -pos[i].x_offset; |
| 150 | } |
| 151 | |
| 152 | pos[j].set_attach_chain(-chain); |
| 153 | pos[j].set_attach_type(attach_type); |
| 154 | } |
| 155 | |