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-priority-queue.hh"
33 #include "hb-serialize.hh"
34 #include "hb-vector.hh"
35
36
37 struct graph_t
38 {
39 struct vertex_t
40 {
vertex_tgraph_t::vertex_t41 vertex_t () :
42 distance (0),
43 incoming_edges (0),
44 start (0),
45 end (0),
46 priority(0) {}
47
finigraph_t::vertex_t48 void fini () { obj.fini (); }
49
50 hb_serialize_context_t::object_t obj;
51 int64_t distance;
52 unsigned incoming_edges;
53 unsigned start;
54 unsigned end;
55 unsigned priority;
56
is_sharedgraph_t::vertex_t57 bool is_shared () const
58 {
59 return incoming_edges > 1;
60 }
61
is_leafgraph_t::vertex_t62 bool is_leaf () const
63 {
64 return !obj.links.length;
65 }
66
raise_prioritygraph_t::vertex_t67 void raise_priority ()
68 {
69 priority++;
70 }
71
modified_distancegraph_t::vertex_t72 int64_t modified_distance (unsigned order) const
73 {
74 // TODO(garretrieger): once priority is high enough, should try
75 // setting distance = 0 which will force to sort immediately after
76 // it's parent where possible.
77
78 int64_t modified_distance =
79 hb_min (hb_max(distance + distance_modifier (), 0), 0x7FFFFFFFFF);
80 return (modified_distance << 24) | (0x00FFFFFF & order);
81 }
82
distance_modifiergraph_t::vertex_t83 int64_t distance_modifier () const
84 {
85 if (!priority) return 0;
86 int64_t table_size = obj.tail - obj.head;
87 return -(table_size - table_size / (1 << hb_min(priority, 16u)));
88 }
89 };
90
91 struct overflow_record_t
92 {
93 unsigned parent;
94 const hb_serialize_context_t::object_t::link_t* link;
95 };
96
97 struct clone_buffer_t
98 {
clone_buffer_tgraph_t::clone_buffer_t99 clone_buffer_t () : head (nullptr), tail (nullptr) {}
100
copygraph_t::clone_buffer_t101 bool copy (const hb_serialize_context_t::object_t& object)
102 {
103 fini ();
104 unsigned size = object.tail - object.head;
105 head = (char*) malloc (size);
106 if (!head) return false;
107
108 memcpy (head, object.head, size);
109 tail = head + size;
110 return true;
111 }
112
113 char* head;
114 char* tail;
115
finigraph_t::clone_buffer_t116 void fini ()
117 {
118 if (!head) return;
119 free (head);
120 head = nullptr;
121 }
122 };
123
124 /*
125 * A topological sorting of an object graph. Ordered
126 * in reverse serialization order (first object in the
127 * serialization is at the end of the list). This matches
128 * the 'packed' object stack used internally in the
129 * serializer
130 */
graph_tgraph_t131 graph_t (const hb_vector_t<hb_serialize_context_t::object_t *>& objects)
132 : edge_count_invalid (true),
133 distance_invalid (true),
134 positions_invalid (true),
135 successful (true)
136 {
137 bool removed_nil = false;
138 for (unsigned i = 0; i < objects.length; i++)
139 {
140 // TODO(grieger): check all links point to valid objects.
141
142 // If this graph came from a serialization buffer object 0 is the
143 // nil object. We don't need it for our purposes here so drop it.
144 if (i == 0 && !objects[i])
145 {
146 removed_nil = true;
147 continue;
148 }
149
150 vertex_t* v = vertices_.push ();
151 if (check_success (!vertices_.in_error ()))
152 v->obj = *objects[i];
153 if (!removed_nil) continue;
154 for (unsigned i = 0; i < v->obj.links.length; i++)
155 // Fix indices to account for removed nil object.
156 v->obj.links[i].objidx--;
157 }
158 }
159
~graph_tgraph_t160 ~graph_t ()
161 {
162 vertices_.fini_deep ();
163 clone_buffers_.fini_deep ();
164 }
165
in_errorgraph_t166 bool in_error () const
167 {
168 return !successful || vertices_.in_error () || clone_buffers_.in_error ();
169 }
170
rootgraph_t171 const vertex_t& root () const
172 {
173 return vertices_[root_idx ()];
174 }
175
root_idxgraph_t176 unsigned root_idx () const
177 {
178 // Object graphs are in reverse order, the first object is at the end
179 // of the vector. Since the graph is topologically sorted it's safe to
180 // assume the first object has no incoming edges.
181 return vertices_.length - 1;
182 }
183
objectgraph_t184 const hb_serialize_context_t::object_t& object(unsigned i) const
185 {
186 return vertices_[i].obj;
187 }
188
189 /*
190 * serialize graph into the provided serialization buffer.
191 */
serializegraph_t192 void serialize (hb_serialize_context_t* c) const
193 {
194 c->start_serialize<void> ();
195 for (unsigned i = 0; i < vertices_.length; i++) {
196 c->push ();
197
198 size_t size = vertices_[i].obj.tail - vertices_[i].obj.head;
199 char* start = c->allocate_size <char> (size);
200 if (!start) return;
201
202 memcpy (start, vertices_[i].obj.head, size);
203
204 for (const auto& link : vertices_[i].obj.links)
205 serialize_link (link, start, c);
206
207 // All duplications are already encoded in the graph, so don't
208 // enable sharing during packing.
209 c->pop_pack (false);
210 }
211 c->end_serialize ();
212 }
213
214 /*
215 * Generates a new topological sorting of graph using Kahn's
216 * algorithm: https://en.wikipedia.org/wiki/Topological_sorting#Algorithms
217 */
sort_kahngraph_t218 void sort_kahn ()
219 {
220 positions_invalid = true;
221
222 if (vertices_.length <= 1) {
223 // Graph of 1 or less doesn't need sorting.
224 return;
225 }
226
227 hb_vector_t<unsigned> queue;
228 hb_vector_t<vertex_t> sorted_graph;
229 hb_vector_t<unsigned> id_map;
230 if (unlikely (!check_success (id_map.resize (vertices_.length)))) return;
231
232 hb_vector_t<unsigned> removed_edges;
233 if (unlikely (!check_success (removed_edges.resize (vertices_.length)))) return;
234 update_incoming_edge_count ();
235
236 queue.push (root_idx ());
237 int new_id = vertices_.length - 1;
238
239 while (!queue.in_error () && queue.length)
240 {
241 unsigned next_id = queue[0];
242 queue.remove (0);
243
244 vertex_t& next = vertices_[next_id];
245 sorted_graph.push (next);
246 id_map[next_id] = new_id--;
247
248 for (const auto& link : next.obj.links) {
249 removed_edges[link.objidx]++;
250 if (!(vertices_[link.objidx].incoming_edges - removed_edges[link.objidx]))
251 queue.push (link.objidx);
252 }
253 }
254
255 check_success (!queue.in_error ());
256 check_success (!sorted_graph.in_error ());
257 if (!check_success (new_id == -1))
258 DEBUG_MSG (SUBSET_REPACK, nullptr, "Graph is not fully connected.");
259
260 remap_obj_indices (id_map, &sorted_graph);
261
262 sorted_graph.as_array ().reverse ();
263
264 vertices_.fini_deep ();
265 vertices_ = sorted_graph;
266 sorted_graph.fini_deep ();
267 }
268
269 /*
270 * Generates a new topological sorting of graph ordered by the shortest
271 * distance to each node.
272 */
sort_shortest_distancegraph_t273 void sort_shortest_distance ()
274 {
275 positions_invalid = true;
276
277 if (vertices_.length <= 1) {
278 // Graph of 1 or less doesn't need sorting.
279 return;
280 }
281
282 update_distances ();
283
284 hb_priority_queue_t queue;
285 hb_vector_t<vertex_t> sorted_graph;
286 hb_vector_t<unsigned> id_map;
287 if (unlikely (!check_success (id_map.resize (vertices_.length)))) return;
288
289 hb_vector_t<unsigned> removed_edges;
290 if (unlikely (!check_success (removed_edges.resize (vertices_.length)))) return;
291 update_incoming_edge_count ();
292
293 queue.insert (root ().modified_distance (0), root_idx ());
294 int new_id = root_idx ();
295 unsigned order = 1;
296 while (!queue.in_error () && !queue.is_empty ())
297 {
298 unsigned next_id = queue.pop_minimum().second;
299
300 vertex_t& next = vertices_[next_id];
301 sorted_graph.push (next);
302 id_map[next_id] = new_id--;
303
304 for (const auto& link : next.obj.links) {
305 removed_edges[link.objidx]++;
306 if (!(vertices_[link.objidx].incoming_edges - removed_edges[link.objidx]))
307 // Add the order that the links were encountered to the priority.
308 // This ensures that ties between priorities objects are broken in a consistent
309 // way. More specifically this is set up so that if a set of objects have the same
310 // distance they'll be added to the topological order in the order that they are
311 // referenced from the parent object.
312 queue.insert (vertices_[link.objidx].modified_distance (order++),
313 link.objidx);
314 }
315 }
316
317 check_success (!queue.in_error ());
318 check_success (!sorted_graph.in_error ());
319 if (!check_success (new_id == -1))
320 DEBUG_MSG (SUBSET_REPACK, nullptr, "Graph is not fully connected.");
321
322 remap_obj_indices (id_map, &sorted_graph);
323
324 sorted_graph.as_array ().reverse ();
325
326 vertices_.fini_deep ();
327 vertices_ = sorted_graph;
328 sorted_graph.fini_deep ();
329 }
330
331 /*
332 * Creates a copy of child and re-assigns the link from
333 * parent to the clone. The copy is a shallow copy, objects
334 * linked from child are not duplicated.
335 */
duplicategraph_t336 void duplicate (unsigned parent_idx, unsigned child_idx)
337 {
338 DEBUG_MSG (SUBSET_REPACK, nullptr, " Duplicating %d => %d",
339 parent_idx, child_idx);
340
341 positions_invalid = true;
342
343 auto* clone = vertices_.push ();
344 auto& child = vertices_[child_idx];
345 clone_buffer_t* buffer = clone_buffers_.push ();
346 if (vertices_.in_error ()
347 || clone_buffers_.in_error ()
348 || !check_success (buffer->copy (child.obj))) {
349 return;
350 }
351
352 clone->obj.head = buffer->head;
353 clone->obj.tail = buffer->tail;
354 clone->distance = child.distance;
355
356 for (const auto& l : child.obj.links)
357 clone->obj.links.push (l);
358
359 check_success (!clone->obj.links.in_error ());
360
361 auto& parent = vertices_[parent_idx];
362 unsigned clone_idx = vertices_.length - 2;
363 for (unsigned i = 0; i < parent.obj.links.length; i++)
364 {
365 auto& l = parent.obj.links[i];
366 if (l.objidx == child_idx)
367 {
368 l.objidx = clone_idx;
369 clone->incoming_edges++;
370 child.incoming_edges--;
371 }
372 }
373
374 // The last object is the root of the graph, so swap back the root to the end.
375 // The root's obj idx does change, however since it's root nothing else refers to it.
376 // all other obj idx's will be unaffected.
377 vertex_t root = vertices_[vertices_.length - 2];
378 vertices_[vertices_.length - 2] = *clone;
379 vertices_[vertices_.length - 1] = root;
380 }
381
382 /*
383 * Raises the sorting priority of all children.
384 */
raise_childrens_prioritygraph_t385 void raise_childrens_priority (unsigned parent_idx)
386 {
387 DEBUG_MSG (SUBSET_REPACK, nullptr, " Raising priority of all children of %d",
388 parent_idx);
389 // This operation doesn't change ordering until a sort is run, so no need
390 // to invalidate positions. It does not change graph structure so no need
391 // to update distances or edge counts.
392 auto& parent = vertices_[parent_idx].obj;
393 for (unsigned i = 0; i < parent.links.length; i++)
394 vertices_[parent.links[i].objidx].raise_priority ();
395 }
396
397 /*
398 * Will any offsets overflow on graph when it's serialized?
399 */
will_overflowgraph_t400 bool will_overflow (hb_vector_t<overflow_record_t>* overflows = nullptr)
401 {
402 if (overflows) overflows->resize (0);
403 update_positions ();
404
405 for (int parent_idx = vertices_.length - 1; parent_idx >= 0; parent_idx--)
406 {
407 for (const auto& link : vertices_[parent_idx].obj.links)
408 {
409 int64_t offset = compute_offset (parent_idx, link);
410 if (is_valid_offset (offset, link))
411 continue;
412
413 if (!overflows) return true;
414
415 overflow_record_t r;
416 r.parent = parent_idx;
417 r.link = &link;
418 overflows->push (r);
419 }
420 }
421
422 if (!overflows) return false;
423 return overflows->length;
424 }
425
print_overflowsgraph_t426 void print_overflows (const hb_vector_t<overflow_record_t>& overflows)
427 {
428 if (!DEBUG_ENABLED(SUBSET_REPACK)) return;
429
430 update_incoming_edge_count ();
431 for (const auto& o : overflows)
432 {
433 const auto& child = vertices_[o.link->objidx];
434 DEBUG_MSG (SUBSET_REPACK, nullptr, " overflow from %d => %d (%d incoming , %d outgoing)",
435 o.parent,
436 o.link->objidx,
437 child.incoming_edges,
438 child.obj.links.length);
439 }
440 }
441
err_other_errorgraph_t442 void err_other_error () { this->successful = false; }
443
444 private:
445
check_successgraph_t446 bool check_success (bool success)
447 { return this->successful && (success || (err_other_error (), false)); }
448
449 /*
450 * Creates a map from objid to # of incoming edges.
451 */
update_incoming_edge_countgraph_t452 void update_incoming_edge_count ()
453 {
454 if (!edge_count_invalid) return;
455
456 for (unsigned i = 0; i < vertices_.length; i++)
457 vertices_[i].incoming_edges = 0;
458
459 for (const vertex_t& v : vertices_)
460 {
461 for (auto& l : v.obj.links)
462 {
463 vertices_[l.objidx].incoming_edges++;
464 }
465 }
466
467 edge_count_invalid = false;
468 }
469
470 /*
471 * compute the serialized start and end positions for each vertex.
472 */
update_positionsgraph_t473 void update_positions ()
474 {
475 if (!positions_invalid) return;
476
477 unsigned current_pos = 0;
478 for (int i = root_idx (); i >= 0; i--)
479 {
480 auto& v = vertices_[i];
481 v.start = current_pos;
482 current_pos += v.obj.tail - v.obj.head;
483 v.end = current_pos;
484 }
485
486 positions_invalid = false;
487 }
488
489 /*
490 * Finds the distance to each object in the graph
491 * from the initial node.
492 */
update_distancesgraph_t493 void update_distances ()
494 {
495 if (!distance_invalid) return;
496
497 // Uses Dijkstra's algorithm to find all of the shortest distances.
498 // https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
499 //
500 // Implementation Note:
501 // Since our priority queue doesn't support fast priority decreases
502 // we instead just add new entries into the queue when a priority changes.
503 // Redundant ones are filtered out later on by the visited set.
504 // According to https://www3.cs.stonybrook.edu/~rezaul/papers/TR-07-54.pdf
505 // for practical performance this is faster then using a more advanced queue
506 // (such as a fibonaacci queue) with a fast decrease priority.
507 for (unsigned i = 0; i < vertices_.length; i++)
508 {
509 if (i == vertices_.length - 1)
510 vertices_[i].distance = 0;
511 else
512 vertices_[i].distance = hb_int_max (int64_t);
513 }
514
515 hb_priority_queue_t queue;
516 queue.insert (0, vertices_.length - 1);
517
518 hb_set_t visited;
519
520 while (!queue.in_error () && !queue.is_empty ())
521 {
522 unsigned next_idx = queue.pop_minimum ().second;
523 if (visited.has (next_idx)) continue;
524 const auto& next = vertices_[next_idx];
525 int64_t next_distance = vertices_[next_idx].distance;
526 visited.add (next_idx);
527
528 for (const auto& link : next.obj.links)
529 {
530 if (visited.has (link.objidx)) continue;
531
532 const auto& child = vertices_[link.objidx].obj;
533 int64_t child_weight = child.tail - child.head +
534 (!link.is_wide ? (1 << 16) : ((int64_t) 1 << 32));
535 int64_t child_distance = next_distance + child_weight;
536
537 if (child_distance < vertices_[link.objidx].distance)
538 {
539 vertices_[link.objidx].distance = child_distance;
540 queue.insert (child_distance, link.objidx);
541 }
542 }
543 }
544
545 check_success (!queue.in_error ());
546 if (!check_success (queue.is_empty ()))
547 {
548 DEBUG_MSG (SUBSET_REPACK, nullptr, "Graph is not fully connected.");
549 return;
550 }
551
552 distance_invalid = false;
553 }
554
compute_offsetgraph_t555 int64_t compute_offset (
556 unsigned parent_idx,
557 const hb_serialize_context_t::object_t::link_t& link) const
558 {
559 const auto& parent = vertices_[parent_idx];
560 const auto& child = vertices_[link.objidx];
561 int64_t offset = 0;
562 switch ((hb_serialize_context_t::whence_t) link.whence) {
563 case hb_serialize_context_t::whence_t::Head:
564 offset = child.start - parent.start; break;
565 case hb_serialize_context_t::whence_t::Tail:
566 offset = child.start - parent.end; break;
567 case hb_serialize_context_t::whence_t::Absolute:
568 offset = child.start; break;
569 }
570
571 assert (offset >= link.bias);
572 offset -= link.bias;
573 return offset;
574 }
575
is_valid_offsetgraph_t576 bool is_valid_offset (int64_t offset,
577 const hb_serialize_context_t::object_t::link_t& link) const
578 {
579 if (link.is_signed)
580 {
581 if (link.is_wide)
582 return offset >= -((int64_t) 1 << 31) && offset < ((int64_t) 1 << 31);
583 else
584 return offset >= -(1 << 15) && offset < (1 << 15);
585 }
586 else
587 {
588 if (link.is_wide)
589 return offset >= 0 && offset < ((int64_t) 1 << 32);
590 else
591 return offset >= 0 && offset < (1 << 16);
592 }
593 }
594
595 /*
596 * Updates all objidx's in all links using the provided mapping.
597 */
remap_obj_indicesgraph_t598 void remap_obj_indices (const hb_vector_t<unsigned>& id_map,
599 hb_vector_t<vertex_t>* sorted_graph) const
600 {
601 for (unsigned i = 0; i < sorted_graph->length; i++)
602 {
603 for (unsigned j = 0; j < (*sorted_graph)[i].obj.links.length; j++)
604 {
605 auto& link = (*sorted_graph)[i].obj.links[j];
606 link.objidx = id_map[link.objidx];
607 }
608 }
609 }
610
611 template <typename O> void
serialize_link_of_typegraph_t612 serialize_link_of_type (const hb_serialize_context_t::object_t::link_t& link,
613 char* head,
614 hb_serialize_context_t* c) const
615 {
616 OT::Offset<O>* offset = reinterpret_cast<OT::Offset<O>*> (head + link.position);
617 *offset = 0;
618 c->add_link (*offset,
619 // serializer has an extra nil object at the start of the
620 // object array. So all id's are +1 of what our id's are.
621 link.objidx + 1,
622 (hb_serialize_context_t::whence_t) link.whence,
623 link.bias);
624 }
625
serialize_linkgraph_t626 void serialize_link (const hb_serialize_context_t::object_t::link_t& link,
627 char* head,
628 hb_serialize_context_t* c) const
629 {
630 if (link.is_wide)
631 {
632 if (link.is_signed)
633 {
634 serialize_link_of_type<OT::HBINT32> (link, head, c);
635 } else {
636 serialize_link_of_type<OT::HBUINT32> (link, head, c);
637 }
638 } else {
639 if (link.is_signed)
640 {
641 serialize_link_of_type<OT::HBINT16> (link, head, c);
642 } else {
643 serialize_link_of_type<OT::HBUINT16> (link, head, c);
644 }
645 }
646 }
647
648 public:
649 // TODO(garretrieger): make private, will need to move most of offset overflow code into graph.
650 hb_vector_t<vertex_t> vertices_;
651 private:
652 hb_vector_t<clone_buffer_t> clone_buffers_;
653 bool edge_count_invalid;
654 bool distance_invalid;
655 bool positions_invalid;
656 bool successful;
657 };
658
659
660 /*
661 * Attempts to modify the topological sorting of the provided object graph to
662 * eliminate offset overflows in the links between objects of the graph. If a
663 * non-overflowing ordering is found the updated graph is serialized it into the
664 * provided serialization context.
665 *
666 * If necessary the structure of the graph may be modified in ways that do not
667 * affect the functionality of the graph. For example shared objects may be
668 * duplicated.
669 */
670 inline void
hb_resolve_overflows(const hb_vector_t<hb_serialize_context_t::object_t * > & packed,hb_serialize_context_t * c)671 hb_resolve_overflows (const hb_vector_t<hb_serialize_context_t::object_t *>& packed,
672 hb_serialize_context_t* c) {
673 // Kahn sort is ~twice as fast as shortest distance sort and works for many fonts
674 // so try it first to save time.
675 graph_t sorted_graph (packed);
676 sorted_graph.sort_kahn ();
677 if (!sorted_graph.will_overflow ())
678 {
679 sorted_graph.serialize (c);
680 return;
681 }
682
683 sorted_graph.sort_shortest_distance ();
684
685 unsigned round = 0;
686 hb_vector_t<graph_t::overflow_record_t> overflows;
687 // TODO(garretrieger): select a good limit for max rounds.
688 while (!sorted_graph.in_error ()
689 && sorted_graph.will_overflow (&overflows)
690 && round++ < 10) {
691 DEBUG_MSG (SUBSET_REPACK, nullptr, "=== Over flow resolution round %d ===", round);
692 sorted_graph.print_overflows (overflows);
693
694 bool resolution_attempted = false;
695 hb_set_t priority_bumped_parents;
696 // Try resolving the furthest overflows first.
697 for (int i = overflows.length - 1; i >= 0; i--)
698 {
699 const graph_t::overflow_record_t& r = overflows[i];
700 const auto& child = sorted_graph.vertices_[r.link->objidx];
701 if (child.is_shared ())
702 {
703 // The child object is shared, we may be able to eliminate the overflow
704 // by duplicating it.
705 sorted_graph.duplicate (r.parent, r.link->objidx);
706 resolution_attempted = true;
707
708 // Stop processing overflows for this round so that object order can be
709 // updated to account for the newly added object.
710 break;
711 }
712
713 if (child.is_leaf () && !priority_bumped_parents.has (r.parent))
714 {
715 // This object is too far from it's parent, attempt to move it closer.
716 //
717 // TODO(garretrieger): initially limiting this to leaf's since they can be
718 // moved closer with fewer consequences. However, this can
719 // likely can be used for non-leafs as well.
720 // TODO(garretrieger): add a maximum priority, don't try to raise past this.
721 // TODO(garretrieger): also try lowering priority of the parent. Make it
722 // get placed further up in the ordering, closer to it's children.
723 // this is probably preferable if the total size of the parent object
724 // is < then the total size of the children (and the parent can be moved).
725 // Since in that case moving the parent will cause a smaller increase in
726 // the length of other offsets.
727 sorted_graph.raise_childrens_priority (r.parent);
728 priority_bumped_parents.add (r.parent);
729 resolution_attempted = true;
730 continue;
731 }
732
733 // TODO(garretrieger): add additional offset resolution strategies
734 // - Promotion to extension lookups.
735 // - Table splitting.
736 }
737
738 if (resolution_attempted)
739 {
740 sorted_graph.sort_shortest_distance ();
741 continue;
742 }
743
744 DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :(");
745 c->err (HB_SERIALIZE_ERROR_OFFSET_OVERFLOW);
746 return;
747 }
748
749 if (sorted_graph.in_error ())
750 {
751 c->err (HB_SERIALIZE_ERROR_OTHER);
752 return;
753 }
754 sorted_graph.serialize (c);
755 }
756
757
758 #endif /* HB_REPACKER_HH */
759