Coverage Report

Created: 2026-09-01 06:21

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