Coverage Report

Created: 2026-11-10 07:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/harfbuzz/src/graph/gsubgpos-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
#include "graph.hh"
28
#include "../hb-ot-layout-gsubgpos.hh"
29
#include "../OT/Layout/GSUB/ExtensionSubst.hh"
30
#include "../OT/Layout/GSUB/SubstLookupSubTable.hh"
31
#include "gsubgpos-context.hh"
32
#include "pairpos-graph.hh"
33
#include "markbasepos-graph.hh"
34
#include "ligature-graph.hh"
35
36
#ifndef GRAPH_GSUBGPOS_GRAPH_HH
37
#define GRAPH_GSUBGPOS_GRAPH_HH
38
39
namespace graph {
40
41
struct Lookup;
42
43
template<typename T>
44
struct ExtensionFormat1 : public OT::ExtensionFormat1<T>
45
{
46
  void reset(unsigned type)
47
0
  {
48
0
    this->format = 1;
49
0
    this->extensionLookupType = type;
50
0
    this->extensionOffset = 0;
51
0
  }
52
53
  graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
54
0
  {
55
0
    size_t vertex_len = vertex.table_size ();
56
0
    if (unlikely (vertex_len < OT::ExtensionFormat1<T>::static_size))
57
0
      return Err(SANITIZE_FAILURE);
58
0
    return Ok();
59
0
  }
60
61
  unsigned get_lookup_type () const
62
0
  {
63
0
    return this->extensionLookupType;
64
0
  }
65
66
  graph_result_t<unsigned> get_subtable_index (graph_t& graph, unsigned this_index) const
67
0
  {
68
0
    return graph.index_for_offset (this_index, &this->extensionOffset);
69
0
  }
70
};
71
72
struct Lookup : public OT::Lookup
73
{
74
  unsigned number_of_subtables () const
75
0
  {
76
0
    return subTable.len;
77
0
  }
78
79
  graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
80
0
  {
81
0
    size_t vertex_len = vertex.table_size ();
82
0
    if (unlikely (vertex_len < OT::Lookup::min_size)) return Err(SANITIZE_FAILURE);
83
0
    hb_barrier ();
84
0
    if (unlikely (vertex_len < this->get_size ())) return Err(SANITIZE_FAILURE);
85
0
    return Ok();
86
0
  }
87
88
  bool is_extension (hb_tag_t table_tag) const
89
0
  {
90
0
    return lookupType == extension_type (table_tag);
91
0
  }
92
93
  bool use_mark_filtering_set () const
94
0
  {
95
0
    unsigned flag = lookupFlag;
96
0
    return flag & 0x0010u;
97
0
  }
98
99
  graph_result_t<void> make_extension (gsubgpos_graph_context_t& c,
100
                                       unsigned this_index)
101
0
  {
102
0
    unsigned type = lookupType;
103
0
    unsigned ext_type = extension_type (c.table_tag);
104
0
    if (!ext_type || is_extension (c.table_tag))
105
0
    {
106
      // NOOP
107
0
      return Ok();
108
0
    }
109
110
0
    DEBUG_MSG (SUBSET_REPACK, nullptr,
111
0
               "Promoting lookup type %u (obj %u) to extension.",
112
0
               type,
113
0
               this_index);
114
115
0
    hb_set_t seen;
116
0
    hb_vector_t<unsigned> subtables;
117
0
    for (unsigned i = 0; i < subTable.len; i++)
118
0
    {
119
0
      TRY_ASSIGN (unsigned subtable_index, c.graph.index_for_offset (this_index, &subTable[i]));
120
0
      if (seen.has (subtable_index))
121
0
        continue;
122
0
      seen.add (subtable_index);
123
0
      subtables.push (subtable_index);
124
0
    }
125
0
    TRY (graph_result_t<void>::from (seen, ALLOCATION_FAILURE));
126
0
    TRY (graph_result_t<void>::from (subtables, ALLOCATION_FAILURE));
127
128
0
    for (unsigned subtable_index : subtables)
129
0
      TRY (make_subtable_extension (c, this_index, subtable_index));
130
131
0
    lookupType = ext_type;
132
0
    return Ok();
133
0
  }
134
135
  graph_result_t<void> split_subtables_if_needed (gsubgpos_graph_context_t& c,
136
                                                  unsigned this_index)
137
0
  {
138
0
    unsigned type = lookupType;
139
0
    bool is_ext = is_extension (c.table_tag);
140
141
0
    if (c.table_tag != HB_OT_TAG_GPOS && c.table_tag != HB_OT_TAG_GSUB)
142
0
      return Ok();
143
144
0
    if (!is_ext && !is_supported_gpos_type(type, c) && !is_supported_gsub_type(type, c))
145
0
      return Ok();
146
147
0
    hb_vector_t<hb_pair_t<unsigned, hb_vector_t<unsigned>>> all_new_subtables;
148
0
    for (unsigned i = 0; i < subTable.len; i++)
149
0
    {
150
      // This assumes we are working with something like a GSUB/GPOS table, if
151
      // sanitization fails we ignore and skip splitting for the particular sub table.
152
0
      auto idx_res = c.graph.index_for_offset (this_index, &subTable[i]);
153
0
      if (!idx_res.is_ok ()) continue;
154
0
      unsigned subtable_index = *idx_res;
155
0
      if (is_ext) {
156
0
        unsigned ext_subtable_index = subtable_index;
157
0
        ExtensionFormat1<OT::Layout::GSUB_impl::ExtensionSubst>* extension =
158
0
            (ExtensionFormat1<OT::Layout::GSUB_impl::ExtensionSubst>*)
159
0
            c.graph.object (ext_subtable_index).head;
160
0
        if (!extension || !extension->sanitize (c.graph.vertices_[ext_subtable_index]).is_ok ())
161
0
          continue;
162
163
0
        TRY_ASSIGN (subtable_index, extension->get_subtable_index (c.graph, ext_subtable_index));
164
0
        type = extension->get_lookup_type ();
165
0
        if (!is_supported_gpos_type(type, c) && !is_supported_gsub_type(type, c))
166
0
          continue;
167
0
      }
168
169
0
      hb_vector_t<unsigned>* split_result;
170
0
      if (c.split_subtables.has (subtable_index, &split_result))
171
0
      {
172
0
        if (split_result->length == 0)
173
0
          continue;
174
0
        all_new_subtables.push (hb_pair(i, *split_result));
175
0
        TRY (graph_result_t<void>::from (all_new_subtables, ALLOCATION_FAILURE));
176
0
      }
177
0
      else
178
0
      {
179
0
        hb_vector_t<unsigned> new_sub_tables;
180
181
0
        if (c.table_tag == HB_OT_TAG_GPOS) {
182
0
          switch (type)
183
0
          {
184
0
          case 2:
185
0
            {
186
0
              TRY_ASSIGN (new_sub_tables, split_subtable<PairPos> (c, subtable_index));
187
0
              break;
188
0
            }
189
0
          case 4:
190
0
            {
191
0
              TRY_ASSIGN (new_sub_tables, split_subtable<MarkBasePos> (c, subtable_index));
192
0
              break;
193
0
            }
194
0
          default:
195
0
            break;
196
0
          }
197
0
        } else if (c.table_tag == HB_OT_TAG_GSUB) {
198
0
          switch (type)
199
0
          {
200
0
          case 4:
201
0
            {
202
0
              TRY_ASSIGN (new_sub_tables, split_subtable<graph::LigatureSubst> (c, subtable_index));
203
0
              break;
204
0
            }
205
0
          default:
206
0
            break;
207
0
          }
208
0
        }
209
210
0
        c.split_subtables.set (subtable_index, new_sub_tables);
211
0
        TRY (graph_result_t<void>::from (c.split_subtables, ALLOCATION_FAILURE));
212
0
        if (new_sub_tables) {
213
0
          all_new_subtables.push (hb_pair (i, std::move (new_sub_tables)));
214
0
          TRY (graph_result_t<void>::from (all_new_subtables, ALLOCATION_FAILURE));
215
0
        }
216
0
      }
217
0
    }
218
219
0
    if (all_new_subtables) {
220
0
      TRY (add_sub_tables (c, this_index, type, all_new_subtables));
221
0
    }
222
223
0
    return Ok();
224
0
  }
225
226
  template<typename T>
227
  graph_result_t<hb_vector_t<unsigned>> split_subtable (gsubgpos_graph_context_t& c,
228
                                                        unsigned objidx)
229
0
  {
230
0
    T* sub_table = (T*) c.graph.object (objidx).head;
231
0
    if (!sub_table || !sub_table->sanitize (c.graph.vertices_[objidx]).is_ok ())
232
0
      return Ok(hb_vector_t<unsigned> ());
233
234
0
    return sub_table->split_subtables (c, objidx);
235
0
  }
Unexecuted instantiation: hb_result_t<hb_vector_t<unsigned int, false>, graph::graph_error_t> graph::Lookup::split_subtable<graph::PairPos>(graph::gsubgpos_graph_context_t&, unsigned int)
Unexecuted instantiation: hb_result_t<hb_vector_t<unsigned int, false>, graph::graph_error_t> graph::Lookup::split_subtable<graph::MarkBasePos>(graph::gsubgpos_graph_context_t&, unsigned int)
Unexecuted instantiation: hb_result_t<hb_vector_t<unsigned int, false>, graph::graph_error_t> graph::Lookup::split_subtable<graph::LigatureSubst>(graph::gsubgpos_graph_context_t&, unsigned int)
236
237
  graph_result_t<void> add_sub_tables (gsubgpos_graph_context_t& c,
238
                                       unsigned this_index,
239
                                       unsigned type,
240
                                       const hb_vector_t<hb_pair_t<unsigned, hb_vector_t<unsigned>>>& subtable_ids)
241
0
  {
242
0
    bool is_ext = is_extension (c.table_tag);
243
0
    auto* v = &c.graph.vertices_[this_index];
244
0
    fix_existing_subtable_links (c, this_index, subtable_ids);
245
246
0
    unsigned new_subtable_count = 0;
247
0
    for (const auto& p : subtable_ids)
248
0
      new_subtable_count += p.second.length;
249
250
0
    size_t new_size = v->table_size ()
251
0
                      + new_subtable_count * OT::Offset16::static_size;
252
0
    char* buffer = (char*) hb_calloc (1, new_size);
253
0
    if (unlikely (!buffer)) return Err(ALLOCATION_FAILURE);
254
0
    auto res = c.add_buffer (buffer);
255
0
    if (unlikely (!res.is_ok ()))
256
0
    {
257
0
      hb_free (buffer);
258
0
      return res;
259
0
    }
260
0
    hb_memcpy (buffer, v->obj ().head, v->table_size());
261
262
0
    if (use_mark_filtering_set ())
263
0
      hb_memcpy (buffer + new_size - 2, v->obj ().tail - 2, 2);
264
265
0
    v->set_buffer (buffer, buffer + new_size);
266
267
0
    Lookup* new_lookup = (Lookup*) buffer;
268
269
0
    unsigned shift = 0;
270
0
    new_lookup->subTable.len = subTable.len + new_subtable_count;
271
0
    for (const auto& p : subtable_ids)
272
0
    {
273
0
      unsigned offset_index = p.first + shift + 1;
274
0
      shift += p.second.length;
275
276
0
      for (unsigned subtable_id : p.second)
277
0
      {
278
0
        if (is_ext)
279
0
        {
280
0
          TRY_ASSIGN (unsigned ext_id, create_extension_subtable (c, subtable_id, type));
281
0
          TRY(c.graph.vertices_[subtable_id].add_parent (ext_id, false));
282
0
          subtable_id = ext_id;
283
          // the reference to v may have changed on adding a node, so reassign it.
284
0
          v = &c.graph.vertices_[this_index];
285
0
        }
286
287
0
        TRY(v->add_real_link(2, subtable_id,
288
0
          (char*) &new_lookup->subTable[offset_index++] - (char*) new_lookup));
289
0
        TRY(c.graph.vertices_[subtable_id].add_parent (this_index, false));
290
0
      }
291
0
    }
292
293
    // Repacker sort order depends on link order, which we've messed up so resort it.
294
0
    v->sort_real_links ();
295
296
    // The head location of the lookup has changed, invalidating the lookups map entry
297
    // in the context. Update the map.
298
0
    c.lookups.set (this_index, new_lookup);
299
0
    TRY (graph_result_t<void>::from (c.lookups, ALLOCATION_FAILURE));
300
0
    return Ok();
301
0
  }
302
303
  void fix_existing_subtable_links (gsubgpos_graph_context_t& c,
304
                                    unsigned this_index,
305
                                    const hb_vector_t<hb_pair_t<unsigned, hb_vector_t<unsigned>>>& subtable_ids)
306
0
  {
307
0
    auto& v = c.graph.vertices_[this_index];
308
0
    unsigned shift = 0;
309
0
    for (const auto& p : subtable_ids)
310
0
    {
311
0
      unsigned insert_index = p.first + shift;
312
0
      unsigned pos_offset = p.second.length * OT::Offset16::static_size;
313
0
      unsigned insert_offset = Lookup::min_size + insert_index * OT::Offset16::static_size;
314
0
      shift += p.second.length;
315
316
0
      for (auto& l : v.all_links_writer ())
317
0
      {
318
0
        if (l.position > insert_offset) l.position += pos_offset;
319
0
      }
320
0
    }
321
0
  }
322
323
  graph_result_t<unsigned> create_extension_subtable (gsubgpos_graph_context_t& c,
324
                                                      unsigned subtable_index,
325
                                                      unsigned type)
326
0
  {
327
0
    unsigned extension_size = OT::ExtensionFormat1<OT::Layout::GSUB_impl::ExtensionSubst>::static_size;
328
329
0
    TRY_ASSIGN (unsigned ext_index, c.create_node (extension_size));
330
331
0
    auto& ext_vertex = c.graph.vertices_[ext_index];
332
0
    ExtensionFormat1<OT::Layout::GSUB_impl::ExtensionSubst>* extension =
333
0
        ext_vertex.as<ExtensionFormat1<OT::Layout::GSUB_impl::ExtensionSubst>*> ();
334
0
    extension->reset (type);
335
336
    // Make extension point at the subtable.
337
0
    TRY(ext_vertex.add_real_link (4, subtable_index, 4));
338
339
0
    return Ok(ext_index);
340
0
  }
341
342
  graph_result_t<void> make_subtable_extension (gsubgpos_graph_context_t& c,
343
                                                unsigned lookup_index,
344
                                                unsigned subtable_index)
345
0
  {
346
0
    unsigned type = lookupType;
347
0
    unsigned ext_index = HB_GRAPH_INVALID;
348
0
    unsigned* existing_ext_index = nullptr;
349
0
    if (c.subtable_to_extension.has(subtable_index, &existing_ext_index)) {
350
0
      ext_index = *existing_ext_index;
351
0
    } else {
352
0
      TRY_ASSIGN (ext_index, create_extension_subtable(c, subtable_index, type));
353
0
      c.subtable_to_extension.set(subtable_index, ext_index);
354
0
      TRY (graph_result_t<void>::from (c.subtable_to_extension, ALLOCATION_FAILURE));
355
0
    }
356
357
0
    auto& subtable_vertex = c.graph.vertices_[subtable_index];
358
0
    auto& lookup_vertex = c.graph.vertices_[lookup_index];
359
0
    unsigned replaced = 0;
360
0
    for (auto& l : lookup_vertex.real_links_writer ())
361
0
    {
362
0
      if (l.objidx == subtable_index) {
363
        // Change lookup to point at the extension.
364
0
        l.objidx = ext_index;
365
0
        replaced++;
366
0
      }
367
0
    }
368
369
    // Make extension point at the subtable.
370
0
    auto& ext_vertex = c.graph.vertices_[ext_index];
371
0
    for (unsigned i = 0; i < replaced; i++)
372
0
    {
373
0
      subtable_vertex.remove_parent (lookup_index);
374
0
      TRY(ext_vertex.add_parent (lookup_index, false));
375
0
    }
376
0
    if (!existing_ext_index)
377
0
      TRY(subtable_vertex.add_parent (ext_index, false));
378
379
0
    return Ok();
380
0
  }
381
382
 private:
383
0
  bool is_supported_gsub_type(unsigned type, gsubgpos_graph_context_t& c) const {
384
0
    return (c.table_tag == HB_OT_TAG_GSUB) && (
385
0
      type == OT::Layout::GSUB_impl::SubstLookupSubTable::Type::Ligature
386
0
    );
387
0
  }
388
389
0
  bool is_supported_gpos_type(unsigned type, gsubgpos_graph_context_t& c) const {
390
0
   return (c.table_tag == HB_OT_TAG_GPOS) && (
391
0
      type == OT::Layout::GPOS_impl::PosLookupSubTable::Type::Pair ||
392
0
      type == OT::Layout::GPOS_impl::PosLookupSubTable::Type::MarkBase
393
0
    );
394
0
  }
395
396
  unsigned extension_type (hb_tag_t table_tag) const
397
0
  {
398
0
    switch (table_tag)
399
0
    {
400
0
    case HB_OT_TAG_GPOS: return 9;
401
0
    case HB_OT_TAG_GSUB: return 7;
402
0
    default: return 0;
403
0
    }
404
0
  }
405
};
406
407
template <typename T>
408
struct LookupList : public OT::LookupList<T>
409
{
410
  graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
411
0
  {
412
0
    size_t vertex_len = vertex.table_size ();
413
0
    if (unlikely (vertex_len < OT::LookupList<T>::min_size)) return Err(SANITIZE_FAILURE);
414
0
    hb_barrier ();
415
0
    if (unlikely (vertex_len < OT::LookupList<T>::item_size * this->len)) return Err(SANITIZE_FAILURE);
416
0
    return Ok();
417
0
  }
Unexecuted instantiation: graph::LookupList<OT::Layout::MediumTypes>::sanitize(graph::graph_t::vertex_t const&) const
Unexecuted instantiation: graph::LookupList<OT::Layout::SmallTypes>::sanitize(graph::graph_t::vertex_t const&) const
418
};
419
420
struct GSTAR : public OT::GSUBGPOS
421
{
422
  static graph_result_t<const GSTAR*> graph_to_gstar (graph_t& graph)
423
0
  {
424
0
    const auto& r = graph.root ();
425
426
0
    const GSTAR* gstar = (GSTAR*) r.obj ().head;
427
0
    if (unlikely (!gstar)) return Err(INVALID_ARGUMENT);
428
0
    TRY (gstar->sanitize (r));
429
0
    hb_barrier ();
430
431
0
    return gstar;
432
0
  }
433
434
  const void* get_lookup_list_field_offset () const
435
0
  {
436
0
    return OT::GSUBGPOS::get_lookup_list_field_offset ();
437
0
  }
438
439
  graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
440
0
  {
441
0
    size_t len = vertex.table_size ();
442
0
    if (unlikely (len < OT::GSUBGPOS::min_size)) return Err(SANITIZE_FAILURE);
443
0
    hb_barrier ();
444
0
    if (unlikely (version.major != 1)) return Err(SANITIZE_FAILURE);
445
0
    if (unlikely (len < get_size ())) return Err(SANITIZE_FAILURE);
446
0
    return Ok();
447
0
  }
448
449
  graph_result_t<void> find_lookups (graph_t& graph,
450
                                     hb_hashmap_t<unsigned, Lookup*>& lookups /* OUT */) const
451
0
  {
452
0
#ifndef HB_NO_BEYOND_64K
453
0
    if (version.to_int () >= 0x00010002u && lookupList2)
454
0
      return find_lookups<MediumTypes> (graph, lookups);
455
0
#endif
456
0
    TRY(find_lookups<SmallTypes> (graph, lookups));
457
0
    return Ok();
458
0
  }
459
460
  graph_result_t<unsigned> get_lookup_list_index (graph_t& graph) const
461
0
  {
462
0
    const void* offset = get_lookup_list_field_offset ();
463
0
    if (unlikely (!offset)) return Err(INVALID_ARGUMENT);
464
0
    return graph.index_for_offset (graph.root_idx (), offset);
465
0
  }
466
467
  template<typename Types>
468
  graph_result_t<void> find_lookups (graph_t& graph,
469
                                     hb_hashmap_t<unsigned, Lookup*>& lookups /* OUT */) const
470
0
  {
471
0
    TRY_ASSIGN (unsigned lookup_list_idx, get_lookup_list_index (graph));
472
0
    const LookupList<Types>* lookupList =
473
0
        (const LookupList<Types>*) graph.object (lookup_list_idx).head;
474
0
    if (unlikely (!lookupList)) return Err(INVALID_ARGUMENT);
475
0
    TRY(lookupList->sanitize (graph.vertices_[lookup_list_idx]));
476
477
0
    for (unsigned i = 0; i < lookupList->len; i++)
478
0
    {
479
0
      TRY_ASSIGN (unsigned lookup_idx, graph.index_for_offset (lookup_list_idx, &(lookupList->arrayZ[i])));
480
0
      Lookup* lookup = (Lookup*) graph.object (lookup_idx).head;
481
0
      if (!lookup || !lookup->sanitize (graph.vertices_[lookup_idx]).is_ok ()) continue;
482
0
      lookups.set (lookup_idx, lookup);
483
0
    }
484
0
    return Ok();
485
0
  }
Unexecuted instantiation: hb_result_t<void, graph::graph_error_t> graph::GSTAR::find_lookups<OT::Layout::MediumTypes>(graph::graph_t&, hb_hashmap_t<unsigned int, graph::Lookup*, false>&) const
Unexecuted instantiation: hb_result_t<void, graph::graph_error_t> graph::GSTAR::find_lookups<OT::Layout::SmallTypes>(graph::graph_t&, hb_hashmap_t<unsigned int, graph::Lookup*, false>&) const
486
};
487
488
489
490
491
}
492
493
#endif  /* GRAPH_GSUBGPOS_GRAPH_HH */