/src/harfbuzz/src/hb-cff-width-optimizer.hh
Line | Count | Source |
1 | | /* |
2 | | * CFF Width Optimizer |
3 | | * |
4 | | * Determines optimal defaultWidthX and nominalWidthX values |
5 | | * to minimize CharString byte cost. |
6 | | * |
7 | | * Based on fontTools.cffLib.width |
8 | | */ |
9 | | |
10 | | #ifndef HB_CFF_WIDTH_OPTIMIZER_HH |
11 | | #define HB_CFF_WIDTH_OPTIMIZER_HH |
12 | | |
13 | | #include "hb.hh" |
14 | | |
15 | | namespace CFF { |
16 | | |
17 | | /* Calculate byte cost for encoding a width delta */ |
18 | | static inline unsigned |
19 | | width_delta_cost (int delta) |
20 | 0 | { |
21 | 0 | delta = abs (delta); |
22 | 0 | if (delta <= 107) return 1; |
23 | 0 | if (delta <= 1131) return 2; |
24 | 0 | return 5; |
25 | 0 | } |
26 | | |
27 | | /* Cumulative sum forward */ |
28 | | static void |
29 | | cumsum_forward (const hb_hashmap_t<unsigned, unsigned> &freq, |
30 | | unsigned min_w, unsigned max_w, |
31 | | hb_vector_t<unsigned> &cumsum) |
32 | 0 | { |
33 | 0 | cumsum.resize (max_w - min_w + 1); |
34 | 0 | unsigned v = 0; |
35 | 0 | for (unsigned x = min_w; x <= max_w; x++) |
36 | 0 | { |
37 | 0 | v += freq.get (x); |
38 | 0 | cumsum[x - min_w] = v; |
39 | 0 | } |
40 | 0 | } |
41 | | |
42 | | /* Cumulative max forward */ |
43 | | static void |
44 | | cummax_forward (const hb_hashmap_t<unsigned, unsigned> &freq, |
45 | | unsigned min_w, unsigned max_w, |
46 | | hb_vector_t<unsigned> &cummax) |
47 | 0 | { |
48 | 0 | cummax.resize (max_w - min_w + 1); |
49 | 0 | unsigned v = 0; |
50 | 0 | for (unsigned x = min_w; x <= max_w; x++) |
51 | 0 | { |
52 | 0 | v = hb_max (v, freq.get (x)); |
53 | 0 | cummax[x - min_w] = v; |
54 | 0 | } |
55 | 0 | } |
56 | | |
57 | | /* Cumulative sum backward */ |
58 | | static void |
59 | | cumsum_backward (const hb_hashmap_t<unsigned, unsigned> &freq, |
60 | | unsigned min_w, unsigned max_w, |
61 | | hb_vector_t<unsigned> &cumsum) |
62 | 0 | { |
63 | 0 | cumsum.resize (max_w - min_w + 1); |
64 | 0 | unsigned v = 0; |
65 | 0 | for (int x = (int) max_w; x >= (int) min_w; x--) |
66 | 0 | { |
67 | 0 | v += freq.get ((unsigned) x); |
68 | 0 | cumsum[x - min_w] = v; |
69 | 0 | } |
70 | 0 | } |
71 | | |
72 | | /* Cumulative max backward */ |
73 | | static void |
74 | | cummax_backward (const hb_hashmap_t<unsigned, unsigned> &freq, |
75 | | unsigned min_w, unsigned max_w, |
76 | | hb_vector_t<unsigned> &cummax) |
77 | 0 | { |
78 | 0 | cummax.resize (max_w - min_w + 1); |
79 | 0 | unsigned v = 0; |
80 | 0 | for (int x = (int) max_w; x >= (int) min_w; x--) |
81 | 0 | { |
82 | 0 | v = hb_max (v, freq.get ((unsigned) x)); |
83 | 0 | cummax[x - min_w] = v; |
84 | 0 | } |
85 | 0 | } |
86 | | |
87 | | /* Helper to safely get cumulative value with bounds checking */ |
88 | | static inline unsigned |
89 | | safe_get (const hb_vector_t<unsigned> &vec, int x, unsigned min_w, unsigned max_w) |
90 | 0 | { |
91 | 0 | if (x < (int) min_w || x > (int) max_w) return 0; |
92 | 0 | return vec[x - min_w]; |
93 | 0 | } |
94 | | |
95 | | /* Optimize defaultWidthX and nominalWidthX from a list of widths |
96 | | * O(UPEM+numGlyphs) algorithm from fontTools.cffLib.width */ |
97 | | static void |
98 | | optimize_widths (const hb_vector_t<unsigned> &width_list, |
99 | | unsigned &default_width, |
100 | | unsigned &nominal_width) |
101 | 0 | { |
102 | 0 | if (width_list.length == 0) |
103 | 0 | { |
104 | 0 | default_width = nominal_width = 0; |
105 | 0 | return; |
106 | 0 | } |
107 | | |
108 | | /* Build frequency map */ |
109 | 0 | hb_hashmap_t<unsigned, unsigned> widths; |
110 | 0 | unsigned min_w = width_list[0]; |
111 | 0 | unsigned max_w = width_list[0]; |
112 | |
|
113 | 0 | for (unsigned w : width_list) |
114 | 0 | { |
115 | 0 | widths.set (w, widths.get (w) + 1); |
116 | 0 | min_w = hb_min (min_w, w); |
117 | 0 | max_w = hb_max (max_w, w); |
118 | 0 | } |
119 | | |
120 | | /* Cumulative sum/max forward/backward */ |
121 | 0 | hb_vector_t<unsigned> cumFrqU, cumMaxU, cumFrqD, cumMaxD; |
122 | 0 | cumsum_forward (widths, min_w, max_w, cumFrqU); |
123 | 0 | cummax_forward (widths, min_w, max_w, cumMaxU); |
124 | 0 | cumsum_backward (widths, min_w, max_w, cumFrqD); |
125 | 0 | cummax_backward (widths, min_w, max_w, cumMaxD); |
126 | | |
127 | | /* Cost per nominal choice, without default consideration */ |
128 | 0 | auto nomnCost = [&] (unsigned x) -> unsigned { |
129 | 0 | return safe_get (cumFrqU, x, min_w, max_w) + |
130 | 0 | safe_get (cumFrqU, x - 108, min_w, max_w) + |
131 | 0 | safe_get (cumFrqU, x - 1132, min_w, max_w) * 3 + |
132 | 0 | safe_get (cumFrqD, x, min_w, max_w) + |
133 | 0 | safe_get (cumFrqD, x + 108, min_w, max_w) + |
134 | 0 | safe_get (cumFrqD, x + 1132, min_w, max_w) * 3 - |
135 | 0 | widths.get (x); |
136 | 0 | }; |
137 | | |
138 | | /* Cost-saving per nominal choice, by best default choice */ |
139 | 0 | auto dfltCost = [&] (unsigned x) -> unsigned { |
140 | 0 | unsigned u = hb_max (hb_max (safe_get (cumMaxU, x, min_w, max_w), |
141 | 0 | safe_get (cumMaxU, x - 108, min_w, max_w) * 2), |
142 | 0 | safe_get (cumMaxU, x - 1132, min_w, max_w) * 5); |
143 | 0 | unsigned d = hb_max (hb_max (safe_get (cumMaxD, x, min_w, max_w), |
144 | 0 | safe_get (cumMaxD, x + 108, min_w, max_w) * 2), |
145 | 0 | safe_get (cumMaxD, x + 1132, min_w, max_w) * 5); |
146 | 0 | return hb_max (u, d); |
147 | 0 | }; |
148 | | |
149 | | /* Find best nominal */ |
150 | 0 | unsigned best_nominal = min_w; |
151 | 0 | unsigned best_cost = nomnCost (min_w) - dfltCost (min_w); |
152 | |
|
153 | 0 | for (unsigned x = min_w + 1; x <= max_w; x++) |
154 | 0 | { |
155 | 0 | unsigned cost = nomnCost (x) - dfltCost (x); |
156 | 0 | if (cost < best_cost) |
157 | 0 | { |
158 | 0 | best_cost = cost; |
159 | 0 | best_nominal = x; |
160 | 0 | } |
161 | 0 | } |
162 | | |
163 | | /* Work back the best default */ |
164 | 0 | unsigned best_default = best_nominal; |
165 | 0 | unsigned best_default_cost = (unsigned) -1; |
166 | | |
167 | | /* Check candidates around best_nominal */ |
168 | 0 | int candidates[] = { |
169 | 0 | (int) best_nominal, |
170 | 0 | (int) best_nominal - 108, |
171 | 0 | (int) best_nominal - 1132, |
172 | 0 | (int) best_nominal + 108, |
173 | 0 | (int) best_nominal + 1132 |
174 | 0 | }; |
175 | |
|
176 | 0 | for (int candidate : candidates) |
177 | 0 | { |
178 | 0 | if (candidate < (int) min_w || candidate > (int) max_w) |
179 | 0 | continue; |
180 | | |
181 | | /* Compute actual cost with this default */ |
182 | 0 | unsigned cost = 0; |
183 | 0 | for (auto kv : widths.iter ()) |
184 | 0 | { |
185 | 0 | unsigned w = kv.first; |
186 | 0 | unsigned freq = kv.second; |
187 | |
|
188 | 0 | if (w == (unsigned) candidate) |
189 | 0 | continue; |
190 | | |
191 | 0 | cost += freq * width_delta_cost ((int) w - (int) best_nominal); |
192 | 0 | } |
193 | |
|
194 | 0 | if (cost < best_default_cost) |
195 | 0 | { |
196 | 0 | best_default_cost = cost; |
197 | 0 | best_default = (unsigned) candidate; |
198 | 0 | } |
199 | 0 | } |
200 | |
|
201 | 0 | default_width = best_default; |
202 | 0 | nominal_width = best_nominal; |
203 | 0 | } |
204 | | |
205 | | } /* namespace CFF */ |
206 | | |
207 | | #endif /* HB_CFF_WIDTH_OPTIMIZER_HH */ |