From 9121ed0cecab2296ee2a18042ed89c0ce95dbbaa Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Tue, 30 Nov 2021 13:45:22 -0800 Subject: [PATCH] [subset] Improve sharing of Ligature subtables. Ligature subtables use virtual links to enforce an ordering constraint between the subtables and the coverage table. Unfortunately this has the sideeffect of prevent the subtables from being shared by another Ligature with a different coverage table since object equality compares all links real and virtual. This change makes virtual links stored separately from real links and updates the equality check to only check real links. If an object is de-duped any virtual links it has are merged into the object that replaces it. --- src/hb-repacker.hh | 84 ++++++---- src/hb-serialize.hh | 50 ++++-- src/test-repacker.cc | 150 +++++++++++------- ...gular.layout-test.retain-all-codepoint.ttf | Bin 46544 -> 46524 bytes ...h-Regular.default.retain-all-codepoint.ttf | Bin 174024 -> 173756 bytes ...egular.drop-hints.retain-all-codepoint.ttf | Bin 99288 -> 99020 bytes ...gular.retain-gids.retain-all-codepoint.ttf | Bin 174324 -> 174056 bytes ...taliqUrdu-Bold.default.627,644,62D,628.ttf | Bin 17732 -> 17640 bytes ...oNastaliqUrdu-Bold.default.63A,64A,631.ttf | Bin 19432 -> 19340 bytes ...qUrdu-Bold.retain-gids.627,644,62D,628.ttf | Bin 23792 -> 23704 bytes ...taliqUrdu-Bold.retain-gids.63A,64A,631.ttf | Bin 25484 -> 25392 bytes ...varaya-Regular.default.c30,c36,c40,c4d.ttf | Bin 10860 -> 10848 bytes ...a-Regular.default.retain-all-codepoint.ttf | Bin 572812 -> 572104 bytes ...ya-Regular.glyph-names.c30,c36,c40,c4d.ttf | Bin 11292 -> 11280 bytes ...gular.glyph-names.retain-all-codepoint.ttf | Bin 608460 -> 607752 bytes ...Regular.notdef-outline.c30,c36,c40,c4d.ttf | Bin 10940 -> 10928 bytes ...ar.notdef-outline.retain-all-codepoint.ttf | Bin 572892 -> 572184 bytes ...ya-Regular.retain-gids.c30,c36,c40,c4d.ttf | Bin 16880 -> 16868 bytes ...gular.retain-gids.retain-all-codepoint.ttf | Bin 572824 -> 572116 bytes 19 files changed, 181 insertions(+), 103 deletions(-) diff --git a/src/hb-repacker.hh b/src/hb-repacker.hh index 26faa56ea..5d3184429 100644 --- a/src/hb-repacker.hh +++ b/src/hb-repacker.hh @@ -100,7 +100,7 @@ struct graph_t bool is_leaf () const { - return !obj.links.length; + return !obj.real_links.length && !obj.virtual_links.length; } void raise_priority () @@ -164,9 +164,11 @@ struct graph_t 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--; + // Fix indices to account for removed nil object. + for (auto& l : hb_concat (v->obj.real_links.writer (), + v->obj.virtual_links.writer ())) { + l.objidx--; + } } } @@ -215,7 +217,8 @@ struct graph_t memcpy (start, vertices_[i].obj.head, size); - for (const auto& link : vertices_[i].obj.links) + // Only real links needs to be serialized. + for (const auto& link : vertices_[i].obj.real_links) serialize_link (link, start, c); // All duplications are already encoded in the graph, so don't @@ -260,7 +263,7 @@ struct graph_t sorted_graph[new_id] = next; id_map[next_id] = new_id--; - for (const auto& link : next.obj.links) { + for (const auto& link : hb_concat (next.obj.real_links, next.obj.virtual_links)) { removed_edges[link.objidx]++; if (!(vertices_[link.objidx].incoming_edges () - removed_edges[link.objidx])) queue.push (link.objidx); @@ -314,7 +317,7 @@ struct graph_t sorted_graph[new_id] = next; id_map[next_id] = new_id--; - for (const auto& link : next.obj.links) { + for (const auto& link : hb_concat (next.obj.real_links, next.obj.virtual_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. @@ -348,7 +351,8 @@ struct graph_t hb_set_t roots; for (unsigned i = 0; i <= root_index; i++) { - for (auto& l : vertices_[i].obj.links) + // Only real links can form 32 bit spaces + for (auto& l : vertices_[i].obj.real_links) { if (l.width == 4 && !l.is_signed) { @@ -466,7 +470,8 @@ struct graph_t void find_subgraph (unsigned node_idx, hb_hashmap_t& subgraph) { - for (const auto& link : vertices_[node_idx].obj.links) + for (const auto& link : hb_concat (vertices_[node_idx].obj.real_links, + vertices_[node_idx].obj.virtual_links)) { if (subgraph.has (link.objidx)) { @@ -482,7 +487,8 @@ struct graph_t { if (subgraph.has (node_idx)) return; subgraph.add (node_idx); - for (const auto& link : vertices_[node_idx].obj.links) + for (const auto& link : hb_concat (vertices_[node_idx].obj.real_links, + vertices_[node_idx].obj.virtual_links)) find_subgraph (link.objidx, subgraph); } @@ -497,7 +503,8 @@ struct graph_t return; index_map.set (node_idx, duplicate (node_idx)); - for (const auto& l : object (node_idx).links) { + for (const auto& l : hb_concat (object (node_idx).real_links, + object (node_idx).virtual_links)) { duplicate_subgraph (l.objidx, index_map); } } @@ -523,13 +530,19 @@ struct graph_t clone->parents.reset (); unsigned clone_idx = vertices_.length - 2; - for (const auto& l : child.obj.links) + for (const auto& l : child.obj.real_links) { - clone->obj.links.push (l); + clone->obj.real_links.push (l); + vertices_[l.objidx].parents.push (clone_idx); + } + for (const auto& l : child.obj.virtual_links) + { + clone->obj.virtual_links.push (l); vertices_[l.objidx].parents.push (clone_idx); } - check_success (!clone->obj.links.in_error ()); + check_success (!clone->obj.real_links.in_error ()); + check_success (!clone->obj.virtual_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. @@ -539,7 +552,8 @@ struct graph_t vertices_[vertices_.length - 1] = root; // Since the root moved, update the parents arrays of all children on the root. - for (const auto& l : root.obj.links) + for (const auto& l : hb_concat (root.obj.real_links, + root.obj.virtual_links)) vertices_[l.objidx].remap_parent (root_idx () - 1, root_idx ()); return clone_idx; @@ -555,7 +569,8 @@ struct graph_t update_parents (); unsigned links_to_child = 0; - for (const auto& l : vertices_[parent_idx].obj.links) + for (const auto& l : hb_concat (vertices_[parent_idx].obj.real_links, + vertices_[parent_idx].obj.virtual_links)) { if (l.objidx == child_idx) links_to_child++; } @@ -578,9 +593,9 @@ struct graph_t if (parent_idx == clone_idx) parent_idx++; auto& parent = vertices_[parent_idx]; - for (unsigned i = 0; i < parent.obj.links.length; i++) + for (auto& l : hb_concat (parent.obj.real_links.writer (), + parent.obj.virtual_links.writer ())) { - auto& l = parent.obj.links[i]; if (l.objidx != child_idx) continue; @@ -601,8 +616,9 @@ struct graph_t // 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 (); + for (auto& l : hb_concat (parent.real_links.writer (), + parent.virtual_links.writer ())) + vertices_[l.objidx].raise_priority (); } /* @@ -615,7 +631,8 @@ struct graph_t for (int parent_idx = vertices_.length - 1; parent_idx >= 0; parent_idx--) { - for (const auto& link : vertices_[parent_idx].obj.links) + // Don't need to check virtual links for overflow + for (const auto& link : vertices_[parent_idx].obj.real_links) { int64_t offset = compute_offset (parent_idx, link); if (is_valid_offset (offset, link)) @@ -665,11 +682,11 @@ struct graph_t "%4d (%4d in, %4d out, space %2d)", o.parent, parent.incoming_edges (), - parent.obj.links.length, + parent.obj.real_links.length + parent.obj.virtual_links.length, space_for (o.parent), o.child, child.incoming_edges (), - child.obj.links.length, + child.obj.real_links.length + child.obj.virtual_links.length, space_for (o.child)); } } @@ -728,7 +745,8 @@ struct graph_t if (visited.has (p)) continue; visited.add (p); - for (const auto& l : vertices_[p].obj.links) + // Only real links can be wide + for (const auto& l : vertices_[p].obj.real_links) { if (l.objidx == node_idx && l.width == 4 && !l.is_signed) { @@ -755,7 +773,8 @@ struct graph_t for (unsigned p = 0; p < vertices_.length; p++) { - for (auto& l : vertices_[p].obj.links) + for (auto& l : hb_concat (vertices_[p].obj.real_links, + vertices_[p].obj.virtual_links)) { vertices_[l.objidx].parents.push (p); } @@ -823,7 +842,8 @@ struct graph_t int64_t next_distance = vertices_[next_idx].distance; visited[next_idx] = true; - for (const auto& link : next.obj.links) + for (const auto& link : hb_concat (next.obj.real_links, + next.obj.virtual_links)) { if (visited[link.objidx]) continue; @@ -922,9 +942,9 @@ struct graph_t if (!id_map) return; for (unsigned i : subgraph) { - for (unsigned j = 0; j < vertices_[i].obj.links.length; j++) + for (auto& link : hb_concat (vertices_[i].obj.real_links.writer (), + vertices_[i].obj.virtual_links.writer ())) { - auto& link = vertices_[i].obj.links[j]; if (!id_map.has (link.objidx)) continue; if (only_wide && !(link.width == 4 && !link.is_signed)) continue; @@ -942,9 +962,10 @@ struct graph_t for (unsigned i = 0; i < sorted_graph->length; i++) { (*sorted_graph)[i].remap_parents (id_map); - for (unsigned j = 0; j < (*sorted_graph)[i].obj.links.length; j++) + for (auto& link : hb_concat ((*sorted_graph)[i].obj.real_links.writer (), + (*sorted_graph)[i].obj.virtual_links.writer ())) + { - auto& link = (*sorted_graph)[i].obj.links[j]; link.objidx = id_map[link.objidx]; } } @@ -1023,7 +1044,8 @@ struct graph_t const auto& v = vertices_[start_idx]; // Graph is treated as undirected so search children and parents of start_idx - for (const auto& l : v.obj.links) + for (const auto& l : hb_concat (v.obj.real_links, + v.obj.virtual_links)) find_connected_nodes (l.objidx, targets, visited, connected); for (unsigned p : v.parents) diff --git a/src/hb-serialize.hh b/src/hb-serialize.hh index d22ae0608..107390155 100644 --- a/src/hb-serialize.hh +++ b/src/hb-serialize.hh @@ -65,19 +65,26 @@ struct hb_serialize_context_t struct object_t { - void fini () { links.fini (); } + void fini () { + real_links.fini (); + virtual_links.fini (); + } bool operator == (const object_t &o) const { + // Virtual links aren't considered for equality since they don't affect the functionality + // of the object. return (tail - head == o.tail - o.head) - && (links.length == o.links.length) + && (real_links.length == o.real_links.length) && 0 == hb_memcmp (head, o.head, tail - head) - && links.as_bytes () == o.links.as_bytes (); + && real_links.as_bytes () == o.real_links.as_bytes (); } uint32_t hash () const { + // Virtual links aren't considered for equality since they don't affect the functionality + // of the object. return hb_bytes_t (head, tail - head).hash () ^ - links.as_bytes ().hash (); + real_links.as_bytes ().hash (); } struct link_t @@ -92,7 +99,8 @@ struct hb_serialize_context_t char *head; char *tail; - hb_vector_t links; + hb_vector_t real_links; + hb_vector_t virtual_links; object_t *next; }; @@ -101,12 +109,14 @@ struct hb_serialize_context_t char *head; char *tail; object_t *current; // Just for sanity check - unsigned num_links; + unsigned num_real_links; + unsigned num_virtual_links; hb_serialize_error_t errors; }; snapshot_t snapshot () - { return snapshot_t { head, tail, current, current->links.length, errors }; } + { return snapshot_t { + head, tail, current, current->real_links.length, current->virtual_links.length, errors }; } hb_serialize_context_t (void *start_, unsigned int size) : start ((char *) start_), @@ -282,7 +292,8 @@ struct hb_serialize_context_t if (!len) { - assert (!obj->links.length); + assert (!obj->real_links.length); + assert (!obj->virtual_links.length); return 0; } @@ -292,6 +303,7 @@ struct hb_serialize_context_t objidx = packed_map.get (obj); if (objidx) { + merge_virtual_links (obj, objidx); obj->fini (); return objidx; } @@ -327,7 +339,8 @@ struct hb_serialize_context_t // Overflows that happened after the snapshot will be erased by the revert. if (unlikely (in_error () && !only_overflow ())) return; assert (snap.current == current); - current->links.shrink (snap.num_links); + current->real_links.shrink (snap.num_real_links); + current->virtual_links.shrink (snap.num_virtual_links); errors = snap.errors; revert (snap.head, snap.tail); } @@ -375,8 +388,8 @@ struct hb_serialize_context_t assert (current); - auto& link = *current->links.push (); - if (current->links.in_error ()) + auto& link = *current->virtual_links.push (); + if (current->virtual_links.in_error ()) err (HB_SERIALIZE_ERROR_OTHER); link.width = 0; @@ -400,8 +413,8 @@ struct hb_serialize_context_t assert (current); assert (current->head <= (const char *) &ofs); - auto& link = *current->links.push (); - if (current->links.in_error ()) + auto& link = *current->real_links.push (); + if (current->real_links.in_error ()) err (HB_SERIALIZE_ERROR_OTHER); link.width = sizeof (T); @@ -440,10 +453,8 @@ struct hb_serialize_context_t assert (packed.length > 1); for (const object_t* parent : ++hb_iter (packed)) - for (const object_t::link_t &link : parent->links) + for (const object_t::link_t &link : parent->real_links) { - if (unlikely (!link.width)) continue; // Don't need to resolve virtual offsets - const object_t* child = packed[link.objidx]; if (unlikely (!child)) { err (HB_SERIALIZE_ERROR_OTHER); return; } unsigned offset = 0; @@ -642,6 +653,13 @@ struct hb_serialize_context_t private: + void merge_virtual_links (const object_t* from, objidx_t to_idx) { + object_t* to = packed[to_idx]; + for (const auto& l : from->virtual_links) { + to->virtual_links.push (l); + } + } + /* Object memory pool. */ hb_pool_t object_pool; diff --git a/src/test-repacker.cc b/src/test-repacker.cc index 4df063629..86ab2ff19 100644 --- a/src/test-repacker.cc +++ b/src/test-repacker.cc @@ -763,20 +763,20 @@ populate_serializer_virtual_link (hb_serialize_context_t* c) start_object ("b", 1, c); add_offset (obj_d, c); - unsigned obj_b = c->pop_pack (); + unsigned obj_b = c->pop_pack (false); start_object ("e", 1, c); add_virtual_offset (obj_b, c); - unsigned obj_e = c->pop_pack(); + unsigned obj_e = c->pop_pack (false); start_object ("c", 1, c); add_offset (obj_e, c); - unsigned obj_c = c->pop_pack (); + unsigned obj_c = c->pop_pack (false); start_object ("a", 1, c); add_offset (obj_b, c); add_offset (obj_c, c); - c->pop_pack (); + c->pop_pack (false); c->end_serialize(); } @@ -792,19 +792,19 @@ static void test_sort_kahn_1 () graph.sort_kahn (); assert(strncmp (graph.object (3).head, "abc", 3) == 0); - assert(graph.object (3).links.length == 2); - assert(graph.object (3).links[0].objidx == 2); - assert(graph.object (3).links[1].objidx == 1); + assert(graph.object (3).real_links.length == 2); + assert(graph.object (3).real_links[0].objidx == 2); + assert(graph.object (3).real_links[1].objidx == 1); assert(strncmp (graph.object (2).head, "def", 3) == 0); - assert(graph.object (2).links.length == 1); - assert(graph.object (2).links[0].objidx == 0); + assert(graph.object (2).real_links.length == 1); + assert(graph.object (2).real_links[0].objidx == 0); assert(strncmp (graph.object (1).head, "jkl", 3) == 0); - assert(graph.object (1).links.length == 0); + assert(graph.object (1).real_links.length == 0); assert(strncmp (graph.object (0).head, "ghi", 3) == 0); - assert(graph.object (0).links.length == 0); + assert(graph.object (0).real_links.length == 0); free (buffer); } @@ -821,24 +821,24 @@ static void test_sort_kahn_2 () assert(strncmp (graph.object (4).head, "abc", 3) == 0); - assert(graph.object (4).links.length == 3); - assert(graph.object (4).links[0].objidx == 3); - assert(graph.object (4).links[1].objidx == 0); - assert(graph.object (4).links[2].objidx == 2); + assert(graph.object (4).real_links.length == 3); + assert(graph.object (4).real_links[0].objidx == 3); + assert(graph.object (4).real_links[1].objidx == 0); + assert(graph.object (4).real_links[2].objidx == 2); assert(strncmp (graph.object (3).head, "def", 3) == 0); - assert(graph.object (3).links.length == 1); - assert(graph.object (3).links[0].objidx == 1); + assert(graph.object (3).real_links.length == 1); + assert(graph.object (3).real_links[0].objidx == 1); assert(strncmp (graph.object (2).head, "mn", 2) == 0); - assert(graph.object (2).links.length == 0); + assert(graph.object (2).real_links.length == 0); assert(strncmp (graph.object (1).head, "ghi", 3) == 0); - assert(graph.object (1).links.length == 1); - assert(graph.object (1).links[0].objidx == 0); + assert(graph.object (1).real_links.length == 1); + assert(graph.object (1).real_links[0].objidx == 0); assert(strncmp (graph.object (0).head, "jkl", 3) == 0); - assert(graph.object (0).links.length == 0); + assert(graph.object (0).real_links.length == 0); free (buffer); } @@ -854,24 +854,24 @@ static void test_sort_shortest () graph.sort_shortest_distance (); assert(strncmp (graph.object (4).head, "abc", 3) == 0); - assert(graph.object (4).links.length == 3); - assert(graph.object (4).links[0].objidx == 2); - assert(graph.object (4).links[1].objidx == 0); - assert(graph.object (4).links[2].objidx == 3); + assert(graph.object (4).real_links.length == 3); + assert(graph.object (4).real_links[0].objidx == 2); + assert(graph.object (4).real_links[1].objidx == 0); + assert(graph.object (4).real_links[2].objidx == 3); assert(strncmp (graph.object (3).head, "mn", 2) == 0); - assert(graph.object (3).links.length == 0); + assert(graph.object (3).real_links.length == 0); assert(strncmp (graph.object (2).head, "def", 3) == 0); - assert(graph.object (2).links.length == 1); - assert(graph.object (2).links[0].objidx == 1); + assert(graph.object (2).real_links.length == 1); + assert(graph.object (2).real_links[0].objidx == 1); assert(strncmp (graph.object (1).head, "ghi", 3) == 0); - assert(graph.object (1).links.length == 1); - assert(graph.object (1).links[0].objidx == 0); + assert(graph.object (1).real_links.length == 1); + assert(graph.object (1).real_links[0].objidx == 0); assert(strncmp (graph.object (0).head, "jkl", 3) == 0); - assert(graph.object (0).links.length == 0); + assert(graph.object (0).real_links.length == 0); free (buffer); } @@ -887,27 +887,27 @@ static void test_duplicate_leaf () graph.duplicate (4, 1); assert(strncmp (graph.object (5).head, "abc", 3) == 0); - assert(graph.object (5).links.length == 3); - assert(graph.object (5).links[0].objidx == 3); - assert(graph.object (5).links[1].objidx == 4); - assert(graph.object (5).links[2].objidx == 0); + assert(graph.object (5).real_links.length == 3); + assert(graph.object (5).real_links[0].objidx == 3); + assert(graph.object (5).real_links[1].objidx == 4); + assert(graph.object (5).real_links[2].objidx == 0); assert(strncmp (graph.object (4).head, "jkl", 3) == 0); - assert(graph.object (4).links.length == 0); + assert(graph.object (4).real_links.length == 0); assert(strncmp (graph.object (3).head, "def", 3) == 0); - assert(graph.object (3).links.length == 1); - assert(graph.object (3).links[0].objidx == 2); + assert(graph.object (3).real_links.length == 1); + assert(graph.object (3).real_links[0].objidx == 2); assert(strncmp (graph.object (2).head, "ghi", 3) == 0); - assert(graph.object (2).links.length == 1); - assert(graph.object (2).links[0].objidx == 1); + assert(graph.object (2).real_links.length == 1); + assert(graph.object (2).real_links[0].objidx == 1); assert(strncmp (graph.object (1).head, "jkl", 3) == 0); - assert(graph.object (1).links.length == 0); + assert(graph.object (1).real_links.length == 0); assert(strncmp (graph.object (0).head, "mn", 2) == 0); - assert(graph.object (0).links.length == 0); + assert(graph.object (0).real_links.length == 0); free (buffer); } @@ -923,32 +923,32 @@ static void test_duplicate_interior () graph.duplicate (3, 2); assert(strncmp (graph.object (6).head, "abc", 3) == 0); - assert(graph.object (6).links.length == 3); - assert(graph.object (6).links[0].objidx == 4); - assert(graph.object (6).links[1].objidx == 2); - assert(graph.object (6).links[2].objidx == 1); + assert(graph.object (6).real_links.length == 3); + assert(graph.object (6).real_links[0].objidx == 4); + assert(graph.object (6).real_links[1].objidx == 2); + assert(graph.object (6).real_links[2].objidx == 1); assert(strncmp (graph.object (5).head, "jkl", 3) == 0); - assert(graph.object (5).links.length == 1); - assert(graph.object (5).links[0].objidx == 0); + assert(graph.object (5).real_links.length == 1); + assert(graph.object (5).real_links[0].objidx == 0); assert(strncmp (graph.object (4).head, "def", 3) == 0); - assert(graph.object (4).links.length == 1); - assert(graph.object (4).links[0].objidx == 3); + assert(graph.object (4).real_links.length == 1); + assert(graph.object (4).real_links[0].objidx == 3); assert(strncmp (graph.object (3).head, "ghi", 3) == 0); - assert(graph.object (3).links.length == 1); - assert(graph.object (3).links[0].objidx == 5); + assert(graph.object (3).real_links.length == 1); + assert(graph.object (3).real_links[0].objidx == 5); assert(strncmp (graph.object (2).head, "jkl", 3) == 0); - assert(graph.object (2).links.length == 1); - assert(graph.object (2).links[0].objidx == 0); + assert(graph.object (2).real_links.length == 1); + assert(graph.object (2).real_links[0].objidx == 0); assert(strncmp (graph.object (1).head, "mn", 2) == 0); - assert(graph.object (1).links.length == 0); + assert(graph.object (1).real_links.length == 0); assert(strncmp (graph.object (0).head, "opqrst", 6) == 0); - assert(graph.object (0).links.length == 0); + assert(graph.object (0).real_links.length == 0); free (buffer); } @@ -1247,6 +1247,43 @@ static void test_virtual_link () free (out_buffer); } +static void +test_shared_node_with_virtual_links () +{ + size_t buffer_size = 100; + void* buffer = malloc (buffer_size); + hb_serialize_context_t c (buffer, buffer_size); + + c.start_serialize (); + + unsigned obj_b = add_object ("b", 1, &c); + unsigned obj_c = add_object ("c", 1, &c); + + start_object ("d", 1, &c); + add_virtual_offset (obj_b, &c); + unsigned obj_d_1 = c.pop_pack (); + + start_object ("d", 1, &c); + add_virtual_offset (obj_c, &c); + unsigned obj_d_2 = c.pop_pack (); + + assert (obj_d_1 == obj_d_2); + + start_object ("a", 1, &c); + add_offset (obj_b, &c); + add_offset (obj_c, &c); + add_offset (obj_d_1, &c); + add_offset (obj_d_2, &c); + c.pop_pack (); + c.end_serialize (); + + assert(c.object_graph() [obj_d_1]->virtual_links.length == 2); + assert(c.object_graph() [obj_d_1]->virtual_links[0].objidx == obj_b); + assert(c.object_graph() [obj_d_1]->virtual_links[1].objidx == obj_c); + free(buffer); +} + + // TODO(garretrieger): update will_overflow tests to check the overflows array. // TODO(garretrieger): add tests for priority raising. @@ -1273,4 +1310,5 @@ main (int argc, char **argv) test_duplicate_leaf (); test_duplicate_interior (); test_virtual_link (); + test_shared_node_with_virtual_links (); } diff --git a/test/subset/data/expected/layout.default_features/FranklinGothic-Regular.layout-test.retain-all-codepoint.ttf b/test/subset/data/expected/layout.default_features/FranklinGothic-Regular.layout-test.retain-all-codepoint.ttf index 81104ce9231ceb5ab7e319571e557ae9852753c7..c341ab86867c0c60ec0bcd4a9747e3cc94beb85d 100644 GIT binary patch delta 129 zcmV-{0Dk|_>jJ#%0+1FKE%X2Y5GF@cRYK_q-l+fp7{~ws3W$*`O94=kcpm{Ak(m4h z*}*hFvzP&68971)m<56bZUs;S&I7aqq5}#7>;kR=kOA-k&H-ovOaUwb5&-xBtO3pd jOaL$d1ONa40RRX90RmE!G#keU3IGBCssN0$796^ndMhO} delta 149 zcmdnd0$D1)_Vud`T!NeJVSgzeS zsNOt-F*xMOlgx%q>oF>F)%TRFmM6ISQvSMY#%U-2guUd%p|jGCIH&%Bs2g3 diff --git a/test/subset/data/expected/layout.gdef.glyphset/IndicTestHowrah-Regular.default.retain-all-codepoint.ttf b/test/subset/data/expected/layout.gdef.glyphset/IndicTestHowrah-Regular.default.retain-all-codepoint.ttf index 0f1c180639fb633c7abb41d5c61b89e94d571eae..615474b3b36b9fdfff3e2e065ed7b57a12200259 100644 GIT binary patch delta 367 zcmX?co@>upt_cP_0q6AJGBDV@U|>+Gi%%JCp20ZBre0xU-{EUo`k%X-#pR<#eHi2t-k0b}u5-?f7UY8ZblH2TOFU!a{VRDW`CnMYDZw~g1 zk}?cTY*q|hK+Aa;*cfj!FfsmQJO?CyY>sf^WEBw>B;B1Ne zr`QbQi4S?cF0bhcI`A(#QuccFSxu=MYPhvz%h--=G3$UM)jVWrG56LVD=W&Jan{&r zNEr@gk5rZ(qzmbkCD{?%XD-%A1-hmY>L4J8OPpZ`OVGdo5Nbv3eXk4|DsgYn-bXKx ztB0dMA(`GFiAe4b7^;G=0(9`tfQ$Qr7TpuH5+(U~Xq#O6eJDuIE>%!8x3yHzn~(zRlEpY5hYNRzn$BPqmPdH8mP#l+Mv6PCKSc@9&v{& zq>;o44zP!x(^PH)7?^NT*p`jD(mIs5}Atd@a&$jQ{rhGUBEfw@3D!cW9wpq~Vyr8lB$uD5TXoLU& diff --git a/test/subset/data/expected/layout.gdef.glyphset/IndicTestHowrah-Regular.drop-hints.retain-all-codepoint.ttf b/test/subset/data/expected/layout.gdef.glyphset/IndicTestHowrah-Regular.drop-hints.retain-all-codepoint.ttf index 148b31812da3f0f359ded491d098c89d46eb0a69..47ae513d0f2512a19651d4b56697624f9f7ccaab 100644 GIT binary patch delta 369 zcmcc7&UU7iZGr($z&ZW53=HaP7#NgzCfZmrdG454@rEVlm*l_A8H^Lu>lN0>Gs#_$ zTO(H`=Oy(-s!l3S%0=Rn!WywJBA57{@g?wB@F;M~aC~F!Vl84}V7|hLkmM5Lj(g80|Nsi0|yY-fN3U%Ba9yySm2@{ znHfOr%E7 Ons>Qu-{rz6XAb~B3{eCC delta 528 zcmX@p%66ljZGr)hx}^L$1_t#t3=GN=6K$-RypBw)c*C+G#X@Cs2IB~sIwG}1s!P&Bafi5-s06iI`^jH46(=9m)R_EVQ*`quO-V+^O_O=F zI~g@LPt&$%R8|A(l40Nik~|D-jJFw>7=JRJ1Cl?0Ca^G`1&W((ey799s;!5jkO#=C zXLt-G>wp-jmEi|ii{p5SDx~3qp8E~;_aMlz!YciZQ5yqO_ g=BBON1>-Ta!dWeFRui1n0Ae+-bKbtrnNiLj012pP#{d8T diff --git a/test/subset/data/expected/layout.gdef.glyphset/IndicTestHowrah-Regular.retain-gids.retain-all-codepoint.ttf b/test/subset/data/expected/layout.gdef.glyphset/IndicTestHowrah-Regular.retain-gids.retain-all-codepoint.ttf index 3b1f3011304c083c6de7fa52ddf8f4384cc29fdb..240cb05a25205941fb98fe3ae4e3330b583904cc 100644 GIT binary patch delta 389 zcmexzk?X~It_cP_4p)OE85ryY7#NiJCfY!Is zt&^*f^O1Tc)gYB1y?6c$7HhIDW8pu@F-XsO345K_+m6J|XD<85A(t!avqE&cLNw!cg=nTLz5q6pQ?~#B delta 444 zcmaEHp6km+t_cP_#@EyyGBDT)Ffb@fO|*$(O4OM+;}46w-1e%?GZ>H9)+_E(5Rkto zze~PDK0^AF)G?`LQazGZio3*ZM5Xxk_*U>V@MLhNaH_HHU|qnX$NYo&8uJ0>RZN?h zmM~3Y(qIx|e8qTz(S=cok%8d}!y$$>3=JF&u1}LA926(3I%rHzb`aeh<{-()xMgyIV<)55=3kEX zjI3%vyJRNYIg7KiFkS$%Ej9-^bF*sefrJ^jfFutC8zVoEZe(~0BpVWEg+)n;31zp2=RZvivPDF@`1( St63zfT_lQeyGRt%6<+`tm1U{` diff --git a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Bold.default.627,644,62D,628.ttf b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Bold.default.627,644,62D,628.ttf index bf2ff01fa0fad07b2b271736c214049d51f02189..b0234259c7528caf06fb34d2b1af3bfe5774c5ae 100644 GIT binary patch delta 573 zcmY+9K}geK7{;IH`##5XqW^7C);5iybDJ_uhSgd%A|yJDs1OPfi--{-6%w_BMh7wJ zAbuhu5+NZYL@0N#4$@&5ga`2;BSb_*#)u&@LL^3RA=Kq}d4BKnzHe%nnb#a?sZ6Z> z_zX0pfjZ-3xSr};`OMP%16mpex@!Zqm98CE$~EDNJI9>fQ|4n~`(t~jJz}r76>PJ% zLECLxtL?b;z`AKoSrgV#>oX&3B#nNf%kWqKs_v`Wsft=^DsGz7=6X|)sitfQccB0f zX0Xf&L*>=7daE9(7P%`w$SzqalA?oq9MU<@33h8!;|rNp+xk`Gld`z#Q@wh>F=keV zs!&Q6bj*9Nd{)lLcXCpW=~eF?OH^KyS7cbW$rjz_yXDvwS+OlPL|Uw(9T9{OL;zm6 z#k79!yXcq@qvDMi6fZ^mzx0N@yu@>CWivhc zbW`mBLku!NFWqz=#s({>P{2O+@DpJOTqrSA;&~XQ5)**~a0@IknI7Tpx^3ai}J(jxp%gyrSAH@ofTL1t6 delta 585 zcmY+APe_w-7{|ZQ@Aqy@C-dFAC~WghhI4bXVKVx&EOrPFAt5GXS%?jhF(h<^7{mhc zVA4VQfrJbXvcniMDo2b(qr>npB1UxZP#{vV!x%C~h>U1cBD{PK-{<>$pFgGNlwYCM z{;e=?VD(p4IuKI##BPMg_7ZgP;7EOfTH6-oGmLsU`5 zFVW>vCvk)tDkxze-(W=%1>})K25+&2K`>lDruUfFAU8v>gOA7}jT92tL=0hih#zMt>!doW9>SxSd4 zyxA(GfNKMQSN|MsCGo|^jo9iiz4g6*=ZSf`JuY|Eoprai80xT6QOZg|SyVF0xYDn5 zD3HI)IXNjmk#*VbJaLws>&}eR;@EZM9ixs;2khJSjNK)jN?TG!GNb`1Y}>WNgJlpn4I28A}-T%^*6b&)KQ~tPF zw2Br{6sr8x|Ey_A_$15=Ss}wm0*{;fg+Aeq5EVLw5Z?|wRDZEUw$Juhi51a{9z+m^ z1|PgE&-LJKb&gH56q{m`EcxH{KD)=$!JBF?>tPWVW*YPToSp(UdX@bUSj7DjM8`_mYgAx>{A&SwX`YO6h-K0|o z1?VcGV7S#_q`|HRby(|76rnI_#de$P4--(1 AWdHyG delta 587 zcmY+9Pe_w-7{|ZQ?|o-;g)MS_-uIoby;v=`y<0M9t{I*@M2A>5Y8J4(U+(ES064bWa+Q`X#enwCCdv-pu5m* zIwhZ6>q?j&cjk;=jy>;KFf=d?5dgsmGNfS^>-=t?pPc+v->k4Iu5hd8z9B3IMU7`X zNu#hU)P$-~;Z4t-&PicHxGltm5kBF)dAv(73XC<`A^Xm2-s{RbTV*S3ndMmy!x%ys zL1=J8W#c^L8&pPFlwD=RY>0*bo0^%4XMGoy4hCvaopd_*w@X#3P?_)hE+|{HNhK;$ zftLQ7X^v)SnkKoXd6X1QQIf`KjADQ8(q#%!KY8gaPibdv1W6+|spKH*Q7aPh3qSD# z-|!U?FkEUg+~z==A(-2y2I|mpfPL&i#V%^7qJlEsVU6pWJ&J97#1=MDLJl{)7eQTqm*@99|Nq`R$L5);uke1p z*Z_n(fYV(c!+xr3zB9dZ_xSYc(1b7P>-713!n^Lx)U4I?S@sj>JvTgUo`9#q{nI_- z?stdXRc6u5o8QbCGi45%ao2+Dg{#XIG!Bg=qpotI(pLV)nQ=7Q{kEj-RM{|kp#Tte zu&f$}+E>eJLfu#Oa!-DcaakeKqK5?z>v-fOZ|g^qCvrz^=|bc|*`OLw{W{T{uq#73 zm6AoBYrf-{kyG-$d?!bAtmS5Tm+X)+8I}#QUQe}LxAw%2*bEvt`WgH9?GaexAT$yErsQew8mWf-Lq8-YXYV;9@VV*~5>f$#W=MP%g4 vQ72pCv0aNzy8nD1b|=w z=@wy@TGD}(mhMYV-rzYt!v|TOO;VFq#Ekb8*~Fsv5zk2((eyslxh0q65OH7Jz>oMo zFY}W4=(}f3@Hrmk5gryH|6S7wZs3glWZzjsy!7AEUb7s_urym|tC&ClZn)sU80;)2 z)PPr;Vz=2%Ho*eS{aeUwo`0o!=R--GePKC68mz}%Cdf;|+G#}=~K#0J)o!gDMmi6ws6 m?Ns~hZQF$}G%Vg44F+EA2Hlb9+!*0qiFgr;?1V$1)4u^-{FJ8v diff --git a/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Bold.retain-gids.63A,64A,631.ttf b/test/subset/data/expected/layout.notonastaliqurdu/NotoNastaliqUrdu-Bold.retain-gids.63A,64A,631.ttf index d69ba9996665f10010c321cca564d03a5e79dbfe..0b15e51b08cc32cf991886cf6b16a2b38b4241e4 100644 GIT binary patch delta 579 zcmY+8QAkr^7>3{P|Ici$s0DgFJO7G2l?cUHGAf4JoNGuIAz6bU!X#wK7&W}85$4q{ z;t*qh3>h+HM2HNr93c|ng;<2M@FFb2i&zjNL_$JBMspI>&2#g<&-;Dm8=6_7r2Bx` zac2{_um~KFJa0ci3d?+ZJbRmdbv`s+8A&5%^cro=@0zb3O*w}|MU=!_kr!zZ7rnwG zn7*iI^#^*t?$tHNj^ndq&XLx(v<1!7LfWyWa#NvcMBPwV)Qmc)2Gtg|t})rj8io!J z9GX8Evzhf7Yhs_f&TW}M3<>~(6=ZS3uEERE1Ticy3HWhrp-+Au{aOapX-Lc32G`x;kjEY`RT`|c8X$wMw`C4-z*&rUW{P{B5S;)lF>QIk^*&JbUzP84h0)eNj- z4Xaqj5*9Fz0;VyE9LAAG5|4PX+TQEE#0;kJ6j_WR1rrI3B93815yrr*KiG5R4{Yh0 AkpKVy delta 580 zcmY+8QAkr^7>3{P|Ic);7^~dbJZDKAEe6G~7!;${rgKO{h}AF|VIyM15E>HZg?SM| zV}$re>_SF}7h}X&>4l6CFNT3k+eL{%7ZEash>RE_Ln2*7bo1Q2-}}5@cA4TIX|m(! zi^Rqk;94GNTYh(?outN1^P{;3wCjG>CTm+-K}&0Kt^IVSb=#J=_1oMw&DNk+)X!>K zeW?c3R%K6FS8~cLC8`7zr*gcd(sHv!v6ie^>!{UdHCi?-Jr=uMm6zl<^0+)`-ZBT9 z^T$4znvDfRsi~^}-WY3$>1v2V0zlA%bWdQC#8*!Toe~D`Q=XP)#f0~<&Lue|huHUq z^}NVec%CncNay{g2!F^2`2fEy);sSWF>*a;Y@hA2ig0z^QnM`0W?7OY*cANmzy&89 zutQ@Zk?Ojt_Om{AgZY_25Fbz?dLKLLHpILI#Wx7b`>8#lFow?&A2iZv@8_9=Hk%V8^!%u9Z z#EW43Q=MyduGU$Ascx#FiXD{k1K*&bh!y0qhy}dITXC=3+KXi@A%}Tnkirb6@fwqe z;W@@I!q*Pet92ggG$fJ06ylh`I7TsyA%qb^5CQb!I$u4wE{Ng@P1yYdbLH-ylMMI` DnR=dv diff --git a/test/subset/data/expected/post/SreeKrushnadevaraya-Regular.default.c30,c36,c40,c4d.ttf b/test/subset/data/expected/post/SreeKrushnadevaraya-Regular.default.c30,c36,c40,c4d.ttf index f96e74fd76fdcc712286b660ee8bc37ede24c00b..89bbc9912ce1eb2d631e4c1fb06cf224ad4f4a44 100644 GIT binary patch delta 219 zcmaD8@*re_A@`3Bvl$rJ?l3Sg^Gvj{W_&TRLY}c<rpa;~ eqWlU#Rbq^D7ft99mH*6$*t1 zuy;f;+1cOD?%tiJvu-7}M;kD1fR^9b9lkeim8;WkMD22(`7h4&jH?E!&MWs^a>9Z) zjdmA97$6`rLC&E`4=9uo7f%jlG7jfJIwX#e{-1o)R+tX1G@-Fg3k~r=N;U#of?^HX lAEVT`$bj-HX0F(fl!ZK!!XolTK@VNR@0kZGz<jNZB9cKUl diff --git a/test/subset/data/expected/post/SreeKrushnadevaraya-Regular.default.retain-all-codepoint.ttf b/test/subset/data/expected/post/SreeKrushnadevaraya-Regular.default.retain-all-codepoint.ttf index dcbcd3e632bbb57b7c52a5793231af1aad723a8d..6d4ea7720b2877604d789f1349ed3b7058828829 100644 GIT binary patch delta 527 zcmeDAt#sn6(gXvZ<(tb+GcZ`JU|=wMFwrK8DZ69i3=Kw>#A&&Kn~yLSnadbv=r7Z~ zrn^n?km4f6BJOkCDcn3PC zZf9VE@ifaeC)pY>>ae~5$}R$nGB8LmFf%a9C;&w!z(wHtWy~h;vkQ`t-o;wLpanKo z9H<;*xD*4E^peRM_C8*W?-*RQ1+>|;{%C#DdZBes>x$MXtpi%ywAN@X(wd>wr`4iW zrB$GnrWKeNTg`u% delta 479 zcmX@{RjKE<(gXvZ-zzhE7#J*8Fff=dnrIWnl;^W?h6W?c&Q&d*n~yLSnah}H8D$vE zFsM*zQAtrT;hVs7h$oBt756gEXPnbE2U*TxX3|+T`GZY}oPq;`Ap;`=0}yjCFe*4P zZf9VE@pLRUC)pY>>ae>3Wfy@(85krOm>C%59sxxrz(wHt<$g@wXBQ+PSHpIWK?`iG zI8Zsra47~RxwOd|_C8+BP7JQh7R-9gD$G*M0?ceof0#ZoybcN{@(*dS!Olz1H zG0kA=V`^clVk%%tV~Sx4V)9_JV=`gVVp3odW8z_AV*J7Qj`7LnefD!1CmUx5FxGBv z$>e67ob0H~rgn&dS!&(n9!E6=wP_4YQcVmZV26kSNk)dV3@k9;sg-QLd$al&@Kyih~sGU1*}&S>v! zW3(DW2LVw5976g%K#MA|eLFi(UFqjOD2a#?(fB{`P7(~QO`W|BT7-oap z62r+o_jk{|ck3S9ZP(mi9YEgzT6yOTdER*qI=7(5#QQXugb%jU1y4;?i%%Z8=7JSJ zZ;Vzmq(DHVgIq$B9#E(wBc7be#Es`bJ|=S^<3IVPtvDTB=|E$j7HsiAPBtQ1gJKiK lAEVTaC~!V>aDAsIU}d>I!iAPh{;`T(hX9CH8w diff --git a/test/subset/data/expected/post/SreeKrushnadevaraya-Regular.glyph-names.retain-all-codepoint.ttf b/test/subset/data/expected/post/SreeKrushnadevaraya-Regular.glyph-names.retain-all-codepoint.ttf index 2db21f8b8f407be391a4be8edc4175ad4fa13498..d96dd3614609e5760f3ae25864a33383972aeef0 100644 GIT binary patch delta 533 zcmX|-PiPZS5XR@dP2Ma`1Y7Ewwy7kvTe?#h+fw{fYAS1sMT&=|2Prv9DJUTn>7fuU zSTIN_iO)kojSz|o=Hx;OQZRTZ5=C+-9&#u>R)MgH@1HkaUcm!-~fzRq+y?rpP`OX0EQ3$N|(b}9NPT8OR$ zz6I_Cf*qS3uKrYayzwh9G;TFyb!y>U{{t8U2B6S^^{=xX;Qupg)pM?nvh)6idM&h6 zfDmZF!V#rp+Df}Wd{O3OZm92rKMB;bJ*&!3%zJ_NZA*Q6{w8a}?8xa6LBHvU8gxMW zRG~7Js7M9M(H3pc8YO9o;^fjC#b|T6L>tRjoa1*UL}8WGp2!4heGs zw4Q6S>fH~a44P3({E@2#9oWmGSAYlXz3?diD zqABX4CaPjj?27l|t;maK;t8-*^Xkx?rC16;JMwia|G$&GcQ@9D9@&Y3=F1=CfYLZw9|Ma6_~0?#3yEbdp_%Q&BLPTOo`Ift1^XVv5jHX(8f4h)72j0_Aw z%)!8@;KaC{feFUbvDoZnYrv?(?go@y1QumrkYHeDV3d0V6qx`Qf$Nw1F?pU{kc3oKb^OEC*DvoZZ)`o#2t=^oP+rc+D@ zn6@#kVOqpAgQ<_Hg{g|EfGLeBhAD{2gUOD`gh`7@fk}*shlz>t2je@&C!6Ql&t;q} zoEgAayE!G3n{l$UqcWS?AqHltb(3=()fCjGF)&FrF^GU2A_gQG8O}1WzFeA@`3Bvl$rJ?l3Sg^Gvj{W_&TRLY}c<Z delta 232 zcmX|)Jq`hJ5XIm7FKbyJ4I4z*h)P8#u?>Ynv6W3k!677~;{XaDjZ&p1+d65yqP(8CvRCd*9QmC?|^2loF-qZX^D&D)}wYA-~1;>;nY-hRfSjXIOl-b z%`noMxzIsCWPqGNlO9kgASRx4WzrAkKr$dbA)P<@rtKj8xl)7125nTu0}0vq&^#2& mNdFk6#zZ>Qrf6hxRY}>f5EmAbFACwnnSR^YQvv=XdbZ#BP#uo| diff --git a/test/subset/data/expected/post/SreeKrushnadevaraya-Regular.notdef-outline.retain-all-codepoint.ttf b/test/subset/data/expected/post/SreeKrushnadevaraya-Regular.notdef-outline.retain-all-codepoint.ttf index 193afd077a2b17d78100217d2262ec10a47af929..3163b3caf4b5981ca15aed5b2e9541405193ec8f 100644 GIT binary patch delta 527 zcmccfTWQ8Or3nT+%Qu&uW?-;b!N6ejV4_VFQ+CJ385)c%Im#;?Hy>dvGM6#T&|ju| zO?R8(A;m?CMcn7OQ@DA!dbol(CUH1z4zirX%%u5Z@&}s`IoUb}Lk30$1|a5OV3ciO z+|IxR<7t*{PO>#%)M0%AlwAZCWnhqCU}j*HQ2>fefQ!KO%a~2xXBQ+Py^FPgK?`iG zI8Zsra47~R=_Qjj?0vi#-!Zsq3uv=x{n7fQ^+M~O))lQ&S_ibYX|2&(q%}jUPpd_% zN~=ICO)Ew#NXtXZPRm3~OG`mZjPZ#UljaZ2cbZQ$Z)sl8Jf^v4^FI5zjEuFDH#ljr zsfI8xOFB$`;H1W;s=>e{DKeSIS%OKGZL^y5enwUe24>})$*)|LCyTqzmJ%z0x?B|K zFh-^bkdGLI7#PK3CZBPYsTZ>XGOgfBSime`;4lO+s4)1k-($bRev177`!@D9?2FiE zu=laIuvf7cu&1%dum`bwu-mbluxqg^u#2(tursm!V0*{*gzXmF1-4^sd)PLytzetS zHifN=fsxk-7|=|NwT!h449uTcM1bZ)Y}$OBjyJzHy>dvGM6#YGRiQR zVNjvcqLQLw!Z(5E5Kk8OEAC~S&p4-T4zirX%%rnw@&}s`IRys>Lk30$1|a5OU{r8o z+|IxR#%)M0l6$}R$nGB8LmFf%a9JpzhMfQ!KO%l(+V&n`$pu7>R#gBI9W zaiDUL;Zh7ta%qz_?0vkLofur1EtvI~RhXri1(?~G{xE%Fdcky$=?c>+rUOjdnAR{Y zVw%C!$JD}9#ZB(K#Q1~p9pjVD`|Rg3PBzXAV65HT zlF7|DIoVN}P3;f^v(&oDJ&tM$YSS2)q?#B+zzz`ul8g*z8CYPxQ!Ckg$#Fj;s}azJ z8k46vE3>mOg)%TP1x?=OJWope2Gn<=Abm^`AYU^GF))fBn4IS#BPBi$$eagP0ymU# z+hnnHzs-8yag3}Ctw7T@e{`9l#>mjzk=@>r%?QLyK+FupEI`Z(#B4y!zP%%xV}d^b DH*Rdm diff --git a/test/subset/data/expected/post/SreeKrushnadevaraya-Regular.retain-gids.c30,c36,c40,c4d.ttf b/test/subset/data/expected/post/SreeKrushnadevaraya-Regular.retain-gids.c30,c36,c40,c4d.ttf index 99419f977ef692b82a8458a162e4202c92803327..7b154997a95dd98c754f0ea55d20eb566432adca 100644 GIT binary patch delta 199 zcmey+%=o04ae@KQjpz^N3=C`s7#NrZCfZmtZkSjh&&V=yhQ6Q&0|Wn_oc!d(jJ0{+ z85lS(OuTV~MI?*;{^kXYlUezTm|U3T7_TtSVyt2eVHBF|#O`SB!NA0%$)v}?$iM)^ z91M(%IShsjY(O3pgA4-~12a&>1t=2%mSscuE!|Gpas^+3ls(EmttUI il$iX9U6Wq{C?du4=57>mSscZo#O)pas^+3ls(EmttUI oRGR#WT~ksED5Ah1#=rv@4{D9A_ea`kW$Jo=EBYTr&u6YNi`#<*W^cDO+{5k)ed&~XE z?P+?^WVv3sV)nxuZw)SM>STEGa_15Z0t3*vfOS4*>%jkKa=}jB8eqN7ny%&=DnJ_; zz`Q=Ktez=ne(zOPj#zCS-<)Zn#@JTfemeXT@Qx2E938#S>JaZ~?h$lCzoBNU-95&6huRpRnk)@4=h$!)nIi*`Bsj#ahMV)M6gRnqHE#l4L48RxXkah7wKnRHf7{$mp&r{KU~$iT?J0K^;&j0#SS z+ZmW(JROV8dA0_OI_z#h*+pPc1_lWRW(G#NM?jGYa1pqExgV2H*#$|+)v%po&;lDP z4pa^@T#A87E^V@jy^j~O6N4+W1+yNr3bPcm05coYAEr-CFPQEzU12)Kbbx6a(;B8l zOf#7Jm|B>smJ=B$@ZB6jCGqQ zWO6f3&UaL1Q#-`KEVXX(3`aEuwP_4YQcVmZV26kSNk)cq3@k9;sg-QL=eVDd)d=W9 zjme9gmDyRC!WfvCf+rtxo+l-K1L`|bkUpkJkgpkp7#PJ5Os;Z~krJN=WX^*tfg8%W zZL(6j-)1ZCI7T*xHlS&~oBz2?S7T&oo|4@@C7Th5nShuXh*^M`6^Pk@n0@<{Yz_y1 E05Icg!~g&Q