| /* |
| * Copyright © 2020 Google, Inc. |
| * |
| * This is part of HarfBuzz, a text shaping library. |
| * |
| * Permission is hereby granted, without written agreement and without |
| * license or royalty fees, to use, copy, modify, and distribute this |
| * software and its documentation for any purpose, provided that the |
| * above copyright notice and the following two paragraphs appear in |
| * all copies of this software. |
| * |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR |
| * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN |
| * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| * |
| * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS |
| * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO |
| * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
| * |
| * Google Author(s): Garret Rieger |
| */ |
| |
| #ifndef HB_REPACKER_HH |
| #define HB_REPACKER_HH |
| |
| #include "hb-open-type.hh" |
| #include "hb-map.hh" |
| #include "hb-priority-queue.hh" |
| #include "hb-serialize.hh" |
| #include "hb-vector.hh" |
| |
| /* |
| * For a detailed writeup on the overflow resolution algorithm see: |
| * docs/overflow_resolution.md |
| */ |
| |
| struct graph_t |
| { |
| struct vertex_t |
| { |
| vertex_t () : |
| distance (0), |
| space (0), |
| incoming_edges (0), |
| start (0), |
| end (0), |
| priority(0) {} |
| |
| void fini () { obj.fini (); } |
| |
| hb_serialize_context_t::object_t obj; |
| int64_t distance; |
| int64_t space; |
| 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 << 22) | (0x003FFFFF & 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))); |
| } |
| }; |
| |
| struct overflow_record_t |
| { |
| unsigned parent; |
| unsigned child; |
| }; |
| |
| struct clone_buffer_t |
| { |
| 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; |
| |
| 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) |
| { |
| 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--; |
| } |
| } |
| |
| ~graph_t () |
| { |
| vertices_.fini_deep (); |
| clone_buffers_.fini_deep (); |
| } |
| |
| bool in_error () const |
| { |
| return !successful || vertices_.in_error () || clone_buffers_.in_error (); |
| } |
| |
| const vertex_t& root () const |
| { |
| return vertices_[root_idx ()]; |
| } |
| |
| unsigned root_idx () const |
| { |
| // 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 hb_serialize_context_t::object_t& object(unsigned i) const |
| { |
| return vertices_[i].obj; |
| } |
| |
| /* |
| * 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); |
| |
| for (const auto& link : vertices_[i].obj.links) |
| serialize_link (link, start, c); |
| |
| // All duplications are already encoded in the graph, so don't |
| // enable sharing during packing. |
| c->pop_pack (false); |
| } |
| c->end_serialize (); |
| } |
| |
| /* |
| * Generates a new topological sorting of graph using Kahn's |
| * algorithm: https://en.wikipedia.org/wiki/Topological_sorting#Algorithms |
| */ |
| void sort_kahn () |
| { |
| 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; |
| |
| while (!queue.in_error () && queue.length) |
| { |
| unsigned next_id = queue[0]; |
| queue.remove (0); |
| |
| 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])) |
| 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."); |
| |
| remap_obj_indices (id_map, &sorted_graph); |
| |
| sorted_graph.as_array ().reverse (); |
| |
| vertices_.fini_deep (); |
| vertices_ = sorted_graph; |
| sorted_graph.fini_deep (); |
| } |
| |
| /* |
| * 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; |
| } |
| |
| update_distances (); |
| |
| 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; |
| |
| hb_vector_t<unsigned> removed_edges; |
| if (unlikely (!check_success (removed_edges.resize (vertices_.length)))) return; |
| update_incoming_edge_count (); |
| |
| 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); |
| } |
| } |
| |
| 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 (); |
| } |
| |
| /* |
| * Finds any links using 32 bits and isolates the subgraphs they point too. |
| */ |
| bool isolate_32bit_links () |
| { |
| bool made_changes = false; |
| hb_set_t target_links; |
| unsigned root_index = root_idx (); |
| int64_t next_space = 0; |
| for (unsigned i = 0; i <= root_index; i++) |
| { |
| if (i == root_index && root_idx () > i) |
| // root index may have moved due to graph modifications. |
| i = root_idx (); |
| |
| for (auto& l : vertices_[i].obj.links) |
| { |
| if (l.width == 4 && !l.is_signed) |
| { |
| isolate_subgraph (l.objidx); |
| vertices_[l.objidx].space = next_space++; |
| distance_invalid = true; |
| made_changes = true; |
| } |
| } |
| } |
| return made_changes; |
| } |
| |
| /* |
| * Isolates the subgraph of nodes reachable from root. Any links to nodes in the subgraph |
| * that originate from outside of the subgraph will be removed by duplicating the linked to |
| * object. |
| */ |
| bool isolate_subgraph (unsigned root_idx) |
| { |
| update_incoming_edge_count (); |
| hb_hashmap_t<unsigned, unsigned> subgraph; |
| |
| // incoming edges to root_idx should be all 32 bit in length so we don't need to de-dup these |
| // set the subgraph incoming edge count to match all of root_idx's incoming edges |
| subgraph.set (root_idx, vertices_[root_idx].incoming_edges); |
| find_subgraph (root_idx, subgraph); |
| |
| hb_hashmap_t<unsigned, unsigned> index_map; |
| bool made_changes = false; |
| for (auto entry : subgraph.iter ()) |
| { |
| const auto& node = vertices_[entry.first]; |
| unsigned subgraph_incoming_edges = entry.second; |
| |
| if (subgraph_incoming_edges < node.incoming_edges) |
| { |
| // Only de-dup objects with incoming links from outside the subgraph. |
| made_changes = true; |
| duplicate_subgraph (entry.first, index_map); |
| } |
| } |
| |
| if (!made_changes) |
| return false; |
| |
| auto new_subgraph = |
| + subgraph.keys () |
| | hb_map([&] (unsigned node_idx) { |
| if (index_map.has (node_idx)) return index_map[node_idx]; |
| return node_idx; |
| }) |
| ; |
| remap_obj_indices (index_map, new_subgraph); |
| return true; |
| } |
| |
| void find_subgraph (unsigned node_idx, hb_hashmap_t<unsigned, unsigned>& subgraph) |
| { |
| for (const auto& link : vertices_[node_idx].obj.links) |
| { |
| if (subgraph.has (link.objidx)) |
| { |
| subgraph.set (link.objidx, subgraph[link.objidx] + 1); |
| continue; |
| } |
| subgraph.set (link.objidx, 1); |
| find_subgraph (link.objidx, subgraph); |
| } |
| } |
| |
| /* |
| * duplicates all nodes in the subgraph reachable from node_idx. Does not re-assign |
| * links. index_map is updated with mappings from old id to new id. If a duplication has already |
| * been performed for a given index, then it will be skipped. |
| */ |
| void duplicate_subgraph (unsigned node_idx, hb_hashmap_t<unsigned, unsigned>& index_map) |
| { |
| if (index_map.has (node_idx)) |
| return; |
| |
| index_map.set (node_idx, duplicate (node_idx)); |
| for (const auto& l : object (node_idx).links) { |
| duplicate_subgraph (l.objidx, index_map); |
| } |
| } |
| |
| /* |
| * Creates a copy of node_idx and returns it's new index. |
| */ |
| unsigned duplicate (unsigned node_idx) |
| { |
| positions_invalid = true; |
| distance_invalid = true; |
| |
| auto* clone = vertices_.push (); |
| auto& child = vertices_[node_idx]; |
| clone_buffer_t* buffer = clone_buffers_.push (); |
| if (vertices_.in_error () |
| || clone_buffers_.in_error () |
| || !check_success (buffer->copy (child.obj))) { |
| return -1; |
| } |
| |
| clone->obj.head = buffer->head; |
| clone->obj.tail = buffer->tail; |
| clone->distance = child.distance; |
| clone->space = child.space; |
| clone->incoming_edges = 0; |
| |
| for (const auto& l : child.obj.links) |
| { |
| clone->obj.links.push (l); |
| vertices_[l.objidx].incoming_edges++; |
| } |
| |
| check_success (!clone->obj.links.in_error ()); |
| |
| // 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; |
| |
| return vertices_.length - 2; |
| } |
| |
| /* |
| * 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. |
| */ |
| bool duplicate (unsigned parent_idx, unsigned child_idx) |
| { |
| update_incoming_edge_count (); |
| |
| unsigned links_to_child = 0; |
| for (const auto& l : vertices_[parent_idx].obj.links) |
| { |
| if (l.objidx == child_idx) links_to_child++; |
| } |
| |
| if (vertices_[child_idx].incoming_edges <= links_to_child) |
| { |
| // Can't duplicate this node, doing so would orphan the original one as all remaining links |
| // to child are from parent. |
| DEBUG_MSG (SUBSET_REPACK, nullptr, " Not duplicating %d => %d", |
| parent_idx, child_idx); |
| return false; |
| } |
| |
| DEBUG_MSG (SUBSET_REPACK, nullptr, " Duplicating %d => %d", |
| parent_idx, child_idx); |
| |
| unsigned clone_idx = duplicate (child_idx); |
| if (clone_idx == (unsigned) -1) return false; |
| // duplicate shifts the root node idx, so if parent_idx was root update it. |
| if (parent_idx == clone_idx) parent_idx++; |
| auto& clone = vertices_[clone_idx]; |
| auto& child = vertices_[child_idx]; |
| |
| auto& parent = vertices_[parent_idx]; |
| 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--; |
| } |
| } |
| |
| return true; |
| } |
| |
| /* |
| * 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; |
| |
| if (!overflows) return true; |
| |
| overflow_record_t r; |
| r.parent = parent_idx; |
| r.child = link.objidx; |
| overflows->push (r); |
| } |
| } |
| |
| if (!overflows) return false; |
| return overflows->length; |
| } |
| |
| void print_overflows (const hb_vector_t<overflow_record_t>& overflows) |
| { |
| if (!DEBUG_ENABLED(SUBSET_REPACK)) return; |
| |
| update_incoming_edge_count (); |
| for (const auto& o : overflows) |
| { |
| const auto& parent = vertices_[o.parent]; |
| const auto& child = vertices_[o.child]; |
| DEBUG_MSG (SUBSET_REPACK, nullptr, |
| " overflow from %d (%d in, %d out) => %d (%d in, %d out)", |
| o.parent, |
| parent.incoming_edges, |
| parent.obj.links.length, |
| o.child, |
| child.incoming_edges, |
| child.obj.links.length); |
| } |
| } |
| |
| 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_) |
| { |
| for (auto& l : v.obj.links) |
| { |
| vertices_[l.objidx].incoming_edges++; |
| } |
| } |
| |
| edge_count_invalid = false; |
| } |
| |
| /* |
| * 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; |
| } |
| |
| /* |
| * Finds the distance to each object in the graph |
| * from the initial node. |
| */ |
| void update_distances () |
| { |
| 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); |
| |
| hb_set_t visited; |
| |
| while (!queue.in_error () && !queue.is_empty ()) |
| { |
| 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)) * (vertices_[link.objidx].space + 1); |
| 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); |
| } |
| } |
| } |
| |
| check_success (!queue.in_error ()); |
| if (!check_success (queue.is_empty ())) |
| { |
| DEBUG_MSG (SUBSET_REPACK, nullptr, "Graph is not fully connected."); |
| return; |
| } |
| |
| distance_invalid = false; |
| } |
| |
| 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; |
| } |
| |
| 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); |
| } |
| } |
| |
| /* |
| * Updates all objidx's in all links using the provided mapping. Corrects incoming edge counts. |
| */ |
| template<typename Iterator, hb_requires (hb_is_iterator (Iterator))> |
| void remap_obj_indices (const hb_hashmap_t<unsigned, unsigned>& id_map, |
| Iterator subgraph) |
| { |
| if (!id_map) return; |
| for (unsigned i : subgraph) |
| { |
| for (unsigned j = 0; j < vertices_[i].obj.links.length; j++) |
| { |
| auto& link = vertices_[i].obj.links[j]; |
| if (!id_map.has (link.objidx)) continue; |
| vertices_[link.objidx].incoming_edges--; |
| link.objidx = id_map[link.objidx]; |
| vertices_[link.objidx].incoming_edges++; |
| } |
| } |
| } |
| |
| /* |
| * 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++) |
| { |
| for (unsigned j = 0; j < (*sorted_graph)[i].obj.links.length; j++) |
| { |
| auto& link = (*sorted_graph)[i].obj.links[j]; |
| link.objidx = id_map[link.objidx]; |
| } |
| } |
| } |
| |
| 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 |
| { |
| 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); |
| } |
| |
| void serialize_link (const hb_serialize_context_t::object_t::link_t& link, |
| char* head, |
| hb_serialize_context_t* c) const |
| { |
| 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); |
| } |
| } |
| |
| 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; |
| }; |
| |
| static bool _process_overflows (const hb_vector_t<graph_t::overflow_record_t>& overflows, |
| hb_set_t& priority_bumped_parents, |
| graph_t& sorted_graph) |
| { |
| bool resolution_attempted = false; |
| |
| // 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.child]; |
| if (child.is_shared ()) |
| { |
| // The child object is shared, we may be able to eliminate the overflow |
| // by duplicating it. |
| if (!sorted_graph.duplicate (r.parent, r.child)) continue; |
| return true; |
| } |
| |
| 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. |
| } |
| |
| return resolution_attempted; |
| } |
| |
| /* |
| * Attempts to modify the topological sorting of the provided object graph to |
| * eliminate offset overflows in the links between objects of the graph. If a |
| * non-overflowing ordering is found the updated graph is serialized it into the |
| * provided serialization context. |
| * |
| * 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/overflow_resolution.md |
| */ |
| inline void |
| hb_resolve_overflows (const hb_vector_t<hb_serialize_context_t::object_t *>& packed, |
| hb_tag_t table_tag, |
| hb_serialize_context_t* c, |
| unsigned max_rounds = 10) { |
| // Kahn sort is ~twice as fast as shortest distance sort and works for many fonts |
| // so try it first to save time. |
| graph_t sorted_graph (packed); |
| sorted_graph.sort_kahn (); |
| if (!sorted_graph.will_overflow ()) |
| { |
| sorted_graph.serialize (c); |
| return; |
| } |
| |
| sorted_graph.sort_shortest_distance (); |
| |
| if ((table_tag == HB_OT_TAG_GPOS |
| || table_tag == HB_OT_TAG_GSUB) |
| && sorted_graph.will_overflow ()) |
| { |
| if (sorted_graph.isolate_32bit_links ()) |
| { |
| DEBUG_MSG (SUBSET_REPACK, nullptr, "Isolated extension sub tables."); |
| 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++ < max_rounds) { |
| DEBUG_MSG (SUBSET_REPACK, nullptr, "=== Overflow resolution round %d ===", round); |
| sorted_graph.print_overflows (overflows); |
| |
| hb_set_t priority_bumped_parents; |
| if (!_process_overflows (overflows, priority_bumped_parents, sorted_graph)) |
| { |
| DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :("); |
| break; |
| } |
| |
| sorted_graph.sort_shortest_distance (); |
| } |
| |
| if (sorted_graph.in_error ()) |
| { |
| c->err (HB_SERIALIZE_ERROR_OTHER); |
| return; |
| } |
| |
| if (sorted_graph.will_overflow ()) |
| { |
| c->err (HB_SERIALIZE_ERROR_OFFSET_OVERFLOW); |
| DEBUG_MSG (SUBSET_REPACK, nullptr, "Offset overflow resolution failed."); |
| return; |
| } |
| sorted_graph.serialize (c); |
| } |
| |
| #endif /* HB_REPACKER_HH */ |