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.
unsigned count = objects.length; unsigned order = objects.length; unsigned skip = 0; for (unsigned i = 0; i < count; i++)
{ // 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.arrayZ[i])
{
removed_nil = true;
order--;
ordering_.resize(objects.length - 1);
skip++; continue;
}
vertex_t* v = vertices_.push (); if (check_success (!vertices_.in_error ()))
v->obj = *objects.arrayZ[i];
// To start we set the ordering to match the provided objects // list. Note: objects are provided to us in reverse order (ie. // the last object is the root). unsigned obj_idx = i - skip;
ordering_[--order] = obj_idx;
if (!removed_nil) continue; // Fix indices to account for removed nil object. for (auto& l : v->obj.all_links_writer ()) {
l.objidx--;
}
}
}
~graph_t ()
{ for (char* b : buffers)
hb_free (b);
}
unsigned root_idx () const
{ // First element of ordering_ is the root. // Since the graph is topologically sorted it's safe to // assume the first object has no incoming edges. return ordering_[0];
}
if (unlikely (!check_success(pos < new_ordering.length))) { // We are out of ids. Which means we've visited a node more than once. // This graph contains a cycle which is not allowed.
DEBUG_MSG (SUBSET_REPACK, nullptr, "Invalid graph. Contains cycle."); return;
}
new_ordering[pos++] = next_id; const vertex_t& next = vertices_[next_id];
for (constauto& link : next.obj.all_links ()) {
removed_edges[link.objidx]++; constauto& v = vertices_[link.objidx]; if (!(v.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 (v.modified_distance (order++),
link.objidx);
}
}
if (!check_success (pos == vertices_.length)) {
print_orphaned_nodes ();
}
}
/* *Findsthesetofnodes(placedintoroots)thatshouldbeassigneduniquespaces. *Morespecificallythislooksforthetopmost24bitor32bitlinksinthegraph. *SomespecialcasingisdonethatisspecifictothelayoutofGSUB/GPOStables.
*/ void find_space_roots (hb_set_t& visited, hb_set_t& roots)
{ unsigned root_index = root_idx (); for (unsigned i : ordering_)
{ if (visited.has (i)) continue;
// Only real links can form 32 bit spaces for (auto& l : vertices_[i].obj.real_links)
{ if (l.is_signed || l.width < 3) continue;
if (i == root_index && l.width == 3) // Ignore 24bit links from the root node, this skips past the single 24bit // pointer to the lookup list. continue;
if (l.width == 3)
{ // A 24bit offset forms a root, unless there is 32bit offsets somewhere // in it's subgraph, then those become the roots instead. This is to make sure // that extension subtables beneath a 24bit lookup become the spaces instead // of the offset to the lookup.
hb_set_t sub_roots;
find_32bit_roots (l.objidx, sub_roots); if (sub_roots) { for (unsigned sub_root_idx : sub_roots) {
roots.add (sub_root_idx);
find_subgraph (sub_root_idx, visited);
} continue;
}
}
if (!r.table->sanitize (*(r.vertex), std::forward<Ts>(ds)...)) return vertex_and_table_t<T> ();
return r;
}
// Finds the object id of the object pointed to by the offset at 'offset' // within object[node_idx]. unsigned index_for_offset (unsigned node_idx, constvoid* offset) const
{ constauto& node = object (node_idx); if (offset < node.head || offset >= node.tail) return -1;
unsigned count = node.real_links.length; for (unsigned i = 0; i < count; i++)
{ // Use direct access for increased performance, this is a hot method. constauto& link = node.real_links.arrayZ[i]; if (offset != node.head + link.position) continue; return link.objidx;
}
return -1;
}
// Finds the object id of the object pointed to by the offset at 'offset' // within object[node_idx]. Ensures that the returned object is safe to mutate. // That is, if the original child object is shared by parents other than node_idx // it will be duplicated and the duplicate will be returned instead. unsigned mutable_index_for_offset (unsigned node_idx, constvoid* offset)
{ unsigned child_idx = index_for_offset (node_idx, offset); auto& child = vertices_[child_idx]; for (unsigned p : child.parents_iter ())
{ if (p != node_idx) { return duplicate (node_idx, child_idx);
}
}
// Mark everything not in the subgraphs of the roots as visited. This prevents // subgraphs from being connected via nodes not in those subgraphs.
visited.invert ();
if (!roots) returnfalse;
while (roots)
{
uint32_t next = HB_SET_VALUE_INVALID; if (unlikely (!check_success (!roots.in_error ()))) break; if (!roots.next (&next)) break;
// TODO(grieger): special case for GSUB/GPOS use extension promotions to move 16 bit space // into the 32 bit space as needed, instead of using isolation.
}
// 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
hb_set_t parents; for (unsigned root_idx : roots)
{
subgraph.set (root_idx, wide_parents (root_idx, parents));
find_subgraph (root_idx, subgraph);
} if (subgraph.in_error ()) returnfalse;
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);
}
}
// Update roots set with new indices as needed. for (auto next : roots)
{ const uint32_t *v; if (index_map.has (next, &v))
{
roots.del (next);
roots.add (*v);
}
}
if (child.incoming_edges () <= links_to_child || child.has_incoming_virtual_edges())
{ // Can't duplicate this node, doing so would orphan the original one as all remaining links // to child are from parent. // // We don't allow duplication of nodes with incoming virtual edges because we don't track // the number of virtual vs real incoming edges. As a result we can't tell if a node // with virtual edges may end up orphaned by duplication (ie. where one copy is only pointed // to by virtual edges).
DEBUG_MSG (SUBSET_REPACK, nullptr, " Not duplicating %u => %u",
parent_idx, child_idx); return -1;
}
unsigned clone_idx = duplicate (child_idx); if (clone_idx == (unsigned) -1) return -1; // duplicate shifts the root node idx, so if parent_idx was root update it. if (parent_idx == clone_idx) parent_idx++;
auto& parent = vertices_[parent_idx]; unsigned count = 0; unsigned num_real = parent.obj.real_links.length; for (auto& l : parent.obj.all_links_writer ())
{
count++; if (l.objidx != child_idx) continue;
if (child.incoming_edges () <= links_to_child || child.has_incoming_virtual_edges())
{ // Can't duplicate this node, doing so would orphan the original one as all remaining links // to child are from parent. // // We don't allow duplication of nodes with incoming virtual edges because we don't track // the number of virtual vs real incoming edges. As a result we can't tell if a node // with virtual edges may end up orphaned by duplication (ie. where one copy is only pointed // to by virtual edges).
DEBUG_MSG (SUBSET_REPACK, nullptr, " Not duplicating %u, ..., %u => %u", first_parent, last_parent, child_idx); return -1;
}
for (unsigned parent_idx : *parents) { // duplicate shifts the root node idx, so if parent_idx was root update it. if (parent_idx == clone_idx) parent_idx++; auto& parent = vertices_[parent_idx]; unsigned count = 0; unsigned num_real = parent.obj.real_links.length; for (auto& l : parent.obj.all_links_writer ())
{
count++; if (l.objidx != child_idx) continue;
auto& parent = vertices_[parent_idx]; for (auto& l : parent.obj.real_links)
{ if (l.objidx != old_child_idx) continue;
reassign_link (l, parent_idx, new_child_idx, false);
}
for (auto& l : parent.obj.virtual_links)
{ if (l.objidx != old_child_idx) continue;
reassign_link (l, parent_idx, new_child_idx, true);
} return new_child_idx;
}
/* *Raisesthesortingpriorityofallchildren.
*/ bool raise_childrens_priority (unsigned parent_idx)
{
DEBUG_MSG (SUBSET_REPACK, nullptr, " Raising priority of all children of %u",
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; bool made_change = false; for (auto& l : parent.all_links_writer ())
made_change |= vertices_[l.objidx].raise_priority (); return made_change;
}
bool is_fully_connected ()
{
update_parents();
if (root().incoming_edges ()) // Root cannot have parents. returnfalse;
for (unsigned i = 0; i < root_idx (); i++)
{ if (!vertices_[i].incoming_edges ()) returnfalse;
} returntrue;
}
for (unsigned i = 0; i < vertices_.length; i++)
{ for (constauto& l : vertices_[i].obj.real_links)
{
link_t link {
(uint16_t) i, (uint16_t) l.objidx,
(uint16_t) l.position, (uint8_t) l.width
};
fwrite ((void*) &link, sizeof (link), 1, f);
}
}
fclose (f);
} #endif
void print_orphaned_nodes ()
{ if (!DEBUG_ENABLED(SUBSET_REPACK)) return;
DEBUG_MSG (SUBSET_REPACK, nullptr, "Graph is not fully connected.");
parents_invalid = true;
update_parents();
if (root().incoming_edges ()) {
DEBUG_MSG (SUBSET_REPACK, nullptr, "Root node has incoming edges.");
}
for (unsigned i = 0; i < root_idx (); i++)
{ constauto& v = vertices_[i]; if (!v.incoming_edges ())
DEBUG_MSG (SUBSET_REPACK, nullptr, "Node %u is orphaned.", i);
}
}
for (unsigned i = 0; i < count; i++)
vertices_.arrayZ[i].reset_parents ();
for (unsigned p = 0; p < count; p++)
{ for (auto& l : vertices_.arrayZ[p].obj.real_links)
vertices_[l.objidx].add_parent (p, false);
for (auto& l : vertices_.arrayZ[p].obj.virtual_links)
vertices_[l.objidx].add_parent (p, true);
}
for (unsigned i = 0; i < count; i++) // parents arrays must be accurate or downstream operations like cycle detection // and sorting won't work correctly.
check_success (!vertices_.arrayZ[i].in_error ());
parents_invalid = false;
}
/* *computetheserializedstartandendpositionsforeachvertex.
*/ void update_positions ()
{ if (!positions_invalid) return;
unsigned current_pos = 0; for (unsigned i : ordering_)
{ auto& v = vertices_[i];
v.start = current_pos;
current_pos += v.obj.tail - v.obj.head;
v.end = current_pos;
}
// 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 fibonacci queue) with a fast decrease priority. unsigned count = vertices_.length; for (unsigned i = 0; i < count; i++)
vertices_.arrayZ[i].distance = hb_int_max (int64_t);
vertices_[root_idx ()].distance = 0;
if (targets.has (start_idx))
{
targets.del (start_idx);
connected.add (start_idx);
}
constauto& v = vertices_[start_idx];
// Graph is treated as undirected so search children and parents of start_idx for (constauto& l : v.obj.all_links ())
find_connected_nodes (l.objidx, targets, visited, connected);
for (unsigned p : v.parents_iter ())
find_connected_nodes (p, targets, visited, connected);
}
public: // TODO(garretrieger): make private, will need to move most of offset overflow code into graph.
hb_vector_t<vertex_t> vertices_;
// Specifies the current topological ordering of this graph // // ordering_[pos] = obj index // // specifies that the 'pos'th spot is filled by the object // given by obj index.
hb_vector_t<unsigned> ordering_;
hb_vector_t<unsigned> ordering_scratch_;
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.53Bemerkung:
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-09-30)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.