/* * Copyright © 2022 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 GRAPH_PAIRPOS_GRAPH_HH #define GRAPH_PAIRPOS_GRAPH_HH #include "split-helpers.hh" #include "coverage-graph.hh" #include "classdef-graph.hh" #include "../OT/Layout/GPOS/PairPos.hh" #include "../OT/Layout/GPOS/PosLookupSubTable.hh" namespace graph { struct PairPosFormat1 : public OT::Layout::GPOS_impl::PairPosFormat1_3 { bool sanitize (graph_t::vertex_t& vertex) const { int64_t vertex_len = vertex.obj.tail - vertex.obj.head; unsigned min_size = OT::Layout::GPOS_impl::PairPosFormat1_3::min_size; if (vertex_len < min_size) return false; return vertex_len >= min_size + pairSet.get_size () - pairSet.len.get_size(); } hb_vector_t split_subtables (gsubgpos_graph_context_t& c, unsigned parent_index, unsigned this_index) { hb_set_t visited; const unsigned coverage_id = c.graph.index_for_offset (this_index, &coverage); const unsigned coverage_size = c.graph.vertices_[coverage_id].table_size (); const unsigned base_size = OT::Layout::GPOS_impl::PairPosFormat1_3::min_size; unsigned partial_coverage_size = 4; unsigned accumulated = base_size; hb_vector_t split_points; for (unsigned i = 0; i < pairSet.len; i++) { unsigned pair_set_index = pair_set_graph_index (c, this_index, i); unsigned accumulated_delta = c.graph.find_subgraph_size (pair_set_index, visited) + SmallTypes::size; // for PairSet offset. partial_coverage_size += OT::HBUINT16::static_size; accumulated += accumulated_delta; unsigned total = accumulated + hb_min (partial_coverage_size, coverage_size); if (total >= (1 << 16)) { split_points.push (i); accumulated = base_size + accumulated_delta; partial_coverage_size = 6; visited.clear (); // node sharing isn't allowed between splits. } } split_context_t split_context { c, this, c.graph.duplicate_if_shared (parent_index, this_index), }; return actuate_subtable_split (split_context, split_points); } private: struct split_context_t { gsubgpos_graph_context_t& c; PairPosFormat1* thiz; unsigned this_index; unsigned original_count () { return thiz->pairSet.len; } unsigned clone_range (unsigned start, unsigned end) { return thiz->clone_range (this->c, this->this_index, start, end); } bool shrink (unsigned count) { return thiz->shrink (this->c, this->this_index, count); } }; bool shrink (gsubgpos_graph_context_t& c, unsigned this_index, unsigned count) { DEBUG_MSG (SUBSET_REPACK, nullptr, " Shrinking PairPosFormat1 (%u) to [0, %u).", this_index, count); unsigned old_count = pairSet.len; if (count >= old_count) return true; pairSet.len = count; c.graph.vertices_[this_index].obj.tail -= (old_count - count) * SmallTypes::size; auto coverage = c.graph.as_mutable_table (this_index, &this->coverage); if (!coverage) return false; unsigned coverage_size = coverage.vertex->table_size (); auto new_coverage = + hb_zip (coverage.table->iter (), hb_range ()) | hb_filter ([&] (hb_pair_t p) { return p.second < count; }) | hb_map_retains_sorting (hb_first) ; return Coverage::make_coverage (c, new_coverage, coverage.index, coverage_size); } // Create a new PairPos including PairSet's from start (inclusive) to end (exclusive). // Returns object id of the new object. unsigned clone_range (gsubgpos_graph_context_t& c, unsigned this_index, unsigned start, unsigned end) const { DEBUG_MSG (SUBSET_REPACK, nullptr, " Cloning PairPosFormat1 (%u) range [%u, %u).", this_index, start, end); unsigned num_pair_sets = end - start; unsigned prime_size = OT::Layout::GPOS_impl::PairPosFormat1_3::min_size + num_pair_sets * SmallTypes::size; unsigned pair_pos_prime_id = c.create_node (prime_size); if (pair_pos_prime_id == (unsigned) -1) return -1; PairPosFormat1* pair_pos_prime = (PairPosFormat1*) c.graph.object (pair_pos_prime_id).head; pair_pos_prime->format = this->format; pair_pos_prime->valueFormat[0] = this->valueFormat[0]; pair_pos_prime->valueFormat[1] = this->valueFormat[1]; pair_pos_prime->pairSet.len = num_pair_sets; for (unsigned i = start; i < end; i++) { c.graph.move_child<> (this_index, &pairSet[i], pair_pos_prime_id, &pair_pos_prime->pairSet[i - start]); } unsigned coverage_id = c.graph.index_for_offset (this_index, &coverage); if (!Coverage::clone_coverage (c, coverage_id, pair_pos_prime_id, 2, start, end)) return -1; return pair_pos_prime_id; } unsigned pair_set_graph_index (gsubgpos_graph_context_t& c, unsigned this_index, unsigned i) const { return c.graph.index_for_offset (this_index, &pairSet[i]); } }; struct PairPosFormat2 : public OT::Layout::GPOS_impl::PairPosFormat2_4 { bool sanitize (graph_t::vertex_t& vertex) const { size_t vertex_len = vertex.table_size (); unsigned min_size = OT::Layout::GPOS_impl::PairPosFormat2_4::min_size; if (vertex_len < min_size) return false; const unsigned class1_count = class1Count; return vertex_len >= min_size + class1_count * get_class1_record_size (); } hb_vector_t split_subtables (gsubgpos_graph_context_t& c, unsigned parent_index, unsigned this_index) { const unsigned base_size = OT::Layout::GPOS_impl::PairPosFormat2_4::min_size; const unsigned class_def_2_size = size_of (c, this_index, &classDef2); const Coverage* coverage = get_coverage (c, this_index); const ClassDef* class_def_1 = get_class_def_1 (c, this_index); auto gid_and_class = + coverage->iter () | hb_map_retains_sorting ([&] (hb_codepoint_t gid) { return hb_pair_t (gid, class_def_1->get_class (gid)); }) ; class_def_size_estimator_t estimator (gid_and_class); const unsigned class1_count = class1Count; const unsigned class2_count = class2Count; const unsigned class1_record_size = get_class1_record_size (); const unsigned value_1_len = valueFormat1.get_len (); const unsigned value_2_len = valueFormat2.get_len (); const unsigned total_value_len = value_1_len + value_2_len; unsigned accumulated = base_size; unsigned coverage_size = 4; unsigned class_def_1_size = 4; unsigned max_coverage_size = coverage_size; unsigned max_class_def_1_size = class_def_1_size; hb_vector_t split_points; hb_hashmap_t device_tables = get_all_device_tables (c, this_index); hb_vector_t format1_device_table_indices = valueFormat1.get_device_table_indices (); hb_vector_t format2_device_table_indices = valueFormat2.get_device_table_indices (); bool has_device_tables = bool(format1_device_table_indices) || bool(format2_device_table_indices); hb_set_t visited; for (unsigned i = 0; i < class1_count; i++) { unsigned accumulated_delta = class1_record_size; coverage_size += estimator.incremental_coverage_size (i); class_def_1_size += estimator.incremental_class_def_size (i); max_coverage_size = hb_max (max_coverage_size, coverage_size); max_class_def_1_size = hb_max (max_class_def_1_size, class_def_1_size); if (has_device_tables) { for (unsigned j = 0; j < class2_count; j++) { unsigned value1_index = total_value_len * (class2_count * i + j); unsigned value2_index = value1_index + value_1_len; accumulated_delta += size_of_value_record_children (c, device_tables, format1_device_table_indices, value1_index, visited); accumulated_delta += size_of_value_record_children (c, device_tables, format2_device_table_indices, value2_index, visited); } } accumulated += accumulated_delta; unsigned total = accumulated + coverage_size + class_def_1_size + class_def_2_size // The largest object will pack last and can exceed the size limit. - hb_max (hb_max (coverage_size, class_def_1_size), class_def_2_size); if (total >= (1 << 16)) { split_points.push (i); // split does not include i, so add the size for i when we reset the size counters. accumulated = base_size + accumulated_delta; coverage_size = 4 + estimator.incremental_coverage_size (i); class_def_1_size = 4 + estimator.incremental_class_def_size (i); visited.clear (); // node sharing isn't allowed between splits. } } split_context_t split_context { c, this, c.graph.duplicate_if_shared (parent_index, this_index), class1_record_size, total_value_len, value_1_len, value_2_len, max_coverage_size, max_class_def_1_size, device_tables, format1_device_table_indices, format2_device_table_indices }; return actuate_subtable_split (split_context, split_points); } private: struct split_context_t { gsubgpos_graph_context_t& c; PairPosFormat2* thiz; unsigned this_index; unsigned class1_record_size; unsigned value_record_len; unsigned value1_record_len; unsigned value2_record_len; unsigned max_coverage_size; unsigned max_class_def_size; const hb_hashmap_t& device_tables; const hb_vector_t& format1_device_table_indices; const hb_vector_t& format2_device_table_indices; unsigned original_count () { return thiz->class1Count; } unsigned clone_range (unsigned start, unsigned end) { return thiz->clone_range (*this, start, end); } bool shrink (unsigned count) { return thiz->shrink (*this, count); } }; size_t get_class1_record_size () const { const size_t class2_count = class2Count; return class2_count * (valueFormat1.get_size () + valueFormat2.get_size ()); } unsigned clone_range (split_context_t& split_context, unsigned start, unsigned end) const { DEBUG_MSG (SUBSET_REPACK, nullptr, " Cloning PairPosFormat2 (%u) range [%u, %u).", split_context.this_index, start, end); graph_t& graph = split_context.c.graph; unsigned num_records = end - start; unsigned prime_size = OT::Layout::GPOS_impl::PairPosFormat2_4::min_size + num_records * split_context.class1_record_size; unsigned pair_pos_prime_id = split_context.c.create_node (prime_size); if (pair_pos_prime_id == (unsigned) -1) return -1; PairPosFormat2* pair_pos_prime = (PairPosFormat2*) graph.object (pair_pos_prime_id).head; pair_pos_prime->format = this->format; pair_pos_prime->valueFormat1 = this->valueFormat1; pair_pos_prime->valueFormat2 = this->valueFormat2; pair_pos_prime->class1Count = num_records; pair_pos_prime->class2Count = this->class2Count; clone_class1_records (split_context, pair_pos_prime_id, start, end); unsigned coverage_id = graph.index_for_offset (split_context.this_index, &coverage); unsigned class_def_1_id = graph.index_for_offset (split_context.this_index, &classDef1); auto& coverage_v = graph.vertices_[coverage_id]; auto& class_def_1_v = graph.vertices_[class_def_1_id]; Coverage* coverage_table = (Coverage*) coverage_v.obj.head; ClassDef* class_def_1_table = (ClassDef*) class_def_1_v.obj.head; if (!coverage_table || !coverage_table->sanitize (coverage_v) || !class_def_1_table || !class_def_1_table->sanitize (class_def_1_v)) return -1; auto klass_map = + coverage_table->iter () | hb_map_retains_sorting ([&] (hb_codepoint_t gid) { return hb_pair_t (gid, class_def_1_table->get_class (gid)); }) | hb_filter ([&] (hb_codepoint_t klass) { return klass >= start && klass < end; }, hb_second) | hb_map_retains_sorting ([&] (hb_pair_t gid_and_class) { // Classes must be from 0...N so subtract start return hb_pair_t (gid_and_class.first, gid_and_class.second - start); }) ; if (!Coverage::add_coverage (split_context.c, pair_pos_prime_id, 2, + klass_map | hb_map_retains_sorting (hb_first), split_context.max_coverage_size)) return -1; // classDef1 if (!ClassDef::add_class_def (split_context.c, pair_pos_prime_id, 8, + klass_map, split_context.max_class_def_size)) return -1; // classDef2 unsigned class_def_2_id = graph.index_for_offset (split_context.this_index, &classDef2); auto* class_def_link = graph.vertices_[pair_pos_prime_id].obj.real_links.push (); class_def_link->width = SmallTypes::size; class_def_link->objidx = class_def_2_id; class_def_link->position = 10; graph.vertices_[class_def_2_id].parents.push (pair_pos_prime_id); graph.duplicate (pair_pos_prime_id, class_def_2_id); return pair_pos_prime_id; } void clone_class1_records (split_context_t& split_context, unsigned pair_pos_prime_id, unsigned start, unsigned end) const { PairPosFormat2* pair_pos_prime = (PairPosFormat2*) split_context.c.graph.object (pair_pos_prime_id).head; char* start_addr = ((char*)&values[0]) + start * split_context.class1_record_size; unsigned num_records = end - start; hb_memcpy (&pair_pos_prime->values[0], start_addr, num_records * split_context.class1_record_size); if (!split_context.format1_device_table_indices && !split_context.format2_device_table_indices) // No device tables to move over. return; unsigned class2_count = class2Count; for (unsigned i = start; i < end; i++) { for (unsigned j = 0; j < class2_count; j++) { unsigned value1_index = split_context.value_record_len * (class2_count * i + j); unsigned value2_index = value1_index + split_context.value1_record_len; unsigned new_value1_index = split_context.value_record_len * (class2_count * (i - start) + j); unsigned new_value2_index = new_value1_index + split_context.value1_record_len; transfer_device_tables (split_context, pair_pos_prime_id, split_context.format1_device_table_indices, value1_index, new_value1_index); transfer_device_tables (split_context, pair_pos_prime_id, split_context.format2_device_table_indices, value2_index, new_value2_index); } } } void transfer_device_tables (split_context_t& split_context, unsigned pair_pos_prime_id, const hb_vector_t& device_table_indices, unsigned old_value_record_index, unsigned new_value_record_index) const { PairPosFormat2* pair_pos_prime = (PairPosFormat2*) split_context.c.graph.object (pair_pos_prime_id).head; for (unsigned i : device_table_indices) { OT::Offset16* record = (OT::Offset16*) &values[old_value_record_index + i]; unsigned record_position = ((char*) record) - ((char*) this); if (!split_context.device_tables.has (record_position)) continue; split_context.c.graph.move_child ( split_context.this_index, record, pair_pos_prime_id, (OT::Offset16*) &pair_pos_prime->values[new_value_record_index + i]); } } bool shrink (split_context_t& split_context, unsigned count) { DEBUG_MSG (SUBSET_REPACK, nullptr, " Shrinking PairPosFormat2 (%u) to [0, %u).", split_context.this_index, count); unsigned old_count = class1Count; if (count >= old_count) return true; graph_t& graph = split_context.c.graph; class1Count = count; graph.vertices_[split_context.this_index].obj.tail -= (old_count - count) * split_context.class1_record_size; auto coverage = graph.as_mutable_table (split_context.this_index, &this->coverage); if (!coverage) return false; auto class_def_1 = graph.as_mutable_table (split_context.this_index, &classDef1); if (!class_def_1) return false; auto klass_map = + coverage.table->iter () | hb_map_retains_sorting ([&] (hb_codepoint_t gid) { return hb_pair_t (gid, class_def_1.table->get_class (gid)); }) | hb_filter ([&] (hb_codepoint_t klass) { return klass < count; }, hb_second) ; auto new_coverage = + klass_map | hb_map_retains_sorting (hb_first); if (!Coverage::make_coverage (split_context.c, + new_coverage, coverage.index, // existing ranges my not be kept, worst case size is a format 1 // coverage table. 4 + new_coverage.len() * 2)) return false; return ClassDef::make_class_def (split_context.c, + klass_map, class_def_1.index, class_def_1.vertex->table_size ()); } hb_hashmap_t get_all_device_tables (gsubgpos_graph_context_t& c, unsigned this_index) const { const auto& v = c.graph.vertices_[this_index]; return v.position_to_index_map (); } const Coverage* get_coverage (gsubgpos_graph_context_t& c, unsigned this_index) const { unsigned coverage_id = c.graph.index_for_offset (this_index, &coverage); auto& coverage_v = c.graph.vertices_[coverage_id]; Coverage* coverage_table = (Coverage*) coverage_v.obj.head; if (!coverage_table || !coverage_table->sanitize (coverage_v)) return &Null(Coverage); return coverage_table; } const ClassDef* get_class_def_1 (gsubgpos_graph_context_t& c, unsigned this_index) const { unsigned class_def_1_id = c.graph.index_for_offset (this_index, &classDef1); auto& class_def_1_v = c.graph.vertices_[class_def_1_id]; ClassDef* class_def_1_table = (ClassDef*) class_def_1_v.obj.head; if (!class_def_1_table || !class_def_1_table->sanitize (class_def_1_v)) return &Null(ClassDef); return class_def_1_table; } unsigned size_of_value_record_children (gsubgpos_graph_context_t& c, const hb_hashmap_t& device_tables, const hb_vector_t device_table_indices, unsigned value_record_index, hb_set_t& visited) { unsigned size = 0; for (unsigned i : device_table_indices) { OT::Layout::GPOS_impl::Value* record = &values[value_record_index + i]; unsigned record_position = ((char*) record) - ((char*) this); unsigned* obj_idx; if (!device_tables.has (record_position, &obj_idx)) continue; size += c.graph.find_subgraph_size (*obj_idx, visited); } return size; } unsigned size_of (gsubgpos_graph_context_t& c, unsigned this_index, const void* offset) const { const unsigned id = c.graph.index_for_offset (this_index, offset); return c.graph.vertices_[id].table_size (); } }; struct PairPos : public OT::Layout::GPOS_impl::PairPos { hb_vector_t split_subtables (gsubgpos_graph_context_t& c, unsigned parent_index, unsigned this_index) { switch (u.format) { case 1: return ((PairPosFormat1*)(&u.format1))->split_subtables (c, parent_index, this_index); case 2: return ((PairPosFormat2*)(&u.format2))->split_subtables (c, parent_index, this_index); #ifndef HB_NO_BEYOND_64K case 3: HB_FALLTHROUGH; case 4: HB_FALLTHROUGH; // Don't split 24bit PairPos's. #endif default: return hb_vector_t (); } } bool sanitize (graph_t::vertex_t& vertex) const { int64_t vertex_len = vertex.obj.tail - vertex.obj.head; if (vertex_len < u.format.get_size ()) return false; switch (u.format) { case 1: return ((PairPosFormat1*)(&u.format1))->sanitize (vertex); case 2: return ((PairPosFormat2*)(&u.format2))->sanitize (vertex); #ifndef HB_NO_BEYOND_64K case 3: HB_FALLTHROUGH; case 4: HB_FALLTHROUGH; #endif default: // We don't handle format 3 and 4 here. return false; } } }; } #endif // GRAPH_PAIRPOS_GRAPH_HH