Coverage Report

Created: 2026-05-30 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/harfbuzz/src/hb-repacker.hh
Line
Count
Source
1
/*
2
 * Copyright © 2020  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 HB_REPACKER_HH
28
#define HB_REPACKER_HH
29
30
#include "hb-open-type.hh"
31
#include "hb-map.hh"
32
#include "hb-vector.hh"
33
#include "graph/graph.hh"
34
#include "graph/gsubgpos-graph.hh"
35
#include "graph/serialize.hh"
36
37
using graph::graph_t;
38
39
/*
40
 * For a detailed writeup on the overflow resolution algorithm see:
41
 * docs/repacker.md
42
 */
43
44
struct lookup_size_t
45
{
46
  unsigned lookup_index;
47
  size_t size;
48
  unsigned num_subtables;
49
50
  static int cmp (const void* a, const void* b)
51
0
  {
52
0
    return cmp ((const lookup_size_t*) a,
53
0
                (const lookup_size_t*) b);
54
0
  }
55
56
  static int cmp (const lookup_size_t* a, const lookup_size_t* b)
57
4.45k
  {
58
4.45k
    double subtables_per_byte_a = (double) a->num_subtables / (double) a->size;
59
4.45k
    double subtables_per_byte_b = (double) b->num_subtables / (double) b->size;
60
4.45k
    if (subtables_per_byte_a == subtables_per_byte_b) {
61
282
      return b->lookup_index - a->lookup_index;
62
282
    }
63
64
4.17k
    double cmp = subtables_per_byte_b - subtables_per_byte_a;
65
4.17k
    if (cmp < 0) return -1;
66
2.73k
    if (cmp > 0) return 1;
67
0
    return 0;
68
2.73k
  }
69
};
70
71
static inline
72
bool _presplit_subtables_if_needed (graph::gsubgpos_graph_context_t& ext_context)
73
1.86k
{
74
  // For each lookup this will check the size of subtables and split them as needed
75
  // so that no subtable is at risk of overflowing. (where we support splitting for
76
  // that subtable type).
77
  //
78
  // TODO(grieger): de-dup newly added nodes as necessary. Probably just want a full de-dup
79
  //                pass after this processing is done. Not super necessary as splits are
80
  //                only done where overflow is likely, so de-dup probably will get undone
81
  //                later anyways.
82
83
  // The loop below can modify the contents of ext_context.lookups if new subtables are added
84
  // to a lookup during a split. So save the initial set of lookup indices so the iteration doesn't
85
  // risk access free'd memory if ext_context.lookups gets resized.
86
1.86k
  hb_set_t lookup_indices(ext_context.lookups.keys ());
87
1.86k
  for (unsigned lookup_index : lookup_indices)
88
4.73k
  {
89
4.73k
    graph::Lookup* lookup = ext_context.lookups.get(lookup_index);
90
4.73k
    if (!lookup->split_subtables_if_needed (ext_context, lookup_index))
91
4
      return false;
92
4.73k
  }
93
94
1.86k
  return true;
95
1.86k
}
hb-subset.cc:_presplit_subtables_if_needed(graph::gsubgpos_graph_context_t&)
Line
Count
Source
73
1.86k
{
74
  // For each lookup this will check the size of subtables and split them as needed
75
  // so that no subtable is at risk of overflowing. (where we support splitting for
76
  // that subtable type).
77
  //
78
  // TODO(grieger): de-dup newly added nodes as necessary. Probably just want a full de-dup
79
  //                pass after this processing is done. Not super necessary as splits are
80
  //                only done where overflow is likely, so de-dup probably will get undone
81
  //                later anyways.
82
83
  // The loop below can modify the contents of ext_context.lookups if new subtables are added
84
  // to a lookup during a split. So save the initial set of lookup indices so the iteration doesn't
85
  // risk access free'd memory if ext_context.lookups gets resized.
86
1.86k
  hb_set_t lookup_indices(ext_context.lookups.keys ());
87
1.86k
  for (unsigned lookup_index : lookup_indices)
88
4.73k
  {
89
4.73k
    graph::Lookup* lookup = ext_context.lookups.get(lookup_index);
90
4.73k
    if (!lookup->split_subtables_if_needed (ext_context, lookup_index))
91
4
      return false;
92
4.73k
  }
93
94
1.86k
  return true;
95
1.86k
}
Unexecuted instantiation: hb-subset-table-layout.cc:_presplit_subtables_if_needed(graph::gsubgpos_graph_context_t&)
Unexecuted instantiation: hb-subset-table-var.cc:_presplit_subtables_if_needed(graph::gsubgpos_graph_context_t&)
Unexecuted instantiation: hb-subset-table-cff.cc:_presplit_subtables_if_needed(graph::gsubgpos_graph_context_t&)
Unexecuted instantiation: hb-subset-table-color.cc:_presplit_subtables_if_needed(graph::gsubgpos_graph_context_t&)
Unexecuted instantiation: hb-subset-table-other.cc:_presplit_subtables_if_needed(graph::gsubgpos_graph_context_t&)
96
97
/*
98
 * Analyze the lookups in a GSUB/GPOS table and decide if any should be promoted
99
 * to extension lookups.
100
 */
101
static inline
102
bool _promote_extensions_if_needed (graph::gsubgpos_graph_context_t& ext_context)
103
1.86k
{
104
  // Simple Algorithm (v1, current):
105
  // 1. Calculate how many bytes each non-extension lookup consumes.
106
  // 2. Select up to 64k of those to remain as non-extension (greedy, highest subtables per byte first)
107
  // 3. Promote the rest.
108
  //
109
  // Advanced Algorithm (v2, not implemented):
110
  // 1. Perform connected component analysis using lookups as roots.
111
  // 2. Compute size of each connected component.
112
  // 3. Select up to 64k worth of connected components to remain as non-extensions.
113
  //    (greedy, highest subtables per byte first)
114
  // 4. Promote the rest.
115
116
  // TODO(garretrieger): support extension demotion, then consider all lookups. Requires advanced algo.
117
  // TODO(garretrieger): also support extension promotion during iterative resolution phase, then
118
  //                     we can use a less conservative threshold here.
119
  // TODO(grieger): skip this for the 24 bit case.
120
1.86k
  if (!ext_context.lookups) return true;
121
122
1.86k
  unsigned total_lookup_table_sizes = 0;
123
1.86k
  hb_vector_t<lookup_size_t> lookup_sizes;
124
1.86k
  lookup_sizes.alloc (ext_context.lookups.get_population (), true);
125
126
1.86k
  for (unsigned lookup_index : ext_context.lookups.keys ())
127
4.72k
  {
128
4.72k
    const auto& lookup_v = ext_context.graph.vertices_[lookup_index];
129
4.72k
    total_lookup_table_sizes += lookup_v.table_size ();
130
131
4.72k
    const graph::Lookup* lookup = ext_context.lookups.get(lookup_index);
132
4.72k
    hb_set_t visited;
133
4.72k
    lookup_sizes.push (lookup_size_t {
134
4.72k
        lookup_index,
135
4.72k
        ext_context.graph.find_subgraph_size (lookup_index, visited),
136
4.72k
        lookup->number_of_subtables (),
137
4.72k
      });
138
4.72k
  }
139
140
1.86k
  lookup_sizes.qsort ();
141
142
1.86k
  size_t lookup_list_size = ext_context.graph.vertices_[ext_context.lookup_list_index].table_size ();
143
1.86k
  size_t l2_l3_size = lookup_list_size + total_lookup_table_sizes; // Lookup List + Lookups
144
1.86k
  size_t l3_l4_size = total_lookup_table_sizes; // Lookups + SubTables
145
1.86k
  size_t l4_plus_size = 0; // SubTables + their descendants
146
147
  // Start by assuming all lookups are using extension subtables, this size will be removed later
148
  // if it's decided to not make a lookup extension.
149
1.86k
  for (auto p : lookup_sizes)
150
4.72k
  {
151
    // TODO(garretrieger): this overestimates the extension subtables size because some extension subtables may be
152
    //                     reused. However, we can't correct this until we have connected component analysis in place.
153
4.72k
    unsigned subtables_size = p.num_subtables * 8;
154
4.72k
    l3_l4_size += subtables_size;
155
4.72k
    l4_plus_size += subtables_size;
156
4.72k
  }
157
158
1.86k
  bool layers_full = false;
159
1.86k
  for (auto p : lookup_sizes)
160
4.72k
  {
161
4.72k
    const graph::Lookup* lookup = ext_context.lookups.get(p.lookup_index);
162
4.72k
    if (lookup->is_extension (ext_context.table_tag))
163
      // already an extension so size is counted by the loop above.
164
8
      continue;
165
166
4.71k
    if (!layers_full)
167
3.91k
    {
168
3.91k
      size_t lookup_size = ext_context.graph.vertices_[p.lookup_index].table_size ();
169
3.91k
      hb_set_t visited;
170
3.91k
      size_t subtables_size = ext_context.graph.find_subgraph_size (p.lookup_index, visited, 1) - lookup_size;
171
3.91k
      size_t remaining_size = p.size - subtables_size - lookup_size;
172
173
3.91k
      l3_l4_size   += subtables_size;
174
3.91k
      l3_l4_size   -= p.num_subtables * 8;
175
3.91k
      l4_plus_size += subtables_size + remaining_size;
176
177
3.91k
      if (l2_l3_size < (1 << 16)
178
3.91k
          && l3_l4_size < (1 << 16)
179
3.90k
          && l4_plus_size < (1 << 16)) continue; // this lookup fits within all layers groups
180
181
1.83k
      layers_full = true;
182
1.83k
    }
183
184
2.63k
    if (!ext_context.lookups.get(p.lookup_index)->make_extension (ext_context, p.lookup_index))
185
0
      return false;
186
2.63k
  }
187
188
1.86k
  return true;
189
1.86k
}
hb-subset.cc:_promote_extensions_if_needed(graph::gsubgpos_graph_context_t&)
Line
Count
Source
103
1.86k
{
104
  // Simple Algorithm (v1, current):
105
  // 1. Calculate how many bytes each non-extension lookup consumes.
106
  // 2. Select up to 64k of those to remain as non-extension (greedy, highest subtables per byte first)
107
  // 3. Promote the rest.
108
  //
109
  // Advanced Algorithm (v2, not implemented):
110
  // 1. Perform connected component analysis using lookups as roots.
111
  // 2. Compute size of each connected component.
112
  // 3. Select up to 64k worth of connected components to remain as non-extensions.
113
  //    (greedy, highest subtables per byte first)
114
  // 4. Promote the rest.
115
116
  // TODO(garretrieger): support extension demotion, then consider all lookups. Requires advanced algo.
117
  // TODO(garretrieger): also support extension promotion during iterative resolution phase, then
118
  //                     we can use a less conservative threshold here.
119
  // TODO(grieger): skip this for the 24 bit case.
120
1.86k
  if (!ext_context.lookups) return true;
121
122
1.86k
  unsigned total_lookup_table_sizes = 0;
123
1.86k
  hb_vector_t<lookup_size_t> lookup_sizes;
124
1.86k
  lookup_sizes.alloc (ext_context.lookups.get_population (), true);
125
126
1.86k
  for (unsigned lookup_index : ext_context.lookups.keys ())
127
4.72k
  {
128
4.72k
    const auto& lookup_v = ext_context.graph.vertices_[lookup_index];
129
4.72k
    total_lookup_table_sizes += lookup_v.table_size ();
130
131
4.72k
    const graph::Lookup* lookup = ext_context.lookups.get(lookup_index);
132
4.72k
    hb_set_t visited;
133
4.72k
    lookup_sizes.push (lookup_size_t {
134
4.72k
        lookup_index,
135
4.72k
        ext_context.graph.find_subgraph_size (lookup_index, visited),
136
4.72k
        lookup->number_of_subtables (),
137
4.72k
      });
138
4.72k
  }
139
140
1.86k
  lookup_sizes.qsort ();
141
142
1.86k
  size_t lookup_list_size = ext_context.graph.vertices_[ext_context.lookup_list_index].table_size ();
143
1.86k
  size_t l2_l3_size = lookup_list_size + total_lookup_table_sizes; // Lookup List + Lookups
144
1.86k
  size_t l3_l4_size = total_lookup_table_sizes; // Lookups + SubTables
145
1.86k
  size_t l4_plus_size = 0; // SubTables + their descendants
146
147
  // Start by assuming all lookups are using extension subtables, this size will be removed later
148
  // if it's decided to not make a lookup extension.
149
1.86k
  for (auto p : lookup_sizes)
150
4.72k
  {
151
    // TODO(garretrieger): this overestimates the extension subtables size because some extension subtables may be
152
    //                     reused. However, we can't correct this until we have connected component analysis in place.
153
4.72k
    unsigned subtables_size = p.num_subtables * 8;
154
4.72k
    l3_l4_size += subtables_size;
155
4.72k
    l4_plus_size += subtables_size;
156
4.72k
  }
157
158
1.86k
  bool layers_full = false;
159
1.86k
  for (auto p : lookup_sizes)
160
4.72k
  {
161
4.72k
    const graph::Lookup* lookup = ext_context.lookups.get(p.lookup_index);
162
4.72k
    if (lookup->is_extension (ext_context.table_tag))
163
      // already an extension so size is counted by the loop above.
164
8
      continue;
165
166
4.71k
    if (!layers_full)
167
3.91k
    {
168
3.91k
      size_t lookup_size = ext_context.graph.vertices_[p.lookup_index].table_size ();
169
3.91k
      hb_set_t visited;
170
3.91k
      size_t subtables_size = ext_context.graph.find_subgraph_size (p.lookup_index, visited, 1) - lookup_size;
171
3.91k
      size_t remaining_size = p.size - subtables_size - lookup_size;
172
173
3.91k
      l3_l4_size   += subtables_size;
174
3.91k
      l3_l4_size   -= p.num_subtables * 8;
175
3.91k
      l4_plus_size += subtables_size + remaining_size;
176
177
3.91k
      if (l2_l3_size < (1 << 16)
178
3.91k
          && l3_l4_size < (1 << 16)
179
3.90k
          && l4_plus_size < (1 << 16)) continue; // this lookup fits within all layers groups
180
181
1.83k
      layers_full = true;
182
1.83k
    }
183
184
2.63k
    if (!ext_context.lookups.get(p.lookup_index)->make_extension (ext_context, p.lookup_index))
185
0
      return false;
186
2.63k
  }
187
188
1.86k
  return true;
189
1.86k
}
Unexecuted instantiation: hb-subset-table-layout.cc:_promote_extensions_if_needed(graph::gsubgpos_graph_context_t&)
Unexecuted instantiation: hb-subset-table-var.cc:_promote_extensions_if_needed(graph::gsubgpos_graph_context_t&)
Unexecuted instantiation: hb-subset-table-cff.cc:_promote_extensions_if_needed(graph::gsubgpos_graph_context_t&)
Unexecuted instantiation: hb-subset-table-color.cc:_promote_extensions_if_needed(graph::gsubgpos_graph_context_t&)
Unexecuted instantiation: hb-subset-table-other.cc:_promote_extensions_if_needed(graph::gsubgpos_graph_context_t&)
190
191
static inline
192
bool _try_isolating_subgraphs (const hb_vector_t<graph::overflow_record_t>& overflows,
193
                               graph_t& sorted_graph)
194
32.3k
{
195
32.3k
  unsigned space = 0;
196
32.3k
  hb_set_t roots_to_isolate;
197
198
8.22M
  for (int i = overflows.length - 1; i >= 0; i--)
199
8.19M
  {
200
8.19M
    const graph::overflow_record_t& r = overflows[i];
201
202
8.19M
    unsigned root;
203
8.19M
    unsigned overflow_space = sorted_graph.space_for (r.parent, &root);
204
8.19M
    if (!overflow_space) continue;
205
2.54M
    if (sorted_graph.num_roots_for_space (overflow_space) <= 1) continue;
206
207
674k
    if (!space) {
208
7.38k
      space = overflow_space;
209
7.38k
    }
210
211
674k
    if (space == overflow_space)
212
338k
      roots_to_isolate.add(root);
213
674k
  }
214
215
32.3k
  if (!roots_to_isolate) return false;
216
217
7.38k
  unsigned maximum_to_move = hb_max ((sorted_graph.num_roots_for_space (space) / 2u), 1u);
218
7.38k
  if (roots_to_isolate.get_population () > maximum_to_move) {
219
    // Only move at most half of the roots in a space at a time.
220
    //
221
    // Note: this was ported from non-stable ids to stable ids. So to retain the same behaviour
222
    // with regards to which roots are removed from the set we need to remove them in the topological
223
    // order, not the object id order.
224
4.06k
    int extra = roots_to_isolate.get_population () - maximum_to_move;
225
2.42M
    for (unsigned id : sorted_graph.ordering_) {
226
2.42M
      if (!extra) break;
227
2.42M
      if (roots_to_isolate.has(id)) {
228
13.0k
        roots_to_isolate.del(id);
229
13.0k
        extra--;
230
13.0k
      }
231
2.42M
    }
232
4.06k
  }
233
234
7.38k
  DEBUG_MSG (SUBSET_REPACK, nullptr,
235
7.38k
             "Overflow in space %u (%u roots). Moving %u roots to space %u.",
236
7.38k
             space,
237
7.38k
             sorted_graph.num_roots_for_space (space),
238
7.38k
             roots_to_isolate.get_population (),
239
7.38k
             sorted_graph.next_space ());
240
241
7.38k
  sorted_graph.isolate_subgraph (roots_to_isolate);
242
7.38k
  sorted_graph.move_to_new_space (roots_to_isolate);
243
244
7.38k
  return true;
245
32.3k
}
hb-subset.cc:_try_isolating_subgraphs(hb_vector_t<graph::overflow_record_t, false> const&, graph::graph_t&)
Line
Count
Source
194
32.3k
{
195
32.3k
  unsigned space = 0;
196
32.3k
  hb_set_t roots_to_isolate;
197
198
8.22M
  for (int i = overflows.length - 1; i >= 0; i--)
199
8.19M
  {
200
8.19M
    const graph::overflow_record_t& r = overflows[i];
201
202
8.19M
    unsigned root;
203
8.19M
    unsigned overflow_space = sorted_graph.space_for (r.parent, &root);
204
8.19M
    if (!overflow_space) continue;
205
2.54M
    if (sorted_graph.num_roots_for_space (overflow_space) <= 1) continue;
206
207
674k
    if (!space) {
208
7.38k
      space = overflow_space;
209
7.38k
    }
210
211
674k
    if (space == overflow_space)
212
338k
      roots_to_isolate.add(root);
213
674k
  }
214
215
32.3k
  if (!roots_to_isolate) return false;
216
217
7.38k
  unsigned maximum_to_move = hb_max ((sorted_graph.num_roots_for_space (space) / 2u), 1u);
218
7.38k
  if (roots_to_isolate.get_population () > maximum_to_move) {
219
    // Only move at most half of the roots in a space at a time.
220
    //
221
    // Note: this was ported from non-stable ids to stable ids. So to retain the same behaviour
222
    // with regards to which roots are removed from the set we need to remove them in the topological
223
    // order, not the object id order.
224
4.06k
    int extra = roots_to_isolate.get_population () - maximum_to_move;
225
2.42M
    for (unsigned id : sorted_graph.ordering_) {
226
2.42M
      if (!extra) break;
227
2.42M
      if (roots_to_isolate.has(id)) {
228
13.0k
        roots_to_isolate.del(id);
229
13.0k
        extra--;
230
13.0k
      }
231
2.42M
    }
232
4.06k
  }
233
234
7.38k
  DEBUG_MSG (SUBSET_REPACK, nullptr,
235
7.38k
             "Overflow in space %u (%u roots). Moving %u roots to space %u.",
236
7.38k
             space,
237
7.38k
             sorted_graph.num_roots_for_space (space),
238
7.38k
             roots_to_isolate.get_population (),
239
7.38k
             sorted_graph.next_space ());
240
241
7.38k
  sorted_graph.isolate_subgraph (roots_to_isolate);
242
7.38k
  sorted_graph.move_to_new_space (roots_to_isolate);
243
244
7.38k
  return true;
245
32.3k
}
Unexecuted instantiation: hb-subset-table-layout.cc:_try_isolating_subgraphs(hb_vector_t<graph::overflow_record_t, false> const&, graph::graph_t&)
Unexecuted instantiation: hb-subset-table-var.cc:_try_isolating_subgraphs(hb_vector_t<graph::overflow_record_t, false> const&, graph::graph_t&)
Unexecuted instantiation: hb-subset-table-cff.cc:_try_isolating_subgraphs(hb_vector_t<graph::overflow_record_t, false> const&, graph::graph_t&)
Unexecuted instantiation: hb-subset-table-color.cc:_try_isolating_subgraphs(hb_vector_t<graph::overflow_record_t, false> const&, graph::graph_t&)
Unexecuted instantiation: hb-subset-table-other.cc:_try_isolating_subgraphs(hb_vector_t<graph::overflow_record_t, false> const&, graph::graph_t&)
246
247
static inline
248
bool _resolve_shared_overflow(const hb_vector_t<graph::overflow_record_t>& overflows,
249
                              int overflow_index,
250
                              graph_t& sorted_graph)
251
150k
{
252
150k
  const graph::overflow_record_t& r = overflows[overflow_index];
253
254
  // Find all of the parents in overflowing links that link to this
255
  // same child node. We will then try duplicating the child node and
256
  // re-assigning all of these parents to the duplicate.
257
150k
  hb_set_t parents;
258
150k
  parents.add(r.parent);
259
89.6M
  for (int i = overflow_index - 1; i >= 0; i--) {
260
89.5M
    const graph::overflow_record_t& r2 = overflows[i];
261
89.5M
    if (r2.child == r.child) {
262
150k
      parents.add(r2.parent);
263
150k
    }
264
89.5M
  }
265
266
150k
  unsigned result = sorted_graph.duplicate(&parents, r.child);
267
150k
  if (result == (unsigned) -1 && parents.get_population() > 2) {
268
    // All links to the child are overflowing, so we can't include all
269
    // in the duplication. Remove one parent from the duplication.
270
    // Remove the lowest index parent, which will be the closest to the child.
271
5.74k
    parents.del(parents.get_min());
272
5.74k
    result = sorted_graph.duplicate(&parents, r.child);
273
5.74k
  }
274
275
150k
  if (result == (unsigned) -1) return false;
276
277
11.6k
  if (parents.get_population() > 1) {
278
    // If the duplicated node has more than one parent pre-emptively raise it's priority to the maximum.
279
    // This will place it close to the parents. Node's with only one parent, don't need this as normal overflow
280
    // resolution will raise priority if needed.
281
    //
282
    // Reasoning: most of the parents to this child are likely at the same layer in the graph. Duplicating
283
    // the child will theoretically allow it to be placed closer to it's parents. However, due to the shortest
284
    // distance sort by default it's placement will remain in the same layer, thus it will remain in roughly the
285
    // same position (and distance from parents) as the original child node. The overflow resolution will attempt
286
    // to move nodes closer, but only for non-shared nodes. Since this node is shared, it will simply be given
287
    // further duplication which defeats the attempt to duplicate with multiple parents. To fix this we
288
    // pre-emptively raise priority now which allows the duplicated node to pack into the same layer as it's parents.
289
6.63k
    sorted_graph.vertices_[result].give_max_priority();
290
6.63k
  }
291
292
11.6k
  return true;
293
150k
}
hb-subset.cc:_resolve_shared_overflow(hb_vector_t<graph::overflow_record_t, false> const&, int, graph::graph_t&)
Line
Count
Source
251
150k
{
252
150k
  const graph::overflow_record_t& r = overflows[overflow_index];
253
254
  // Find all of the parents in overflowing links that link to this
255
  // same child node. We will then try duplicating the child node and
256
  // re-assigning all of these parents to the duplicate.
257
150k
  hb_set_t parents;
258
150k
  parents.add(r.parent);
259
89.6M
  for (int i = overflow_index - 1; i >= 0; i--) {
260
89.5M
    const graph::overflow_record_t& r2 = overflows[i];
261
89.5M
    if (r2.child == r.child) {
262
150k
      parents.add(r2.parent);
263
150k
    }
264
89.5M
  }
265
266
150k
  unsigned result = sorted_graph.duplicate(&parents, r.child);
267
150k
  if (result == (unsigned) -1 && parents.get_population() > 2) {
268
    // All links to the child are overflowing, so we can't include all
269
    // in the duplication. Remove one parent from the duplication.
270
    // Remove the lowest index parent, which will be the closest to the child.
271
5.74k
    parents.del(parents.get_min());
272
5.74k
    result = sorted_graph.duplicate(&parents, r.child);
273
5.74k
  }
274
275
150k
  if (result == (unsigned) -1) return false;
276
277
11.6k
  if (parents.get_population() > 1) {
278
    // If the duplicated node has more than one parent pre-emptively raise it's priority to the maximum.
279
    // This will place it close to the parents. Node's with only one parent, don't need this as normal overflow
280
    // resolution will raise priority if needed.
281
    //
282
    // Reasoning: most of the parents to this child are likely at the same layer in the graph. Duplicating
283
    // the child will theoretically allow it to be placed closer to it's parents. However, due to the shortest
284
    // distance sort by default it's placement will remain in the same layer, thus it will remain in roughly the
285
    // same position (and distance from parents) as the original child node. The overflow resolution will attempt
286
    // to move nodes closer, but only for non-shared nodes. Since this node is shared, it will simply be given
287
    // further duplication which defeats the attempt to duplicate with multiple parents. To fix this we
288
    // pre-emptively raise priority now which allows the duplicated node to pack into the same layer as it's parents.
289
6.63k
    sorted_graph.vertices_[result].give_max_priority();
290
6.63k
  }
291
292
11.6k
  return true;
293
150k
}
Unexecuted instantiation: hb-subset-table-layout.cc:_resolve_shared_overflow(hb_vector_t<graph::overflow_record_t, false> const&, int, graph::graph_t&)
Unexecuted instantiation: hb-subset-table-var.cc:_resolve_shared_overflow(hb_vector_t<graph::overflow_record_t, false> const&, int, graph::graph_t&)
Unexecuted instantiation: hb-subset-table-cff.cc:_resolve_shared_overflow(hb_vector_t<graph::overflow_record_t, false> const&, int, graph::graph_t&)
Unexecuted instantiation: hb-subset-table-color.cc:_resolve_shared_overflow(hb_vector_t<graph::overflow_record_t, false> const&, int, graph::graph_t&)
Unexecuted instantiation: hb-subset-table-other.cc:_resolve_shared_overflow(hb_vector_t<graph::overflow_record_t, false> const&, int, graph::graph_t&)
294
295
static inline
296
bool _process_overflows (const hb_vector_t<graph::overflow_record_t>& overflows,
297
                         hb_set_t& priority_bumped_parents,
298
                         graph_t& sorted_graph)
299
24.9k
{
300
24.9k
  bool resolution_attempted = false;
301
302
  // Try resolving the furthest overflows first.
303
1.67M
  for (int i = overflows.length - 1; i >= 0; i--)
304
1.65M
  {
305
1.65M
    const graph::overflow_record_t& r = overflows[i];
306
1.65M
    const auto& child = sorted_graph.vertices_[r.child];
307
1.65M
    if (child.is_shared ())
308
150k
    {
309
      // The child object is shared, we may be able to eliminate the overflow
310
      // by duplicating it.
311
150k
      if (_resolve_shared_overflow(overflows, i, sorted_graph))
312
11.6k
        return true;
313
314
      // Sometimes we can't duplicate a node which looks shared because it's not actually shared
315
      // (eg. all links from the same parent) in this case continue on to other resolution options.
316
150k
    }
317
318
1.64M
    if (child.is_leaf () && !priority_bumped_parents.has (r.parent))
319
857k
    {
320
      // This object is too far from it's parent, attempt to move it closer.
321
      //
322
      // TODO(garretrieger): initially limiting this to leaf's since they can be
323
      //                     moved closer with fewer consequences. However, this can
324
      //                     likely can be used for non-leafs as well.
325
      // TODO(garretrieger): also try lowering priority of the parent. Make it
326
      //                     get placed further up in the ordering, closer to it's children.
327
      //                     this is probably preferable if the total size of the parent object
328
      //                     is < then the total size of the children (and the parent can be moved).
329
      //                     Since in that case moving the parent will cause a smaller increase in
330
      //                     the length of other offsets.
331
857k
      if (sorted_graph.raise_childrens_priority (r.parent)) {
332
35.2k
        priority_bumped_parents.add (r.parent);
333
35.2k
        resolution_attempted = true;
334
35.2k
      }
335
857k
      continue;
336
857k
    }
337
338
    // TODO(garretrieger): add additional offset resolution strategies
339
    // - Promotion to extension lookups.
340
    // - Table splitting.
341
1.64M
  }
342
343
13.2k
  return resolution_attempted;
344
24.9k
}
hb-subset.cc:_process_overflows(hb_vector_t<graph::overflow_record_t, false> const&, hb_set_t&, graph::graph_t&)
Line
Count
Source
299
24.9k
{
300
24.9k
  bool resolution_attempted = false;
301
302
  // Try resolving the furthest overflows first.
303
1.67M
  for (int i = overflows.length - 1; i >= 0; i--)
304
1.65M
  {
305
1.65M
    const graph::overflow_record_t& r = overflows[i];
306
1.65M
    const auto& child = sorted_graph.vertices_[r.child];
307
1.65M
    if (child.is_shared ())
308
150k
    {
309
      // The child object is shared, we may be able to eliminate the overflow
310
      // by duplicating it.
311
150k
      if (_resolve_shared_overflow(overflows, i, sorted_graph))
312
11.6k
        return true;
313
314
      // Sometimes we can't duplicate a node which looks shared because it's not actually shared
315
      // (eg. all links from the same parent) in this case continue on to other resolution options.
316
150k
    }
317
318
1.64M
    if (child.is_leaf () && !priority_bumped_parents.has (r.parent))
319
857k
    {
320
      // This object is too far from it's parent, attempt to move it closer.
321
      //
322
      // TODO(garretrieger): initially limiting this to leaf's since they can be
323
      //                     moved closer with fewer consequences. However, this can
324
      //                     likely can be used for non-leafs as well.
325
      // TODO(garretrieger): also try lowering priority of the parent. Make it
326
      //                     get placed further up in the ordering, closer to it's children.
327
      //                     this is probably preferable if the total size of the parent object
328
      //                     is < then the total size of the children (and the parent can be moved).
329
      //                     Since in that case moving the parent will cause a smaller increase in
330
      //                     the length of other offsets.
331
857k
      if (sorted_graph.raise_childrens_priority (r.parent)) {
332
35.2k
        priority_bumped_parents.add (r.parent);
333
35.2k
        resolution_attempted = true;
334
35.2k
      }
335
857k
      continue;
336
857k
    }
337
338
    // TODO(garretrieger): add additional offset resolution strategies
339
    // - Promotion to extension lookups.
340
    // - Table splitting.
341
1.64M
  }
342
343
13.2k
  return resolution_attempted;
344
24.9k
}
Unexecuted instantiation: hb-subset-table-layout.cc:_process_overflows(hb_vector_t<graph::overflow_record_t, false> const&, hb_set_t&, graph::graph_t&)
Unexecuted instantiation: hb-subset-table-var.cc:_process_overflows(hb_vector_t<graph::overflow_record_t, false> const&, hb_set_t&, graph::graph_t&)
Unexecuted instantiation: hb-subset-table-cff.cc:_process_overflows(hb_vector_t<graph::overflow_record_t, false> const&, hb_set_t&, graph::graph_t&)
Unexecuted instantiation: hb-subset-table-color.cc:_process_overflows(hb_vector_t<graph::overflow_record_t, false> const&, hb_set_t&, graph::graph_t&)
Unexecuted instantiation: hb-subset-table-other.cc:_process_overflows(hb_vector_t<graph::overflow_record_t, false> const&, hb_set_t&, graph::graph_t&)
345
346
inline bool
347
hb_resolve_graph_overflows (hb_tag_t table_tag,
348
                            unsigned max_rounds ,
349
                            bool always_recalculate_extensions,
350
                            graph_t& sorted_graph /* IN/OUT */)
351
4.19k
{
352
4.19k
  DEBUG_MSG (SUBSET_REPACK, nullptr, "Repacking %c%c%c%c.", HB_UNTAG(table_tag));
353
4.19k
  sorted_graph.sort_shortest_distance ();
354
4.19k
  if (sorted_graph.in_error ())
355
0
  {
356
0
    DEBUG_MSG (SUBSET_REPACK, nullptr, "Sorted graph in error state after initial sort.");
357
0
    return false;
358
0
  }
359
360
4.19k
  bool will_overflow = graph::will_overflow (sorted_graph);
361
4.19k
  if (!will_overflow)
362
340
    return true;
363
364
3.85k
  bool is_gsub_or_gpos = (table_tag == HB_OT_TAG_GPOS ||  table_tag == HB_OT_TAG_GSUB);
365
3.85k
  graph::gsubgpos_graph_context_t ext_context (table_tag, sorted_graph);
366
3.85k
  if (is_gsub_or_gpos && will_overflow)
367
3.76k
  {
368
3.76k
    DEBUG_MSG (SUBSET_REPACK, nullptr, "Applying GSUB/GPOS repacking specializations.");
369
3.76k
    if (always_recalculate_extensions)
370
1.86k
    {
371
1.86k
      DEBUG_MSG (SUBSET_REPACK, nullptr, "Splitting subtables if needed.");
372
1.86k
      if (!_presplit_subtables_if_needed (ext_context)) {
373
4
        DEBUG_MSG (SUBSET_REPACK, nullptr, "Subtable splitting failed.");
374
4
        return false;
375
4
      }
376
377
1.86k
      DEBUG_MSG (SUBSET_REPACK, nullptr, "Promoting lookups to extensions if needed.");
378
1.86k
      if (!_promote_extensions_if_needed (ext_context)) {
379
0
        DEBUG_MSG (SUBSET_REPACK, nullptr, "Extensions promotion failed.");
380
0
        return false;
381
0
      }
382
1.86k
    }
383
384
3.76k
    DEBUG_MSG (SUBSET_REPACK, nullptr, "Assigning spaces to 32 bit subgraphs.");
385
3.76k
    if (sorted_graph.assign_spaces ())
386
2.32k
      sorted_graph.sort_shortest_distance ();
387
1.43k
    else
388
1.43k
      sorted_graph.sort_shortest_distance_if_needed ();
389
3.76k
  }
390
391
3.85k
  unsigned round = 0;
392
3.85k
  unsigned total_iterations = 0;
393
3.85k
  hb_vector_t<graph::overflow_record_t> overflows;
394
  // TODO(garretrieger): select a good limit for max rounds.
395
32.9k
  while (!sorted_graph.in_error ()
396
32.6k
         && graph::will_overflow (sorted_graph, &overflows)
397
32.5k
         && round < max_rounds
398
32.3k
         && total_iterations < HB_REPACKER_MAX_ITERATIONS) {
399
32.3k
    DEBUG_MSG (SUBSET_REPACK, nullptr, "=== Overflow resolution round %u ===", round);
400
32.3k
    print_overflows (sorted_graph, overflows);
401
402
32.3k
    total_iterations++;
403
32.3k
    hb_set_t priority_bumped_parents;
404
405
32.3k
    if (!_try_isolating_subgraphs (overflows, sorted_graph))
406
24.9k
    {
407
24.9k
      round++;
408
24.9k
      if (!_process_overflows (overflows, priority_bumped_parents, sorted_graph))
409
3.26k
      {
410
3.26k
        DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :(");
411
3.26k
        break;
412
3.26k
      }
413
24.9k
    }
414
415
29.0k
    sorted_graph.sort_shortest_distance ();
416
29.0k
  }
417
418
3.85k
  if (sorted_graph.in_error ())
419
292
  {
420
292
    DEBUG_MSG (SUBSET_REPACK, nullptr, "Sorted graph in error state.");
421
292
    return false;
422
292
  }
423
424
3.56k
  if (graph::will_overflow (sorted_graph))
425
3.45k
  {
426
3.45k
    if (is_gsub_or_gpos && !always_recalculate_extensions) {
427
      // If this a GSUB/GPOS table and we didn't try to extension promotion and table splitting then
428
      // as a last ditch effort, re-run the repacker with it enabled.
429
1.86k
      DEBUG_MSG (SUBSET_REPACK, nullptr, "Failed to find a resolution. Re-running with extension promotion and table splitting enabled.");
430
1.86k
      return hb_resolve_graph_overflows (table_tag, max_rounds, true, sorted_graph);
431
1.86k
    }
432
433
1.58k
    DEBUG_MSG (SUBSET_REPACK, nullptr, "Offset overflow resolution failed.");
434
1.58k
    return false;
435
3.45k
  }
436
437
106
  return true;
438
3.56k
}
439
440
/*
441
 * Attempts to modify the topological sorting of the provided object graph to
442
 * eliminate offset overflows in the links between objects of the graph. If a
443
 * non-overflowing ordering is found the updated graph is serialized it into the
444
 * provided serialization context.
445
 *
446
 * If necessary the structure of the graph may be modified in ways that do not
447
 * affect the functionality of the graph. For example shared objects may be
448
 * duplicated.
449
 *
450
 * For a detailed writeup describing how the algorithm operates see:
451
 * docs/repacker.md
452
 */
453
template<typename T>
454
inline hb_blob_t*
455
hb_resolve_overflows (const T& packed,
456
                      hb_tag_t table_tag,
457
                      unsigned max_rounds = 32,
458
2.33k
                      bool recalculate_extensions = false) {
459
2.33k
  graph_t sorted_graph (packed);
460
2.33k
  if (sorted_graph.in_error ())
461
0
  {
462
    // Invalid graph definition.
463
0
    return nullptr;
464
0
  }
465
466
2.33k
  if (!sorted_graph.is_fully_connected ())
467
0
  {
468
0
    sorted_graph.print_orphaned_nodes ();
469
0
    return nullptr;
470
0
  }
471
472
2.33k
  if (sorted_graph.in_error ())
473
0
  {
474
    // Allocations failed somewhere
475
0
    DEBUG_MSG (SUBSET_REPACK, nullptr,
476
0
               "Graph is in error, likely due to a memory allocation error.");
477
0
    return nullptr;
478
0
  }
479
480
2.33k
  if (!hb_resolve_graph_overflows (table_tag, max_rounds, recalculate_extensions, sorted_graph))
481
1.88k
    return nullptr;
482
483
446
  return graph::serialize (sorted_graph);
484
2.33k
}
485
486
#endif /* HB_REPACKER_HH */