Coverage Report

Created: 2026-11-10 07:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/harfbuzz/src/graph/ligature-graph.hh
Line
Count
Source
1
2
/*
3
 * Copyright © 2025  Google, Inc.
4
 *
5
 *  This is part of HarfBuzz, a text shaping library.
6
 *
7
 * Permission is hereby granted, without written agreement and without
8
 * license or royalty fees, to use, copy, modify, and distribute this
9
 * software and its documentation for any purpose, provided that the
10
 * above copyright notice and the following two paragraphs appear in
11
 * all copies of this software.
12
 *
13
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
14
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
15
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
16
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
17
 * DAMAGE.
18
 *
19
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
20
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
22
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
23
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
24
 *
25
 * Google Author(s): Garret Rieger
26
 */
27
28
#ifndef GRAPH_LIGATURE_GRAPH_HH
29
#define GRAPH_LIGATURE_GRAPH_HH
30
31
#include "graph.hh"
32
#include "../OT/Layout/GSUB/LigatureSubst.hh"
33
#include "../OT/Layout/GSUB/LigatureSubstFormat1.hh"
34
#include "../OT/Layout/GSUB/LigatureSet.hh"
35
#include "../hb-ot-dual.hh"
36
#include <algorithm>
37
#include <utility>
38
39
namespace graph {
40
41
struct LigatureSet : public OT::Layout::GSUB_impl::LigatureSet<SmallTypes>
42
{
43
  graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
44
0
  {
45
0
    size_t vertex_len = vertex.table_size ();
46
0
    if (unlikely (vertex_len < OT::Layout::GSUB_impl::LigatureSet<SmallTypes>::min_size)) return Err(SANITIZE_FAILURE);
47
0
    hb_barrier ();
48
49
0
    size_t total_len = ligature.get_size() + OT::Layout::GSUB_impl::LigatureSet<SmallTypes>::min_size - ligature.len.get_size();
50
0
    if (unlikely (vertex_len < total_len)) {
51
0
      return Err(SANITIZE_FAILURE);
52
0
    }
53
0
    return Ok();
54
0
  }
55
};
56
57
struct LigatureSubstFormat1 : public OT::Layout::GSUB_impl::LigatureSubstFormat1_2<SmallTypes>
58
{
59
  graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
60
0
  {
61
0
    size_t vertex_len = vertex.table_size ();
62
0
    unsigned min_size = OT::Layout::GSUB_impl::LigatureSubstFormat1_2<SmallTypes>::min_size;
63
0
    if (unlikely (vertex_len < min_size)) return Err(SANITIZE_FAILURE);
64
0
    hb_barrier ();
65
66
0
    if (unlikely (vertex_len < min_size + ligatureSet.get_size() - ligatureSet.len.get_size()))
67
0
      return Err(SANITIZE_FAILURE);
68
0
    return Ok();
69
0
  }
70
71
  graph_result_t<hb_vector_t<unsigned>> split_subtables (gsubgpos_graph_context_t& c,
72
                                                         unsigned this_index)
73
0
  {
74
0
    TRY_ASSIGN (auto split_points, compute_split_points(c, this_index));
75
0
    if (!split_points)
76
0
      return Ok(hb_vector_t<unsigned> ());
77
78
0
    TRY_ASSIGN (unsigned total_ligas, total_number_ligas(c, this_index));
79
0
    TRY_ASSIGN (hb_vector_t<unsigned> counts, liga_counts(c, this_index));
80
0
    split_context_t split_context {
81
0
      c,
82
0
      this,
83
0
      this_index,
84
0
      total_ligas,
85
0
      std::move (counts),
86
0
    };
87
0
    return actuate_subtable_split<split_context_t> (split_context, split_points);
88
0
  }
89
90
 private:
91
0
  graph_result_t<unsigned> total_number_ligas(gsubgpos_graph_context_t& c, unsigned this_index) const {
92
0
    unsigned total = 0;
93
0
    for (unsigned i = 0; i < ligatureSet.len; i++)
94
0
    {
95
0
      TRY_ASSIGN (auto liga_set, c.graph.as_table<LigatureSet>(this_index, &ligatureSet[i]));
96
0
      total += liga_set.table->ligature.len;
97
0
    }
98
0
    return Ok(total);
99
0
  }
100
101
0
  graph_result_t<hb_vector_t<unsigned>> liga_counts(gsubgpos_graph_context_t& c, unsigned this_index) const {
102
0
    hb_vector_t<unsigned> result;
103
0
    for (unsigned i = 0; i < ligatureSet.len; i++)
104
0
    {
105
0
      TRY_ASSIGN (auto liga_set, c.graph.as_table<LigatureSet>(this_index, &ligatureSet[i]));
106
0
      result.push(!liga_set.table ? 0 : liga_set.table->ligature.len);
107
0
    }
108
0
    return graph_result_t<hb_vector_t<unsigned>>::from(std::move(result), ALLOCATION_FAILURE);
109
0
  }
110
111
  template <graph_t::vertex_mutability_t mutability>
112
0
  graph_result_t<hb_vector_t<unsigned>> ligature_index_to_object_id(const graph_t::vertex_and_table_t<LigatureSet, mutability>& liga_set) const {
113
0
    hb_vector_t<unsigned> map;
114
0
    if (!map.resize_exact(liga_set.table->ligature.len)) return Err(ALLOCATION_FAILURE);
115
116
0
    for (unsigned i = 0; i < map.length; i++) {
117
0
      map[i] = HB_GRAPH_INVALID;
118
0
    }
119
120
0
    for (const auto& l : liga_set.vertex->obj ().real_links) {
121
0
      if (l.position < 2) continue;
122
0
      unsigned array_index = (l.position - 2) / 2;
123
0
      map[array_index] = l.objidx;
124
0
    }
125
0
    return map;
126
0
  }
Unexecuted instantiation: hb_result_t<hb_vector_t<unsigned int, false>, graph::graph_error_t> graph::LigatureSubstFormat1::ligature_index_to_object_id<(graph::graph_t::vertex_mutability_t)0>(graph::graph_t::vertex_and_table_t<graph::LigatureSet, (graph::graph_t::vertex_mutability_t)0> const&) const
Unexecuted instantiation: hb_result_t<hb_vector_t<unsigned int, false>, graph::graph_error_t> graph::LigatureSubstFormat1::ligature_index_to_object_id<(graph::graph_t::vertex_mutability_t)1>(graph::graph_t::vertex_and_table_t<graph::LigatureSet, (graph::graph_t::vertex_mutability_t)1> const&) const
127
128
  graph_result_t<hb_vector_t<unsigned>> compute_split_points(gsubgpos_graph_context_t& c,
129
                                                             unsigned this_index) const
130
0
  {
131
    // For ligature subst coverage is always packed last, and as a result is where an overflow
132
    // will happen if there is one, so we can check the estimate length of the
133
    // LigatureSubstFormat1 -> Coverage offset length which is the sum of all data in the
134
    // retained sub graph except for the coverage table itself.
135
0
    const unsigned base_size = OT::Layout::GSUB_impl::LigatureSubstFormat1_2<SmallTypes>::min_size;
136
0
    unsigned accumulated = base_size;
137
138
0
    unsigned ligature_index = 0;
139
0
    hb_vector_t<unsigned> split_points;
140
0
    for (unsigned i = 0; i < ligatureSet.len; i++)
141
0
    {
142
0
      accumulated += OT::HBUINT16::static_size; // for ligature set offset
143
0
      accumulated += OT::Layout::GSUB_impl::LigatureSet<SmallTypes>::min_size; // for ligature set table
144
145
0
      TRY_ASSIGN (auto liga_set, c.graph.as_table<LigatureSet>(this_index, &ligatureSet[i]));
146
147
      // Finding the object id associated with an array index is O(n)
148
      // so to avoid O(n^2), precompute the mapping by scanning through
149
      // all links
150
0
      TRY_ASSIGN (auto index_to_id, ligature_index_to_object_id (liga_set));
151
152
      // Split points and liga_counts () must count the same offset slots,
153
      // including null offsets.
154
0
      for (unsigned j = 0; j < liga_set.table->ligature.len; j++, ligature_index++)
155
0
      {
156
0
        const unsigned liga_id = index_to_id[j];
157
0
        if (liga_id == HB_GRAPH_INVALID) continue; // no outgoing link, ignore
158
0
        const unsigned liga_size = c.graph.vertices_[liga_id].table_size ();
159
160
0
        accumulated += OT::HBUINT16::static_size; // for ligature offset
161
0
        accumulated += liga_size; // for the ligature table
162
163
0
        if (accumulated >= (1 << 16))
164
0
        {
165
0
          split_points.push(ligature_index);
166
          // We're going to split such that the current ligature will be in the new sub table.
167
          // That means we'll have one ligature subst (base_base), one ligature set, and one liga table
168
0
          accumulated = base_size + // for liga subst subtable
169
0
            (OT::HBUINT16::static_size * 2) + // for liga set and liga offset
170
0
            OT::Layout::GSUB_impl::LigatureSet<SmallTypes>::min_size + // for liga set subtable
171
0
            liga_size; // for liga sub table
172
0
        }
173
0
      }
174
0
    }
175
176
0
    return split_points;
177
0
  }
178
179
  struct split_context_t
180
  {
181
    gsubgpos_graph_context_t& c;
182
    LigatureSubstFormat1* thiz;
183
    unsigned this_index;
184
    unsigned original_count_;
185
    hb_vector_t<unsigned> liga_counts;
186
187
    unsigned original_count ()
188
0
    {
189
0
      return original_count_;
190
0
    }
191
192
    graph_result_t<unsigned> clone_range (unsigned start, unsigned end)
193
0
    {
194
0
      return thiz->clone_range (c, this_index, liga_counts, start, end);
195
0
    }
196
197
    graph_result_t<void> shrink (unsigned count)
198
0
    {
199
0
      return thiz->shrink (c, this_index, original_count(), liga_counts, count);
200
0
    }
201
  };
202
203
0
  graph_result_t<hb_pair_t<unsigned, LigatureSet*>> new_liga_set(gsubgpos_graph_context_t& c, unsigned count) const {
204
0
    unsigned prime_size = OT::Layout::GSUB_impl::LigatureSet<SmallTypes>::min_size
205
0
                          + count * SmallTypes::size;
206
207
0
    TRY_ASSIGN (unsigned prime_id, c.create_node (prime_size));
208
209
0
    LigatureSet* prime = (LigatureSet*) c.graph.object (prime_id).head;
210
0
    prime->ligature.len = count;
211
0
    return Ok(hb_pair(prime_id, prime));
212
0
  }
213
214
  void clear_virtual_links (gsubgpos_graph_context_t& c, unsigned node_index) const
215
0
  {
216
0
    auto& v  = c.graph.vertices_[node_index];
217
0
    for (const auto& l : v.obj ().virtual_links)
218
0
    {
219
0
      auto& child = c.graph.vertices_[l.objidx];
220
0
      child.remove_parent(node_index);
221
0
    }
222
0
    v.clear_virtual_links ();
223
0
  }
224
225
0
  graph_result_t<void> add_virtual_link(gsubgpos_graph_context_t& c, unsigned from, unsigned to) const {
226
0
    if (unlikely (from >= c.graph.vertices_.length || to >= c.graph.vertices_.length)) return Err(OUT_OF_BOUNDS);
227
0
    TRY(c.graph.vertices_[to].add_parent (from, true));
228
0
    TRY(c.graph.vertices_[from].add_virtual_link (to));
229
0
    return Ok();
230
0
  }
231
232
  hb_pair_t<unsigned, unsigned> current_liga_set_bounds (gsubgpos_graph_context_t& c,
233
                                                         unsigned liga_set_index,
234
                                                         const hb_serialize_context_t::object_t& liga_set) const
235
0
  {
236
    // Finds the actual liga indices present in the liga set currently. Takes
237
    // into account those that have been removed by processing.
238
0
    unsigned min_index = HB_GRAPH_INVALID;
239
0
    unsigned max_index = 0;
240
0
    for (const auto& l : liga_set.real_links) {
241
0
      if (l.position < 2) continue;
242
243
0
      unsigned liga_index = (l.position - 2) / 2;
244
0
      min_index = hb_min(min_index, liga_index);
245
0
      max_index = hb_max(max_index, liga_index);
246
0
    }
247
0
    return hb_pair(min_index, max_index + 1);
248
0
  }
249
250
  void compact_liga_set (gsubgpos_graph_context_t& c, LigatureSet* table, graph_t::vertex_t* v) const
251
0
  {
252
0
    const auto& obj = v->obj ();
253
0
    if (table->ligature.len <= obj.real_links.length) return;
254
255
    // compact the remaining linked liga offsets into a continous array and shrink the node as needed.
256
0
    unsigned to_remove = table->ligature.len - obj.real_links.length;
257
0
    unsigned new_position = SmallTypes::size;
258
0
    v->sort_real_links (); // for this to work we need to process links in order of position.
259
0
    for (auto& l : v->real_links_writer ())
260
0
    {
261
0
      l.position = new_position;
262
0
      new_position += SmallTypes::size;
263
0
    }
264
265
0
    table->ligature.len = obj.real_links.length;
266
0
    v->shrink_buffer (to_remove * SmallTypes::size);
267
0
  }
268
269
  graph_result_t<unsigned> clone_range (gsubgpos_graph_context_t& c,
270
                                        unsigned this_index,
271
                                        hb_vector_t<unsigned> liga_counts,
272
                                        unsigned start, unsigned end) const
273
0
  {
274
0
    DEBUG_MSG (SUBSET_REPACK, nullptr,
275
0
               "  Cloning LigatureSubstFormat1 (%u) range [%u, %u).", this_index, start, end);
276
277
    // Create an oversized new liga subst, we'll adjust the size down later. We don't know
278
    // the final size until we process it but we also need it to exist while we're processing
279
    // so that nodes can be moved to it as needed.
280
0
    unsigned prime_size = OT::Layout::GSUB_impl::LigatureSubstFormat1_2<SmallTypes>::min_size
281
0
                          + ligatureSet.get_size() - ligatureSet.len.get_size();
282
283
0
    TRY_ASSIGN (unsigned liga_subst_prime_id, c.create_node (prime_size));
284
285
0
    LigatureSubstFormat1* liga_subst_prime = (LigatureSubstFormat1*) c.graph.object (liga_subst_prime_id).head;
286
0
    liga_subst_prime->format = this->format;
287
0
    liga_subst_prime->ligatureSet.len = this->ligatureSet.len;
288
289
    // Create a place holder coverage prime id since we need to add virtual links to it while
290
    // generating liga and liga sets. Afterwards it will be updated to have the correct coverage.
291
0
    TRY_ASSIGN (unsigned coverage_id, c.graph.index_for_offset (this_index, &coverage));
292
0
    TRY_ASSIGN (unsigned coverage_prime_id, c.graph.duplicate(coverage_id));
293
0
    auto& coverage_prime_vertex = c.graph.vertices_[coverage_prime_id];
294
295
0
    TRY(c.graph.vertices_[liga_subst_prime_id].add_real_link (SmallTypes::size, coverage_prime_id, 2));
296
0
    TRY(coverage_prime_vertex.add_parent (liga_subst_prime_id, false));
297
298
    // Locate all liga sets with ligas between start and end.
299
    // Clone or move them as needed.
300
0
    unsigned count = 0;
301
0
    unsigned liga_set_count = 0;
302
0
    unsigned liga_set_start = HB_GRAPH_INVALID;
303
0
    unsigned liga_set_end = 0; // inclusive
304
0
    for (unsigned i = 0; i < liga_counts.length; i++)
305
0
    {
306
0
      unsigned num_ligas = liga_counts[i];
307
308
0
      unsigned current_start = count;
309
0
      unsigned current_end = count + num_ligas;
310
311
0
      if (current_start >= end || start >= current_end) {
312
        // No intersection, so just skip
313
0
        count += num_ligas;
314
0
        continue;
315
0
      }
316
317
0
      TRY_ASSIGN (auto liga_set, c.graph.as_mutable_table<LigatureSet>(this_index, &ligatureSet[i]));
318
319
0
      unsigned liga_set_index = liga_set.index;
320
      // Bounds may need to be adjusted if some ligas have been previously removed.
321
0
      hb_pair_t<unsigned, unsigned> liga_bounds = current_liga_set_bounds(c, liga_set_index, liga_set.vertex->obj ());
322
0
      current_start = hb_max(count + liga_bounds.first, current_start);
323
0
      current_end = hb_min(count + liga_bounds.second, current_end);
324
325
0
      unsigned liga_set_prime_id;
326
0
      if (current_start >= start && current_end <= end) {
327
        // This liga set is fully contined within [start, end)
328
        // We can move the entire ligaset to the new liga subset object.
329
0
        liga_set_end = i;
330
0
        if (i < liga_set_start) liga_set_start = i;
331
0
        TRY_ASSIGN (liga_set_prime_id, c.graph.move_child<> (this_index,
332
0
                              &ligatureSet[i],
333
0
                              liga_subst_prime_id,
334
0
                              &liga_subst_prime->ligatureSet[liga_set_count++]));
335
0
        compact_liga_set (c, liga_set.table, liga_set.vertex);
336
0
      }
337
0
      else
338
0
      {
339
        // This liga set partially overlaps [start, end). We'll need to create
340
        // a new liga set sub table and move the intersecting ligas to it.
341
0
        unsigned start_index = hb_max(start, current_start) - count;
342
0
        unsigned end_index = hb_min(end, current_end) - count;
343
        // Narrowing to the remaining links may leave an inverted range.
344
0
        if (unlikely (end_index < start_index))
345
0
          return Err(INVALID_ARGUMENT);
346
0
        unsigned liga_count = end_index - start_index;
347
0
        TRY_ASSIGN (auto result, new_liga_set(c, liga_count));
348
0
        liga_set_prime_id = result.first;
349
350
0
        TRY (c.graph.move_children<OT::Offset16>(
351
0
          liga_set_index,
352
0
          2 + start_index * 2,
353
0
          2 + end_index * 2,
354
0
          liga_set_prime_id,
355
0
          2));
356
357
0
        liga_set_end = i;
358
0
        if (i < liga_set_start) liga_set_start = i;
359
0
        TRY (c.graph.add_link(&liga_subst_prime->ligatureSet[liga_set_count++], liga_subst_prime_id, liga_set_prime_id));
360
0
      }
361
362
      // The new liga and all children set needs to have a virtual link to the new coverage table:
363
0
      const auto& liga_set_prime = c.graph.vertices_[liga_set_prime_id].obj ();
364
0
      clear_virtual_links(c, liga_set_prime_id);
365
0
      TRY(add_virtual_link(c, liga_set_prime_id, coverage_prime_id));
366
0
      for (const auto& l : liga_set_prime.real_links) {
367
0
        clear_virtual_links(c, l.objidx);
368
0
        TRY(add_virtual_link(c, l.objidx, coverage_prime_id));
369
0
      }
370
371
0
      count += num_ligas;
372
0
    }
373
374
0
    c.graph.vertices_[liga_subst_prime_id].shrink_buffer ((liga_subst_prime->ligatureSet.len - liga_set_count) * SmallTypes::size);
375
0
    liga_subst_prime->ligatureSet.len = liga_set_count;
376
377
0
    TRY (Coverage::filter_coverage (c,
378
0
                                    coverage_prime_id,
379
0
                                    liga_set_start, liga_set_end + 1));
380
381
0
    return Ok(liga_subst_prime_id);
382
0
  }
383
384
  graph_result_t<void> shrink (gsubgpos_graph_context_t& c,
385
                               unsigned this_index,
386
                               unsigned old_count,
387
                               hb_vector_t<unsigned> liga_counts,
388
                               unsigned count)
389
0
  {
390
0
    DEBUG_MSG (SUBSET_REPACK, nullptr,
391
0
               "  Shrinking LigatureSubstFormat1 (%u) to [0, %u).",
392
0
               this_index,
393
0
               count);
394
0
    if (count >= old_count)
395
0
      return Ok();
396
397
0
    hb_set_t retained_indices;
398
0
    unsigned new_liga_set_count = 0;
399
0
    for (unsigned i = 0; i < liga_counts.length; i++)
400
0
    {
401
0
      TRY_ASSIGN (auto liga_set, c.graph.as_mutable_table<LigatureSet>(this_index, &ligatureSet[i]));
402
403
      // We need the virtual links to coverage removed from all descendants on this liga subst.
404
      // If any are left when we try to mutate the coverage table later it will be unnessecarily
405
      // duplicated. Code later on will re-add the virtual links as needed (via retained_indices).
406
0
      clear_virtual_links(c, liga_set.index);
407
0
      retained_indices.add(liga_set.index);
408
409
0
      TRY_ASSIGN (auto index_to_id, ligature_index_to_object_id (liga_set));
410
411
0
      for (unsigned i = 0; i < liga_set.table->ligature.len; i++) {
412
0
        unsigned liga_index = index_to_id[i];
413
0
        if (liga_index != HB_GRAPH_INVALID) {
414
0
          clear_virtual_links(c, liga_index);
415
0
          retained_indices.add(liga_index);
416
0
        }
417
0
      }
418
419
0
      unsigned num_ligas = liga_counts[i];
420
0
      if (num_ligas >= count) {
421
        // drop the trailing liga's from this set and all subsequent liga sets
422
0
        unsigned num_ligas_to_remove = num_ligas - count;
423
0
        new_liga_set_count = i + 1;
424
0
        c.graph.vertices_[liga_set.index].shrink_buffer (num_ligas_to_remove * SmallTypes::size);
425
0
        liga_set.table->ligature.len = count;
426
0
        break;
427
0
      } else {
428
0
        count -= num_ligas;
429
0
      }
430
0
    }
431
432
    // Adjust liga set array
433
0
    auto& this_vertex = c.graph.vertices_[this_index];
434
0
    this_vertex.shrink_buffer ((ligatureSet.len - new_liga_set_count) * SmallTypes::size);
435
0
    ligatureSet.len = new_liga_set_count;
436
437
    // Coverage matches the number of liga sets so rebuild as needed
438
0
    TRY_ASSIGN (unsigned coverage_idx, c.graph.index_for_offset (this_index, &this->coverage));
439
440
0
    auto& coverage_v = c.graph.vertices_[coverage_idx];
441
0
    unsigned coverage_size = coverage_v.table_size ();
442
0
    const Coverage* coverage_table = (const Coverage*) coverage_v.obj ().head;
443
444
0
    if (coverage_v.is_shared ())
445
0
    {
446
0
      TRY_ASSIGN (coverage_idx, c.graph.remap_child (this_index, coverage_idx));
447
0
    }
448
449
0
    for (unsigned i : retained_indices.iter())
450
0
      TRY(add_virtual_link(c, i, coverage_idx));
451
452
0
    auto new_coverage =
453
0
        + hb_zip (coverage_table->iter (), hb_range ())
454
0
        | hb_filter ([&] (hb_pair_t<unsigned, unsigned> p) {
455
0
          return p.second < new_liga_set_count;
456
0
        })
457
0
        | hb_map_retains_sorting (hb_first)
458
0
        ;
459
460
0
    return Coverage::make_coverage (c, new_coverage, coverage_idx, coverage_size);
461
0
  }
462
};
463
464
struct LigatureSubst : public OT::Layout::GSUB_impl::LigatureSubst
465
{
466
467
  graph_result_t<hb_vector_t<unsigned>> split_subtables (gsubgpos_graph_context_t& c,
468
                                                         unsigned this_index)
469
0
  {
470
0
    switch (u.format.v) {
471
0
    case 1:
472
0
      hb_barrier ();
473
0
      return ((LigatureSubstFormat1*)(&u.format1))->split_subtables (c, this_index);
474
0
#ifndef HB_NO_BEYOND_64K
475
0
    case 2: HB_FALLTHROUGH;
476
      // 24-bit LigatureSubst already has widened offsets; leave it unsplit.
477
0
#endif
478
0
    default:
479
0
      return Ok(hb_vector_t<unsigned> ());
480
0
    }
481
0
  }
482
483
  graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const
484
0
  {
485
0
    size_t vertex_len = vertex.table_size ();
486
0
    if (unlikely (vertex_len < u.format.v.get_size ())) return Err(SANITIZE_FAILURE);
487
0
    hb_barrier ();
488
489
0
    switch (u.format.v) {
490
0
    case 1:
491
0
      hb_barrier ();
492
0
      return ((LigatureSubstFormat1*)(&u.format1))->sanitize (vertex);
493
0
#ifndef HB_NO_BEYOND_64K
494
0
    case 2:  HB_FALLTHROUGH;
495
0
#endif
496
0
    default:
497
      // We don't handle format 2 here.
498
0
      return Err(SANITIZE_FAILURE);
499
0
    }
500
0
  }
501
};
502
503
}
504
505
#endif  // GRAPH_LIGATURE_GRAPH_HH