diff --git a/src/graph/classdef-graph.hh b/src/graph/classdef-graph.hh index 4dfd5d6d8..a2aa8990f 100644 --- a/src/graph/classdef-graph.hh +++ b/src/graph/classdef-graph.hh @@ -135,7 +135,13 @@ struct ClassDef : public OT::ClassDef struct class_def_size_estimator_t { - // TODO(garretrieger): for coverage take the same approach as class def (compute the running total for each format). + // TODO(garretrieger): update to support beyond64k coverage/classdef tables. + constexpr static unsigned class_def_format1_base_size = 6; + constexpr static unsigned class_def_format2_base_size = 4; + constexpr static unsigned coverage_base_size = 4; + constexpr static unsigned bytes_per_range = 6; + constexpr static unsigned bytes_per_glyph = 2; + template class_def_size_estimator_t (It glyph_and_class) : num_ranges_per_class (), glyphs_per_class () @@ -176,8 +182,8 @@ struct class_def_size_estimator_t } void reset() { - class_def_1_size = 4; - class_def_2_size = 4; + class_def_1_size = class_def_format1_base_size; + class_def_2_size = class_def_format2_base_size; included_glyphs.clear(); included_classes.clear(); } @@ -185,10 +191,8 @@ struct class_def_size_estimator_t // Compute the size of coverage for all glyphs added via 'add_class_def_size'. unsigned coverage_size () const { - // format 1 is 2 bytes per glyph. - unsigned format1_size = 4 + 2 * included_glyphs.get_population(); - // format 2 is 6 bytes per range. - unsigned format2_size = 4 + 6 * num_glyph_ranges(); + unsigned format1_size = coverage_base_size + bytes_per_glyph * included_glyphs.get_population(); + unsigned format2_size = coverage_base_size + bytes_per_range * num_glyph_ranges(); return hb_min(format1_size, format2_size); } @@ -196,23 +200,23 @@ struct class_def_size_estimator_t unsigned add_class_def_size (unsigned klass) { if (!included_classes.has(klass)) { - // ClassDef 1 takes 2 bytes per glyph. - class_def_1_size += 2 * glyphs_per_class.get (klass).get_population (); - // ClassDef 2 takes 6 bytes per range. - class_def_2_size += 6 * num_ranges_per_class.get (klass); - hb_set_t* glyphs = nullptr; if (glyphs_per_class.has(klass, &glyphs)) { included_glyphs.union_(*glyphs); } + class_def_1_size = class_def_format1_base_size; + if (!included_glyphs.is_empty()) { + unsigned min_glyph = included_glyphs.get_min(); + unsigned max_glyph = included_glyphs.get_max(); + class_def_1_size += bytes_per_glyph * (max_glyph - min_glyph + 1); + } + + class_def_2_size += bytes_per_range * num_ranges_per_class.get (klass); + included_classes.add(klass); } - if (!gids_consecutive()) - return class_def_2_size; - - // ClassDef1 can only be used when gids are consecutive. return hb_min (class_def_1_size, class_def_2_size); } @@ -227,18 +231,6 @@ struct class_def_size_estimator_t return count; } - bool gids_consecutive() const { - hb_codepoint_t start = HB_SET_VALUE_INVALID; - hb_codepoint_t end = HB_SET_VALUE_INVALID; - - unsigned count = 0; - while (included_glyphs.next_range (&start, &end)) { - count++; - if (count > 1) return false; - } - return true; - } - bool in_error () { if (num_ranges_per_class.in_error ()) return true; diff --git a/src/graph/test-classdef-graph.cc b/src/graph/test-classdef-graph.cc index 4bd4793a2..bb569e1e7 100644 --- a/src/graph/test-classdef-graph.cc +++ b/src/graph/test-classdef-graph.cc @@ -26,27 +26,113 @@ #include "gsubgpos-context.hh" #include "classdef-graph.hh" +#include "hb-iter.hh" +#include "hb-serialize.hh" typedef hb_codepoint_pair_t gid_and_class_t; typedef hb_vector_t gid_and_class_list_t; +template +static unsigned actual_class_def_size(It glyph_and_class) { + char buffer[100]; + hb_serialize_context_t serializer(buffer, 100); + OT::ClassDef_serialize (&serializer, glyph_and_class); + serializer.end_serialize (); + assert(!serializer.in_error()); + hb_bytes_t class_def_copy = serializer.copy_bytes (); + return class_def_copy.get_size(); +} -static bool incremental_size_is (const gid_and_class_list_t& list, unsigned klass, - unsigned cov_expected, unsigned class_def_expected) +static unsigned actual_class_def_size(gid_and_class_list_t consecutive_map, hb_vector_t classes) { + auto filtered_it = + + consecutive_map.as_sorted_array().iter() + | hb_filter([&] (unsigned c) { + for (unsigned klass : classes) { + if (c == klass) { + return true; + } + } + return false; + }, hb_second); + return actual_class_def_size(+ filtered_it); +} + +template +static unsigned actual_coverage_size(It glyphs) { + char buffer[100]; + hb_serialize_context_t serializer(buffer, 100); + OT::Layout::Common::Coverage_serialize (&serializer, glyphs); + serializer.end_serialize (); + assert(!serializer.in_error()); + hb_bytes_t coverage_copy = serializer.copy_bytes (); + return coverage_copy.get_size(); +} + +static unsigned actual_coverage_size(gid_and_class_list_t consecutive_map, hb_vector_t classes) { + auto filtered_it = + + consecutive_map.as_sorted_array().iter() + | hb_filter([&] (unsigned c) { + for (unsigned klass : classes) { + if (c == klass) { + return true; + } + } + return false; + }, hb_second); + return actual_coverage_size(+ filtered_it | hb_map_retains_sorting(hb_first)); +} + +static bool check_coverage_size(graph::class_def_size_estimator_t& estimator, + const gid_and_class_list_t& map, + hb_vector_t klasses) +{ + unsigned result = estimator.coverage_size(); + unsigned expected = actual_coverage_size(map, klasses); + if (result != expected) { + printf ("FAIL: estimated coverage expected size %u but was %u\n", expected, result); + return false; + } + return true; +} + +static bool check_add_class_def_size(graph::class_def_size_estimator_t& estimator, + const gid_and_class_list_t& map, + unsigned klass, hb_vector_t klasses) +{ + unsigned result = estimator.add_class_def_size(klass); + unsigned expected = actual_class_def_size(map, klasses); + if (result != expected) { + printf ("FAIL: estimated class def expected size %u but was %u\n", expected, result); + return false; + } + + return check_coverage_size(estimator, map, klasses); +} + +static bool check_add_class_def_size (const gid_and_class_list_t& list, unsigned klass) { graph::class_def_size_estimator_t estimator (list.iter ()); unsigned result = estimator.add_class_def_size (klass); - if (result != class_def_expected) + auto filtered_it = + + list.as_sorted_array().iter() + | hb_filter([&] (unsigned c) { + return c == klass; + }, hb_second); + + unsigned expected = actual_class_def_size(filtered_it); + if (result != expected) { - printf ("FAIL: class def expected size %u but was %u\n", class_def_expected, result); + printf ("FAIL: class def expected size %u but was %u\n", expected, result); return false; } + auto cov_it = + filtered_it | hb_map_retains_sorting(hb_first); result = estimator.coverage_size (); - if (result != cov_expected) + expected = actual_coverage_size(cov_it); + if (result != expected) { - printf ("FAIL: coverage expected size %u but was %u\n", cov_expected, result); + printf ("FAIL: coverage expected size %u but was %u\n", expected, result); return false; } @@ -55,30 +141,31 @@ static bool incremental_size_is (const gid_and_class_list_t& list, unsigned klas static void test_class_and_coverage_size_estimates () { - // TODO(garretrieger): test against the actual serialized sizes of class def tables gid_and_class_list_t empty = { }; - assert (incremental_size_is (empty, 0, 4, 4)); - assert (incremental_size_is (empty, 1, 4, 4)); + assert (check_add_class_def_size (empty, 0)); + assert (check_add_class_def_size (empty, 1)); gid_and_class_list_t class_zero = { {5, 0}, }; - assert (incremental_size_is (class_zero, 0, 6, 4)); + assert (check_add_class_def_size (class_zero, 0)); gid_and_class_list_t consecutive = { {4, 0}, {5, 0}, + {6, 1}, {7, 1}, + {8, 2}, {9, 2}, {10, 2}, {11, 2}, }; - assert (incremental_size_is (consecutive, 0, 8, 4)); - assert (incremental_size_is (consecutive, 1, 8, 8)); - assert (incremental_size_is (consecutive, 2, 10, 10)); + assert (check_add_class_def_size (consecutive, 0)); + assert (check_add_class_def_size (consecutive, 1)); + assert (check_add_class_def_size (consecutive, 2)); gid_and_class_list_t non_consecutive = { {4, 0}, @@ -92,9 +179,9 @@ static void test_class_and_coverage_size_estimates () {11, 2}, {13, 2}, }; - assert (incremental_size_is (non_consecutive, 0, 8, 4)); - assert (incremental_size_is (non_consecutive, 1, 8, 4 + 2*6)); - assert (incremental_size_is (non_consecutive, 2, 12, 4 + 2*6)); + assert (check_add_class_def_size (non_consecutive, 0)); + assert (check_add_class_def_size (non_consecutive, 1)); + assert (check_add_class_def_size (non_consecutive, 2)); gid_and_class_list_t multiple_ranges = { {4, 0}, @@ -109,8 +196,8 @@ static void test_class_and_coverage_size_estimates () {12, 1}, {13, 1}, }; - assert (incremental_size_is (multiple_ranges, 0, 8, 4)); - assert (incremental_size_is (multiple_ranges, 1, 4 + 2 * 6, 4 + 3 * 6)); + assert (check_add_class_def_size (multiple_ranges, 0)); + assert (check_add_class_def_size (multiple_ranges, 1)); } static void test_running_class_and_coverage_size_estimates () { @@ -136,18 +223,13 @@ static void test_running_class_and_coverage_size_estimates () { }; graph::class_def_size_estimator_t estimator1(consecutive_map.iter()); - assert(estimator1.add_class_def_size(1) == 4 + 6); // format 2, 1 range - assert(estimator1.coverage_size() == 4 + 6); // format 2, 1 range - assert(estimator1.add_class_def_size(2) == 4 + 10); // format 1, 5 glyphs - assert(estimator1.coverage_size() == 4 + 6); // format 2, 1 range - assert(estimator1.add_class_def_size(3) == 4 + 18); // format 2, 3 ranges - assert(estimator1.coverage_size() == 4 + 6); // format 2, 1 range + assert(check_add_class_def_size(estimator1, consecutive_map, 1, {1})); + assert(check_add_class_def_size(estimator1, consecutive_map, 2, {1, 2})); + assert(check_add_class_def_size(estimator1, consecutive_map, 3, {1, 2, 3})); estimator1.reset(); - assert(estimator1.add_class_def_size(2) == 4 + 2); // format 1, 1 glyph - assert(estimator1.coverage_size() == 4 + 2); // format 1, 1 glyph - assert(estimator1.add_class_def_size(3) == 4 + 12); // format 2, 2 ranges - assert(estimator1.coverage_size() == 4 + 6); // format 1, 1 range + assert(check_add_class_def_size(estimator1, consecutive_map, 2, {2})); + assert(check_add_class_def_size(estimator1, consecutive_map, 3, {2, 3})); // #### With non-consecutive gids: always uses format 2 ### gid_and_class_list_t non_consecutive_map = { @@ -172,35 +254,30 @@ static void test_running_class_and_coverage_size_estimates () { }; graph::class_def_size_estimator_t estimator2(non_consecutive_map.iter()); - assert(estimator2.add_class_def_size(1) == 4 + 6); // format 2, 1 range - assert(estimator2.coverage_size() == 4 + 6); // format 2, 1 range - assert(estimator2.add_class_def_size(2) == 4 + 18); // format 2, 3 ranges - assert(estimator2.coverage_size() == 4 + 2 * 6); // format 1, 6 glyphs - assert(estimator2.add_class_def_size(3) == 4 + 24); // format 2, 4 ranges - assert(estimator2.coverage_size() == 4 + 3 * 6); // format 2, 3 ranges + assert(check_add_class_def_size(estimator2, non_consecutive_map, 1, {1})); + assert(check_add_class_def_size(estimator2, non_consecutive_map, 2, {1, 2})); + assert(check_add_class_def_size(estimator2, non_consecutive_map, 3, {1, 2, 3})); estimator2.reset(); - assert(estimator2.add_class_def_size(2) == 4 + 12); // format 1, 1 range - assert(estimator2.coverage_size() == 4 + 4); // format 1, 2 glyphs - assert(estimator2.add_class_def_size(3) == 4 + 18); // format 2, 2 ranges - assert(estimator2.coverage_size() == 4 + 2 * 6); // format 2, 2 ranges + assert(check_add_class_def_size(estimator2, non_consecutive_map, 2, {2})); + assert(check_add_class_def_size(estimator2, non_consecutive_map, 3, {2, 3})); } static void test_running_class_size_estimates_with_locally_consecutive_glyphs () { - gid_and_class_list_t consecutive_map = { + gid_and_class_list_t map = { {1, 1}, {6, 2}, {7, 3}, }; - graph::class_def_size_estimator_t estimator(consecutive_map.iter()); - assert(estimator.add_class_def_size(1) == 4 + 2); // format 1, 1 glyph - assert(estimator.add_class_def_size(2) == 4 + 12); // format 2, 2 ranges - assert(estimator.add_class_def_size(3) == 4 + 18); // format 2, 3 ranges + graph::class_def_size_estimator_t estimator(map.iter()); + assert(check_add_class_def_size(estimator, map, 1, {1})); + assert(check_add_class_def_size(estimator, map, 2, {1, 2})); + assert(check_add_class_def_size(estimator, map, 3, {1, 2, 3})); estimator.reset(); - assert(estimator.add_class_def_size(2) == 4 + 2); // format 1, 1 glyphs - assert(estimator.add_class_def_size(3) == 4 + 4); // format 1, 2 glyphs + assert(check_add_class_def_size(estimator, map, 2, {2})); + assert(check_add_class_def_size(estimator, map, 3, {2, 3})); } int