Coverage Report

Created: 2025-11-04 06:44

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