summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/harfbuzz-ng/src/hb-repacker.hh
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdparty/harfbuzz-ng/src/hb-repacker.hh')
-rw-r--r--src/3rdparty/harfbuzz-ng/src/hb-repacker.hh913
1 files changed, 282 insertions, 631 deletions
diff --git a/src/3rdparty/harfbuzz-ng/src/hb-repacker.hh b/src/3rdparty/harfbuzz-ng/src/hb-repacker.hh
index b02128b5c4..e9cd376ad3 100644
--- a/src/3rdparty/harfbuzz-ng/src/hb-repacker.hh
+++ b/src/3rdparty/harfbuzz-ng/src/hb-repacker.hh
@@ -29,644 +29,347 @@
#include "hb-open-type.hh"
#include "hb-map.hh"
-#include "hb-priority-queue.hh"
-#include "hb-serialize.hh"
#include "hb-vector.hh"
+#include "graph/graph.hh"
+#include "graph/gsubgpos-graph.hh"
+#include "graph/serialize.hh"
+using graph::graph_t;
-struct graph_t
-{
- struct vertex_t
- {
- vertex_t () :
- distance (0),
- incoming_edges (0),
- start (0),
- end (0),
- priority(0) {}
-
- void fini () { obj.fini (); }
-
- hb_serialize_context_t::object_t obj;
- int64_t distance;
- unsigned incoming_edges;
- unsigned start;
- unsigned end;
- unsigned priority;
-
- bool is_shared () const
- {
- return incoming_edges > 1;
- }
-
- bool is_leaf () const
- {
- return !obj.links.length;
- }
-
- void raise_priority ()
- {
- priority++;
- }
-
- int64_t modified_distance (unsigned order) const
- {
- // TODO(garretrieger): once priority is high enough, should try
- // setting distance = 0 which will force to sort immediately after
- // it's parent where possible.
-
- int64_t modified_distance =
- hb_min (hb_max(distance + distance_modifier (), 0), 0x7FFFFFFFFF);
- return (modified_distance << 24) | (0x00FFFFFF & order);
- }
-
- int64_t distance_modifier () const
- {
- if (!priority) return 0;
- int64_t table_size = obj.tail - obj.head;
- return -(table_size - table_size / (1 << hb_min(priority, 16u)));
- }
- };
+/*
+ * For a detailed writeup on the overflow resolution algorithm see:
+ * docs/repacker.md
+ */
- struct overflow_record_t
- {
- unsigned parent;
- const hb_serialize_context_t::object_t::link_t* link;
- };
+struct lookup_size_t
+{
+ unsigned lookup_index;
+ size_t size;
+ unsigned num_subtables;
- struct clone_buffer_t
+ static int cmp (const void* a, const void* b)
{
- clone_buffer_t () : head (nullptr), tail (nullptr) {}
-
- bool copy (const hb_serialize_context_t::object_t& object)
- {
- fini ();
- unsigned size = object.tail - object.head;
- head = (char*) hb_malloc (size);
- if (!head) return false;
-
- memcpy (head, object.head, size);
- tail = head + size;
- return true;
- }
-
- char* head;
- char* tail;
+ return cmp ((const lookup_size_t*) a,
+ (const lookup_size_t*) b);
+ }
- void fini ()
- {
- if (!head) return;
- hb_free (head);
- head = nullptr;
- }
- };
-
- /*
- * A topological sorting of an object graph. Ordered
- * in reverse serialization order (first object in the
- * serialization is at the end of the list). This matches
- * the 'packed' object stack used internally in the
- * serializer
- */
- graph_t (const hb_vector_t<hb_serialize_context_t::object_t *>& objects)
- : edge_count_invalid (true),
- distance_invalid (true),
- positions_invalid (true),
- successful (true)
+ static int cmp (const lookup_size_t* a, const lookup_size_t* b)
{
- bool removed_nil = false;
- for (unsigned i = 0; i < objects.length; i++)
- {
- // TODO(grieger): check all links point to valid objects.
-
- // If this graph came from a serialization buffer object 0 is the
- // nil object. We don't need it for our purposes here so drop it.
- if (i == 0 && !objects[i])
- {
- removed_nil = true;
- continue;
- }
-
- vertex_t* v = vertices_.push ();
- if (check_success (!vertices_.in_error ()))
- v->obj = *objects[i];
- if (!removed_nil) continue;
- for (unsigned i = 0; i < v->obj.links.length; i++)
- // Fix indices to account for removed nil object.
- v->obj.links[i].objidx--;
+ double subtables_per_byte_a = (double) a->num_subtables / (double) a->size;
+ double subtables_per_byte_b = (double) b->num_subtables / (double) b->size;
+ if (subtables_per_byte_a == subtables_per_byte_b) {
+ return b->lookup_index - a->lookup_index;
}
- }
- ~graph_t ()
- {
- vertices_.fini_deep ();
- clone_buffers_.fini_deep ();
+ double cmp = subtables_per_byte_b - subtables_per_byte_a;
+ if (cmp < 0) return -1;
+ if (cmp > 0) return 1;
+ return 0;
}
+};
- bool in_error () const
+static inline
+bool _presplit_subtables_if_needed (graph::gsubgpos_graph_context_t& ext_context)
+{
+ // For each lookup this will check the size of subtables and split them as needed
+ // so that no subtable is at risk of overflowing. (where we support splitting for
+ // that subtable type).
+ //
+ // TODO(grieger): de-dup newly added nodes as necessary. Probably just want a full de-dup
+ // pass after this processing is done. Not super necessary as splits are
+ // only done where overflow is likely, so de-dup probably will get undone
+ // later anyways.
+
+ // The loop below can modify the contents of ext_context.lookups if new subtables are added
+ // to a lookup during a split. So save the initial set of lookup indices so the iteration doesn't
+ // risk access free'd memory if ext_context.lookups gets resized.
+ hb_set_t lookup_indices(ext_context.lookups.keys ());
+ for (unsigned lookup_index : lookup_indices)
{
- return !successful || vertices_.in_error () || clone_buffers_.in_error ();
+ graph::Lookup* lookup = ext_context.lookups.get(lookup_index);
+ if (!lookup->split_subtables_if_needed (ext_context, lookup_index))
+ return false;
}
- const vertex_t& root () const
- {
- return vertices_[root_idx ()];
- }
+ return true;
+}
- unsigned root_idx () const
+/*
+ * Analyze the lookups in a GSUB/GPOS table and decide if any should be promoted
+ * to extension lookups.
+ */
+static inline
+bool _promote_extensions_if_needed (graph::gsubgpos_graph_context_t& ext_context)
+{
+ // Simple Algorithm (v1, current):
+ // 1. Calculate how many bytes each non-extension lookup consumes.
+ // 2. Select up to 64k of those to remain as non-extension (greedy, highest subtables per byte first)
+ // 3. Promote the rest.
+ //
+ // Advanced Algorithm (v2, not implemented):
+ // 1. Perform connected component analysis using lookups as roots.
+ // 2. Compute size of each connected component.
+ // 3. Select up to 64k worth of connected components to remain as non-extensions.
+ // (greedy, highest subtables per byte first)
+ // 4. Promote the rest.
+
+ // TODO(garretrieger): support extension demotion, then consider all lookups. Requires advanced algo.
+ // TODO(garretrieger): also support extension promotion during iterative resolution phase, then
+ // we can use a less conservative threshold here.
+ // TODO(grieger): skip this for the 24 bit case.
+ if (!ext_context.lookups) return true;
+
+ unsigned total_lookup_table_sizes = 0;
+ hb_vector_t<lookup_size_t> lookup_sizes;
+ lookup_sizes.alloc (ext_context.lookups.get_population (), true);
+
+ for (unsigned lookup_index : ext_context.lookups.keys ())
{
- // Object graphs are in reverse order, the first object is at the end
- // of the vector. Since the graph is topologically sorted it's safe to
- // assume the first object has no incoming edges.
- return vertices_.length - 1;
- }
+ const auto& lookup_v = ext_context.graph.vertices_[lookup_index];
+ total_lookup_table_sizes += lookup_v.table_size ();
- const hb_serialize_context_t::object_t& object(unsigned i) const
- {
- return vertices_[i].obj;
+ const graph::Lookup* lookup = ext_context.lookups.get(lookup_index);
+ hb_set_t visited;
+ lookup_sizes.push (lookup_size_t {
+ lookup_index,
+ ext_context.graph.find_subgraph_size (lookup_index, visited),
+ lookup->number_of_subtables (),
+ });
}
- /*
- * serialize graph into the provided serialization buffer.
- */
- void serialize (hb_serialize_context_t* c) const
- {
- c->start_serialize<void> ();
- for (unsigned i = 0; i < vertices_.length; i++) {
- c->push ();
-
- size_t size = vertices_[i].obj.tail - vertices_[i].obj.head;
- char* start = c->allocate_size <char> (size);
- if (!start) return;
-
- memcpy (start, vertices_[i].obj.head, size);
+ lookup_sizes.qsort ();
- for (const auto& link : vertices_[i].obj.links)
- serialize_link (link, start, c);
+ size_t lookup_list_size = ext_context.graph.vertices_[ext_context.lookup_list_index].table_size ();
+ size_t l2_l3_size = lookup_list_size + total_lookup_table_sizes; // Lookup List + Lookups
+ size_t l3_l4_size = total_lookup_table_sizes; // Lookups + SubTables
+ size_t l4_plus_size = 0; // SubTables + their descendants
- // All duplications are already encoded in the graph, so don't
- // enable sharing during packing.
- c->pop_pack (false);
- }
- c->end_serialize ();
+ // Start by assuming all lookups are using extension subtables, this size will be removed later
+ // if it's decided to not make a lookup extension.
+ for (auto p : lookup_sizes)
+ {
+ // TODO(garretrieger): this overestimates the extension subtables size because some extension subtables may be
+ // reused. However, we can't correct this until we have connected component analysis in place.
+ unsigned subtables_size = p.num_subtables * 8;
+ l3_l4_size += subtables_size;
+ l4_plus_size += subtables_size;
}
- /*
- * Generates a new topological sorting of graph using Kahn's
- * algorithm: https://en.wikipedia.org/wiki/Topological_sorting#Algorithms
- */
- void sort_kahn ()
+ bool layers_full = false;
+ for (auto p : lookup_sizes)
{
- positions_invalid = true;
-
- if (vertices_.length <= 1) {
- // Graph of 1 or less doesn't need sorting.
- return;
- }
-
- hb_vector_t<unsigned> queue;
- hb_vector_t<vertex_t> sorted_graph;
- hb_vector_t<unsigned> id_map;
- if (unlikely (!check_success (id_map.resize (vertices_.length)))) return;
-
- hb_vector_t<unsigned> removed_edges;
- if (unlikely (!check_success (removed_edges.resize (vertices_.length)))) return;
- update_incoming_edge_count ();
-
- queue.push (root_idx ());
- int new_id = vertices_.length - 1;
+ const graph::Lookup* lookup = ext_context.lookups.get(p.lookup_index);
+ if (lookup->is_extension (ext_context.table_tag))
+ // already an extension so size is counted by the loop above.
+ continue;
- while (!queue.in_error () && queue.length)
+ if (!layers_full)
{
- unsigned next_id = queue[0];
- queue.remove (0);
-
- vertex_t& next = vertices_[next_id];
- sorted_graph.push (next);
- id_map[next_id] = new_id--;
+ size_t lookup_size = ext_context.graph.vertices_[p.lookup_index].table_size ();
+ hb_set_t visited;
+ size_t subtables_size = ext_context.graph.find_subgraph_size (p.lookup_index, visited, 1) - lookup_size;
+ size_t remaining_size = p.size - subtables_size - lookup_size;
- for (const auto& link : next.obj.links) {
- removed_edges[link.objidx]++;
- if (!(vertices_[link.objidx].incoming_edges - removed_edges[link.objidx]))
- queue.push (link.objidx);
- }
- }
-
- check_success (!queue.in_error ());
- check_success (!sorted_graph.in_error ());
- if (!check_success (new_id == -1))
- DEBUG_MSG (SUBSET_REPACK, nullptr, "Graph is not fully connected.");
+ l3_l4_size += subtables_size;
+ l3_l4_size -= p.num_subtables * 8;
+ l4_plus_size += subtables_size + remaining_size;
- remap_obj_indices (id_map, &sorted_graph);
+ if (l2_l3_size < (1 << 16)
+ && l3_l4_size < (1 << 16)
+ && l4_plus_size < (1 << 16)) continue; // this lookup fits within all layers groups
- sorted_graph.as_array ().reverse ();
+ layers_full = true;
+ }
- vertices_.fini_deep ();
- vertices_ = sorted_graph;
- sorted_graph.fini_deep ();
+ if (!ext_context.lookups.get(p.lookup_index)->make_extension (ext_context, p.lookup_index))
+ return false;
}
- /*
- * Generates a new topological sorting of graph ordered by the shortest
- * distance to each node.
- */
- void sort_shortest_distance ()
- {
- positions_invalid = true;
-
- if (vertices_.length <= 1) {
- // Graph of 1 or less doesn't need sorting.
- return;
- }
+ return true;
+}
- update_distances ();
+static inline
+bool _try_isolating_subgraphs (const hb_vector_t<graph::overflow_record_t>& overflows,
+ graph_t& sorted_graph)
+{
+ unsigned space = 0;
+ hb_set_t roots_to_isolate;
- hb_priority_queue_t queue;
- hb_vector_t<vertex_t> sorted_graph;
- hb_vector_t<unsigned> id_map;
- if (unlikely (!check_success (id_map.resize (vertices_.length)))) return;
+ for (int i = overflows.length - 1; i >= 0; i--)
+ {
+ const graph::overflow_record_t& r = overflows[i];
- hb_vector_t<unsigned> removed_edges;
- if (unlikely (!check_success (removed_edges.resize (vertices_.length)))) return;
- update_incoming_edge_count ();
+ unsigned root;
+ unsigned overflow_space = sorted_graph.space_for (r.parent, &root);
+ if (!overflow_space) continue;
+ if (sorted_graph.num_roots_for_space (overflow_space) <= 1) continue;
- queue.insert (root ().modified_distance (0), root_idx ());
- int new_id = root_idx ();
- unsigned order = 1;
- while (!queue.in_error () && !queue.is_empty ())
- {
- unsigned next_id = queue.pop_minimum().second;
-
- vertex_t& next = vertices_[next_id];
- sorted_graph.push (next);
- id_map[next_id] = new_id--;
-
- for (const auto& link : next.obj.links) {
- removed_edges[link.objidx]++;
- if (!(vertices_[link.objidx].incoming_edges - removed_edges[link.objidx]))
- // Add the order that the links were encountered to the priority.
- // This ensures that ties between priorities objects are broken in a consistent
- // way. More specifically this is set up so that if a set of objects have the same
- // distance they'll be added to the topological order in the order that they are
- // referenced from the parent object.
- queue.insert (vertices_[link.objidx].modified_distance (order++),
- link.objidx);
- }
+ if (!space) {
+ space = overflow_space;
}
- check_success (!queue.in_error ());
- check_success (!sorted_graph.in_error ());
- if (!check_success (new_id == -1))
- DEBUG_MSG (SUBSET_REPACK, nullptr, "Graph is not fully connected.");
-
- remap_obj_indices (id_map, &sorted_graph);
-
- sorted_graph.as_array ().reverse ();
-
- vertices_.fini_deep ();
- vertices_ = sorted_graph;
- sorted_graph.fini_deep ();
+ if (space == overflow_space)
+ roots_to_isolate.add(root);
}
- /*
- * Creates a copy of child and re-assigns the link from
- * parent to the clone. The copy is a shallow copy, objects
- * linked from child are not duplicated.
- */
- void duplicate (unsigned parent_idx, unsigned child_idx)
- {
- DEBUG_MSG (SUBSET_REPACK, nullptr, " Duplicating %d => %d",
- parent_idx, child_idx);
-
- positions_invalid = true;
-
- auto* clone = vertices_.push ();
- auto& child = vertices_[child_idx];
- clone_buffer_t* buffer = clone_buffers_.push ();
- if (vertices_.in_error ()
- || clone_buffers_.in_error ()
- || !check_success (buffer->copy (child.obj))) {
- return;
- }
-
- clone->obj.head = buffer->head;
- clone->obj.tail = buffer->tail;
- clone->distance = child.distance;
+ if (!roots_to_isolate) return false;
- for (const auto& l : child.obj.links)
- clone->obj.links.push (l);
-
- check_success (!clone->obj.links.in_error ());
-
- auto& parent = vertices_[parent_idx];
- unsigned clone_idx = vertices_.length - 2;
- for (unsigned i = 0; i < parent.obj.links.length; i++)
- {
- auto& l = parent.obj.links[i];
- if (l.objidx == child_idx)
- {
- l.objidx = clone_idx;
- clone->incoming_edges++;
- child.incoming_edges--;
- }
+ unsigned maximum_to_move = hb_max ((sorted_graph.num_roots_for_space (space) / 2u), 1u);
+ if (roots_to_isolate.get_population () > maximum_to_move) {
+ // Only move at most half of the roots in a space at a time.
+ unsigned extra = roots_to_isolate.get_population () - maximum_to_move;
+ while (extra--) {
+ uint32_t root = HB_SET_VALUE_INVALID;
+ roots_to_isolate.previous (&root);
+ roots_to_isolate.del (root);
}
-
- // The last object is the root of the graph, so swap back the root to the end.
- // The root's obj idx does change, however since it's root nothing else refers to it.
- // all other obj idx's will be unaffected.
- vertex_t root = vertices_[vertices_.length - 2];
- vertices_[vertices_.length - 2] = *clone;
- vertices_[vertices_.length - 1] = root;
}
- /*
- * Raises the sorting priority of all children.
- */
- void raise_childrens_priority (unsigned parent_idx)
- {
- DEBUG_MSG (SUBSET_REPACK, nullptr, " Raising priority of all children of %d",
- parent_idx);
- // This operation doesn't change ordering until a sort is run, so no need
- // to invalidate positions. It does not change graph structure so no need
- // to update distances or edge counts.
- auto& parent = vertices_[parent_idx].obj;
- for (unsigned i = 0; i < parent.links.length; i++)
- vertices_[parent.links[i].objidx].raise_priority ();
- }
-
- /*
- * Will any offsets overflow on graph when it's serialized?
- */
- bool will_overflow (hb_vector_t<overflow_record_t>* overflows = nullptr)
- {
- if (overflows) overflows->resize (0);
- update_positions ();
-
- for (int parent_idx = vertices_.length - 1; parent_idx >= 0; parent_idx--)
- {
- for (const auto& link : vertices_[parent_idx].obj.links)
- {
- int64_t offset = compute_offset (parent_idx, link);
- if (is_valid_offset (offset, link))
- continue;
+ DEBUG_MSG (SUBSET_REPACK, nullptr,
+ "Overflow in space %u (%u roots). Moving %u roots to space %u.",
+ space,
+ sorted_graph.num_roots_for_space (space),
+ roots_to_isolate.get_population (),
+ sorted_graph.next_space ());
- if (!overflows) return true;
+ sorted_graph.isolate_subgraph (roots_to_isolate);
+ sorted_graph.move_to_new_space (roots_to_isolate);
- overflow_record_t r;
- r.parent = parent_idx;
- r.link = &link;
- overflows->push (r);
- }
- }
+ return true;
+}
- if (!overflows) return false;
- return overflows->length;
- }
+static inline
+bool _process_overflows (const hb_vector_t<graph::overflow_record_t>& overflows,
+ hb_set_t& priority_bumped_parents,
+ graph_t& sorted_graph)
+{
+ bool resolution_attempted = false;
- void print_overflows (const hb_vector_t<overflow_record_t>& overflows)
+ // Try resolving the furthest overflows first.
+ for (int i = overflows.length - 1; i >= 0; i--)
{
- if (!DEBUG_ENABLED(SUBSET_REPACK)) return;
-
- update_incoming_edge_count ();
- for (const auto& o : overflows)
+ const graph::overflow_record_t& r = overflows[i];
+ const auto& child = sorted_graph.vertices_[r.child];
+ if (child.is_shared ())
{
- const auto& child = vertices_[o.link->objidx];
- DEBUG_MSG (SUBSET_REPACK, nullptr, " overflow from %d => %d (%d incoming , %d outgoing)",
- o.parent,
- o.link->objidx,
- child.incoming_edges,
- child.obj.links.length);
+ // The child object is shared, we may be able to eliminate the overflow
+ // by duplicating it.
+ if (sorted_graph.duplicate (r.parent, r.child) == (unsigned) -1) continue;
+ return true;
}
- }
-
- void err_other_error () { this->successful = false; }
-
- private:
-
- bool check_success (bool success)
- { return this->successful && (success || (err_other_error (), false)); }
- /*
- * Creates a map from objid to # of incoming edges.
- */
- void update_incoming_edge_count ()
- {
- if (!edge_count_invalid) return;
-
- for (unsigned i = 0; i < vertices_.length; i++)
- vertices_[i].incoming_edges = 0;
-
- for (const vertex_t& v : vertices_)
+ if (child.is_leaf () && !priority_bumped_parents.has (r.parent))
{
- for (auto& l : v.obj.links)
- {
- vertices_[l.objidx].incoming_edges++;
+ // This object is too far from it's parent, attempt to move it closer.
+ //
+ // TODO(garretrieger): initially limiting this to leaf's since they can be
+ // moved closer with fewer consequences. However, this can
+ // likely can be used for non-leafs as well.
+ // TODO(garretrieger): also try lowering priority of the parent. Make it
+ // get placed further up in the ordering, closer to it's children.
+ // this is probably preferable if the total size of the parent object
+ // is < then the total size of the children (and the parent can be moved).
+ // Since in that case moving the parent will cause a smaller increase in
+ // the length of other offsets.
+ if (sorted_graph.raise_childrens_priority (r.parent)) {
+ priority_bumped_parents.add (r.parent);
+ resolution_attempted = true;
}
+ continue;
}
- edge_count_invalid = false;
+ // TODO(garretrieger): add additional offset resolution strategies
+ // - Promotion to extension lookups.
+ // - Table splitting.
}
- /*
- * compute the serialized start and end positions for each vertex.
- */
- void update_positions ()
- {
- if (!positions_invalid) return;
-
- unsigned current_pos = 0;
- for (int i = root_idx (); i >= 0; i--)
- {
- auto& v = vertices_[i];
- v.start = current_pos;
- current_pos += v.obj.tail - v.obj.head;
- v.end = current_pos;
- }
-
- positions_invalid = false;
- }
+ return resolution_attempted;
+}
- /*
- * Finds the distance to each object in the graph
- * from the initial node.
- */
- void update_distances ()
+inline bool
+hb_resolve_graph_overflows (hb_tag_t table_tag,
+ unsigned max_rounds ,
+ bool recalculate_extensions,
+ graph_t& sorted_graph /* IN/OUT */)
+{
+ sorted_graph.sort_shortest_distance ();
+ if (sorted_graph.in_error ())
{
- if (!distance_invalid) return;
-
- // Uses Dijkstra's algorithm to find all of the shortest distances.
- // https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
- //
- // Implementation Note:
- // Since our priority queue doesn't support fast priority decreases
- // we instead just add new entries into the queue when a priority changes.
- // Redundant ones are filtered out later on by the visited set.
- // According to https://www3.cs.stonybrook.edu/~rezaul/papers/TR-07-54.pdf
- // for practical performance this is faster then using a more advanced queue
- // (such as a fibonaacci queue) with a fast decrease priority.
- for (unsigned i = 0; i < vertices_.length; i++)
- {
- if (i == vertices_.length - 1)
- vertices_[i].distance = 0;
- else
- vertices_[i].distance = hb_int_max (int64_t);
- }
-
- hb_priority_queue_t queue;
- queue.insert (0, vertices_.length - 1);
+ DEBUG_MSG (SUBSET_REPACK, nullptr, "Sorted graph in error state after initial sort.");
+ return false;
+ }
- hb_set_t visited;
+ bool will_overflow = graph::will_overflow (sorted_graph);
+ if (!will_overflow)
+ return true;
- while (!queue.in_error () && !queue.is_empty ())
+ graph::gsubgpos_graph_context_t ext_context (table_tag, sorted_graph);
+ if ((table_tag == HB_OT_TAG_GPOS
+ || table_tag == HB_OT_TAG_GSUB)
+ && will_overflow)
+ {
+ if (recalculate_extensions)
{
- unsigned next_idx = queue.pop_minimum ().second;
- if (visited.has (next_idx)) continue;
- const auto& next = vertices_[next_idx];
- int64_t next_distance = vertices_[next_idx].distance;
- visited.add (next_idx);
-
- for (const auto& link : next.obj.links)
- {
- if (visited.has (link.objidx)) continue;
-
- const auto& child = vertices_[link.objidx].obj;
- int64_t child_weight = child.tail - child.head +
- ((int64_t) 1 << (link.width * 8));
- int64_t child_distance = next_distance + child_weight;
-
- if (child_distance < vertices_[link.objidx].distance)
- {
- vertices_[link.objidx].distance = child_distance;
- queue.insert (child_distance, link.objidx);
- }
+ DEBUG_MSG (SUBSET_REPACK, nullptr, "Splitting subtables if needed.");
+ if (!_presplit_subtables_if_needed (ext_context)) {
+ DEBUG_MSG (SUBSET_REPACK, nullptr, "Subtable splitting failed.");
+ return false;
}
- }
- check_success (!queue.in_error ());
- if (!check_success (queue.is_empty ()))
- {
- DEBUG_MSG (SUBSET_REPACK, nullptr, "Graph is not fully connected.");
- return;
+ DEBUG_MSG (SUBSET_REPACK, nullptr, "Promoting lookups to extensions if needed.");
+ if (!_promote_extensions_if_needed (ext_context)) {
+ DEBUG_MSG (SUBSET_REPACK, nullptr, "Extensions promotion failed.");
+ return false;
+ }
}
- distance_invalid = false;
+ DEBUG_MSG (SUBSET_REPACK, nullptr, "Assigning spaces to 32 bit subgraphs.");
+ if (sorted_graph.assign_spaces ())
+ sorted_graph.sort_shortest_distance ();
+ else
+ sorted_graph.sort_shortest_distance_if_needed ();
}
- int64_t compute_offset (
- unsigned parent_idx,
- const hb_serialize_context_t::object_t::link_t& link) const
- {
- const auto& parent = vertices_[parent_idx];
- const auto& child = vertices_[link.objidx];
- int64_t offset = 0;
- switch ((hb_serialize_context_t::whence_t) link.whence) {
- case hb_serialize_context_t::whence_t::Head:
- offset = child.start - parent.start; break;
- case hb_serialize_context_t::whence_t::Tail:
- offset = child.start - parent.end; break;
- case hb_serialize_context_t::whence_t::Absolute:
- offset = child.start; break;
- }
-
- assert (offset >= link.bias);
- offset -= link.bias;
- return offset;
- }
+ unsigned round = 0;
+ hb_vector_t<graph::overflow_record_t> overflows;
+ // TODO(garretrieger): select a good limit for max rounds.
+ while (!sorted_graph.in_error ()
+ && graph::will_overflow (sorted_graph, &overflows)
+ && round < max_rounds) {
+ DEBUG_MSG (SUBSET_REPACK, nullptr, "=== Overflow resolution round %u ===", round);
+ print_overflows (sorted_graph, overflows);
- bool is_valid_offset (int64_t offset,
- const hb_serialize_context_t::object_t::link_t& link) const
- {
- if (link.is_signed)
- {
- if (link.width == 4)
- return offset >= -((int64_t) 1 << 31) && offset < ((int64_t) 1 << 31);
- else
- return offset >= -(1 << 15) && offset < (1 << 15);
- }
- else
- {
- if (link.width == 4)
- return offset >= 0 && offset < ((int64_t) 1 << 32);
- else if (link.width == 3)
- return offset >= 0 && offset < ((int32_t) 1 << 24);
- else
- return offset >= 0 && offset < (1 << 16);
- }
- }
+ hb_set_t priority_bumped_parents;
- /*
- * Updates all objidx's in all links using the provided mapping.
- */
- void remap_obj_indices (const hb_vector_t<unsigned>& id_map,
- hb_vector_t<vertex_t>* sorted_graph) const
- {
- for (unsigned i = 0; i < sorted_graph->length; i++)
+ if (!_try_isolating_subgraphs (overflows, sorted_graph))
{
- for (unsigned j = 0; j < (*sorted_graph)[i].obj.links.length; j++)
+ // Don't count space isolation towards round limit. Only increment
+ // round counter if space isolation made no changes.
+ round++;
+ if (!_process_overflows (overflows, priority_bumped_parents, sorted_graph))
{
- auto& link = (*sorted_graph)[i].obj.links[j];
- link.objidx = id_map[link.objidx];
+ DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :(");
+ break;
}
}
+
+ sorted_graph.sort_shortest_distance ();
}
- template <typename O> void
- serialize_link_of_type (const hb_serialize_context_t::object_t::link_t& link,
- char* head,
- hb_serialize_context_t* c) const
+ if (sorted_graph.in_error ())
{
- OT::Offset<O>* offset = reinterpret_cast<OT::Offset<O>*> (head + link.position);
- *offset = 0;
- c->add_link (*offset,
- // serializer has an extra nil object at the start of the
- // object array. So all id's are +1 of what our id's are.
- link.objidx + 1,
- (hb_serialize_context_t::whence_t) link.whence,
- link.bias);
+ DEBUG_MSG (SUBSET_REPACK, nullptr, "Sorted graph in error state.");
+ return false;
}
- void serialize_link (const hb_serialize_context_t::object_t::link_t& link,
- char* head,
- hb_serialize_context_t* c) const
+ if (graph::will_overflow (sorted_graph))
{
- switch (link.width)
- {
- case 4:
- if (link.is_signed)
- {
- serialize_link_of_type<OT::HBINT32> (link, head, c);
- } else {
- serialize_link_of_type<OT::HBUINT32> (link, head, c);
- }
- return;
- case 2:
- if (link.is_signed)
- {
- serialize_link_of_type<OT::HBINT16> (link, head, c);
- } else {
- serialize_link_of_type<OT::HBUINT16> (link, head, c);
- }
- return;
- case 3:
- serialize_link_of_type<OT::HBUINT24> (link, head, c);
- return;
- default:
- // Unexpected link width.
- assert (0);
- }
+ DEBUG_MSG (SUBSET_REPACK, nullptr, "Offset overflow resolution failed.");
+ return false;
}
- public:
- // TODO(garretrieger): make private, will need to move most of offset overflow code into graph.
- hb_vector_t<vertex_t> vertices_;
- private:
- hb_vector_t<clone_buffer_t> clone_buffers_;
- bool edge_count_invalid;
- bool distance_invalid;
- bool positions_invalid;
- bool successful;
-};
-
+ return true;
+}
/*
* Attempts to modify the topological sorting of the provided object graph to
@@ -677,93 +380,41 @@ struct graph_t
* If necessary the structure of the graph may be modified in ways that do not
* affect the functionality of the graph. For example shared objects may be
* duplicated.
+ *
+ * For a detailed writeup describing how the algorithm operates see:
+ * docs/repacker.md
*/
-inline void
-hb_resolve_overflows (const hb_vector_t<hb_serialize_context_t::object_t *>& packed,
- hb_serialize_context_t* c) {
- // Kahn sort is ~twice as fast as shortest distance sort and works for many fonts
- // so try it first to save time.
+template<typename T>
+inline hb_blob_t*
+hb_resolve_overflows (const T& packed,
+ hb_tag_t table_tag,
+ unsigned max_rounds = 20,
+ bool recalculate_extensions = false) {
graph_t sorted_graph (packed);
- sorted_graph.sort_kahn ();
- if (!sorted_graph.will_overflow ())
+ if (sorted_graph.in_error ())
{
- sorted_graph.serialize (c);
- return;
+ // Invalid graph definition.
+ return nullptr;
}
- sorted_graph.sort_shortest_distance ();
-
- unsigned round = 0;
- hb_vector_t<graph_t::overflow_record_t> overflows;
- // TODO(garretrieger): select a good limit for max rounds.
- while (!sorted_graph.in_error ()
- && sorted_graph.will_overflow (&overflows)
- && round++ < 10) {
- DEBUG_MSG (SUBSET_REPACK, nullptr, "=== Over flow resolution round %d ===", round);
- sorted_graph.print_overflows (overflows);
-
- bool resolution_attempted = false;
- hb_set_t priority_bumped_parents;
- // Try resolving the furthest overflows first.
- for (int i = overflows.length - 1; i >= 0; i--)
- {
- const graph_t::overflow_record_t& r = overflows[i];
- const auto& child = sorted_graph.vertices_[r.link->objidx];
- if (child.is_shared ())
- {
- // The child object is shared, we may be able to eliminate the overflow
- // by duplicating it.
- sorted_graph.duplicate (r.parent, r.link->objidx);
- resolution_attempted = true;
-
- // Stop processing overflows for this round so that object order can be
- // updated to account for the newly added object.
- break;
- }
-
- if (child.is_leaf () && !priority_bumped_parents.has (r.parent))
- {
- // This object is too far from it's parent, attempt to move it closer.
- //
- // TODO(garretrieger): initially limiting this to leaf's since they can be
- // moved closer with fewer consequences. However, this can
- // likely can be used for non-leafs as well.
- // TODO(garretrieger): add a maximum priority, don't try to raise past this.
- // TODO(garretrieger): also try lowering priority of the parent. Make it
- // get placed further up in the ordering, closer to it's children.
- // this is probably preferable if the total size of the parent object
- // is < then the total size of the children (and the parent can be moved).
- // Since in that case moving the parent will cause a smaller increase in
- // the length of other offsets.
- sorted_graph.raise_childrens_priority (r.parent);
- priority_bumped_parents.add (r.parent);
- resolution_attempted = true;
- continue;
- }
-
- // TODO(garretrieger): add additional offset resolution strategies
- // - Promotion to extension lookups.
- // - Table splitting.
- }
-
- if (resolution_attempted)
- {
- sorted_graph.sort_shortest_distance ();
- continue;
- }
-
- DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :(");
- c->err (HB_SERIALIZE_ERROR_OFFSET_OVERFLOW);
- return;
+ if (!sorted_graph.is_fully_connected ())
+ {
+ sorted_graph.print_orphaned_nodes ();
+ return nullptr;
}
if (sorted_graph.in_error ())
{
- c->err (HB_SERIALIZE_ERROR_OTHER);
- return;
+ // Allocations failed somewhere
+ DEBUG_MSG (SUBSET_REPACK, nullptr,
+ "Graph is in error, likely due to a memory allocation error.");
+ return nullptr;
}
- sorted_graph.serialize (c);
-}
+ if (!hb_resolve_graph_overflows (table_tag, max_rounds, recalculate_extensions, sorted_graph))
+ return nullptr;
+
+ return graph::serialize (sorted_graph);
+}
#endif /* HB_REPACKER_HH */