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