From f072de9359da08c136a7b1131fc12bbe17a7d441 Mon Sep 17 00:00:00 2001 From: Qunxin Liu Date: Tue, 30 Jan 2024 10:31:14 -0800 Subject: [PATCH] [instancer] move iup code to a seprate file --- src/Makefile.am | 4 +- src/Makefile.sources | 2 + src/harfbuzz-subset.cc | 1 + src/hb-ot-var-common.hh | 526 +------------------------------- src/hb-subset-instancer-iup.cc | 532 +++++++++++++++++++++++++++++++++ src/hb-subset-instancer-iup.hh | 37 +++ src/meson.build | 6 +- 7 files changed, 592 insertions(+), 516 deletions(-) create mode 100644 src/hb-subset-instancer-iup.cc create mode 100644 src/hb-subset-instancer-iup.hh diff --git a/src/Makefile.am b/src/Makefile.am index ff6a6d6f3..6a289e18d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -533,11 +533,11 @@ test_instancer_solver_SOURCES = test-subset-instancer-solver.cc hb-subset-instan test_instancer_solver_CPPFLAGS = $(COMPILED_TESTS_CPPFLAGS) test_instancer_solver_LDADD = $(COMPILED_TESTS_LDADD) -test_tuple_varstore_SOURCES = test-tuple-varstore.cc hb-subset-instancer-solver.cc hb-static.cc +test_tuple_varstore_SOURCES = test-tuple-varstore.cc hb-subset-instancer-solver.cc hb-subset-instancer-iup.cc hb-static.cc test_tuple_varstore_CPPFLAGS = $(COMPILED_TESTS_CPPFLAGS) test_tuple_varstore_LDADD = $(COMPILED_TESTS_LDADD) -test_item_varstore_SOURCES = test-item-varstore.cc hb-subset-instancer-solver.cc hb-static.cc +test_item_varstore_SOURCES = test-item-varstore.cc hb-subset-instancer-solver.cc hb-subset-instancer-iup.cc hb-static.cc test_item_varstore_CPPFLAGS = $(COMPILED_TESTS_CPPFLAGS) test_item_varstore_LDADD = $(COMPILED_TESTS_LDADD) diff --git a/src/Makefile.sources b/src/Makefile.sources index fbbff5325..33b5ef808 100644 --- a/src/Makefile.sources +++ b/src/Makefile.sources @@ -368,6 +368,8 @@ HB_SUBSET_sources = \ hb-subset-cff2.cc \ hb-subset-input.cc \ hb-subset-input.hh \ + hb-subset-instancer-iup.hh \ + hb-subset-instancer-iup.cc \ hb-subset-instancer-solver.hh \ hb-subset-instancer-solver.cc \ hb-subset-accelerator.hh \ diff --git a/src/harfbuzz-subset.cc b/src/harfbuzz-subset.cc index c0e23b3eb..f80c004cb 100644 --- a/src/harfbuzz-subset.cc +++ b/src/harfbuzz-subset.cc @@ -54,6 +54,7 @@ #include "hb-subset-cff1.cc" #include "hb-subset-cff2.cc" #include "hb-subset-input.cc" +#include "hb-subset-instancer-iup.cc" #include "hb-subset-instancer-solver.cc" #include "hb-subset-plan.cc" #include "hb-subset-repacker.cc" diff --git a/src/hb-ot-var-common.hh b/src/hb-ot-var-common.hh index 97128f211..862123faa 100644 --- a/src/hb-ot-var-common.hh +++ b/src/hb-ot-var-common.hh @@ -28,6 +28,7 @@ #include "hb-ot-layout-common.hh" #include "hb-priority-queue.hh" +#include "hb-subset-instancer-iup.hh" namespace OT { @@ -1038,12 +1039,21 @@ struct tuple_delta_t return false; hb_vector_t opt_indices; - hb_vector_t opt_deltas_x, opt_deltas_y; + hb_vector_t rounded_x_deltas, rounded_y_deltas; - if (!opt_indices.resize (count)) + if (unlikely (!rounded_x_deltas.alloc (count) || + !rounded_y_deltas.alloc (count))) return false; - if (!iup_delta_optimize (contour_points, opt_indices, tolerance)) + for (unsigned i = 0; i < count; i++) + { + int rounded_x_delta = (int) roundf (deltas_x.arrayZ[i]); + int rounded_y_delta = (int) roundf (deltas_y.arrayZ[i]); + rounded_x_deltas.push (rounded_x_delta); + rounded_y_deltas.push (rounded_y_delta); + } + + if (!iup_delta_optimize (contour_points, rounded_x_deltas, rounded_y_deltas, opt_indices, tolerance)) return false; unsigned ref_count = 0; @@ -1052,6 +1062,7 @@ struct tuple_delta_t if (ref_count == count) return true; + hb_vector_t opt_deltas_x, opt_deltas_y; bool is_comp_glyph_wo_deltas = (is_composite && ref_count == 0); if (is_comp_glyph_wo_deltas) { @@ -1098,515 +1109,6 @@ struct tuple_delta_t return !indices.in_error () && !deltas_x.in_error () && !deltas_y.in_error (); } - bool iup_delta_optimize (const contour_point_vector_t& contour_points, - hb_vector_t& opt_indices, /* OUT */ - float tolerance = 0.f) - { - hb_vector_t end_points; - unsigned count = contour_points.length; - if (unlikely (!end_points.alloc (count))) - return false; - - for (unsigned i = 0; i < count - 4; i++) - if (contour_points.arrayZ[i].is_end_point) - end_points.push (i); - - /* phantom points */ - for (unsigned i = count - 4; i < count; i++) - end_points.push (i); - - if (end_points.in_error ()) return false; - - hb_vector_t rounded_x_deltas, rounded_y_deltas; - - if (unlikely (!rounded_x_deltas.alloc (count) || - !rounded_y_deltas.alloc (count))) - return false; - - for (unsigned i = 0; i < count; i++) - { - int rounded_x_delta = (int) roundf (deltas_x.arrayZ[i]); - int rounded_y_delta = (int) roundf (deltas_y.arrayZ[i]); - rounded_x_deltas.push (rounded_x_delta); - rounded_y_deltas.push (rounded_y_delta); - } - - - unsigned start = 0; - for (unsigned end : end_points) - { - unsigned len = end - start + 1; - if (!_iup_contour_optimize (contour_points.as_array ().sub_array (start, len), - rounded_x_deltas.as_array ().sub_array (start, len), - rounded_y_deltas.as_array ().sub_array (start, len), - opt_indices.as_array ().sub_array (start, len), - tolerance)) - return false; - start = end + 1; - } - return true; - } - - static bool _iup_contour_optimize (const hb_array_t contour_points, - const hb_array_t x_deltas, - const hb_array_t y_deltas, - hb_array_t opt_indices, /* OUT */ - float tolerance = 0.f) - { - unsigned n = contour_points.length; - if (opt_indices.length != n || - x_deltas.length != n || - y_deltas.length != n) - return false; - - bool all_within_tolerance = true; - for (unsigned i = 0; i < n; i++) - { - int dx = x_deltas.arrayZ[i]; - int dy = y_deltas.arrayZ[i]; - if (sqrtf ((float)dx * dx + (float)dy * dy) > tolerance) - { - all_within_tolerance = false; - break; - } - } - - /* If all are within tolerance distance, do nothing, opt_indices is - * initilized to false */ - if (all_within_tolerance) - return true; - - /* If there's exactly one point, return it */ - if (n == 1) - { - opt_indices.arrayZ[0] = true; - return true; - } - - /* If all deltas are exactly the same, return just one (the first one) */ - bool all_deltas_are_equal = true; - for (unsigned i = 1; i < n; i++) - if (x_deltas.arrayZ[i] != x_deltas.arrayZ[0] || - y_deltas.arrayZ[i] != y_deltas.arrayZ[0]) - { - all_deltas_are_equal = false; - break; - } - - if (all_deltas_are_equal) - { - opt_indices.arrayZ[0] = true; - return true; - } - - /* else, solve the general problem using Dynamic Programming */ - hb_set_t forced_set; - _iup_contour_bound_forced_set (contour_points, x_deltas, y_deltas, forced_set, tolerance); - - if (!forced_set.is_empty ()) - { - int k = n - 1 - forced_set.get_max (); - if (k < 0) - return false; - - hb_vector_t rot_x_deltas, rot_y_deltas; - contour_point_vector_t rot_points; - hb_set_t rot_forced_set; - if (!rotate_array (contour_points, k, rot_points) || - !rotate_array (x_deltas, k, rot_x_deltas) || - !rotate_array (y_deltas, k, rot_y_deltas) || - !rotate_set (forced_set, k, n, rot_forced_set)) - return false; - - hb_vector_t costs; - hb_vector_t chain; - - if (!_iup_contour_optimize_dp (rot_points, rot_x_deltas, rot_y_deltas, - rot_forced_set, tolerance, n, - costs, chain)) - return false; - - hb_set_t solution; - int index = n - 1; - while (index != -1) - { - solution.add (index); - index = chain.arrayZ[index]; - } - - if (solution.is_empty () || - forced_set.get_population () > solution.get_population ()) - return false; - - for (unsigned i : solution) - opt_indices.arrayZ[i] = true; - - hb_vector_t rot_indices; - const hb_array_t opt_indices_array (opt_indices.arrayZ, opt_indices.length); - rotate_array (opt_indices_array, -k, rot_indices); - - for (unsigned i = 0; i < n; i++) - opt_indices.arrayZ[i] = rot_indices.arrayZ[i]; - } - else - { - hb_vector_t repeat_x_deltas, repeat_y_deltas; - contour_point_vector_t repeat_points; - - if (unlikely (!repeat_x_deltas.resize (n * 2, false) || - !repeat_y_deltas.resize (n * 2, false) || - !repeat_points.resize (n * 2, false))) - return false; - - unsigned contour_point_size = hb_static_size (contour_point_t); - for (unsigned i = 0; i < n; i++) - { - hb_memcpy ((void *) repeat_x_deltas.arrayZ, (const void *) x_deltas.arrayZ, n * sizeof (float)); - hb_memcpy ((void *) (repeat_x_deltas.arrayZ + n), (const void *) x_deltas.arrayZ, n * sizeof (float)); - - hb_memcpy ((void *) repeat_y_deltas.arrayZ, (const void *) y_deltas.arrayZ, n * sizeof (float)); - hb_memcpy ((void *) (repeat_y_deltas.arrayZ + n), (const void *) y_deltas.arrayZ, n * sizeof (float)); - - hb_memcpy ((void *) repeat_points.arrayZ, (const void *) contour_points.arrayZ, n * contour_point_size); - hb_memcpy ((void *) (repeat_points.arrayZ + n), (const void *) contour_points.arrayZ, n * contour_point_size); - } - - hb_vector_t costs; - hb_vector_t chain; - if (!_iup_contour_optimize_dp (repeat_points, repeat_x_deltas, repeat_y_deltas, - forced_set, tolerance, n, - costs, chain)) - return false; - - unsigned best_cost = n + 1; - int len = costs.length; - hb_set_t best_sol; - for (int start = n - 1; start < len; start++) - { - hb_set_t solution; - int i = start; - int lookback = start - (int) n; - while (i > lookback) - { - solution.add (i % n); - i = chain.arrayZ[i]; - } - if (i == lookback) - { - unsigned cost_i = i < 0 ? 0 : costs.arrayZ[i]; - unsigned cost = costs.arrayZ[start] - cost_i; - if (cost <= best_cost) - { - best_sol.set (solution); - best_cost = cost; - } - } - } - - for (unsigned i = 0; i < n; i++) - if (best_sol.has (i)) - opt_indices.arrayZ[i] = true; - } - return true; - } - - static bool _iup_contour_optimize_dp (const contour_point_vector_t& contour_points, - const hb_vector_t& x_deltas, - const hb_vector_t& y_deltas, - const hb_set_t& forced_set, - float tolerance, - unsigned lookback, - hb_vector_t& costs, /* OUT */ - hb_vector_t& chain /* OUT */) - { - unsigned n = contour_points.length; - if (unlikely (!costs.resize (n, false) || - !chain.resize (n, false))) - return false; - - constexpr unsigned max_lookback = 8; - lookback = hb_min (lookback, max_lookback); - - for (unsigned i = 0; i < n; i++) - { - unsigned best_cost = (i == 0 ? 1 : costs.arrayZ[i-1] + 1); - - costs.arrayZ[i] = best_cost; - chain.arrayZ[i] = (i == 0 ? -1 : i - 1); - - if (i > 0 && forced_set.has (i - 1)) - continue; - - int lookback_index = hb_max ((int) i - (int) lookback + 1, -1); - for (int j = i - 2; j >= lookback_index; j--) - { - unsigned cost = j == -1 ? 1 : costs.arrayZ[j] + 1; - /* num points between i and j */ - unsigned num_points = i - j - 1; - unsigned p1 = (j == -1 ? n - 1 : j); - if (cost < best_cost && - _can_iup_in_between (contour_points.as_array ().sub_array (j + 1, num_points), - x_deltas.as_array ().sub_array (j + 1, num_points), - y_deltas.as_array ().sub_array (j + 1, num_points), - contour_points.arrayZ[p1], contour_points.arrayZ[i], - x_deltas.arrayZ[p1], x_deltas.arrayZ[i], - y_deltas.arrayZ[p1], y_deltas.arrayZ[i], - tolerance)) - { - best_cost = cost; - costs.arrayZ[i] = best_cost; - chain.arrayZ[i] = j; - } - - if (j > 0 && forced_set.has (j)) - break; - } - } - return true; - } - - static bool _can_iup_in_between (const hb_array_t contour_points, - const hb_array_t x_deltas, - const hb_array_t y_deltas, - const contour_point_t& p1, const contour_point_t& p2, - int p1_dx, int p2_dx, - int p1_dy, int p2_dy, - float tolerance) - { - hb_vector_t interp_x_deltas, interp_y_deltas; - if (!_iup_segment (contour_points, x_deltas, y_deltas, - p1, p2, p1_dx, p2_dx, p1_dy, p2_dy, - interp_x_deltas, interp_y_deltas)) - return false; - - unsigned num = contour_points.length; - - for (unsigned i = 0; i < num; i++) - { - float dx = x_deltas.arrayZ[i] - interp_x_deltas.arrayZ[i]; - float dy = y_deltas.arrayZ[i] - interp_y_deltas.arrayZ[i]; - - if (sqrtf ((float)dx * dx + (float)dy * dy) > tolerance) - return false; - } - return true; - } - - /* Given two reference coordinates (start and end of contour_points array), - * output interpolated deltas for points in between */ - static bool _iup_segment (const hb_array_t contour_points, - const hb_array_t x_deltas, - const hb_array_t y_deltas, - const contour_point_t& p1, const contour_point_t& p2, - int p1_dx, int p2_dx, - int p1_dy, int p2_dy, - hb_vector_t& interp_x_deltas, /* OUT */ - hb_vector_t& interp_y_deltas /* OUT */) - { - unsigned n = contour_points.length; - if (unlikely (!interp_x_deltas.resize (n, false) || - !interp_y_deltas.resize (n, false))) - return false; - - for (unsigned j = 0; j < 2; j++) - { - float x1, x2, d1, d2; - float *out; - if (j == 0) - { - x1 = p1.x; - x2 = p2.x; - d1 = p1_dx; - d2 = p2_dx; - out = interp_x_deltas.arrayZ; - } - else - { - x1 = p1.y; - x2 = p2.y; - d1 = p1_dy; - d2 = p2_dy; - out = interp_y_deltas.arrayZ; - } - - if (x1 == x2) - { - if (d1 == d2) - { - for (unsigned i = 0; i < n; i++) - out[i] = d1; - } - else - { - for (unsigned i = 0; i < n; i++) - out[i] = 0.f; - } - continue; - } - - if (x1 > x2) - { - hb_swap (x1, x2); - hb_swap (d1, d2); - } - - float scale = (d2 - d1) / (x2 - x1); - for (unsigned i = 0; i < n; i++) - { - float x = j == 0 ? contour_points.arrayZ[i].x : contour_points.arrayZ[i].y; - float d; - if (x <= x1) - d = d1; - else if (x >= x2) - d = d2; - else - d = d1 + (x - x1) * scale; - - out[i] = d; - } - } - return true; - } - - template - static bool rotate_array (const hb_array_t& org_array, - int k, - hb_vector_t& out) - { - unsigned n = org_array.length; - if (!n) return true; - if (unlikely (!out.resize (n, false))) - return false; - - unsigned item_size = hb_static_size (T); - if (k < 0) - k = n - (-k) % n; - else - k %= n; - - hb_memcpy ((void *) out.arrayZ, (const void *) (org_array.arrayZ + n - k), k * item_size); - hb_memcpy ((void *) (out.arrayZ + k), (const void *) org_array.arrayZ, (n - k) * item_size); - return true; - } - - static bool rotate_set (const hb_set_t& org_set, - int k, - unsigned n, - hb_set_t& out) - { - if (!n) return false; - k %= n; - if (k < 0) - k = n + k; - - if (k == 0) - { - out.set (org_set); - } - else - { - for (auto v : org_set) - out.add ((v + k) % n); - } - return !out.in_error (); - } - - /* ported from fonttools */ - static void _iup_contour_bound_forced_set (const hb_array_t contour_points, - const hb_array_t x_deltas, - const hb_array_t y_deltas, - hb_set_t& forced_set, /* OUT */ - float tolerance = 0.f) - { - unsigned len = contour_points.length; - unsigned next_i = 0; - for (int i = len - 1; i >= 0; i--) - { - unsigned last_i = (len + i -1) % len; - for (unsigned j = 0; j < 2; j++) - { - float cj, lcj, ncj; - int dj, ldj, ndj; - if (j == 0) - { - cj = contour_points.arrayZ[i].x; - dj = x_deltas.arrayZ[i]; - lcj = contour_points.arrayZ[last_i].x; - ldj = x_deltas.arrayZ[last_i]; - ncj = contour_points.arrayZ[next_i].x; - ndj = x_deltas.arrayZ[next_i]; - } - else - { - cj = contour_points.arrayZ[i].y; - dj = y_deltas.arrayZ[i]; - lcj = contour_points.arrayZ[last_i].y; - ldj = y_deltas.arrayZ[last_i]; - ncj = contour_points.arrayZ[next_i].y; - ndj = y_deltas.arrayZ[next_i]; - } - - float c1, c2; - int d1, d2; - if (lcj <= ncj) - { - c1 = lcj; - c2 = ncj; - d1 = ldj; - d2 = ndj; - } - else - { - c1 = ncj; - c2 = lcj; - d1 = ndj; - d2 = ldj; - } - - bool force = false; - if (c1 == c2) - { - if (abs (d1 - d2) > tolerance && abs (dj) > tolerance) - force = true; - } - else if (c1 <= cj && cj <= c2) - { - if (!(hb_min (d1, d2) - tolerance <= dj && - dj <= hb_max (d1, d2) + tolerance)) - force = true; - } - else - { - if (d1 != d2) - { - if (cj < c1) - { - if (abs (dj) > tolerance && - abs (dj - d1) > tolerance && - ((dj - tolerance < d1) != (d1 < d2))) - force = true; - } - else - { - if (abs (dj) > tolerance && - abs (dj - d2) > tolerance && - ((d2 < dj + tolerance) != (d1 < d2))) - force = true; - } - } - } - - if (force) - { - forced_set.add (i); - break; - } - } - next_i = i; - } - } - static bool compile_point_set (const hb_vector_t &point_indices, hb_vector_t& compiled_points /* OUT */) { diff --git a/src/hb-subset-instancer-iup.cc b/src/hb-subset-instancer-iup.cc new file mode 100644 index 000000000..35a964d08 --- /dev/null +++ b/src/hb-subset-instancer-iup.cc @@ -0,0 +1,532 @@ +/* + * Copyright © 2024 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. + */ + +#include "hb-subset-instancer-iup.hh" + +/* This file is a straight port of the following: + * + * https://github.com/fonttools/fonttools/blob/main/Lib/fontTools/varLib/iup.py + * + * Where that file returns optimzied deltas vector, we return optimized + * referenced point indices. + */ + +constexpr static unsigned MAX_LOOKBACK = 8; + +static void _iup_contour_bound_forced_set (const hb_array_t contour_points, + const hb_array_t x_deltas, + const hb_array_t y_deltas, + hb_set_t& forced_set, /* OUT */ + float tolerance = 0.f) +{ + unsigned len = contour_points.length; + unsigned next_i = 0; + for (int i = len - 1; i >= 0; i--) + { + unsigned last_i = (len + i -1) % len; + for (unsigned j = 0; j < 2; j++) + { + float cj, lcj, ncj; + int dj, ldj, ndj; + if (j == 0) + { + cj = contour_points.arrayZ[i].x; + dj = x_deltas.arrayZ[i]; + lcj = contour_points.arrayZ[last_i].x; + ldj = x_deltas.arrayZ[last_i]; + ncj = contour_points.arrayZ[next_i].x; + ndj = x_deltas.arrayZ[next_i]; + } + else + { + cj = contour_points.arrayZ[i].y; + dj = y_deltas.arrayZ[i]; + lcj = contour_points.arrayZ[last_i].y; + ldj = y_deltas.arrayZ[last_i]; + ncj = contour_points.arrayZ[next_i].y; + ndj = y_deltas.arrayZ[next_i]; + } + + float c1, c2; + int d1, d2; + if (lcj <= ncj) + { + c1 = lcj; + c2 = ncj; + d1 = ldj; + d2 = ndj; + } + else + { + c1 = ncj; + c2 = lcj; + d1 = ndj; + d2 = ldj; + } + + bool force = false; + if (c1 == c2) + { + if (abs (d1 - d2) > tolerance && abs (dj) > tolerance) + force = true; + } + else if (c1 <= cj && cj <= c2) + { + if (!(hb_min (d1, d2) - tolerance <= dj && + dj <= hb_max (d1, d2) + tolerance)) + force = true; + } + else + { + if (d1 != d2) + { + if (cj < c1) + { + if (abs (dj) > tolerance && + abs (dj - d1) > tolerance && + ((dj - tolerance < d1) != (d1 < d2))) + force = true; + } + else + { + if (abs (dj) > tolerance && + abs (dj - d2) > tolerance && + ((d2 < dj + tolerance) != (d1 < d2))) + force = true; + } + } + } + + if (force) + { + forced_set.add (i); + break; + } + } + next_i = i; + } +} + +template +static bool rotate_array (const hb_array_t& org_array, + int k, + hb_vector_t& out) +{ + unsigned n = org_array.length; + if (!n) return true; + if (unlikely (!out.resize (n, false))) + return false; + + unsigned item_size = hb_static_size (T); + if (k < 0) + k = n - (-k) % n; + else + k %= n; + + hb_memcpy ((void *) out.arrayZ, (const void *) (org_array.arrayZ + n - k), k * item_size); + hb_memcpy ((void *) (out.arrayZ + k), (const void *) org_array.arrayZ, (n - k) * item_size); + return true; +} + +static bool rotate_set (const hb_set_t& org_set, + int k, + unsigned n, + hb_set_t& out) +{ + if (!n) return false; + k %= n; + if (k < 0) + k = n + k; + + if (k == 0) + { + out.set (org_set); + } + else + { + for (auto v : org_set) + out.add ((v + k) % n); + } + return !out.in_error (); +} + +/* Given two reference coordinates (start and end of contour_points array), + * output interpolated deltas for points in between */ +static bool _iup_segment (const hb_array_t contour_points, + const hb_array_t x_deltas, + const hb_array_t y_deltas, + const contour_point_t& p1, const contour_point_t& p2, + int p1_dx, int p2_dx, + int p1_dy, int p2_dy, + hb_vector_t& interp_x_deltas, /* OUT */ + hb_vector_t& interp_y_deltas /* OUT */) +{ + unsigned n = contour_points.length; + if (unlikely (!interp_x_deltas.resize (n, false) || + !interp_y_deltas.resize (n, false))) + return false; + + for (unsigned j = 0; j < 2; j++) + { + float x1, x2, d1, d2; + float *out; + if (j == 0) + { + x1 = p1.x; + x2 = p2.x; + d1 = p1_dx; + d2 = p2_dx; + out = interp_x_deltas.arrayZ; + } + else + { + x1 = p1.y; + x2 = p2.y; + d1 = p1_dy; + d2 = p2_dy; + out = interp_y_deltas.arrayZ; + } + + if (x1 == x2) + { + if (d1 == d2) + { + for (unsigned i = 0; i < n; i++) + out[i] = d1; + } + else + { + for (unsigned i = 0; i < n; i++) + out[i] = 0.f; + } + continue; + } + + if (x1 > x2) + { + hb_swap (x1, x2); + hb_swap (d1, d2); + } + + float scale = (d2 - d1) / (x2 - x1); + for (unsigned i = 0; i < n; i++) + { + float x = j == 0 ? contour_points.arrayZ[i].x : contour_points.arrayZ[i].y; + float d; + if (x <= x1) + d = d1; + else if (x >= x2) + d = d2; + else + d = d1 + (x - x1) * scale; + + out[i] = d; + } + } + return true; +} + +static bool _can_iup_in_between (const hb_array_t contour_points, + const hb_array_t x_deltas, + const hb_array_t y_deltas, + const contour_point_t& p1, const contour_point_t& p2, + int p1_dx, int p2_dx, + int p1_dy, int p2_dy, + float tolerance) +{ + hb_vector_t interp_x_deltas, interp_y_deltas; + if (!_iup_segment (contour_points, x_deltas, y_deltas, + p1, p2, p1_dx, p2_dx, p1_dy, p2_dy, + interp_x_deltas, interp_y_deltas)) + return false; + + unsigned num = contour_points.length; + + for (unsigned i = 0; i < num; i++) + { + float dx = x_deltas.arrayZ[i] - interp_x_deltas.arrayZ[i]; + float dy = y_deltas.arrayZ[i] - interp_y_deltas.arrayZ[i]; + + if (sqrtf ((float)dx * dx + (float)dy * dy) > tolerance) + return false; + } + return true; +} + +static bool _iup_contour_optimize_dp (const contour_point_vector_t& contour_points, + const hb_vector_t& x_deltas, + const hb_vector_t& y_deltas, + const hb_set_t& forced_set, + float tolerance, + unsigned lookback, + hb_vector_t& costs, /* OUT */ + hb_vector_t& chain /* OUT */) +{ + unsigned n = contour_points.length; + if (unlikely (!costs.resize (n, false) || + !chain.resize (n, false))) + return false; + + lookback = hb_min (lookback, MAX_LOOKBACK); + + for (unsigned i = 0; i < n; i++) + { + unsigned best_cost = (i == 0 ? 1 : costs.arrayZ[i-1] + 1); + + costs.arrayZ[i] = best_cost; + chain.arrayZ[i] = (i == 0 ? -1 : i - 1); + + if (i > 0 && forced_set.has (i - 1)) + continue; + + int lookback_index = hb_max ((int) i - (int) lookback + 1, -1); + for (int j = i - 2; j >= lookback_index; j--) + { + unsigned cost = j == -1 ? 1 : costs.arrayZ[j] + 1; + /* num points between i and j */ + unsigned num_points = i - j - 1; + unsigned p1 = (j == -1 ? n - 1 : j); + if (cost < best_cost && + _can_iup_in_between (contour_points.as_array ().sub_array (j + 1, num_points), + x_deltas.as_array ().sub_array (j + 1, num_points), + y_deltas.as_array ().sub_array (j + 1, num_points), + contour_points.arrayZ[p1], contour_points.arrayZ[i], + x_deltas.arrayZ[p1], x_deltas.arrayZ[i], + y_deltas.arrayZ[p1], y_deltas.arrayZ[i], + tolerance)) + { + best_cost = cost; + costs.arrayZ[i] = best_cost; + chain.arrayZ[i] = j; + } + + if (j > 0 && forced_set.has (j)) + break; + } + } + return true; +} + +static bool _iup_contour_optimize (const hb_array_t contour_points, + const hb_array_t x_deltas, + const hb_array_t y_deltas, + hb_array_t opt_indices, /* OUT */ + float tolerance = 0.f) +{ + unsigned n = contour_points.length; + if (opt_indices.length != n || + x_deltas.length != n || + y_deltas.length != n) + return false; + + bool all_within_tolerance = true; + for (unsigned i = 0; i < n; i++) + { + int dx = x_deltas.arrayZ[i]; + int dy = y_deltas.arrayZ[i]; + if (sqrtf ((float)dx * dx + (float)dy * dy) > tolerance) + { + all_within_tolerance = false; + break; + } + } + + /* If all are within tolerance distance, do nothing, opt_indices is + * initilized to false */ + if (all_within_tolerance) + return true; + + /* If there's exactly one point, return it */ + if (n == 1) + { + opt_indices.arrayZ[0] = true; + return true; + } + + /* If all deltas are exactly the same, return just one (the first one) */ + bool all_deltas_are_equal = true; + for (unsigned i = 1; i < n; i++) + if (x_deltas.arrayZ[i] != x_deltas.arrayZ[0] || + y_deltas.arrayZ[i] != y_deltas.arrayZ[0]) + { + all_deltas_are_equal = false; + break; + } + + if (all_deltas_are_equal) + { + opt_indices.arrayZ[0] = true; + return true; + } + + /* else, solve the general problem using Dynamic Programming */ + hb_set_t forced_set; + _iup_contour_bound_forced_set (contour_points, x_deltas, y_deltas, forced_set, tolerance); + + if (!forced_set.is_empty ()) + { + int k = n - 1 - forced_set.get_max (); + if (k < 0) + return false; + + hb_vector_t rot_x_deltas, rot_y_deltas; + contour_point_vector_t rot_points; + hb_set_t rot_forced_set; + if (!rotate_array (contour_points, k, rot_points) || + !rotate_array (x_deltas, k, rot_x_deltas) || + !rotate_array (y_deltas, k, rot_y_deltas) || + !rotate_set (forced_set, k, n, rot_forced_set)) + return false; + + hb_vector_t costs; + hb_vector_t chain; + + if (!_iup_contour_optimize_dp (rot_points, rot_x_deltas, rot_y_deltas, + rot_forced_set, tolerance, n, + costs, chain)) + return false; + + hb_set_t solution; + int index = n - 1; + while (index != -1) + { + solution.add (index); + index = chain.arrayZ[index]; + } + + if (solution.is_empty () || + forced_set.get_population () > solution.get_population ()) + return false; + + for (unsigned i : solution) + opt_indices.arrayZ[i] = true; + + hb_vector_t rot_indices; + const hb_array_t opt_indices_array (opt_indices.arrayZ, opt_indices.length); + rotate_array (opt_indices_array, -k, rot_indices); + + for (unsigned i = 0; i < n; i++) + opt_indices.arrayZ[i] = rot_indices.arrayZ[i]; + } + else + { + hb_vector_t repeat_x_deltas, repeat_y_deltas; + contour_point_vector_t repeat_points; + + if (unlikely (!repeat_x_deltas.resize (n * 2, false) || + !repeat_y_deltas.resize (n * 2, false) || + !repeat_points.resize (n * 2, false))) + return false; + + unsigned contour_point_size = hb_static_size (contour_point_t); + for (unsigned i = 0; i < n; i++) + { + hb_memcpy ((void *) repeat_x_deltas.arrayZ, (const void *) x_deltas.arrayZ, n * sizeof (float)); + hb_memcpy ((void *) (repeat_x_deltas.arrayZ + n), (const void *) x_deltas.arrayZ, n * sizeof (float)); + + hb_memcpy ((void *) repeat_y_deltas.arrayZ, (const void *) y_deltas.arrayZ, n * sizeof (float)); + hb_memcpy ((void *) (repeat_y_deltas.arrayZ + n), (const void *) y_deltas.arrayZ, n * sizeof (float)); + + hb_memcpy ((void *) repeat_points.arrayZ, (const void *) contour_points.arrayZ, n * contour_point_size); + hb_memcpy ((void *) (repeat_points.arrayZ + n), (const void *) contour_points.arrayZ, n * contour_point_size); + } + + hb_vector_t costs; + hb_vector_t chain; + if (!_iup_contour_optimize_dp (repeat_points, repeat_x_deltas, repeat_y_deltas, + forced_set, tolerance, n, + costs, chain)) + return false; + + unsigned best_cost = n + 1; + int len = costs.length; + hb_set_t best_sol; + for (int start = n - 1; start < len; start++) + { + hb_set_t solution; + int i = start; + int lookback = start - (int) n; + while (i > lookback) + { + solution.add (i % n); + i = chain.arrayZ[i]; + } + if (i == lookback) + { + unsigned cost_i = i < 0 ? 0 : costs.arrayZ[i]; + unsigned cost = costs.arrayZ[start] - cost_i; + if (cost <= best_cost) + { + best_sol.set (solution); + best_cost = cost; + } + } + } + + for (unsigned i = 0; i < n; i++) + if (best_sol.has (i)) + opt_indices.arrayZ[i] = true; + } + return true; +} + +bool iup_delta_optimize (const contour_point_vector_t& contour_points, + const hb_vector_t& x_deltas, + const hb_vector_t& y_deltas, + hb_vector_t& opt_indices, /* OUT */ + float tolerance) +{ + if (!opt_indices.resize (contour_points.length)) + return false; + + hb_vector_t end_points; + unsigned count = contour_points.length; + if (unlikely (!end_points.alloc (count))) + return false; + + for (unsigned i = 0; i < count - 4; i++) + if (contour_points.arrayZ[i].is_end_point) + end_points.push (i); + + /* phantom points */ + for (unsigned i = count - 4; i < count; i++) + end_points.push (i); + + if (end_points.in_error ()) return false; + + unsigned start = 0; + for (unsigned end : end_points) + { + unsigned len = end - start + 1; + if (!_iup_contour_optimize (contour_points.as_array ().sub_array (start, len), + x_deltas.as_array ().sub_array (start, len), + y_deltas.as_array ().sub_array (start, len), + opt_indices.as_array ().sub_array (start, len), + tolerance)) + return false; + start = end + 1; + } + return true; +} diff --git a/src/hb-subset-instancer-iup.hh b/src/hb-subset-instancer-iup.hh new file mode 100644 index 000000000..7eac5935a --- /dev/null +++ b/src/hb-subset-instancer-iup.hh @@ -0,0 +1,37 @@ +/* + * Copyright © 2024 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. + */ + +#ifndef HB_SUBSET_INSTANCER_IUP_HH +#define HB_SUBSET_INSTANCER_IUP_HH + +#include "hb-subset-plan.hh" +/* given contour points and deltas, optimize a set of referenced points within error + * tolerance. Returns optimized referenced point indices */ +HB_INTERNAL bool iup_delta_optimize (const contour_point_vector_t& contour_points, + const hb_vector_t& x_deltas, + const hb_vector_t& y_deltas, + hb_vector_t& opt_indices, /* OUT */ + float tolerance = 0.f); + +#endif /* HB_SUBSET_INSTANCER_IUP_HH */ diff --git a/src/meson.build b/src/meson.build index 77c7e7501..c64afa5a9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -371,6 +371,8 @@ hb_subset_sources = files( 'hb-subset-cff2.cc', 'hb-subset-input.cc', 'hb-subset-input.hh', + 'hb-subset-instancer-iup.hh', + 'hb-subset-instancer-iup.cc', 'hb-subset-instancer-solver.hh', 'hb-subset-instancer-solver.cc', 'hb-subset-plan.cc', @@ -722,8 +724,8 @@ if get_option('tests').enabled() 'test-repacker': ['test-repacker.cc', 'hb-static.cc', 'graph/gsubgpos-context.cc'], 'test-instancer-solver': ['test-subset-instancer-solver.cc', 'hb-subset-instancer-solver.cc', 'hb-static.cc'], 'test-priority-queue': ['test-priority-queue.cc', 'hb-static.cc'], - 'test-tuple-varstore': ['test-tuple-varstore.cc', 'hb-subset-instancer-solver.cc', 'hb-static.cc'], - 'test-item-varstore': ['test-item-varstore.cc', 'hb-subset-instancer-solver.cc', 'hb-static.cc'], + 'test-tuple-varstore': ['test-tuple-varstore.cc', 'hb-subset-instancer-solver.cc', 'hb-subset-instancer-iup.cc', 'hb-static.cc'], + 'test-item-varstore': ['test-item-varstore.cc', 'hb-subset-instancer-solver.cc', 'hb-subset-instancer-iup.cc', 'hb-static.cc'], 'test-unicode-ranges': ['test-unicode-ranges.cc'], } foreach name, source : compiled_tests