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