| 1 | #ifndef OT_LAYOUT_GPOS_CURSIVEPOSFORMAT1_HH |
| 2 | #define OT_LAYOUT_GPOS_CURSIVEPOSFORMAT1_HH |
| 3 | |
| 4 | #include "Anchor.hh" |
| 5 | |
| 6 | namespace OT { |
| 7 | namespace Layout { |
| 8 | namespace GPOS_impl { |
| 9 | |
| 10 | struct EntryExitRecord |
| 11 | { |
| 12 | friend struct CursivePosFormat1; |
| 13 | |
| 14 | bool sanitize (hb_sanitize_context_t *c, const void *base) const |
| 15 | { |
| 16 | TRACE_SANITIZE (this); |
| 17 | return_trace (entryAnchor.sanitize (c, base) && exitAnchor.sanitize (c, base)); |
| 18 | } |
| 19 | |
| 20 | void collect_variation_indices (hb_collect_variation_indices_context_t *c, |
| 21 | const void *src_base) const |
| 22 | { |
| 23 | (src_base+entryAnchor).collect_variation_indices (c); |
| 24 | (src_base+exitAnchor).collect_variation_indices (c); |
| 25 | } |
| 26 | |
| 27 | EntryExitRecord* subset (hb_subset_context_t *c, |
| 28 | const void *src_base) const |
| 29 | { |
| 30 | TRACE_SERIALIZE (this); |
| 31 | auto *out = c->serializer->embed (this); |
| 32 | if (unlikely (!out)) return_trace (nullptr); |
| 33 | |
| 34 | out->entryAnchor.serialize_subset (c, entryAnchor, src_base); |
| 35 | out->exitAnchor.serialize_subset (c, exitAnchor, src_base); |
| 36 | return_trace (out); |
| 37 | } |
| 38 | |
| 39 | protected: |
| 40 | Offset16To<Anchor> |
| 41 | entryAnchor; /* Offset to EntryAnchor table--from |
| 42 | * beginning of CursivePos |
| 43 | * subtable--may be NULL */ |
| 44 | Offset16To<Anchor> |
| 45 | exitAnchor; /* Offset to ExitAnchor table--from |
| 46 | * beginning of CursivePos |
| 47 | * subtable--may be NULL */ |
| 48 | public: |
| 49 | DEFINE_SIZE_STATIC (4); |
| 50 | }; |
| 51 | |
| 52 | static void |
| 53 | reverse_cursive_minor_offset (hb_glyph_position_t *pos, unsigned int i, hb_direction_t direction, unsigned int new_parent) { |
| 54 | int chain = pos[i].attach_chain(), type = pos[i].attach_type(); |
| 55 | if (likely (!chain || 0 == (type & ATTACH_TYPE_CURSIVE))) |
| 56 | return; |
| 57 | |
| 58 | pos[i].attach_chain() = 0; |
| 59 | |
| 60 | unsigned int j = (int) i + chain; |
| 61 | |
| 62 | /* Stop if we see new parent in the chain. */ |
| 63 | if (j == new_parent) |
| 64 | return; |
| 65 | |
| 66 | reverse_cursive_minor_offset (pos, j, direction, new_parent); |
| 67 | |
| 68 | if (HB_DIRECTION_IS_HORIZONTAL (direction)) |
| 69 | pos[j].y_offset = -pos[i].y_offset; |
| 70 | else |
| 71 | pos[j].x_offset = -pos[i].x_offset; |
| 72 | |
| 73 | pos[j].attach_chain() = -chain; |
| 74 | pos[j].attach_type() = type; |
| 75 | } |
| 76 | |
| 77 | |
| 78 | struct CursivePosFormat1 |
| 79 | { |
| 80 | protected: |
| 81 | HBUINT16 format; /* Format identifier--format = 1 */ |
| 82 | Offset16To<Coverage> |
| 83 | coverage; /* Offset to Coverage table--from |
| 84 | * beginning of subtable */ |
| 85 | Array16Of<EntryExitRecord> |
| 86 | entryExitRecord; /* Array of EntryExit records--in |
| 87 | * Coverage Index order */ |
| 88 | public: |
| 89 | DEFINE_SIZE_ARRAY (6, entryExitRecord); |
| 90 | |
| 91 | bool sanitize (hb_sanitize_context_t *c) const |
| 92 | { |
| 93 | TRACE_SANITIZE (this); |
| 94 | if (unlikely (!coverage.sanitize (c, this))) |
| 95 | return_trace (false); |
| 96 | |
| 97 | if (c->lazy_some_gpos) |
| 98 | return_trace (entryExitRecord.sanitize_shallow (c)); |
| 99 | else |
| 100 | return_trace (entryExitRecord.sanitize (c, this)); |
| 101 | } |
| 102 | |
| 103 | bool intersects (const hb_set_t *glyphs) const |
| 104 | { return (this+coverage).intersects (glyphs); } |
| 105 | |
| 106 | void closure_lookups (hb_closure_lookups_context_t *c) const {} |
| 107 | |
| 108 | void collect_variation_indices (hb_collect_variation_indices_context_t *c) const |
| 109 | { |
| 110 | + hb_zip (this+coverage, entryExitRecord) |
| 111 | | hb_filter (c->glyph_set, hb_first) |
| 112 | | hb_map (hb_second) |
| 113 | | hb_apply ([&] (const EntryExitRecord& record) { record.collect_variation_indices (c, this); }) |
| 114 | ; |
| 115 | } |
| 116 | |
| 117 | void collect_glyphs (hb_collect_glyphs_context_t *c) const |
| 118 | { if (unlikely (!(this+coverage).collect_coverage (c->input))) return; } |
| 119 | |
| 120 | const Coverage &get_coverage () const { return this+coverage; } |
| 121 | |
| 122 | bool apply (hb_ot_apply_context_t *c) const |
| 123 | { |
| 124 | TRACE_APPLY (this); |
| 125 | hb_buffer_t *buffer = c->buffer; |
| 126 | |
| 127 | const EntryExitRecord &this_record = entryExitRecord[(this+coverage).get_coverage (buffer->cur().codepoint)]; |
| 128 | if (!this_record.entryAnchor || |
| 129 | unlikely (!this_record.entryAnchor.sanitize (&c->sanitizer, this))) return_trace (false); |
| 130 | |
| 131 | hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input; |
| 132 | skippy_iter.reset_fast (buffer->idx); |
| 133 | unsigned unsafe_from; |
| 134 | if (unlikely (!skippy_iter.prev (&unsafe_from))) |
| 135 | { |
| 136 | buffer->unsafe_to_concat_from_outbuffer (unsafe_from, buffer->idx + 1); |
| 137 | return_trace (false); |
| 138 | } |
| 139 | |
| 140 | const EntryExitRecord &prev_record = entryExitRecord[(this+coverage).get_coverage (buffer->info[skippy_iter.idx].codepoint)]; |
| 141 | if (!prev_record.exitAnchor || |
| 142 | unlikely (!prev_record.exitAnchor.sanitize (&c->sanitizer, this))) |
| 143 | { |
| 144 | buffer->unsafe_to_concat_from_outbuffer (skippy_iter.idx, buffer->idx + 1); |
| 145 | return_trace (false); |
| 146 | } |
| 147 | |
| 148 | unsigned int i = skippy_iter.idx; |
| 149 | unsigned int j = buffer->idx; |
| 150 | |
| 151 | if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ()) |
| 152 | { |
| 153 | c->buffer->message (c->font, |
| 154 | "cursive attaching glyph at %u to glyph at %u" , |
| 155 | i, j); |
| 156 | } |
| 157 | |
| 158 | buffer->unsafe_to_break (i, j + 1); |
| 159 | float entry_x, entry_y, exit_x, exit_y; |
| 160 | (this+prev_record.exitAnchor).get_anchor (c, buffer->info[i].codepoint, &exit_x, &exit_y); |
| 161 | (this+this_record.entryAnchor).get_anchor (c, buffer->info[j].codepoint, &entry_x, &entry_y); |
| 162 | |
| 163 | hb_glyph_position_t *pos = buffer->pos; |
| 164 | |
| 165 | hb_position_t d; |
| 166 | /* Main-direction adjustment */ |
| 167 | switch (c->direction) { |
| 168 | case HB_DIRECTION_LTR: |
| 169 | pos[i].x_advance = roundf (exit_x) + pos[i].x_offset; |
| 170 | |
| 171 | d = roundf (entry_x) + pos[j].x_offset; |
| 172 | pos[j].x_advance -= d; |
| 173 | pos[j].x_offset -= d; |
| 174 | break; |
| 175 | case HB_DIRECTION_RTL: |
| 176 | d = roundf (exit_x) + pos[i].x_offset; |
| 177 | pos[i].x_advance -= d; |
| 178 | pos[i].x_offset -= d; |
| 179 | |
| 180 | pos[j].x_advance = roundf (entry_x) + pos[j].x_offset; |
| 181 | break; |
| 182 | case HB_DIRECTION_TTB: |
| 183 | pos[i].y_advance = roundf (exit_y) + pos[i].y_offset; |
| 184 | |
| 185 | d = roundf (entry_y) + pos[j].y_offset; |
| 186 | pos[j].y_advance -= d; |
| 187 | pos[j].y_offset -= d; |
| 188 | break; |
| 189 | case HB_DIRECTION_BTT: |
| 190 | d = roundf (exit_y) + pos[i].y_offset; |
| 191 | pos[i].y_advance -= d; |
| 192 | pos[i].y_offset -= d; |
| 193 | |
| 194 | pos[j].y_advance = roundf (entry_y); |
| 195 | break; |
| 196 | case HB_DIRECTION_INVALID: |
| 197 | default: |
| 198 | break; |
| 199 | } |
| 200 | |
| 201 | /* Cross-direction adjustment */ |
| 202 | |
| 203 | /* We attach child to parent (think graph theory and rooted trees whereas |
| 204 | * the root stays on baseline and each node aligns itself against its |
| 205 | * parent. |
| 206 | * |
| 207 | * Optimize things for the case of RightToLeft, as that's most common in |
| 208 | * Arabic. */ |
| 209 | unsigned int child = i; |
| 210 | unsigned int parent = j; |
| 211 | hb_position_t x_offset = roundf (entry_x - exit_x); |
| 212 | hb_position_t y_offset = roundf (entry_y - exit_y); |
| 213 | if (!(c->lookup_props & LookupFlag::RightToLeft)) |
| 214 | { |
| 215 | unsigned int k = child; |
| 216 | child = parent; |
| 217 | parent = k; |
| 218 | x_offset = -x_offset; |
| 219 | y_offset = -y_offset; |
| 220 | } |
| 221 | |
| 222 | /* If child was already connected to someone else, walk through its old |
| 223 | * chain and reverse the link direction, such that the whole tree of its |
| 224 | * previous connection now attaches to new parent. Watch out for case |
| 225 | * where new parent is on the path from old chain... |
| 226 | */ |
| 227 | reverse_cursive_minor_offset (pos, child, c->direction, parent); |
| 228 | |
| 229 | pos[child].attach_type() = ATTACH_TYPE_CURSIVE; |
| 230 | pos[child].attach_chain() = (int) parent - (int) child; |
| 231 | buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT; |
| 232 | if (likely (HB_DIRECTION_IS_HORIZONTAL (c->direction))) |
| 233 | pos[child].y_offset = y_offset; |
| 234 | else |
| 235 | pos[child].x_offset = x_offset; |
| 236 | |
| 237 | /* If parent was attached to child, separate them. |
| 238 | * https://github.com/harfbuzz/harfbuzz/issues/2469 |
| 239 | */ |
| 240 | if (unlikely (pos[parent].attach_chain() == -pos[child].attach_chain())) |
| 241 | { |
| 242 | pos[parent].attach_chain() = 0; |
| 243 | if (likely (HB_DIRECTION_IS_HORIZONTAL (c->direction))) |
| 244 | pos[parent].y_offset = 0; |
| 245 | else |
| 246 | pos[parent].x_offset = 0; |
| 247 | } |
| 248 | |
| 249 | if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ()) |
| 250 | { |
| 251 | c->buffer->message (c->font, |
| 252 | "cursive attached glyph at %u to glyph at %u" , |
| 253 | i, j); |
| 254 | } |
| 255 | |
| 256 | buffer->idx++; |
| 257 | return_trace (true); |
| 258 | } |
| 259 | |
| 260 | template <typename Iterator, |
| 261 | hb_requires (hb_is_iterator (Iterator))> |
| 262 | void serialize (hb_subset_context_t *c, |
| 263 | Iterator it, |
| 264 | const void *src_base) |
| 265 | { |
| 266 | if (unlikely (!c->serializer->extend_min ((*this)))) return; |
| 267 | this->format = 1; |
| 268 | this->entryExitRecord.len = it.len (); |
| 269 | |
| 270 | for (const EntryExitRecord& entry_record : + it |
| 271 | | hb_map (hb_second)) |
| 272 | entry_record.subset (c, src_base); |
| 273 | |
| 274 | auto glyphs = |
| 275 | + it |
| 276 | | hb_map_retains_sorting (hb_first) |
| 277 | ; |
| 278 | |
| 279 | coverage.serialize_serialize (c->serializer, glyphs); |
| 280 | } |
| 281 | |
| 282 | bool subset (hb_subset_context_t *c) const |
| 283 | { |
| 284 | TRACE_SUBSET (this); |
| 285 | const hb_set_t &glyphset = *c->plan->glyphset_gsub (); |
| 286 | const hb_map_t &glyph_map = *c->plan->glyph_map; |
| 287 | |
| 288 | auto *out = c->serializer->start_embed (*this); |
| 289 | |
| 290 | auto it = |
| 291 | + hb_zip (this+coverage, entryExitRecord) |
| 292 | | hb_filter (glyphset, hb_first) |
| 293 | | hb_map_retains_sorting ([&] (hb_pair_t<hb_codepoint_t, const EntryExitRecord&> p) -> hb_pair_t<hb_codepoint_t, const EntryExitRecord&> |
| 294 | { return hb_pair (glyph_map[p.first], p.second);}) |
| 295 | ; |
| 296 | |
| 297 | bool ret = bool (it); |
| 298 | out->serialize (c, it, this); |
| 299 | return_trace (ret); |
| 300 | } |
| 301 | }; |
| 302 | |
| 303 | |
| 304 | } |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | #endif /* OT_LAYOUT_GPOS_CURSIVEPOSFORMAT1_HH */ |
| 309 | |