Coverage Report

Created: 2026-03-30 06:34

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