/work/workdir/UnpackedTarball/harfbuzz/src/hb-subset-plan-var.cc
Line | Count | Source |
1 | | /* |
2 | | * Copyright © 2023 Google, Inc. |
3 | | * |
4 | | * This is part of HarfBuzz, a text shaping library. |
5 | | * |
6 | | * Permission is hereby granted, without written agreement and without |
7 | | * license or royalty fees, to use, copy, modify, and distribute this |
8 | | * software and its documentation for any purpose, provided that the |
9 | | * above copyright notice and the following two paragraphs appear in |
10 | | * all copies of this software. |
11 | | * |
12 | | * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR |
13 | | * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES |
14 | | * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN |
15 | | * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
16 | | * DAMAGE. |
17 | | * |
18 | | * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, |
19 | | * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
20 | | * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS |
21 | | * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO |
22 | | * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
23 | | * |
24 | | * Google Author(s): Garret Rieger, Qunxin Liu, Roderick Sheeter |
25 | | */ |
26 | | |
27 | | #include "hb-ot-layout-common.hh" |
28 | | #include "hb-subset-plan.hh" |
29 | | |
30 | | #include "hb-ot-var-common.hh" |
31 | | #include "hb-ot-layout-base-table.hh" |
32 | | #include "hb-ot-glyf-table.hh" |
33 | | #include "hb-ot-var-fvar-table.hh" |
34 | | #include "hb-ot-var-avar-table.hh" |
35 | | #include "hb-ot-cff2-table.hh" |
36 | | |
37 | | #ifndef HB_NO_VAR |
38 | | |
39 | | void |
40 | | generate_varstore_inner_maps (const hb_set_t& varidx_set, |
41 | | unsigned subtable_count, |
42 | | hb_vector_t<hb_inc_bimap_t> &inner_maps /* OUT */) |
43 | 0 | { |
44 | 0 | if (varidx_set.is_empty () || subtable_count == 0) return; |
45 | | |
46 | 0 | if (unlikely (!inner_maps.resize (subtable_count))) return; |
47 | 0 | for (unsigned idx : varidx_set) |
48 | 0 | { |
49 | 0 | uint16_t major = idx >> 16; |
50 | 0 | uint16_t minor = idx & 0xFFFF; |
51 | |
|
52 | 0 | if (major >= subtable_count) |
53 | 0 | continue; |
54 | 0 | inner_maps[major].add (minor); |
55 | 0 | } |
56 | 0 | } |
57 | | |
58 | | /* Evaluate a variation-region tent at v, mirroring VarRegionAxis::evaluate |
59 | | * (invalid regions evaluate as constant 1). */ |
60 | | static double |
61 | | _tent_eval (const Triple &tent, double v) |
62 | 0 | { |
63 | 0 | double start = tent.minimum, peak = tent.middle, end = tent.maximum; |
64 | 0 | if (unlikely (start > peak || peak > end)) return 1.0; |
65 | 0 | if (unlikely (start < 0.0 && end > 0.0 && peak != 0.0)) return 1.0; |
66 | 0 | if (peak == 0.0 || v == peak) return 1.0; |
67 | 0 | if (v <= start || end <= v) return 0.0; |
68 | 0 | if (v < peak) return (v - start) / (peak - start); |
69 | 0 | return (end - v) / (end - peak); |
70 | 0 | } |
71 | | |
72 | | /* Range of a tent's scalar over an input interval [lo, hi]. The tent is |
73 | | * unimodal, so the extremes are at the interval endpoints, plus the peak |
74 | | * when it falls inside. */ |
75 | | static void |
76 | | _tent_value_range (const Triple &tent, double lo, double hi, |
77 | | double *tmin, double *tmax) |
78 | 0 | { |
79 | 0 | double a = _tent_eval (tent, lo); |
80 | 0 | double b = _tent_eval (tent, hi); |
81 | 0 | *tmin = hb_min (a, b); |
82 | 0 | *tmax = hb_max (a, b); |
83 | 0 | if (lo <= tent.middle && tent.middle <= hi) |
84 | 0 | *tmax = hb_max (*tmax, 1.0); |
85 | 0 | } |
86 | | |
87 | | /* Compute conservative reachable old-space final-coord ranges per axis for |
88 | | * avar2 partial instancing. With offset compensation, the instanced font's |
89 | | * final coordinates equal the original font's over the retained user box, |
90 | | * so the reachable range of axis i's final coordinate is the range of |
91 | | * final_i = intermediate_i + delta_i(intermediates) |
92 | | * over the box of retained old-intermediate ranges: restricted axes span |
93 | | * their retained [a, b], free public axes [-1, +1], pinned axes sit at |
94 | | * their d_i, and hidden (private) axes at 0. Interval arithmetic over the |
95 | | * avar2 VarStore regions bounds delta_i. Mirrors fontTools' |
96 | | * _computeReachableRangesForAvar2, but computed from the original store at |
97 | | * plan time (the same quantity fontTools bounds via getExtremes on the |
98 | | * instanced store). Only constraining ranges (narrower than [-1, +1]) are |
99 | | * recorded. */ |
100 | | static bool |
101 | | _compute_avar2_reachable_ranges (hb_subset_plan_t *plan, |
102 | | hb_array_t<const OT::AxisRecord> axes, |
103 | | const OT::avar *avar_table, |
104 | | bool detect_self_contained) |
105 | 0 | { |
106 | 0 | const OT::ItemVariationStore *var_store; |
107 | 0 | const OT::DeltaSetIndexMap *varidx_map; |
108 | 0 | if (!avar_table->get_v2_store_and_map (&var_store, &varidx_map)) |
109 | 0 | return false; |
110 | | |
111 | | /* Old-intermediate input box per axis. */ |
112 | 0 | hb_hashmap_t<hb_tag_t, hb_pair_t<double, double>> box; |
113 | 0 | for (const auto &axis : axes) |
114 | 0 | { |
115 | 0 | hb_tag_t tag = axis.get_axis_tag (); |
116 | 0 | double lo = -1.0, hi = +1.0; |
117 | 0 | Triple *old_int; |
118 | 0 | if (plan->user_axes_location.has (tag)) |
119 | 0 | { |
120 | 0 | if (!plan->old_intermediates.has (tag, &old_int)) return false; |
121 | 0 | lo = old_int->minimum; |
122 | 0 | hi = old_int->maximum; |
123 | 0 | } |
124 | 0 | else if (axis.is_hidden ()) |
125 | 0 | lo = hi = 0.0; /* private axis: intermediate is always 0 */ |
126 | 0 | if (!box.set (tag, hb_pair (lo, hi))) return false; |
127 | 0 | } |
128 | | |
129 | 0 | hb_vector_t<hb_hashmap_t<hb_tag_t, Triple>> regions; |
130 | 0 | if (!var_store->get_region_list ().get_var_regions (plan->axes_old_index_tag_map, regions)) |
131 | 0 | return false; |
132 | | |
133 | 0 | hb_hashmap_t<hb_tag_t, unsigned> grid_pos; |
134 | 0 | hb_vector_t<hb_vector_t<double>> grid_points; |
135 | 0 | for (unsigned i = 0; i < axes.length; i++) |
136 | 0 | { |
137 | 0 | grid_pos.reset (); |
138 | 0 | grid_points.reset (); |
139 | |
|
140 | 0 | hb_tag_t tag = axes[i].get_axis_tag (); |
141 | 0 | hb_pair_t<double, double> *identity; |
142 | 0 | if (!box.has (tag, &identity)) return false; |
143 | | |
144 | | /* Collect the row's active regions (non-zero delta). A varidx (or the |
145 | | * implicit identity mapping) that doesn't resolve to a real store row |
146 | | * behaves as "no variation" at runtime, so it contributes no regions; |
147 | | * this also covers a NULL VarStore. */ |
148 | 0 | hb_vector_t<hb_pair_t<int, unsigned>> active; /* (delta, region index) */ |
149 | 0 | uint32_t varidx = varidx_map->map (i); |
150 | | /* Runtime treats out-of-range delta-set indices as zero. */ |
151 | 0 | if (varidx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX && |
152 | 0 | !var_store->has_delta_set (varidx)) |
153 | 0 | varidx = HB_OT_LAYOUT_NO_VARIATIONS_INDEX; |
154 | 0 | if (varidx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX) |
155 | 0 | { |
156 | 0 | unsigned outer = varidx >> 16; |
157 | 0 | unsigned inner = varidx & 0xFFFF; |
158 | 0 | const OT::VarData &vd = var_store->get_sub_table (outer); |
159 | 0 | const OT::HBUINT8 *delta_bytes = vd.get_delta_bytes (); |
160 | 0 | unsigned row_size = vd.get_row_size (); |
161 | 0 | unsigned region_count = vd.get_region_index_count (); |
162 | 0 | for (unsigned r = 0; r < region_count; r++) |
163 | 0 | { |
164 | 0 | int delta = vd.get_item_delta_fast (inner, r, delta_bytes, row_size); |
165 | 0 | if (!delta) continue; |
166 | 0 | unsigned region_idx = vd.get_region_index (r); |
167 | 0 | if (region_idx >= regions.length) return false; |
168 | 0 | active.push (hb_pair (delta, region_idx)); |
169 | 0 | } |
170 | 0 | if (active.in_error ()) return false; |
171 | 0 | } |
172 | | |
173 | | /* The delta is piecewise multilinear in the axis coordinates (each |
174 | | * region scalar is a product of independent per-axis tents), so over |
175 | | * the box its exact extremes are attained at vertices of the grid of |
176 | | * per-axis tent breakpoints. Enumerate that grid when it is small |
177 | | * (real fonts); otherwise fall back to per-region interval |
178 | | * arithmetic, which ignores correlations between regions and is |
179 | | * looser but still conservative. Mirrors fontTools' |
180 | | * VarStore.getExtremes. */ |
181 | 0 | constexpr unsigned MAX_GRID = 1u << 14; |
182 | 0 | bool exact = true; |
183 | 0 | double dmin = 0.0, dmax = 0.0; /* delta extremes, F2Dot14 units */ |
184 | 0 | double vmin = 0.0, vmax = 0.0; /* identity + delta extremes, coord units */ |
185 | | |
186 | | /* Grid axes: the target axis (identity term), plus every axis a valid |
187 | | * tent of an active region references. */ |
188 | 0 | auto add_grid_axis = [&] (hb_tag_t t) -> int |
189 | 0 | { |
190 | 0 | unsigned *pos; |
191 | 0 | if (grid_pos.has (t, &pos)) return (int) *pos; |
192 | 0 | unsigned new_pos = grid_pos.get_population (); |
193 | 0 | hb_pair_t<double, double> *input; |
194 | 0 | double blo = -1.0, bhi = +1.0; |
195 | 0 | if (box.has (t, &input)) { blo = input->first; bhi = input->second; } |
196 | 0 | grid_points.push (hb_vector_t<double> ()); |
197 | 0 | if (grid_points.in_error ()) return -1; |
198 | 0 | auto &points = grid_points[new_pos]; |
199 | 0 | points.push (blo); |
200 | 0 | points.push (bhi); |
201 | 0 | if (points.in_error () || !grid_pos.set (t, new_pos)) return -1; |
202 | 0 | return (int) new_pos; |
203 | 0 | }; |
204 | |
|
205 | 0 | if (add_grid_axis (tag) < 0) return false; |
206 | | /* Per active region: (grid axis position, tent) for its valid tents. */ |
207 | 0 | hb_vector_t<hb_vector_t<hb_pair_t<unsigned, Triple>>> region_tents; |
208 | 0 | for (const auto &ar : active) |
209 | 0 | { |
210 | 0 | region_tents.push (hb_vector_t<hb_pair_t<unsigned, Triple>> ()); |
211 | 0 | if (region_tents.in_error ()) return false; |
212 | 0 | auto &tents = region_tents[region_tents.length - 1]; |
213 | 0 | for (const auto &_ : regions[ar.second]) |
214 | 0 | { |
215 | 0 | const Triple &tent = _.second; |
216 | | /* Tents the runtime ignores (constant scalar 1) add no breakpoints. */ |
217 | 0 | if (tent.middle == 0.0 || |
218 | 0 | tent.minimum > tent.middle || tent.middle > tent.maximum || |
219 | 0 | (tent.minimum < 0.0 && tent.maximum > 0.0)) |
220 | 0 | continue; |
221 | 0 | int pos = add_grid_axis (_.first); |
222 | 0 | if (pos < 0) return false; |
223 | 0 | auto &points = grid_points[pos]; |
224 | 0 | double blo = points[0], bhi = points[1]; |
225 | 0 | points.push (hb_clamp (tent.minimum, blo, bhi)); |
226 | 0 | points.push (hb_clamp (tent.middle, blo, bhi)); |
227 | 0 | points.push (hb_clamp (tent.maximum, blo, bhi)); |
228 | 0 | tents.push (hb_pair ((unsigned) pos, tent)); |
229 | 0 | if (points.in_error () || tents.in_error ()) return false; |
230 | 0 | } |
231 | 0 | } |
232 | | |
233 | 0 | unsigned grid_size = 1; |
234 | 0 | for (auto &points : grid_points) |
235 | 0 | { |
236 | 0 | points.qsort ([] (const double &a, const double &b) -> int |
237 | 0 | { return a < b ? -1 : a > b ? +1 : 0; }); |
238 | 0 | unsigned n = 0; |
239 | 0 | for (unsigned j = 0; j < points.length; j++) |
240 | 0 | if (!n || points.arrayZ[j] != points.arrayZ[n - 1]) |
241 | 0 | points.arrayZ[n++] = points.arrayZ[j]; |
242 | 0 | points.shrink (n); |
243 | 0 | if (grid_size > MAX_GRID / points.length) { exact = false; break; } |
244 | 0 | grid_size *= points.length; |
245 | 0 | } |
246 | |
|
247 | 0 | if (exact) |
248 | 0 | { |
249 | 0 | unsigned target_pos = 0; /* the target axis was added first */ |
250 | 0 | hb_vector_t<unsigned> odometer; |
251 | 0 | if (!odometer.resize (grid_pos.get_population ())) return false; |
252 | 0 | bool first = true; |
253 | 0 | while (true) |
254 | 0 | { |
255 | 0 | double delta_sum = 0.0; |
256 | 0 | for (unsigned r = 0; r < active.length; r++) |
257 | 0 | { |
258 | 0 | double scalar = 1.0; |
259 | 0 | for (const auto &pt : region_tents[r]) |
260 | 0 | { |
261 | 0 | double v = grid_points[pt.first][odometer[pt.first]]; |
262 | 0 | scalar *= _tent_eval (pt.second, v); |
263 | 0 | if (scalar == 0.0) break; |
264 | 0 | } |
265 | 0 | delta_sum += scalar * active[r].first; |
266 | 0 | } |
267 | 0 | double coord = grid_points[target_pos][odometer[target_pos]]; |
268 | 0 | double value = coord + delta_sum / 16384.0; |
269 | 0 | if (first) |
270 | 0 | { |
271 | 0 | dmin = dmax = delta_sum; |
272 | 0 | vmin = vmax = value; |
273 | 0 | first = false; |
274 | 0 | } |
275 | 0 | else |
276 | 0 | { |
277 | 0 | dmin = hb_min (dmin, delta_sum); dmax = hb_max (dmax, delta_sum); |
278 | 0 | vmin = hb_min (vmin, value); vmax = hb_max (vmax, value); |
279 | 0 | } |
280 | 0 | unsigned k = 0; |
281 | 0 | for (; k < odometer.length; k++) |
282 | 0 | { |
283 | 0 | if (++odometer[k] < grid_points[k].length) break; |
284 | 0 | odometer[k] = 0; |
285 | 0 | } |
286 | 0 | if (k == odometer.length) break; |
287 | 0 | } |
288 | 0 | } |
289 | 0 | else |
290 | 0 | { |
291 | 0 | for (const auto &ar : active) |
292 | 0 | { |
293 | 0 | int delta = ar.first; |
294 | 0 | double smin = 1.0, smax = 1.0; |
295 | 0 | for (const auto &_ : regions[ar.second]) |
296 | 0 | { |
297 | 0 | hb_pair_t<double, double> *input; |
298 | 0 | double blo = -1.0, bhi = +1.0; |
299 | 0 | if (box.has (_.first, &input)) { blo = input->first; bhi = input->second; } |
300 | 0 | double tmin, tmax; |
301 | 0 | _tent_value_range (_.second, blo, bhi, &tmin, &tmax); |
302 | 0 | smin *= tmin; |
303 | 0 | smax *= tmax; |
304 | 0 | if (smax == 0.0) break; |
305 | 0 | } |
306 | 0 | if (delta > 0) { dmin += smin * delta; dmax += smax * delta; } |
307 | 0 | else { dmin += smax * delta; dmax += smin * delta; } |
308 | 0 | } |
309 | 0 | vmin = identity->first + dmin / 16384.0; |
310 | 0 | vmax = identity->second + dmax / 16384.0; |
311 | 0 | } |
312 | | /* A pinned axis whose delta is constant over the box is self-contained: |
313 | | * its final coordinate is a constant. It can be removed from fvar/avar |
314 | | * and its contribution baked into the variation tables like an ordinary |
315 | | * pin at that coordinate (in old final space). Round the delta alone |
316 | | * and add it to the exact quantized intermediate, matching the runtime |
317 | | * (map_coords_2_14) and fontTools. */ |
318 | 0 | Triple *user; |
319 | 0 | if (detect_self_contained && dmin == dmax && |
320 | 0 | plan->user_axes_location.has (tag, &user) && user->is_point ()) |
321 | 0 | { |
322 | 0 | int v_int = (int) roundf ((float) identity->first * 16384.f) + |
323 | 0 | (int) roundf ((float) dmin); |
324 | 0 | v_int = hb_clamp (v_int, -(1 << 14), +(1 << 14)); |
325 | 0 | if (!plan->avar2_self_contained.set (tag, v_int / 16384.0)) return false; |
326 | 0 | continue; /* axis is dropped; no region can reference it afterwards */ |
327 | 0 | } |
328 | | |
329 | | /* Pad by one F2Dot14 unit against rounding differences with the |
330 | | * runtime's fixed-point evaluation. */ |
331 | 0 | double lo = hb_clamp (vmin - 1.0 / 16384.0, -1.0, +1.0); |
332 | 0 | double hi = hb_clamp (vmax + 1.0 / 16384.0, -1.0, +1.0); |
333 | 0 | lo = floor (lo * 16384.0) / 16384.0; |
334 | 0 | hi = ceil (hi * 16384.0) / 16384.0; |
335 | 0 | if (lo <= -1.0 && hi >= +1.0) |
336 | 0 | continue; /* not constraining */ |
337 | 0 | if (!plan->avar2_reachable_ranges.set (tag, Triple (lo, hb_clamp (0.0, lo, hi), hi))) |
338 | 0 | return false; |
339 | 0 | } |
340 | 0 | return true; |
341 | 0 | } |
342 | | |
343 | | #ifndef HB_NO_OT_FONT_CFF |
344 | | static inline hb_font_t* |
345 | | _get_hb_font_with_variations (const hb_subset_plan_t *plan) |
346 | 0 | { |
347 | 0 | hb_font_t *font = hb_font_create (plan->source); |
348 | |
|
349 | 0 | if (plan->has_avar2) |
350 | 0 | { |
351 | | /* Under avar2, instancing applies only to the self-contained pins, |
352 | | * whose constant final coordinates the plan holds in normalized_coords. |
353 | | * Setting user-space variations would run the full avar2 mapping and |
354 | | * bake contributions that remain live in the instance's variations. */ |
355 | 0 | hb_font_set_var_coords_normalized (font, plan->normalized_coords.arrayZ, |
356 | 0 | plan->normalized_coords.length); |
357 | 0 | return font; |
358 | 0 | } |
359 | | |
360 | 0 | hb_vector_t<hb_variation_t> vars; |
361 | 0 | if (!vars.alloc (plan->user_axes_location.get_population ())) { |
362 | 0 | hb_font_destroy (font); |
363 | 0 | return nullptr; |
364 | 0 | } |
365 | | |
366 | 0 | for (auto _ : plan->user_axes_location) |
367 | 0 | { |
368 | 0 | hb_variation_t var; |
369 | 0 | var.tag = _.first; |
370 | 0 | var.value = _.second.middle; |
371 | 0 | vars.push (var); |
372 | 0 | } |
373 | |
|
374 | 0 | hb_font_set_variations (font, vars.arrayZ, plan->user_axes_location.get_population ()); |
375 | 0 | return font; |
376 | 0 | } |
377 | | #endif |
378 | | |
379 | | template<typename ItemVarStore> |
380 | | void |
381 | | remap_variation_indices (const ItemVarStore &var_store, |
382 | | const hb_set_t &variation_indices, |
383 | | const hb_vector_t<int>& normalized_coords, |
384 | | bool calculate_delta, /* not pinned at default */ |
385 | | bool no_variations, /* all axes pinned */ |
386 | | hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map /* OUT */) |
387 | 0 | { |
388 | 0 | if (&var_store == &Null (OT::ItemVariationStore)) return; |
389 | 0 | unsigned subtable_count = var_store.get_sub_table_count (); |
390 | 0 | auto *store_cache = var_store.create_cache (); |
391 | |
|
392 | 0 | unsigned new_major = 0, new_minor = 0; |
393 | 0 | unsigned last_major = (variation_indices.get_min ()) >> 16; |
394 | 0 | for (unsigned idx : variation_indices) |
395 | 0 | { |
396 | 0 | int delta = 0; |
397 | 0 | if (calculate_delta) |
398 | 0 | delta = roundf (var_store.get_delta (idx, normalized_coords.arrayZ, |
399 | 0 | normalized_coords.length, store_cache)); |
400 | |
|
401 | 0 | if (no_variations) |
402 | 0 | { |
403 | 0 | variation_idx_delta_map.set (idx, hb_pair_t<unsigned, int> (HB_OT_LAYOUT_NO_VARIATIONS_INDEX, delta)); |
404 | 0 | continue; |
405 | 0 | } |
406 | | |
407 | 0 | uint16_t major = idx >> 16; |
408 | 0 | if (major >= subtable_count) break; |
409 | 0 | if (major != last_major) |
410 | 0 | { |
411 | 0 | new_minor = 0; |
412 | 0 | ++new_major; |
413 | 0 | } |
414 | |
|
415 | 0 | unsigned new_idx = (new_major << 16) + new_minor; |
416 | 0 | variation_idx_delta_map.set (idx, hb_pair_t<unsigned, int> (new_idx, delta)); |
417 | 0 | ++new_minor; |
418 | 0 | last_major = major; |
419 | 0 | } |
420 | 0 | var_store.destroy_cache (store_cache); |
421 | 0 | } |
422 | | |
423 | | template |
424 | | void |
425 | | remap_variation_indices<OT::ItemVariationStore> (const OT::ItemVariationStore &var_store, |
426 | | const hb_set_t &variation_indices, |
427 | | const hb_vector_t<int>& normalized_coords, |
428 | | bool calculate_delta, /* not pinned at default */ |
429 | | bool no_variations, /* all axes pinned */ |
430 | | hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map /* OUT */); |
431 | | |
432 | | #ifndef HB_NO_BASE |
433 | | void |
434 | | collect_base_variation_indices (hb_subset_plan_t* plan) |
435 | 0 | { |
436 | 0 | hb_blob_ptr_t<OT::BASE> base = plan->source_table<OT::BASE> (); |
437 | 0 | if (!base->has_var_store ()) |
438 | 0 | { |
439 | 0 | base.destroy (); |
440 | 0 | return; |
441 | 0 | } |
442 | | |
443 | 0 | hb_set_t varidx_set; |
444 | 0 | base->collect_variation_indices (plan, varidx_set); |
445 | 0 | const OT::ItemVariationStore &var_store = base->get_var_store (); |
446 | 0 | unsigned subtable_count = var_store.get_sub_table_count (); |
447 | | |
448 | |
|
449 | 0 | remap_variation_indices (var_store, varidx_set, |
450 | 0 | plan->normalized_coords, |
451 | 0 | !plan->pinned_at_default, |
452 | 0 | plan->all_axes_pinned, |
453 | 0 | plan->base_variation_idx_map); |
454 | 0 | generate_varstore_inner_maps (varidx_set, subtable_count, plan->base_varstore_inner_maps); |
455 | |
|
456 | 0 | base.destroy (); |
457 | 0 | } |
458 | | |
459 | | #endif |
460 | | |
461 | | bool |
462 | | normalize_axes_location (hb_face_t *face, hb_subset_plan_t *plan) |
463 | 15.5k | { |
464 | 15.5k | if (plan->user_axes_location.is_empty ()) |
465 | 15.5k | return true; |
466 | | |
467 | 0 | hb_array_t<const OT::AxisRecord> axes = face->table.fvar->get_axes (); |
468 | 0 | if (!plan->check_success (plan->normalized_coords.resize (axes.length))) |
469 | 0 | return false; |
470 | | |
471 | 0 | bool has_avar = face->table.avar->has_data (); |
472 | 0 | hb_vector_t<float> normalized_mins; |
473 | 0 | hb_vector_t<float> normalized_defaults; |
474 | 0 | hb_vector_t<float> normalized_maxs; |
475 | 0 | if (has_avar) |
476 | 0 | { |
477 | 0 | if (!plan->check_success (normalized_mins.resize (axes.length)) || |
478 | 0 | !plan->check_success (normalized_defaults.resize (axes.length)) || |
479 | 0 | !plan->check_success (normalized_maxs.resize (axes.length))) |
480 | 0 | return false; |
481 | 0 | } |
482 | | |
483 | 0 | bool axis_not_pinned = false; |
484 | 0 | unsigned new_axis_idx = 0; |
485 | 0 | unsigned last_idx = 0; |
486 | 0 | for (const auto& _ : + hb_enumerate (axes)) |
487 | 0 | { |
488 | 0 | unsigned i = _.first; |
489 | 0 | const OT::AxisRecord &axis = _.second; |
490 | 0 | hb_tag_t axis_tag = axis.get_axis_tag (); |
491 | 0 | plan->axes_old_index_tag_map.set (i, axis_tag); |
492 | |
|
493 | 0 | if (!plan->user_axes_location.has (axis_tag) || |
494 | 0 | !plan->user_axes_location.get (axis_tag).is_point ()) |
495 | 0 | { |
496 | 0 | axis_not_pinned = true; |
497 | 0 | plan->axes_index_map.set (i, new_axis_idx); |
498 | 0 | plan->axis_tags.push (axis_tag); |
499 | 0 | new_axis_idx++; |
500 | 0 | } |
501 | |
|
502 | 0 | Triple *axis_range; |
503 | 0 | if (plan->user_axes_location.has (axis_tag, &axis_range)) |
504 | 0 | { |
505 | 0 | plan->axes_triple_distances.set (axis_tag, axis.get_triple_distances ()); |
506 | |
|
507 | 0 | float normalized_min = axis.normalize_axis_value (axis_range->minimum); |
508 | 0 | float normalized_default = axis.normalize_axis_value (axis_range->middle); |
509 | 0 | float normalized_max = axis.normalize_axis_value (axis_range->maximum); |
510 | | |
511 | | // TODO(behdad): Spec says axis normalization should be done in 16.16; |
512 | | // We used to do it in 2.14, but that's not correct. I fixed this in |
513 | | // the fvar/avar code, but keeping 2.14 here for now to keep tests |
514 | | // happy. We might need to adjust fonttools as well. |
515 | | // I'm only fairly confident in the above statement. Anyway, |
516 | | // we should look deeper into this, and also update fonttools if |
517 | | // needed. |
518 | | |
519 | | // Round to 2.14 |
520 | 0 | normalized_min = roundf (normalized_min * 16384.f) / 16384.f; |
521 | 0 | normalized_default = roundf (normalized_default * 16384.f) / 16384.f; |
522 | 0 | normalized_max = roundf (normalized_max * 16384.f) / 16384.f; |
523 | |
|
524 | 0 | if (has_avar) |
525 | 0 | { |
526 | 0 | normalized_mins[i] = normalized_min; |
527 | 0 | normalized_defaults[i] = normalized_default; |
528 | 0 | normalized_maxs[i] = normalized_max; |
529 | 0 | last_idx = i; |
530 | 0 | } |
531 | 0 | else |
532 | 0 | { |
533 | 0 | plan->axes_location.set (axis_tag, Triple ((double) normalized_min, |
534 | 0 | (double) normalized_default, |
535 | 0 | (double) normalized_max)); |
536 | 0 | if (normalized_default == -0.f) |
537 | 0 | normalized_default = 0.f; // Normalize -0 to 0 |
538 | 0 | if (normalized_default != 0.f) |
539 | 0 | plan->pinned_at_default = false; |
540 | |
|
541 | 0 | plan->normalized_coords[i] = roundf (normalized_default * 16384.f); |
542 | 0 | } |
543 | 0 | } |
544 | 0 | } |
545 | 0 | plan->all_axes_pinned = !axis_not_pinned; |
546 | | |
547 | | // TODO: use avar map_coords_16_16() when normalization is changed to 16.16 |
548 | | // in fonttools |
549 | 0 | if (has_avar) |
550 | 0 | { |
551 | 0 | const OT::avar* avar_table = face->table.avar; |
552 | 0 | unsigned coords_len = last_idx + 1; |
553 | |
|
554 | 0 | if (avar_table->has_v2_data () && !plan->all_axes_pinned) |
555 | 0 | { |
556 | | /* avar2 partial instancing: use v1-only mapping to get intermediate-space |
557 | | * coords. These are used for IVS rebasing and offset compensation. */ |
558 | 0 | plan->has_avar2 = true; |
559 | |
|
560 | 0 | if (!plan->check_success (avar_table->map_coords_2_14 (normalized_mins.arrayZ, coords_len, true)) || |
561 | 0 | !plan->check_success (avar_table->map_coords_2_14 (normalized_defaults.arrayZ, coords_len, true)) || |
562 | 0 | !plan->check_success (avar_table->map_coords_2_14 (normalized_maxs.arrayZ, coords_len, true))) |
563 | 0 | return false; |
564 | | |
565 | 0 | for (const auto& _ : + hb_enumerate (axes)) |
566 | 0 | { |
567 | 0 | unsigned i = _.first; |
568 | 0 | hb_tag_t axis_tag = _.second.get_axis_tag (); |
569 | 0 | if (plan->user_axes_location.has (axis_tag)) |
570 | 0 | { |
571 | | /* Store intermediate-space coords for offset compensation, |
572 | | * segment-map renormalization, and IVS rebasing. */ |
573 | 0 | plan->old_intermediates.set (axis_tag, Triple ((double) normalized_mins[i], |
574 | 0 | (double) normalized_defaults[i], |
575 | 0 | (double) normalized_maxs[i])); |
576 | 0 | } |
577 | 0 | } |
578 | | |
579 | | /* Reachable-range computation also detects self-contained pinned |
580 | | * axes: pinned axes whose final coordinate is constant. Those are |
581 | | * removed from fvar, so suppress the detection when the face has |
582 | | * tables that cannot follow: CFF2 has no partial-pin instancing path |
583 | | * (its 'pinned' path flattens ALL blends; lift this once |
584 | | * https://github.com/harfbuzz/harfbuzz/pull/4710 lands, porting |
585 | | * fontTools' instantiateCFF2 from |
586 | | * https://github.com/fonttools/fonttools/pull/3506), and VARC passes |
587 | | * through verbatim with explicit fvar axis indices that renumbering |
588 | | * would desynchronize. Such axes stay in fvar as ordinary hidden |
589 | | * pins. */ |
590 | 0 | bool detect_self_contained = true; |
591 | 0 | for (hb_tag_t table_tag : { HB_TAG ('C','F','F','2'), HB_TAG ('V','A','R','C') }) |
592 | 0 | { |
593 | 0 | hb_blob_t *blob = hb_face_reference_table (face, table_tag); |
594 | 0 | if (hb_blob_get_length (blob)) |
595 | 0 | detect_self_contained = false; |
596 | 0 | hb_blob_destroy (blob); |
597 | 0 | } |
598 | 0 | if (!_compute_avar2_reachable_ranges (plan, axes, avar_table, |
599 | 0 | detect_self_contained)) |
600 | 0 | return false; |
601 | | |
602 | | /* Keep all axes in fvar (pinned ones as hidden), EXCEPT self-contained |
603 | | * pinned axes, which are removed entirely. */ |
604 | 0 | plan->axes_index_map.reset (); |
605 | 0 | plan->axis_tags.reset (); |
606 | 0 | unsigned retained_axis_idx = 0; |
607 | 0 | for (const auto& _ : + hb_enumerate (axes)) |
608 | 0 | { |
609 | 0 | unsigned i = _.first; |
610 | 0 | hb_tag_t axis_tag = _.second.get_axis_tag (); |
611 | 0 | if (plan->avar2_self_contained.has (axis_tag)) |
612 | 0 | continue; |
613 | 0 | plan->axes_index_map.set (i, retained_axis_idx++); |
614 | 0 | plan->axis_tags.push (axis_tag); |
615 | 0 | } |
616 | | /* The other tables see ONLY the self-contained pins, as an ordinary |
617 | | * partial instancing in old final-coordinate space; the standard |
618 | | * instancing machinery bakes those axes' contributions in. The |
619 | | * remaining restriction is carried entirely by avar. */ |
620 | 0 | plan->axes_location.reset (); |
621 | 0 | if (plan->avar2_self_contained.get_population ()) |
622 | 0 | { |
623 | 0 | bool sc_pinned_at_default = true; |
624 | 0 | for (auto _ : plan->avar2_self_contained) |
625 | 0 | { |
626 | 0 | plan->axes_location.set (_.first, Triple (_.second, _.second, _.second)); |
627 | 0 | if (_.second != 0.0) |
628 | 0 | sc_pinned_at_default = false; |
629 | 0 | } |
630 | 0 | for (const auto& _ : + hb_enumerate (axes)) |
631 | 0 | { |
632 | 0 | double *v; |
633 | 0 | plan->normalized_coords[_.first] = |
634 | 0 | plan->avar2_self_contained.has (_.second.get_axis_tag (), &v) |
635 | 0 | ? (int) roundf ((float) (*v * 16384.0)) : 0; |
636 | 0 | } |
637 | 0 | plan->pinned_at_default = sc_pinned_at_default; |
638 | 0 | } |
639 | 0 | else |
640 | 0 | plan->normalized_coords.shrink (0); |
641 | 0 | } |
642 | 0 | else |
643 | 0 | { |
644 | | /* Standard avar v1 (or v1+v2 with all axes pinned) */ |
645 | 0 | if (!plan->check_success (avar_table->map_coords_2_14 (normalized_mins.arrayZ, coords_len)) || |
646 | 0 | !plan->check_success (avar_table->map_coords_2_14 (normalized_defaults.arrayZ, coords_len)) || |
647 | 0 | !plan->check_success (avar_table->map_coords_2_14 (normalized_maxs.arrayZ, coords_len))) |
648 | 0 | return false; |
649 | | |
650 | 0 | for (const auto& _ : + hb_enumerate (axes)) |
651 | 0 | { |
652 | 0 | unsigned i = _.first; |
653 | 0 | hb_tag_t axis_tag = _.second.get_axis_tag (); |
654 | 0 | if (plan->user_axes_location.has (axis_tag)) |
655 | 0 | { |
656 | 0 | plan->axes_location.set (axis_tag, Triple ((double) normalized_mins[i], |
657 | 0 | (double) normalized_defaults[i], |
658 | 0 | (double) normalized_maxs[i])); |
659 | 0 | float normalized_default = normalized_defaults[i]; |
660 | 0 | if (normalized_default == -0.f) |
661 | 0 | normalized_default = 0.f; |
662 | 0 | if (normalized_default != 0.f) |
663 | 0 | plan->pinned_at_default = false; |
664 | |
|
665 | 0 | plan->normalized_coords[i] = roundf (normalized_default * 16384.f); |
666 | 0 | } |
667 | 0 | } |
668 | 0 | } |
669 | 0 | } |
670 | 0 | return true; |
671 | 0 | } |
672 | | |
673 | | #ifndef HB_NO_OT_FONT_CFF |
674 | | void |
675 | | update_instance_metrics_map_from_cff2 (hb_subset_plan_t *plan) |
676 | 15.5k | { |
677 | 15.5k | if (!plan->normalized_coords) return; |
678 | 0 | OT::cff2::accelerator_t cff2 (plan->source); |
679 | 0 | if (!cff2.is_valid ()) return; |
680 | | |
681 | 0 | hb_font_t *font = _get_hb_font_with_variations (plan); |
682 | 0 | if (unlikely (!plan->check_success (font != nullptr))) |
683 | 0 | { |
684 | 0 | hb_font_destroy (font); |
685 | 0 | return; |
686 | 0 | } |
687 | | |
688 | 0 | hb_glyph_extents_t extents = {0x7FFF, -0x7FFF}; |
689 | 0 | OT::hmtx_accelerator_t _hmtx (plan->source); |
690 | 0 | OT::hb_scalar_cache_t *hvar_store_cache = nullptr; |
691 | 0 | if (_hmtx.has_data () && _hmtx.var_table.get_length ()) |
692 | 0 | hvar_store_cache = _hmtx.var_table->get_var_store ().create_cache (); |
693 | |
|
694 | 0 | OT::vmtx_accelerator_t _vmtx (plan->source); |
695 | 0 | OT::hb_scalar_cache_t *vvar_store_cache = nullptr; |
696 | 0 | if (_vmtx.has_data () && _vmtx.var_table.get_length ()) |
697 | 0 | vvar_store_cache = _vmtx.var_table->get_var_store ().create_cache (); |
698 | |
|
699 | 0 | for (auto p : *plan->glyph_map) |
700 | 0 | { |
701 | 0 | hb_codepoint_t old_gid = p.first; |
702 | 0 | hb_codepoint_t new_gid = p.second; |
703 | 0 | if (!cff2.get_extents (font, old_gid, &extents)) continue; |
704 | 0 | bool has_bounds_info = true; |
705 | 0 | if (extents.x_bearing == 0 && extents.width == 0 && |
706 | 0 | extents.height == 0 && extents.y_bearing == 0) |
707 | 0 | has_bounds_info = false; |
708 | |
|
709 | 0 | if (has_bounds_info) |
710 | 0 | { |
711 | 0 | plan->head_maxp_info.xMin = hb_min (plan->head_maxp_info.xMin, extents.x_bearing); |
712 | 0 | plan->head_maxp_info.xMax = hb_max (plan->head_maxp_info.xMax, |
713 | 0 | hb_saturate_add (extents.x_bearing, extents.width)); |
714 | 0 | plan->head_maxp_info.yMax = hb_max (plan->head_maxp_info.yMax, extents.y_bearing); |
715 | 0 | plan->head_maxp_info.yMin = hb_min (plan->head_maxp_info.yMin, |
716 | 0 | hb_saturate_add (extents.y_bearing, extents.height)); |
717 | 0 | } |
718 | |
|
719 | 0 | if (_hmtx.has_data ()) |
720 | 0 | { |
721 | 0 | double hori_aw = _hmtx.get_advance_without_var_unscaled (old_gid); |
722 | 0 | if (_hmtx.var_table.get_length ()) |
723 | 0 | hori_aw += (double) roundf (_hmtx.var_table->get_advance_delta_unscaled (old_gid, font->coords, font->num_coords, |
724 | 0 | hvar_store_cache)); |
725 | | /* Malicious deltas can take the advance out of the UFWORD range that |
726 | | * hmtx serialization stores. Clamp, to keep downstream consumers |
727 | | * (e.g. CFF width optimizer) bounded and consistent with hmtx. */ |
728 | 0 | hori_aw = hb_clamp (hori_aw, 0., 65535.); |
729 | 0 | int lsb = extents.x_bearing; |
730 | 0 | if (!has_bounds_info) |
731 | 0 | { |
732 | 0 | _hmtx.get_leading_bearing_without_var_unscaled (old_gid, &lsb); |
733 | 0 | } |
734 | 0 | plan->hmtx_map.set (new_gid, hb_pair ((unsigned) hori_aw, lsb)); |
735 | 0 | plan->bounds_width_vec[new_gid] = extents.width; |
736 | 0 | } |
737 | |
|
738 | 0 | if (_vmtx.has_data ()) |
739 | 0 | { |
740 | 0 | double vert_aw = _vmtx.get_advance_without_var_unscaled (old_gid); |
741 | 0 | if (_vmtx.var_table.get_length ()) |
742 | 0 | vert_aw += (double) roundf (_vmtx.var_table->get_advance_delta_unscaled (old_gid, font->coords, font->num_coords, |
743 | 0 | vvar_store_cache)); |
744 | 0 | vert_aw = hb_clamp (vert_aw, 0., 65535.); |
745 | 0 | hb_position_t vorg_x = 0; |
746 | 0 | hb_position_t vorg_y = 0; |
747 | 0 | int tsb = 0; |
748 | 0 | if (has_bounds_info && |
749 | 0 | hb_font_get_glyph_v_origin (font, old_gid, &vorg_x, &vorg_y)) |
750 | 0 | { |
751 | 0 | tsb = hb_saturate_sub (vorg_y, extents.y_bearing); |
752 | 0 | } else { |
753 | 0 | _vmtx.get_leading_bearing_without_var_unscaled (old_gid, &tsb); |
754 | 0 | } |
755 | |
|
756 | 0 | plan->vmtx_map.set (new_gid, hb_pair ((unsigned) vert_aw, tsb)); |
757 | 0 | plan->bounds_height_vec[new_gid] = extents.height; |
758 | 0 | } |
759 | 0 | } |
760 | 0 | hb_font_destroy (font); |
761 | 0 | if (hvar_store_cache) |
762 | 0 | _hmtx.var_table->get_var_store ().destroy_cache (hvar_store_cache); |
763 | 0 | if (vvar_store_cache) |
764 | 0 | _vmtx.var_table->get_var_store ().destroy_cache (vvar_store_cache); |
765 | 0 | } |
766 | | #endif |
767 | | |
768 | | bool |
769 | | get_instance_glyphs_contour_points (hb_subset_plan_t *plan) |
770 | 15.5k | { |
771 | | /* contour_points vector only needed for updating gvar table (infer delta and |
772 | | * iup delta optimization) during partial instancing */ |
773 | 15.5k | if (plan->user_axes_location.is_empty () || plan->all_axes_pinned) |
774 | 15.5k | return true; |
775 | | |
776 | 0 | OT::glyf_accelerator_t glyf (plan->source); |
777 | |
|
778 | 0 | for (auto &_ : plan->new_to_old_gid_list) |
779 | 0 | { |
780 | 0 | hb_codepoint_t new_gid = _.first; |
781 | 0 | contour_point_vector_t all_points; |
782 | 0 | if (new_gid == 0 && !(plan->flags & HB_SUBSET_FLAGS_NOTDEF_OUTLINE)) |
783 | 0 | { |
784 | 0 | if (unlikely (!plan->new_gid_contour_points_map.set (new_gid, all_points))) |
785 | 0 | return false; |
786 | 0 | continue; |
787 | 0 | } |
788 | | |
789 | 0 | hb_codepoint_t old_gid = _.second; |
790 | 0 | auto glyph = glyf.glyph_for_gid (old_gid); |
791 | 0 | if (unlikely (!glyph.get_all_points_without_var (plan->source, all_points))) |
792 | 0 | return false; |
793 | 0 | if (unlikely (!plan->new_gid_contour_points_map.set (new_gid, all_points))) |
794 | 0 | return false; |
795 | | |
796 | | /* composite new gids are only needed by iup delta optimization */ |
797 | 0 | if ((plan->flags & HB_SUBSET_FLAGS_OPTIMIZE_IUP_DELTAS) && glyph.is_composite ()) |
798 | 0 | plan->composite_new_gids.add (new_gid); |
799 | 0 | } |
800 | 0 | return true; |
801 | 0 | } |
802 | | |
803 | | template<typename DeltaSetIndexMap> |
804 | | void |
805 | | remap_colrv1_delta_set_index_indices (const DeltaSetIndexMap &index_map, |
806 | | const hb_set_t &delta_set_idxes, |
807 | | hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map, /* IN/OUT */ |
808 | | hb_map_t &new_deltaset_idx_varidx_map /* OUT */) |
809 | 0 | { |
810 | 0 | if (!index_map.get_map_count ()) |
811 | 0 | return; |
812 | | |
813 | 0 | hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> delta_set_idx_delta_map; |
814 | 0 | unsigned new_delta_set_idx = 0; |
815 | 0 | for (unsigned delta_set_idx : delta_set_idxes) |
816 | 0 | { |
817 | 0 | unsigned var_idx = index_map.map (delta_set_idx); |
818 | 0 | unsigned new_varidx = HB_OT_LAYOUT_NO_VARIATIONS_INDEX; |
819 | 0 | int delta = 0; |
820 | |
|
821 | 0 | if (var_idx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX) |
822 | 0 | { |
823 | 0 | hb_pair_t<unsigned, int> *new_varidx_delta; |
824 | 0 | if (!variation_idx_delta_map.has (var_idx, &new_varidx_delta)) continue; |
825 | | |
826 | 0 | new_varidx = hb_first (*new_varidx_delta); |
827 | 0 | delta = hb_second (*new_varidx_delta); |
828 | 0 | } |
829 | | |
830 | 0 | new_deltaset_idx_varidx_map.set (new_delta_set_idx, new_varidx); |
831 | 0 | delta_set_idx_delta_map.set (delta_set_idx, hb_pair_t<unsigned, int> (new_delta_set_idx, delta)); |
832 | 0 | new_delta_set_idx++; |
833 | 0 | } |
834 | 0 | variation_idx_delta_map = std::move (delta_set_idx_delta_map); |
835 | 0 | } |
836 | | |
837 | | template void |
838 | | remap_colrv1_delta_set_index_indices<OT::DeltaSetIndexMap> (const OT::DeltaSetIndexMap &index_map, |
839 | | const hb_set_t &delta_set_idxes, |
840 | | hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map, /* IN/OUT */ |
841 | | hb_map_t &new_deltaset_idx_varidx_map /* OUT */); |
842 | | |
843 | | #endif |