Coverage Report

Created: 2026-09-28 08:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/harfbuzz/src/graph/markbasepos-graph.hh
Line
Count
Source
1
/*
2
 * Copyright © 2022  Google, Inc.
3
 *
4
 *  This is part of HarfBuzz, a text shaping library.
5
 *
6
 * Permission is hereby granted, without written agreement and without
7
 * license or royalty fees, to use, copy, modify, and distribute this
8
 * software and its documentation for any purpose, provided that the
9
 * above copyright notice and the following two paragraphs appear in
10
 * all copies of this software.
11
 *
12
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16
 * DAMAGE.
17
 *
18
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
21
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23
 *
24
 * Google Author(s): Garret Rieger
25
 */
26
27
#ifndef GRAPH_MARKBASEPOS_GRAPH_HH
28
#define GRAPH_MARKBASEPOS_GRAPH_HH
29
30
#include "split-helpers.hh"
31
#include "coverage-graph.hh"
32
#include "../OT/Layout/GPOS/MarkBasePos.hh"
33
#include "../OT/Layout/GPOS/PosLookupSubTable.hh"
34
35
namespace graph {
36
37
struct AnchorMatrix : public OT::Layout::GPOS_impl::AnchorMatrix
38
{
39
  graph_result_t<void> sanitize (const graph_t::vertex_t& vertex, unsigned class_count) const
40
0
  {
41
0
    size_t vertex_len = vertex.table_size();
42
0
    if (unlikely (vertex_len < AnchorMatrix::min_size)) return Err(SANITIZE_FAILURE);
43
0
    hb_barrier ();
44
45
0
    if (unlikely (vertex_len < AnchorMatrix::min_size +
46
0
                  OT::Offset16::static_size * class_count * this->rows))
47
0
      return Err(SANITIZE_FAILURE);
48
0
    return Ok();
49
0
  }
50
51
  graph_result_t<void> shrink (gsubgpos_graph_context_t& c,
52
                               unsigned this_index,
53
                               unsigned old_class_count,
54
                               unsigned new_class_count)
55
0
  {
56
0
    if (unlikely (new_class_count >= old_class_count)) return Err(INVALID_ARGUMENT);
57
0
    auto& v = c.graph.vertices_[this_index];
58
0
    const auto& o = v.obj ();
59
0
    unsigned base_count = rows;
60
0
    v.set_buffer (o.head,
61
0
      o.head +
62
0
      AnchorMatrix::min_size +
63
0
      OT::Offset16::static_size * base_count * new_class_count);
64
65
    // Reposition links into the new indexing scheme.
66
0
    for (auto& link : v.real_links_writer ())
67
0
    {
68
0
      unsigned index = (link.position - 2) / 2;
69
0
      unsigned base = index / old_class_count;
70
0
      unsigned klass = index % old_class_count;
71
0
      if (unlikely (klass >= new_class_count))
72
        // should have already been removed
73
0
        return Err(INVALID_ARGUMENT);
74
75
0
      unsigned new_index = base * new_class_count + klass;
76
77
0
      link.position = (char*) &(this->matrixZ[new_index]) - (char*) this;
78
0
    }
79
80
0
    return Ok();
81
0
  }
82
83
  graph_result_t<unsigned> clone (gsubgpos_graph_context_t& c,
84
                                  unsigned this_index,
85
                                  unsigned start,
86
                                  unsigned end,
87
                                  unsigned class_count)
88
0
  {
89
0
    unsigned base_count = rows;
90
0
    unsigned new_class_count = end - start;
91
0
    unsigned size = AnchorMatrix::min_size +
92
0
                    OT::Offset16::static_size * new_class_count * rows;
93
0
    TRY_ASSIGN (unsigned prime_id, c.create_node (size));
94
0
    AnchorMatrix* prime = (AnchorMatrix*) c.graph.object (prime_id).head;
95
0
    prime->rows = base_count;
96
97
0
    auto& v = c.graph.vertices_[this_index];
98
0
    const auto& o = v.obj ();
99
100
0
    int num_links = o.real_links.length;
101
0
    for (int i = 0; i < num_links; i++)
102
0
    {
103
0
      const auto& link = o.real_links[i];
104
0
      unsigned old_index = (link.position - 2) / OT::Offset16::static_size;
105
0
      unsigned klass = old_index % class_count;
106
0
      if (klass < start || klass >= end) continue;
107
108
0
      unsigned base = old_index / class_count;
109
0
      unsigned new_klass = klass - start;
110
0
      unsigned new_index = base * new_class_count + new_klass;
111
112
113
0
      unsigned child_idx = link.objidx;
114
0
      TRY (c.graph.add_link (&(prime->matrixZ[new_index]),
115
0
                             prime_id,
116
0
                             child_idx));
117
118
0
      auto& child = c.graph.vertices_[child_idx];
119
0
      child.remove_parent (this_index);
120
121
0
      v.remove_real_link_unordered (i);
122
0
      num_links--;
123
0
      i--;
124
0
    }
125
126
    // Restore correct ordering after the above removes, which mess up link ordering.
127
0
    v.sort_real_links ();
128
129
0
    return Ok(prime_id);
130
0
  }
131
};
132
133
struct MarkArray : public OT::Layout::GPOS_impl::MarkArray
134
{
135
  graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
136
0
  {
137
0
    size_t vertex_len = vertex.table_size();
138
0
    unsigned min_size = MarkArray::min_size;
139
0
    if (unlikely (vertex_len < min_size)) return Err(SANITIZE_FAILURE);
140
0
    hb_barrier ();
141
142
0
    if (unlikely (vertex_len < get_size ())) return Err(SANITIZE_FAILURE);
143
0
    return Ok();
144
0
  }
145
146
  graph_result_t<void> shrink (gsubgpos_graph_context_t& c,
147
                               const hb_hashmap_t<unsigned, unsigned>& mark_array_links,
148
                               unsigned this_index,
149
                               unsigned new_class_count)
150
0
  {
151
0
    auto& v = c.graph.vertices_[this_index];
152
0
    const auto& o = v.obj ();
153
0
    for (const auto& link : o.real_links)
154
0
      c.graph.vertices_[link.objidx].remove_parent (this_index);
155
0
    v.clear_real_links ();
156
157
0
    unsigned new_index = 0;
158
0
    for (const auto& record : this->iter ())
159
0
    {
160
0
      unsigned klass = record.klass;
161
0
      if (klass >= new_class_count) continue;
162
163
0
      (*this)[new_index].klass = klass;
164
0
      unsigned position = (char*) &record.markAnchor - (char*) this;
165
0
      unsigned* objidx;
166
0
      if (!mark_array_links.has (position, &objidx))
167
0
      {
168
0
        new_index++;
169
0
        continue;
170
0
      }
171
172
0
      TRY (c.graph.add_link (&(*this)[new_index].markAnchor, this_index, *objidx));
173
0
      new_index++;
174
0
    }
175
176
0
    this->len = new_index;
177
0
    v.set_buffer (o.head, o.head + MarkArray::min_size +
178
0
                  OT::Layout::GPOS_impl::MarkRecord::static_size * new_index);
179
180
0
    return Ok();
181
0
  }
182
183
  graph_result_t<unsigned> clone (gsubgpos_graph_context_t& c,
184
                                  unsigned this_index,
185
                                  const hb_hashmap_t<unsigned, unsigned>& pos_to_index,
186
                                  hb_set_t& marks,
187
                                  unsigned start_class)
188
0
  {
189
0
    unsigned size = MarkArray::min_size +
190
0
                    OT::Layout::GPOS_impl::MarkRecord::static_size *
191
0
                    marks.get_population ();
192
0
    TRY_ASSIGN (unsigned prime_id, c.create_node (size));
193
0
    MarkArray* prime = (MarkArray*) c.graph.object (prime_id).head;
194
0
    prime->len = marks.get_population ();
195
196
197
0
    unsigned i = 0;
198
0
    for (hb_codepoint_t mark : marks)
199
0
    {
200
0
      (*prime)[i].klass = (*this)[mark].klass - start_class;
201
0
      unsigned offset_pos = (char*) &((*this)[mark].markAnchor) - (char*) this;
202
0
      unsigned* anchor_index;
203
0
      if (pos_to_index.has (offset_pos, &anchor_index))
204
0
        TRY (c.graph.move_child (this_index,
205
0
                                 &((*this)[mark].markAnchor),
206
0
                                 prime_id,
207
0
                                 &((*prime)[i].markAnchor)));
208
209
0
      i++;
210
0
    }
211
212
    // Restore correct ordering of the vertex links which are disturbed by move_child.
213
0
    c.graph.vertices_[this_index].sort_real_links ();
214
215
0
    return Ok(prime_id);
216
0
  }
217
};
218
219
struct MarkBasePosFormat1 : public OT::Layout::GPOS_impl::MarkBasePosFormat1_2<SmallTypes>
220
{
221
  graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
222
0
  {
223
0
    size_t vertex_len = vertex.table_size ();
224
0
    if (unlikely (vertex_len < MarkBasePosFormat1::static_size))
225
0
      return Err(SANITIZE_FAILURE);
226
0
    return Ok();
227
0
  }
228
229
  graph_result_t<hb_vector_t<unsigned>> split_subtables (gsubgpos_graph_context_t& c,
230
                                                         unsigned this_index)
231
0
  {
232
0
    hb_set_t visited;
233
234
0
    TRY_ASSIGN (const unsigned base_coverage_id, c.graph.index_for_offset (this_index, &baseCoverage));
235
236
0
    const unsigned base_size =
237
0
        OT::Layout::GPOS_impl::MarkBasePosFormat1_2<SmallTypes>::min_size +
238
0
        MarkArray::min_size +
239
0
        AnchorMatrix::min_size +
240
0
        c.graph.vertices_[base_coverage_id].table_size ();
241
242
0
    TRY_ASSIGN (hb_vector_t<class_info_t> class_to_info, get_class_info (c, this_index));
243
244
0
    unsigned class_count = classCount;
245
0
    TRY_ASSIGN (auto base_array, c.graph.as_table<AnchorMatrix> (this_index,
246
0
                                                          &baseArray,
247
0
                                                          class_count));
248
0
    unsigned base_count = base_array.table->rows;
249
250
0
    unsigned partial_coverage_size = 4;
251
0
    unsigned accumulated = base_size;
252
0
    hb_vector_t<unsigned> split_points;
253
254
0
    for (unsigned klass = 0; klass < class_count; klass++)
255
0
    {
256
0
      class_info_t& info = class_to_info[klass];
257
0
      partial_coverage_size += OT::HBUINT16::static_size * info.marks.get_population ();
258
0
      unsigned accumulated_delta =
259
0
          OT::Layout::GPOS_impl::MarkRecord::static_size * info.marks.get_population () +
260
0
          OT::Offset16::static_size * base_count;
261
262
0
      for (unsigned objidx : info.child_indices)
263
0
        accumulated_delta += c.graph.find_subgraph_size (objidx, visited).value_or (0);
264
265
0
      accumulated += accumulated_delta;
266
0
      unsigned total = accumulated + partial_coverage_size;
267
268
0
      if (total >= (1 << 16))
269
0
      {
270
0
        split_points.push (klass);
271
0
        TRY (graph_result_t<void>::from (split_points, ALLOCATION_FAILURE));
272
0
        accumulated = base_size + accumulated_delta;
273
0
        partial_coverage_size = 4 + OT::HBUINT16::static_size * info.marks.get_population ();
274
0
        visited.clear (); // node sharing isn't allowed between splits.
275
0
      }
276
0
    }
277
278
279
0
    TRY_ASSIGN (const unsigned mark_array_id, c.graph.index_for_offset (this_index, &markArray));
280
0
    TRY_ASSIGN (auto mark_array_links, c.graph.vertices_[mark_array_id].position_to_index_map ());
281
282
0
    split_context_t split_context {
283
0
      c,
284
0
      this,
285
0
      this_index,
286
0
      std::move (class_to_info),
287
0
      std::move (mark_array_links),
288
0
    };
289
290
0
    return actuate_subtable_split<split_context_t> (split_context, split_points);
291
0
  }
292
293
 private:
294
295
  struct class_info_t {
296
    hb_set_t marks;
297
    hb_vector_t<unsigned> child_indices;
298
  };
299
300
  struct split_context_t {
301
    gsubgpos_graph_context_t& c;
302
    MarkBasePosFormat1* thiz;
303
    unsigned this_index;
304
    hb_vector_t<class_info_t> class_to_info;
305
    hb_hashmap_t<unsigned, unsigned> mark_array_links;
306
307
    hb_set_t marks_for (unsigned start, unsigned end)
308
0
    {
309
0
      hb_set_t marks;
310
0
      for (unsigned klass = start; klass < end; klass++)
311
0
      {
312
0
        + class_to_info[klass].marks.iter ()
313
0
        | hb_sink (marks)
314
0
        ;
315
0
      }
316
0
      return marks;
317
0
    }
318
319
    unsigned original_count ()
320
0
    {
321
0
      return thiz->classCount;
322
0
    }
323
324
    graph_result_t<unsigned> clone_range (unsigned start, unsigned end)
325
0
    {
326
0
      return thiz->clone_range (*this, this->this_index, start, end);
327
0
    }
328
329
    graph_result_t<void> shrink (unsigned count)
330
0
    {
331
0
      return thiz->shrink (*this, this->this_index, count);
332
0
    }
333
  };
334
335
  graph_result_t<hb_vector_t<class_info_t>> get_class_info (gsubgpos_graph_context_t& c,
336
                                                            unsigned this_index)
337
0
  {
338
0
    hb_vector_t<class_info_t> class_to_info;
339
340
0
    unsigned class_count = classCount;
341
0
    if (!class_count) return class_to_info;
342
343
0
    if (unlikely (!class_to_info.resize (class_count)))
344
0
      return Err(ALLOCATION_FAILURE);
345
346
0
    TRY_ASSIGN (auto mark_array, c.graph.as_table<MarkArray> (this_index, &markArray));
347
348
0
    unsigned mark_count = mark_array.table->len;
349
0
    for (unsigned mark = 0; mark < mark_count; mark++)
350
0
    {
351
0
      unsigned klass = (*mark_array.table)[mark].get_class ();
352
0
      if (klass >= class_count) continue;
353
0
      class_to_info[klass].marks.add (mark);
354
0
    }
355
356
0
    for (const auto& link : mark_array.vertex->obj ().real_links)
357
0
    {
358
0
      unsigned mark = (link.position - 2) /
359
0
                     OT::Layout::GPOS_impl::MarkRecord::static_size;
360
0
      unsigned klass = (*mark_array.table)[mark].get_class ();
361
0
      if (klass >= class_count) continue;
362
0
      class_to_info[klass].child_indices.push (link.objidx);
363
0
    }
364
365
0
    TRY_ASSIGN (unsigned base_array_id,
366
0
         c.graph.index_for_offset (this_index, &baseArray));
367
368
0
    auto& base_array_v = c.graph.vertices_[base_array_id];
369
370
0
    for (const auto& link : base_array_v.obj ().real_links)
371
0
    {
372
0
      unsigned index = (link.position - 2) / OT::Offset16::static_size;
373
0
      unsigned klass = index % class_count;
374
0
      class_to_info[klass].child_indices.push (link.objidx);
375
0
    }
376
377
0
    return class_to_info;
378
0
  }
379
380
  graph_result_t<void> shrink (split_context_t& sc,
381
                               unsigned this_index,
382
                               unsigned count)
383
0
  {
384
0
    DEBUG_MSG (SUBSET_REPACK, nullptr,
385
0
               "  Shrinking MarkBasePosFormat1 (%u) to [0, %u).",
386
0
               this_index,
387
0
               count);
388
389
0
    unsigned old_count = classCount;
390
0
    if (count >= old_count)
391
0
      return Ok();
392
393
0
    classCount = count;
394
395
0
    TRY_ASSIGN (auto mark_coverage, sc.c.graph.as_mutable_table<Coverage> (this_index,
396
0
                                                                    &markCoverage));
397
398
0
    hb_set_t marks = sc.marks_for (0, count);
399
0
    auto new_coverage =
400
0
        + hb_enumerate (mark_coverage.table->iter ())
401
0
        | hb_filter (marks, hb_first)
402
0
        | hb_map_retains_sorting (hb_second)
403
0
        ;
404
0
    TRY (Coverage::make_coverage (sc.c, + new_coverage,
405
0
                                  mark_coverage.index,
406
0
                                  4 + 2 * marks.get_population ()));
407
408
409
0
    TRY_ASSIGN (auto base_array, sc.c.graph.as_mutable_table<AnchorMatrix> (this_index,
410
0
                                                                     &baseArray,
411
0
                                                                     old_count));
412
413
0
    TRY (base_array.table->shrink (sc.c,
414
0
                                   base_array.index,
415
0
                                   old_count,
416
0
                                   count));
417
418
0
    TRY_ASSIGN (auto mark_array, sc.c.graph.as_mutable_table<MarkArray> (this_index,
419
0
                                                                  &markArray));
420
421
0
    TRY (mark_array.table->shrink (sc.c,
422
0
                                   sc.mark_array_links,
423
0
                                   mark_array.index,
424
0
                                   count));
425
426
0
    return Ok();
427
0
  }
428
429
  // Create a new MarkBasePos that has all of the data for classes from [start, end).
430
  graph_result_t<unsigned> clone_range (split_context_t& sc,
431
                                        unsigned this_index,
432
                                        unsigned start, unsigned end) const
433
0
  {
434
0
    DEBUG_MSG (SUBSET_REPACK, nullptr,
435
0
               "  Cloning MarkBasePosFormat1 (%u) range [%u, %u).", this_index, start, end);
436
437
0
    graph_t& graph = sc.c.graph;
438
0
    unsigned prime_size = OT::Layout::GPOS_impl::MarkBasePosFormat1_2<SmallTypes>::static_size;
439
440
0
    TRY_ASSIGN (unsigned prime_id, sc.c.create_node (prime_size));
441
442
0
    MarkBasePosFormat1* prime = (MarkBasePosFormat1*) graph.object (prime_id).head;
443
0
    prime->format = this->format;
444
0
    unsigned new_class_count = end - start;
445
0
    prime->classCount = new_class_count;
446
447
0
    TRY_ASSIGN (unsigned base_coverage_id,
448
0
         graph.index_for_offset (sc.this_index, &baseCoverage));
449
450
0
    TRY (graph.add_link (&(prime->baseCoverage), prime_id, base_coverage_id));
451
452
0
    TRY_ASSIGN (auto mark_coverage, sc.c.graph.as_table<Coverage> (this_index,
453
0
                                                            &markCoverage));
454
455
0
    hb_set_t marks = sc.marks_for (start, end);
456
0
    auto new_coverage =
457
0
        + hb_enumerate (mark_coverage.table->iter ())
458
0
        | hb_filter (marks, hb_first)
459
0
        | hb_map_retains_sorting (hb_second)
460
0
        ;
461
0
    TRY (Coverage::add_coverage (sc.c,
462
0
                                 prime_id,
463
0
                                 2,
464
0
                                 + new_coverage,
465
0
                                 marks.get_population () * 2 + 4));
466
467
0
    TRY_ASSIGN (auto mark_array,
468
0
         graph.as_mutable_table <MarkArray> (sc.this_index, &markArray));
469
470
0
    TRY_ASSIGN (unsigned new_mark_array,
471
0
         mark_array.table->clone (sc.c,
472
0
                                  mark_array.index,
473
0
                                  sc.mark_array_links,
474
0
                                  marks,
475
0
                                  start));
476
0
    TRY (graph.add_link (&(prime->markArray), prime_id, new_mark_array));
477
478
0
    unsigned class_count = classCount;
479
0
    TRY_ASSIGN (auto base_array,
480
0
         graph.as_mutable_table<AnchorMatrix> (sc.this_index, &baseArray, class_count));
481
482
0
    TRY_ASSIGN (unsigned new_base_array,
483
0
         base_array.table->clone (sc.c,
484
0
                                  base_array.index,
485
0
                                  start, end, this->classCount));
486
0
    TRY (graph.add_link (&(prime->baseArray), prime_id, new_base_array));
487
488
0
    return Ok(prime_id);
489
0
  }
490
};
491
492
493
struct MarkBasePos : public OT::Layout::GPOS_impl::MarkBasePos
494
{
495
  graph_result_t<hb_vector_t<unsigned>> split_subtables (gsubgpos_graph_context_t& c,
496
                                                         unsigned this_index)
497
0
  {
498
0
    switch (u.format.v) {
499
0
    case 1:
500
0
      hb_barrier ();
501
0
      return ((MarkBasePosFormat1*)(&u.format1))->split_subtables (c, this_index);
502
#ifndef HB_NO_BEYOND_64K
503
    case 2: HB_FALLTHROUGH;
504
      // Don't split 24bit MarkBasePos's.
505
#endif
506
0
    default:
507
0
      return Ok(hb_vector_t<unsigned> ());
508
0
    }
509
0
  }
510
511
  graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
512
0
  {
513
0
    size_t vertex_len = vertex.table_size ();
514
0
    if (unlikely (vertex_len < u.format.v.get_size ())) return Err(SANITIZE_FAILURE);
515
0
    hb_barrier ();
516
517
0
    switch (u.format.v) {
518
0
    case 1:
519
0
      hb_barrier ();
520
0
      return ((MarkBasePosFormat1*)(&u.format1))->sanitize (vertex);
521
#ifndef HB_NO_BEYOND_64K
522
    case 2: HB_FALLTHROUGH;
523
#endif
524
0
    default:
525
      // We don't handle format 3 and 4 here.
526
0
      return Err(SANITIZE_FAILURE);
527
0
    }
528
0
  }
529
};
530
531
532
}
533
534
#endif  // GRAPH_MARKBASEPOS_GRAPH_HH