| /* | |
| * CFF Width Optimizer | |
| * | |
| * Determines optimal defaultWidthX and nominalWidthX values | |
| * to minimize CharString byte cost. | |
| * | |
| * Based on fontTools.cffLib.width | |
| */ | |
| #ifndef HB_CFF_WIDTH_OPTIMIZER_HH | |
| #define HB_CFF_WIDTH_OPTIMIZER_HH | |
| #include "hb.hh" | |
| namespace CFF { | |
| /* Calculate byte cost for encoding a width delta */ | |
| static inline unsigned | |
| width_delta_cost (int delta) | |
| { | |
| delta = abs (delta); | |
| if (delta <= 107) return 1; | |
| if (delta <= 1131) return 2; | |
| return 5; | |
| } | |
| /* Cumulative sum forward */ | |
| static void | |
| cumsum_forward (const hb_hashmap_t<unsigned, unsigned> &freq, | |
| unsigned min_w, unsigned max_w, | |
| hb_vector_t<unsigned> &cumsum) | |
| { | |
| cumsum.resize (max_w - min_w + 1); | |
| unsigned v = 0; | |
| for (unsigned x = min_w; x <= max_w; x++) | |
| { | |
| v += freq.get (x); | |
| cumsum[x - min_w] = v; | |
| } | |
| } | |
| /* Cumulative max forward */ | |
| static void | |
| cummax_forward (const hb_hashmap_t<unsigned, unsigned> &freq, | |
| unsigned min_w, unsigned max_w, | |
| hb_vector_t<unsigned> &cummax) | |
| { | |
| cummax.resize (max_w - min_w + 1); | |
| unsigned v = 0; | |
| for (unsigned x = min_w; x <= max_w; x++) | |
| { | |
| v = hb_max (v, freq.get (x)); | |
| cummax[x - min_w] = v; | |
| } | |
| } | |
| /* Cumulative sum backward */ | |
| static void | |
| cumsum_backward (const hb_hashmap_t<unsigned, unsigned> &freq, | |
| unsigned min_w, unsigned max_w, | |
| hb_vector_t<unsigned> &cumsum) | |
| { | |
| cumsum.resize (max_w - min_w + 1); | |
| unsigned v = 0; | |
| for (int x = (int) max_w; x >= (int) min_w; x--) | |
| { | |
| v += freq.get ((unsigned) x); | |
| cumsum[x - min_w] = v; | |
| } | |
| } | |
| /* Cumulative max backward */ | |
| static void | |
| cummax_backward (const hb_hashmap_t<unsigned, unsigned> &freq, | |
| unsigned min_w, unsigned max_w, | |
| hb_vector_t<unsigned> &cummax) | |
| { | |
| cummax.resize (max_w - min_w + 1); | |
| unsigned v = 0; | |
| for (int x = (int) max_w; x >= (int) min_w; x--) | |
| { | |
| v = hb_max (v, freq.get ((unsigned) x)); | |
| cummax[x - min_w] = v; | |
| } | |
| } | |
| /* Helper to safely get cumulative value with bounds checking */ | |
| static inline unsigned | |
| safe_get (const hb_vector_t<unsigned> &vec, int x, unsigned min_w, unsigned max_w) | |
| { | |
| if (x < (int) min_w || x > (int) max_w) return 0; | |
| return vec[x - min_w]; | |
| } | |
| /* Optimize defaultWidthX and nominalWidthX from a list of widths | |
| * O(UPEM+numGlyphs) algorithm from fontTools.cffLib.width */ | |
| static void | |
| optimize_widths (const hb_vector_t<unsigned> &width_list, | |
| unsigned &default_width, | |
| unsigned &nominal_width) | |
| { | |
| if (width_list.length == 0) | |
| { | |
| default_width = nominal_width = 0; | |
| return; | |
| } | |
| /* Build frequency map */ | |
| hb_hashmap_t<unsigned, unsigned> widths; | |
| unsigned min_w = width_list[0]; | |
| unsigned max_w = width_list[0]; | |
| for (unsigned w : width_list) | |
| { | |
| widths.set (w, widths.get (w) + 1); | |
| min_w = hb_min (min_w, w); | |
| max_w = hb_max (max_w, w); | |
| } | |
| /* Cumulative sum/max forward/backward */ | |
| hb_vector_t<unsigned> cumFrqU, cumMaxU, cumFrqD, cumMaxD; | |
| cumsum_forward (widths, min_w, max_w, cumFrqU); | |
| cummax_forward (widths, min_w, max_w, cumMaxU); | |
| cumsum_backward (widths, min_w, max_w, cumFrqD); | |
| cummax_backward (widths, min_w, max_w, cumMaxD); | |
| /* Cost per nominal choice, without default consideration */ | |
| auto nomnCost = [&] (unsigned x) -> unsigned { | |
| return safe_get (cumFrqU, x, min_w, max_w) + | |
| safe_get (cumFrqU, x - 108, min_w, max_w) + | |
| safe_get (cumFrqU, x - 1132, min_w, max_w) * 3 + | |
| safe_get (cumFrqD, x, min_w, max_w) + | |
| safe_get (cumFrqD, x + 108, min_w, max_w) + | |
| safe_get (cumFrqD, x + 1132, min_w, max_w) * 3 - | |
| widths.get (x); | |
| }; | |
| /* Cost-saving per nominal choice, by best default choice */ | |
| auto dfltCost = [&] (unsigned x) -> unsigned { | |
| unsigned u = hb_max (hb_max (safe_get (cumMaxU, x, min_w, max_w), | |
| safe_get (cumMaxU, x - 108, min_w, max_w) * 2), | |
| safe_get (cumMaxU, x - 1132, min_w, max_w) * 5); | |
| unsigned d = hb_max (hb_max (safe_get (cumMaxD, x, min_w, max_w), | |
| safe_get (cumMaxD, x + 108, min_w, max_w) * 2), | |
| safe_get (cumMaxD, x + 1132, min_w, max_w) * 5); | |
| return hb_max (u, d); | |
| }; | |
| /* Find best nominal */ | |
| unsigned best_nominal = min_w; | |
| unsigned best_cost = nomnCost (min_w) - dfltCost (min_w); | |
| for (unsigned x = min_w + 1; x <= max_w; x++) | |
| { | |
| unsigned cost = nomnCost (x) - dfltCost (x); | |
| if (cost < best_cost) | |
| { | |
| best_cost = cost; | |
| best_nominal = x; | |
| } | |
| } | |
| /* Work back the best default */ | |
| unsigned best_default = best_nominal; | |
| unsigned best_default_cost = (unsigned) -1; | |
| /* Check candidates around best_nominal */ | |
| int candidates[] = { | |
| (int) best_nominal, | |
| (int) best_nominal - 108, | |
| (int) best_nominal - 1132, | |
| (int) best_nominal + 108, | |
| (int) best_nominal + 1132 | |
| }; | |
| for (int candidate : candidates) | |
| { | |
| if (candidate < (int) min_w || candidate > (int) max_w) | |
| continue; | |
| /* Compute actual cost with this default */ | |
| unsigned cost = 0; | |
| for (auto kv : widths.iter ()) | |
| { | |
| unsigned w = kv.first; | |
| unsigned freq = kv.second; | |
| if (w == (unsigned) candidate) | |
| continue; | |
| cost += freq * width_delta_cost ((int) w - (int) best_nominal); | |
| } | |
| if (cost < best_default_cost) | |
| { | |
| best_default_cost = cost; | |
| best_default = (unsigned) candidate; | |
| } | |
| } | |
| default_width = best_default; | |
| nominal_width = best_nominal; | |
| } | |
| } /* namespace CFF */ | |
| #endif /* HB_CFF_WIDTH_OPTIMIZER_HH */ |