Coverage Report

Created: 2025-10-10 06:25

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
  bool sanitize (graph_t::vertex_t& vertex, unsigned class_count) const
40
0
  {
41
0
    int64_t vertex_len = vertex.obj.tail - vertex.obj.head;
42
0
    if (vertex_len < AnchorMatrix::min_size) return false;
43
0
    hb_barrier ();
44
45
0
    return vertex_len >= AnchorMatrix::min_size +
46
0
        OT::Offset16::static_size * class_count * this->rows;
47
0
  }
48
49
  bool shrink (gsubgpos_graph_context_t& c,
50
               unsigned this_index,
51
               unsigned old_class_count,
52
               unsigned new_class_count)
53
0
  {
54
0
    if (new_class_count >= old_class_count) return false;
55
0
    auto& o = c.graph.vertices_[this_index].obj;
56
0
    unsigned base_count = rows;
57
0
    o.tail = o.head +
58
0
             AnchorMatrix::min_size +
59
0
             OT::Offset16::static_size * base_count * new_class_count;
60
61
    // Reposition links into the new indexing scheme.
62
0
    for (auto& link : o.real_links.writer ())
63
0
    {
64
0
      unsigned index = (link.position - 2) / 2;
65
0
      unsigned base = index / old_class_count;
66
0
      unsigned klass = index % old_class_count;
67
0
      if (klass >= new_class_count)
68
        // should have already been removed
69
0
        return false;
70
71
0
      unsigned new_index = base * new_class_count + klass;
72
73
0
      link.position = (char*) &(this->matrixZ[new_index]) - (char*) this;
74
0
    }
75
76
0
    return true;
77
0
  }
78
79
  unsigned clone (gsubgpos_graph_context_t& c,
80
                  unsigned this_index,
81
                  unsigned start,
82
                  unsigned end,
83
                  unsigned class_count)
84
0
  {
85
0
    unsigned base_count = rows;
86
0
    unsigned new_class_count = end - start;
87
0
    unsigned size = AnchorMatrix::min_size +
88
0
                    OT::Offset16::static_size * new_class_count * rows;
89
0
    unsigned prime_id = c.create_node (size);
90
0
    if (prime_id == (unsigned) -1) return -1;
91
0
    AnchorMatrix* prime = (AnchorMatrix*) c.graph.object (prime_id).head;
92
0
    prime->rows = base_count;
93
94
0
    auto& o = c.graph.vertices_[this_index].obj;
95
0
    int num_links = o.real_links.length;
96
0
    for (int i = 0; i < num_links; i++)
97
0
    {
98
0
      const auto& link = o.real_links[i];
99
0
      unsigned old_index = (link.position - 2) / OT::Offset16::static_size;
100
0
      unsigned klass = old_index % class_count;
101
0
      if (klass < start || klass >= end) continue;
102
103
0
      unsigned base = old_index / class_count;
104
0
      unsigned new_klass = klass - start;
105
0
      unsigned new_index = base * new_class_count + new_klass;
106
107
108
0
      unsigned child_idx = link.objidx;
109
0
      c.graph.add_link (&(prime->matrixZ[new_index]),
110
0
                        prime_id,
111
0
                        child_idx);
112
113
0
      auto& child = c.graph.vertices_[child_idx];
114
0
      child.remove_parent (this_index);
115
116
0
      o.real_links.remove_unordered (i);
117
0
      num_links--;
118
0
      i--;
119
0
    }
120
121
0
    return prime_id;
122
0
  }
123
};
124
125
struct MarkArray : public OT::Layout::GPOS_impl::MarkArray
126
{
127
  bool sanitize (graph_t::vertex_t& vertex) const
128
0
  {
129
0
    int64_t vertex_len = vertex.obj.tail - vertex.obj.head;
130
0
    unsigned min_size = MarkArray::min_size;
131
0
    if (vertex_len < min_size) return false;
132
0
    hb_barrier ();
133
134
0
    return vertex_len >= get_size ();
135
0
  }
136
137
  bool shrink (gsubgpos_graph_context_t& c,
138
               const hb_hashmap_t<unsigned, unsigned>& mark_array_links,
139
               unsigned this_index,
140
               unsigned new_class_count)
141
0
  {
142
0
    auto& o = c.graph.vertices_[this_index].obj;
143
0
    for (const auto& link : o.real_links)
144
0
      c.graph.vertices_[link.objidx].remove_parent (this_index);
145
0
    o.real_links.reset ();
146
147
0
    unsigned new_index = 0;
148
0
    for (const auto& record : this->iter ())
149
0
    {
150
0
      unsigned klass = record.klass;
151
0
      if (klass >= new_class_count) continue;
152
153
0
      (*this)[new_index].klass = klass;
154
0
      unsigned position = (char*) &record.markAnchor - (char*) this;
155
0
      unsigned* objidx;
156
0
      if (!mark_array_links.has (position, &objidx))
157
0
      {
158
0
        new_index++;
159
0
        continue;
160
0
      }
161
162
0
      c.graph.add_link (&(*this)[new_index].markAnchor, this_index, *objidx);
163
0
      new_index++;
164
0
    }
165
166
0
    this->len = new_index;
167
0
    o.tail = o.head + MarkArray::min_size +
168
0
             OT::Layout::GPOS_impl::MarkRecord::static_size * new_index;
169
0
    return true;
170
0
  }
171
172
  unsigned clone (gsubgpos_graph_context_t& c,
173
                  unsigned this_index,
174
                  const hb_hashmap_t<unsigned, unsigned>& pos_to_index,
175
                  hb_set_t& marks,
176
                  unsigned start_class)
177
0
  {
178
0
    unsigned size = MarkArray::min_size +
179
0
                    OT::Layout::GPOS_impl::MarkRecord::static_size *
180
0
                    marks.get_population ();
181
0
    unsigned prime_id = c.create_node (size);
182
0
    if (prime_id == (unsigned) -1) return -1;
183
0
    MarkArray* prime = (MarkArray*) c.graph.object (prime_id).head;
184
0
    prime->len = marks.get_population ();
185
186
187
0
    unsigned i = 0;
188
0
    for (hb_codepoint_t mark : marks)
189
0
    {
190
0
      (*prime)[i].klass = (*this)[mark].klass - start_class;
191
0
      unsigned offset_pos = (char*) &((*this)[mark].markAnchor) - (char*) this;
192
0
      unsigned* anchor_index;
193
0
      if (pos_to_index.has (offset_pos, &anchor_index))
194
0
        c.graph.move_child (this_index,
195
0
                            &((*this)[mark].markAnchor),
196
0
                            prime_id,
197
0
                            &((*prime)[i].markAnchor));
198
199
0
      i++;
200
0
    }
201
202
0
    return prime_id;
203
0
  }
204
};
205
206
struct MarkBasePosFormat1 : public OT::Layout::GPOS_impl::MarkBasePosFormat1_2<SmallTypes>
207
{
208
  bool sanitize (graph_t::vertex_t& vertex) const
209
0
  {
210
0
    int64_t vertex_len = vertex.obj.tail - vertex.obj.head;
211
0
    return vertex_len >= MarkBasePosFormat1::static_size;
212
0
  }
213
214
  hb_vector_t<unsigned> split_subtables (gsubgpos_graph_context_t& c,
215
                                         unsigned parent_index,
216
                                         unsigned this_index)
217
0
  {
218
0
    hb_set_t visited;
219
220
0
    const unsigned base_coverage_id = c.graph.index_for_offset (this_index, &baseCoverage);
221
0
    const unsigned base_size =
222
0
        OT::Layout::GPOS_impl::MarkBasePosFormat1_2<SmallTypes>::min_size +
223
0
        MarkArray::min_size +
224
0
        AnchorMatrix::min_size +
225
0
        c.graph.vertices_[base_coverage_id].table_size ();
226
227
0
    hb_vector_t<class_info_t> class_to_info = get_class_info (c, this_index);
228
229
0
    unsigned class_count = classCount;
230
0
    auto base_array = c.graph.as_table<AnchorMatrix> (this_index,
231
0
                                                      &baseArray,
232
0
                                                      class_count);
233
0
    if (!base_array) return hb_vector_t<unsigned> ();
234
0
    unsigned base_count = base_array.table->rows;
235
236
0
    unsigned partial_coverage_size = 4;
237
0
    unsigned accumulated = base_size;
238
0
    hb_vector_t<unsigned> split_points;
239
240
0
    for (unsigned klass = 0; klass < class_count; klass++)
241
0
    {
242
0
      class_info_t& info = class_to_info[klass];
243
0
      partial_coverage_size += OT::HBUINT16::static_size * info.marks.get_population ();
244
0
      unsigned accumulated_delta =
245
0
          OT::Layout::GPOS_impl::MarkRecord::static_size * info.marks.get_population () +
246
0
          OT::Offset16::static_size * base_count;
247
248
0
      for (unsigned objidx : info.child_indices)
249
0
        accumulated_delta += c.graph.find_subgraph_size (objidx, visited);
250
251
0
      accumulated += accumulated_delta;
252
0
      unsigned total = accumulated + partial_coverage_size;
253
254
0
      if (total >= (1 << 16))
255
0
      {
256
0
        split_points.push (klass);
257
0
        accumulated = base_size + accumulated_delta;
258
0
        partial_coverage_size = 4 + OT::HBUINT16::static_size * info.marks.get_population ();
259
0
        visited.clear (); // node sharing isn't allowed between splits.
260
0
      }
261
0
    }
262
263
264
0
    const unsigned mark_array_id = c.graph.index_for_offset (this_index, &markArray);
265
0
    split_context_t split_context {
266
0
      c,
267
0
      this,
268
0
      c.graph.duplicate_if_shared (parent_index, this_index),
269
0
      std::move (class_to_info),
270
0
      c.graph.vertices_[mark_array_id].position_to_index_map (),
271
0
    };
272
273
0
    return actuate_subtable_split<split_context_t> (split_context, split_points);
274
0
  }
275
276
 private:
277
278
  struct class_info_t {
279
    hb_set_t marks;
280
    hb_vector_t<unsigned> child_indices;
281
  };
282
283
  struct split_context_t {
284
    gsubgpos_graph_context_t& c;
285
    MarkBasePosFormat1* thiz;
286
    unsigned this_index;
287
    hb_vector_t<class_info_t> class_to_info;
288
    hb_hashmap_t<unsigned, unsigned> mark_array_links;
289
290
    hb_set_t marks_for (unsigned start, unsigned end)
291
0
    {
292
0
      hb_set_t marks;
293
0
      for (unsigned klass = start; klass < end; klass++)
294
0
      {
295
0
        + class_to_info[klass].marks.iter ()
296
0
        | hb_sink (marks)
297
0
        ;
298
0
      }
299
0
      return marks;
300
0
    }
301
302
    unsigned original_count ()
303
0
    {
304
0
      return thiz->classCount;
305
0
    }
306
307
    unsigned clone_range (unsigned start, unsigned end)
308
0
    {
309
0
      return thiz->clone_range (*this, this->this_index, start, end);
310
0
    }
311
312
    bool shrink (unsigned count)
313
0
    {
314
0
      return thiz->shrink (*this, this->this_index, count);
315
0
    }
316
  };
317
318
  hb_vector_t<class_info_t> get_class_info (gsubgpos_graph_context_t& c,
319
                                            unsigned this_index)
320
0
  {
321
0
    hb_vector_t<class_info_t> class_to_info;
322
323
0
    unsigned class_count = classCount;
324
0
    if (!class_count) return class_to_info;
325
326
0
    if (!class_to_info.resize (class_count))
327
0
      return hb_vector_t<class_info_t>();
328
329
0
    auto mark_array = c.graph.as_table<MarkArray> (this_index, &markArray);
330
0
    if (!mark_array) return hb_vector_t<class_info_t> ();
331
0
    unsigned mark_count = mark_array.table->len;
332
0
    for (unsigned mark = 0; mark < mark_count; mark++)
333
0
    {
334
0
      unsigned klass = (*mark_array.table)[mark].get_class ();
335
0
      if (klass >= class_count) continue;
336
0
      class_to_info[klass].marks.add (mark);
337
0
    }
338
339
0
    for (const auto& link : mark_array.vertex->obj.real_links)
340
0
    {
341
0
      unsigned mark = (link.position - 2) /
342
0
                     OT::Layout::GPOS_impl::MarkRecord::static_size;
343
0
      unsigned klass = (*mark_array.table)[mark].get_class ();
344
0
      if (klass >= class_count) continue;
345
0
      class_to_info[klass].child_indices.push (link.objidx);
346
0
    }
347
348
0
    unsigned base_array_id =
349
0
        c.graph.index_for_offset (this_index, &baseArray);
350
0
    auto& base_array_v = c.graph.vertices_[base_array_id];
351
352
0
    for (const auto& link : base_array_v.obj.real_links)
353
0
    {
354
0
      unsigned index = (link.position - 2) / OT::Offset16::static_size;
355
0
      unsigned klass = index % class_count;
356
0
      class_to_info[klass].child_indices.push (link.objidx);
357
0
    }
358
359
0
    return class_to_info;
360
0
  }
361
362
  bool shrink (split_context_t& sc,
363
               unsigned this_index,
364
               unsigned count)
365
0
  {
366
0
    DEBUG_MSG (SUBSET_REPACK, nullptr,
367
0
               "  Shrinking MarkBasePosFormat1 (%u) to [0, %u).",
368
0
               this_index,
369
0
               count);
370
371
0
    unsigned old_count = classCount;
372
0
    if (count >= old_count)
373
0
      return true;
374
375
0
    classCount = count;
376
377
0
    auto mark_coverage = sc.c.graph.as_mutable_table<Coverage> (this_index,
378
0
                                                                &markCoverage);
379
0
    if (!mark_coverage) return false;
380
0
    hb_set_t marks = sc.marks_for (0, count);
381
0
    auto new_coverage =
382
0
        + hb_enumerate (mark_coverage.table->iter ())
383
0
        | hb_filter (marks, hb_first)
384
0
        | hb_map_retains_sorting (hb_second)
385
0
        ;
386
0
    if (!Coverage::make_coverage (sc.c, + new_coverage,
387
0
                                  mark_coverage.index,
388
0
                                  4 + 2 * marks.get_population ()))
389
0
      return false;
390
391
392
0
    auto base_array = sc.c.graph.as_mutable_table<AnchorMatrix> (this_index,
393
0
                                                                 &baseArray,
394
0
                                                                 old_count);
395
0
    if (!base_array || !base_array.table->shrink (sc.c,
396
0
                                                  base_array.index,
397
0
                                                  old_count,
398
0
                                                  count))
399
0
      return false;
400
401
0
    auto mark_array = sc.c.graph.as_mutable_table<MarkArray> (this_index,
402
0
                                                              &markArray);
403
0
    if (!mark_array || !mark_array.table->shrink (sc.c,
404
0
                                                  sc.mark_array_links,
405
0
                                                  mark_array.index,
406
0
                                                  count))
407
0
      return false;
408
409
0
    return true;
410
0
  }
411
412
  // Create a new MarkBasePos that has all of the data for classes from [start, end).
413
  unsigned clone_range (split_context_t& sc,
414
                        unsigned this_index,
415
                        unsigned start, unsigned end) const
416
0
  {
417
0
    DEBUG_MSG (SUBSET_REPACK, nullptr,
418
0
               "  Cloning MarkBasePosFormat1 (%u) range [%u, %u).", this_index, start, end);
419
420
0
    graph_t& graph = sc.c.graph;
421
0
    unsigned prime_size = OT::Layout::GPOS_impl::MarkBasePosFormat1_2<SmallTypes>::static_size;
422
423
0
    unsigned prime_id = sc.c.create_node (prime_size);
424
0
    if (prime_id == (unsigned) -1) return -1;
425
426
0
    MarkBasePosFormat1* prime = (MarkBasePosFormat1*) graph.object (prime_id).head;
427
0
    prime->format = this->format;
428
0
    unsigned new_class_count = end - start;
429
0
    prime->classCount = new_class_count;
430
431
0
    unsigned base_coverage_id =
432
0
        graph.index_for_offset (sc.this_index, &baseCoverage);
433
0
    graph.add_link (&(prime->baseCoverage), prime_id, base_coverage_id);
434
0
    graph.duplicate (prime_id, base_coverage_id);
435
436
0
    auto mark_coverage = sc.c.graph.as_table<Coverage> (this_index,
437
0
                                                        &markCoverage);
438
0
    if (!mark_coverage) return false;
439
0
    hb_set_t marks = sc.marks_for (start, end);
440
0
    auto new_coverage =
441
0
        + hb_enumerate (mark_coverage.table->iter ())
442
0
        | hb_filter (marks, hb_first)
443
0
        | hb_map_retains_sorting (hb_second)
444
0
        ;
445
0
    if (!Coverage::add_coverage (sc.c,
446
0
                                 prime_id,
447
0
                                 2,
448
0
                                 + new_coverage,
449
0
                                 marks.get_population () * 2 + 4))
450
0
      return -1;
451
452
0
    auto mark_array =
453
0
        graph.as_table <MarkArray> (sc.this_index, &markArray);
454
0
    if (!mark_array) return -1;
455
0
    unsigned new_mark_array =
456
0
        mark_array.table->clone (sc.c,
457
0
                                 mark_array.index,
458
0
                                 sc.mark_array_links,
459
0
                                 marks,
460
0
                                 start);
461
0
    graph.add_link (&(prime->markArray), prime_id, new_mark_array);
462
463
0
    unsigned class_count = classCount;
464
0
    auto base_array =
465
0
        graph.as_table<AnchorMatrix> (sc.this_index, &baseArray, class_count);
466
0
    if (!base_array) return -1;
467
0
    unsigned new_base_array =
468
0
        base_array.table->clone (sc.c,
469
0
                                 base_array.index,
470
0
                                 start, end, this->classCount);
471
0
    graph.add_link (&(prime->baseArray), prime_id, new_base_array);
472
473
0
    return prime_id;
474
0
  }
475
};
476
477
478
struct MarkBasePos : public OT::Layout::GPOS_impl::MarkBasePos
479
{
480
  hb_vector_t<unsigned> split_subtables (gsubgpos_graph_context_t& c,
481
                                         unsigned parent_index,
482
                                         unsigned this_index)
483
0
  {
484
0
    switch (u.format) {
485
0
    case 1:
486
0
      return ((MarkBasePosFormat1*)(&u.format1))->split_subtables (c, parent_index, this_index);
487
0
#ifndef HB_NO_BEYOND_64K
488
0
    case 2: HB_FALLTHROUGH;
489
      // Don't split 24bit MarkBasePos's.
490
0
#endif
491
0
    default:
492
0
      return hb_vector_t<unsigned> ();
493
0
    }
494
0
  }
495
496
  bool sanitize (graph_t::vertex_t& vertex) const
497
0
  {
498
0
    int64_t vertex_len = vertex.obj.tail - vertex.obj.head;
499
0
    if (vertex_len < u.format.get_size ()) return false;
500
0
    hb_barrier ();
501
502
0
    switch (u.format) {
503
0
    case 1:
504
0
      return ((MarkBasePosFormat1*)(&u.format1))->sanitize (vertex);
505
0
#ifndef HB_NO_BEYOND_64K
506
0
    case 2: HB_FALLTHROUGH;
507
0
#endif
508
0
    default:
509
      // We don't handle format 3 and 4 here.
510
0
      return false;
511
0
    }
512
0
  }
513
};
514
515
516
}
517
518
#endif  // GRAPH_MARKBASEPOS_GRAPH_HH