/* * 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" struct graph_t { // TODO(garretrieger): add an error tracking system similar to what serialize_context_t // does. struct overflow_record_t { unsigned parent; const hb_serialize_context_t::object_t::link_t* link; }; struct clone_buffer_t { clone_buffer_t () : head (nullptr), tail (nullptr) {} void copy (const hb_serialize_context_t::object_t& object) { fini (); unsigned size = object.tail - object.head; head = (char*) malloc (size); memcpy (head, object.head, size); tail = head + size; } char* head; char* tail; void fini () { if (!head) return; 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& objects) { 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; } auto* copy = objects_.push (*objects[i]); if (!removed_nil) continue; for (unsigned i = 0; i < copy->links.length; i++) // Fix indices to account for removed nil object. copy->links[i].objidx--; } } ~graph_t () { objects_.fini_deep (); clone_buffers_.fini_deep (); } /* * serialize graph into the provided serialization buffer. */ void serialize (hb_serialize_context_t* c) { c->start_serialize (); for (unsigned i = 0; i < objects_.length; i++) { c->push (); size_t size = objects_[i].tail - objects_[i].head; char* start = c->allocate_size (size); if (!start) return; memcpy (start, objects_[i].head, size); for (const auto& link : objects_[i].links) serialize_link (link, start, c); 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 () { if (objects_.length <= 1) { // Graph of 1 or less doesn't need sorting. return; } hb_vector_t queue; hb_vector_t sorted_graph; hb_vector_t id_map; id_map.resize (objects_.length); hb_vector_t edge_count; incoming_edge_count (&edge_count); // 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. queue.push (objects_.length - 1); int new_id = objects_.length - 1; while (queue.length) { unsigned next_id = queue[0]; queue.remove(0); hb_serialize_context_t::object_t& next = objects_[next_id]; sorted_graph.push (next); id_map[next_id] = new_id--; for (const auto& link : next.links) { // TODO(garretrieger): sort children from smallest to largest edge_count[link.objidx] -= 1; if (!edge_count[link.objidx]) queue.push (link.objidx); } } if (new_id != -1) { // Graph is not fully connected, there are unsorted objects. // TODO(garretrieger): handle this. assert (false); } remap_obj_indices (id_map, &sorted_graph); sorted_graph.as_array ().reverse (); objects_ = 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 () { if (objects_.length <= 1) { // Graph of 1 or less doesn't need sorting. return; } hb_vector_t distance_to; compute_distances (&distance_to); hb_priority_queue_t queue; hb_vector_t sorted_graph; hb_vector_t id_map; id_map.resize (objects_.length); hb_vector_t edge_count; incoming_edge_count (&edge_count); // 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. queue.insert (objects_.length - 1, add_order(distance_to[objects_.length - 1], 0)); int new_id = objects_.length - 1; unsigned order = 1; while (!queue.is_empty ()) { unsigned next_id = queue.extract_minimum().first; hb_serialize_context_t::object_t& next = objects_[next_id]; sorted_graph.push (next); id_map[next_id] = new_id--; for (const auto& link : next.links) { edge_count[link.objidx] -= 1; if (!edge_count[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 topolical order in the order that they are // referenced from the parent object. queue.insert (link.objidx, add_order(distance_to[link.objidx], order++)); } } if (new_id != -1) { // Graph is not fully connected, there are unsorted objects. // TODO(garretrieger): handle this. assert (false); } remap_obj_indices (id_map, &sorted_graph); sorted_graph.as_array ().reverse (); objects_ = sorted_graph; sorted_graph.fini_deep (); } /* * 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) { const auto& child = objects_[child_idx]; clone_buffer_t* buffer = clone_buffers_.push (); buffer->copy (child); auto* clone = objects_.push (); clone->head = buffer->head; clone->tail = buffer->tail; for (const auto& l : child.links) clone->links.push (l); auto& parent = objects_[parent_idx]; unsigned clone_idx = objects_.length - 2; for (unsigned i = 0; i < parent.links.length; i++) { auto& l = parent.links[i]; if (l.objidx == child_idx) l.objidx = clone_idx; } // 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. hb_serialize_context_t::object_t root = objects_[objects_.length - 2]; objects_[objects_.length - 2] = *clone; objects_[objects_.length - 1] = root; } /* * Will any offsets overflow on graph when it's serialized? */ bool will_overflow (hb_vector_t* overflows) { if (overflows) overflows->resize (0); hb_vector_t start_positions; start_positions.resize (objects_.length); hb_vector_t end_positions; end_positions.resize (objects_.length); unsigned current_pos = 0; for (int i = objects_.length - 1; i >= 0; i--) { start_positions[i] = current_pos; current_pos += objects_[i].tail - objects_[i].head; end_positions[i] = current_pos; } for (int parent_idx = objects_.length - 1; parent_idx >= 0; parent_idx--) { for (const auto& link : objects_[parent_idx].links) { int64_t offset = compute_offset (parent_idx, link, start_positions, end_positions); if (is_valid_offset (offset, link)) continue; if (!overflows) return true; overflow_record_t r; r.parent = parent_idx; r.link = &link; overflows->push (r); } } if (!overflows) return false; return overflows->length; } /* * Creates a map from objid to # of incoming edges. */ void incoming_edge_count (hb_vector_t* out) const { out->resize (0); out->resize (objects_.length); for (const auto& o : objects_) { for (const auto& l : o.links) { (*out)[l.objidx] += 1; } } } void print_overflows (const hb_vector_t& overflows) const { if (!DEBUG_ENABLED(SUBSET_REPACK)) return; hb_vector_t edge_count; incoming_edge_count (&edge_count); for (const auto& o : overflows) { DEBUG_MSG (SUBSET_REPACK, nullptr, "overflow from %d => %d (%d incoming , %d outgoing)", o.parent, o.link->objidx, edge_count[o.link->objidx], objects_[o.link->objidx].links.length); } } private: int64_t add_order (int64_t distance, unsigned order) { return (distance << 24) | (0x00FFFFFF & order); } /* * Finds the distance too each object in the graph * from the initial node. */ void compute_distances (hb_vector_t* distance_to) { // 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. distance_to->resize (0); distance_to->resize (objects_.length); for (unsigned i = 0; i < objects_.length; i++) { if (i == objects_.length - 1) (*distance_to)[i] = 0; else (*distance_to)[i] = hb_int_max (int64_t); } hb_priority_queue_t queue; queue.insert (objects_.length - 1, 0); hb_set_t visited; while (!queue.is_empty ()) { unsigned next_idx = queue.extract_minimum ().first; if (visited.has (next_idx)) continue; const auto& next = objects_[next_idx]; int64_t next_distance = (*distance_to)[next_idx]; visited.add (next_idx); for (const auto& link : next.links) { if (visited.has (link.objidx)) continue; const auto& child = objects_[link.objidx]; int64_t child_weight = child.tail - child.head + (!link.is_wide ? (1 << 16) : ((int64_t) 1 << 32)); int64_t child_distance = next_distance + child_weight; if (child_distance < (*distance_to)[link.objidx]) { (*distance_to)[link.objidx] = child_distance; queue.insert (link.objidx, child_distance); } } } // TODO(garretrieger): Handle this. If anything is left, part of the graph is disconnected. assert (queue.is_empty ()); } int64_t compute_offset ( unsigned parent_idx, const hb_serialize_context_t::object_t::link_t& link, const hb_vector_t& start_positions, const hb_vector_t& end_positions) { unsigned child_idx = link.objidx; int64_t offset = 0; switch ((hb_serialize_context_t::whence_t) link.whence) { case hb_serialize_context_t::whence_t::Head: offset = start_positions[child_idx] - start_positions[parent_idx]; break; case hb_serialize_context_t::whence_t::Tail: offset = start_positions[child_idx] - end_positions[parent_idx]; break; case hb_serialize_context_t::whence_t::Absolute: offset = start_positions[child_idx]; 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) { if (link.is_signed) { if (link.is_wide) return offset >= -((int64_t) 1 << 31) && offset < ((int64_t) 1 << 31); else return offset >= -(1 << 15) && offset < (1 << 15); } else { if (link.is_wide) return offset >= 0 && offset < ((int64_t) 1 << 32); else return offset >= 0 && offset < (1 << 16); } } /* * Updates all objidx's in all links using the provided mapping. */ void remap_obj_indices (const hb_vector_t& id_map, hb_vector_t* sorted_graph) { for (unsigned i = 0; i < sorted_graph->length; i++) { for (unsigned j = 0; j < (*sorted_graph)[i].links.length; j++) { auto& link = (*sorted_graph)[i].links[j]; link.objidx = id_map[link.objidx]; } } } template void serialize_link_of_type (const hb_serialize_context_t::object_t::link_t& link, char* head, hb_serialize_context_t* c) { OT::Offset* offset = reinterpret_cast*> (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) { if (link.is_wide) { if (link.is_signed) { serialize_link_of_type (link, head, c); } else { serialize_link_of_type (link, head, c); } } else { if (link.is_signed) { serialize_link_of_type (link, head, c); } else { serialize_link_of_type (link, head, c); } } } public: hb_vector_t objects_; hb_vector_t clone_buffers_; }; /* * Re-serialize the provided object graph into the serialization context * using BFS (Breadth First Search) to produce the topological ordering. */ inline void hb_resolve_overflows (const hb_vector_t& packed, hb_serialize_context_t* c) { graph_t sorted_graph (packed); sorted_graph.sort_kahn (); if (!sorted_graph.will_overflow (nullptr)) return; sorted_graph.sort_shortest_distance (); unsigned round = 0; hb_vector_t overflows; // TODO(garretrieger): select a good limit for max rounds. while (sorted_graph.will_overflow (&overflows) && round++ < 10) { DEBUG_MSG (SUBSET_REPACK, nullptr, "Over flow resolution round %d", round); sorted_graph.print_overflows (overflows); // TODO(garretrieger): cache ege count in the graph object . Will need to be invalidated // by graph modifications. hb_vector_t edge_count; sorted_graph.incoming_edge_count (&edge_count); // Try resolving the furthest overflow first. bool resolution_attempted = false; for (int i = overflows.length - 1; i >= 0; i--) { const graph_t::overflow_record_t& r = overflows[i]; if (edge_count[r.link->objidx] > 1) { DEBUG_MSG (SUBSET_REPACK, nullptr, "Duplicating %d => %d", r.parent, r.link->objidx); // The child object is shared, we may be able to eliminate the overflow // by duplicating it. sorted_graph.duplicate (r.parent, r.link->objidx); sorted_graph.sort_shortest_distance (); resolution_attempted = true; break; } // TODO(garretrieger): add additional offset resolution strategies // - Promotion to extension lookups. // - Table splitting. } if (!resolution_attempted) { DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :("); break; } } sorted_graph.serialize (c); } #endif /* HB_REPACKER_HH */