Coverage Report

Created: 2025-07-11 06:34

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