/work/workdir/UnpackedTarball/harfbuzz/src/hb-ot-var-avar-table.hh
Line | Count | Source |
1 | | /* |
2 | | * Copyright © 2017 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): Behdad Esfahbod |
25 | | */ |
26 | | |
27 | | #ifndef HB_OT_VAR_AVAR_TABLE_HH |
28 | | #define HB_OT_VAR_AVAR_TABLE_HH |
29 | | |
30 | | #include "hb-open-type.hh" |
31 | | #include "hb-ot-var-common.hh" |
32 | | #include "hb-ot-var-fvar-table.hh" |
33 | | |
34 | | |
35 | | /* |
36 | | * avar -- Axis Variations |
37 | | * https://docs.microsoft.com/en-us/typography/opentype/spec/avar |
38 | | */ |
39 | | |
40 | | #define HB_OT_TAG_avar HB_TAG('a','v','a','r') |
41 | | |
42 | | |
43 | | namespace OT { |
44 | | |
45 | | |
46 | | /* "Spec": https://github.com/be-fonts/boring-expansion-spec/issues/14 */ |
47 | | struct avarV2Tail |
48 | | { |
49 | | friend struct avar; |
50 | | |
51 | | bool sanitize (hb_sanitize_context_t *c, |
52 | | const void *base) const |
53 | 0 | { |
54 | 0 | TRACE_SANITIZE (this); |
55 | 0 | return_trace (varIdxMap.sanitize (c, base) && |
56 | 0 | varStore.sanitize (c, base)); |
57 | 0 | } |
58 | | |
59 | | protected: |
60 | | Offset32To<DeltaSetIndexMap> varIdxMap; /* Offset from the beginning of 'avar' table. */ |
61 | | Offset32To<ItemVariationStore> varStore; /* Offset from the beginning of 'avar' table. */ |
62 | | |
63 | | public: |
64 | | DEFINE_SIZE_STATIC (8); |
65 | | }; |
66 | | |
67 | | |
68 | | struct AxisValueMap |
69 | | { |
70 | | bool sanitize (hb_sanitize_context_t *c) const |
71 | 0 | { |
72 | 0 | TRACE_SANITIZE (this); |
73 | 0 | return_trace (c->check_struct (this)); |
74 | 0 | } |
75 | | |
76 | | void set_mapping (float from_coord, float to_coord) |
77 | 0 | { |
78 | 0 | coords[0].set_float (from_coord); |
79 | 0 | coords[1].set_float (to_coord); |
80 | 0 | } |
81 | | |
82 | | bool is_outside_axis_range (const Triple& axis_range) const |
83 | 0 | { |
84 | 0 | double from_coord = (double) coords[0].to_float (); |
85 | 0 | return !axis_range.contains (from_coord); |
86 | 0 | } |
87 | | |
88 | | bool must_include () const |
89 | 0 | { |
90 | 0 | float from_coord = coords[0].to_float (); |
91 | 0 | float to_coord = coords[1].to_float (); |
92 | 0 | return (from_coord == -1.f && to_coord == -1.f) || |
93 | 0 | (from_coord == 0.f && to_coord == 0.f) || |
94 | 0 | (from_coord == 1.f && to_coord == 1.f); |
95 | 0 | } |
96 | | |
97 | | void instantiate (const Triple& axis_range, |
98 | | const Triple& unmapped_range, |
99 | | const TripleDistances& triple_distances) |
100 | 0 | { |
101 | 0 | float from_coord = coords[0].to_float (); |
102 | 0 | float to_coord = coords[1].to_float (); |
103 | |
|
104 | 0 | from_coord = renormalizeValue ((double) from_coord, unmapped_range, triple_distances); |
105 | 0 | to_coord = renormalizeValue ((double) to_coord, axis_range, triple_distances); |
106 | | |
107 | | /* Malformed input can renormalize outside [-1,1], which would |
108 | | * overflow F2DOT14. */ |
109 | 0 | from_coord = hb_clamp (from_coord, -1.f, 1.f); |
110 | 0 | to_coord = hb_clamp (to_coord, -1.f, 1.f); |
111 | |
|
112 | 0 | coords[0].set_float (from_coord); |
113 | 0 | coords[1].set_float (to_coord); |
114 | 0 | } |
115 | | |
116 | | HB_INTERNAL static int cmp (const void *pa, const void *pb) |
117 | 0 | { |
118 | 0 | const AxisValueMap *a = (const AxisValueMap *) pa; |
119 | 0 | const AxisValueMap *b = (const AxisValueMap *) pb; |
120 | |
|
121 | 0 | int a_from = a->coords[0].to_int (); |
122 | 0 | int b_from = b->coords[0].to_int (); |
123 | 0 | if (a_from != b_from) |
124 | 0 | return a_from - b_from; |
125 | | |
126 | | /* this should never be reached. according to the spec, all of the axis |
127 | | * value map records for a given axis must have different fromCoord values |
128 | | * */ |
129 | 0 | int a_to = a->coords[1].to_int (); |
130 | 0 | int b_to = b->coords[1].to_int (); |
131 | 0 | return a_to - b_to; |
132 | 0 | } |
133 | | |
134 | | bool serialize (hb_serialize_context_t *c) const |
135 | 0 | { |
136 | 0 | TRACE_SERIALIZE (this); |
137 | 0 | return_trace (c->embed (this)); |
138 | 0 | } |
139 | | |
140 | | public: |
141 | | F2DOT14 coords[2]; |
142 | | // F2DOT14 fromCoord; /* A normalized coordinate value obtained using |
143 | | // * default normalization. */ |
144 | | // F2DOT14 toCoord; /* The modified, normalized coordinate value. */ |
145 | | |
146 | | public: |
147 | | DEFINE_SIZE_STATIC (4); |
148 | | }; |
149 | | |
150 | | struct SegmentMaps : Array16Of<AxisValueMap> |
151 | | { |
152 | | float map_float (float value, unsigned int from_offset = 0, unsigned int to_offset = 1) const |
153 | 0 | { |
154 | 0 | #define fromCoord coords[from_offset].to_float () |
155 | 0 | #define toCoord coords[to_offset].to_float () |
156 | |
|
157 | 0 | const auto *map = arrayZ; |
158 | | |
159 | | /* The following special-cases are not part of OpenType, which requires |
160 | | * that at least -1, 0, and +1 must be mapped. But we include these as |
161 | | * part of a better error recovery scheme. */ |
162 | 0 | if (len < 2) |
163 | 0 | { |
164 | 0 | if (!len) |
165 | 0 | return value; |
166 | 0 | else /* len == 1*/ |
167 | 0 | return value - map[0].fromCoord + map[0].toCoord; |
168 | 0 | } |
169 | | |
170 | | // At least two mappings now. |
171 | | |
172 | | /* CoreText is wild... |
173 | | * PingFangUI avar needs all this special-casing... |
174 | | * So we implement an extended version of the spec here, |
175 | | * which is more robust and more likely to be compatible with |
176 | | * the wild. */ |
177 | | |
178 | 0 | unsigned start = 0; |
179 | 0 | unsigned end = len; |
180 | 0 | if (map[start].fromCoord == -1 && map[start].toCoord == -1 && map[start+1].fromCoord == -1) |
181 | 0 | start++; |
182 | 0 | if (map[end-1].fromCoord == +1 && map[end-1].toCoord == +1 && map[end-2].fromCoord == +1) |
183 | 0 | end--; |
184 | | |
185 | | /* Look for exact match first, and do lots of special-casing. */ |
186 | 0 | unsigned i; |
187 | 0 | for (i = start; i < end; i++) |
188 | 0 | if (value == map[i].fromCoord) |
189 | 0 | break; |
190 | 0 | if (i < end) |
191 | 0 | { |
192 | | // There's at least one exact match. See if there are more. |
193 | 0 | unsigned j = i; |
194 | 0 | for (; j + 1 < end; j++) |
195 | 0 | if (value != map[j + 1].fromCoord) |
196 | 0 | break; |
197 | | |
198 | | // [i,j] inclusive are all exact matches: |
199 | | |
200 | | // If there's only one, return it. This is the only spec-compliant case. |
201 | 0 | if (i == j) |
202 | 0 | return map[i].toCoord; |
203 | | // If there's exactly three, return the middle one. |
204 | 0 | if (i + 2 == j) |
205 | 0 | return map[i + 1].toCoord; |
206 | | |
207 | | // Ignore the middle ones. Return the one mapping closer to 0. |
208 | 0 | if (value < 0) return map[j].toCoord; |
209 | 0 | if (value > 0) return map[i].toCoord; |
210 | | |
211 | | // Mapping 0? CoreText seems confused. It seems to prefer 0 here... |
212 | | // So we'll just return the smallest one. lol |
213 | 0 | return fabsf (map[i].toCoord) < fabsf (map[j].toCoord) ? map[i].toCoord : map[j].toCoord; |
214 | | |
215 | | // Mapping 0? Return one not mapping to 0. |
216 | 0 | if (map[i].toCoord == 0) |
217 | 0 | return map[j].toCoord; |
218 | 0 | else |
219 | 0 | return map[i].toCoord; |
220 | 0 | } |
221 | | |
222 | | /* There's at least two and we're not an exact match. Prepare to lerp. */ |
223 | | |
224 | | // Find the segment we're in. |
225 | 0 | for (i = start; i < end; i++) |
226 | 0 | if (value < map[i].fromCoord) |
227 | 0 | break; |
228 | |
|
229 | 0 | if (i == start) |
230 | 0 | { |
231 | | // Value before all segments; Shift. |
232 | 0 | return value - map[start].fromCoord + map[start].toCoord; |
233 | 0 | } |
234 | 0 | if (i == end) |
235 | 0 | { |
236 | | // Value after all segments; Shift. |
237 | 0 | return value - map[end - 1].fromCoord + map[end - 1].toCoord; |
238 | 0 | } |
239 | | |
240 | | // Actually interpolate. |
241 | 0 | auto &before = map[i-1]; |
242 | 0 | auto &after = map[i]; |
243 | 0 | float denom = after.fromCoord - before.fromCoord; // Can't be zero by now. |
244 | 0 | return before.toCoord + ((after.toCoord - before.toCoord) * (value - before.fromCoord)) / denom; |
245 | |
|
246 | 0 | #undef toCoord |
247 | 0 | #undef fromCoord |
248 | 0 | } |
249 | | |
250 | 0 | float unmap_float (float value) const { return map_float (value, 1, 0); } |
251 | | |
252 | | |
253 | | // TODO Kill this. |
254 | | Triple unmap_axis_range (const Triple& axis_range) const |
255 | 0 | { |
256 | 0 | float unmapped_min = unmap_float (axis_range.minimum); |
257 | 0 | float unmapped_middle = unmap_float (axis_range.middle); |
258 | 0 | float unmapped_max = unmap_float (axis_range.maximum); |
259 | |
|
260 | 0 | return Triple{(double) unmapped_min, (double) unmapped_middle, (double) unmapped_max}; |
261 | 0 | } |
262 | | |
263 | | bool subset (hb_subset_context_t *c, hb_tag_t axis_tag, |
264 | | hb_vector_t<AxisValueMap> *out_mappings = nullptr) const |
265 | 0 | { |
266 | 0 | TRACE_SUBSET (this); |
267 | | |
268 | | /* This function cannot work on avar2 table (and currently doesn't). |
269 | | * We should instead keep the design coords in the shape plan and use |
270 | | * those. unmap_axis_range needs to be killed. */ |
271 | | |
272 | | /* avar mapped normalized axis range. Under avar2, axes_location holds |
273 | | * only the self-contained pins for the other tables; avar itself uses |
274 | | * the intermediate-space ranges of all restricted axes. */ |
275 | 0 | const auto &axes_location = c->plan->has_avar2 |
276 | 0 | ? c->plan->old_intermediates |
277 | 0 | : c->plan->axes_location; |
278 | 0 | Triple *axis_range; |
279 | 0 | if (!axes_location.has (axis_tag, &axis_range)) |
280 | 0 | return c->serializer->embed (*this); |
281 | | |
282 | 0 | TripleDistances *axis_triple_distances; |
283 | 0 | if (!c->plan->axes_triple_distances.has (axis_tag, &axis_triple_distances)) |
284 | 0 | return_trace (false); |
285 | | |
286 | 0 | auto *out = c->serializer->start_embed (this); |
287 | 0 | if (unlikely (!c->serializer->extend_min (out))) return_trace (false); |
288 | | |
289 | 0 | Triple unmapped_range = unmap_axis_range (*axis_range); |
290 | | |
291 | | /* create a vector of retained mappings and sort */ |
292 | 0 | hb_vector_t<AxisValueMap> value_mappings; |
293 | 0 | for (const auto& _ : as_array ()) |
294 | 0 | { |
295 | 0 | if (_.is_outside_axis_range (unmapped_range)) |
296 | 0 | continue; |
297 | 0 | AxisValueMap mapping; |
298 | 0 | mapping = _; |
299 | 0 | mapping.instantiate (*axis_range, unmapped_range, *axis_triple_distances); |
300 | | /* (-1, -1), (0, 0), (1, 1) mappings will be added later, so avoid |
301 | | * duplicates here */ |
302 | 0 | if (mapping.must_include ()) |
303 | 0 | continue; |
304 | 0 | value_mappings.push (mapping); |
305 | 0 | } |
306 | |
|
307 | 0 | AxisValueMap m; |
308 | 0 | m.set_mapping (-1.f, -1.f); |
309 | 0 | value_mappings.push (m); |
310 | |
|
311 | 0 | m.set_mapping (0.f, 0.f); |
312 | 0 | value_mappings.push (m); |
313 | |
|
314 | 0 | m.set_mapping (1.f, 1.f); |
315 | 0 | value_mappings.push (m); |
316 | |
|
317 | 0 | value_mappings.qsort (); |
318 | |
|
319 | 0 | if (unlikely (value_mappings.in_error ())) |
320 | 0 | return_trace (false); |
321 | | |
322 | 0 | for (const auto& _ : value_mappings) |
323 | 0 | { |
324 | 0 | if (!_.serialize (c->serializer)) |
325 | 0 | return_trace (false); |
326 | 0 | } |
327 | 0 | if (!c->serializer->check_assign (out->len, value_mappings.length, HB_SERIALIZE_ERROR_INT_OVERFLOW)) |
328 | 0 | return_trace (false); |
329 | | /* Hand the instantiated mappings to the caller; avar2 offset |
330 | | * compensation needs them to locate the new mapping's kinks. */ |
331 | 0 | if (out_mappings) |
332 | 0 | *out_mappings = std::move (value_mappings); |
333 | 0 | return_trace (true); |
334 | 0 | } |
335 | | |
336 | | public: |
337 | | DEFINE_SIZE_ARRAY (2, *this); |
338 | | }; |
339 | | |
340 | | /* One knot of the avar2 offset-compensation function |
341 | | * offset(z) = inv_renorm(z) - z, in new intermediate space. */ |
342 | | struct avar2_offset_knot_t |
343 | | { |
344 | | double z; |
345 | | double offset; |
346 | | }; |
347 | | |
348 | | /* Insert a knot keeping the vector sorted by z; first insertion wins on |
349 | | * duplicate z. */ |
350 | | static inline void |
351 | | _avar2_add_knot (hb_vector_t<avar2_offset_knot_t> &knots, double z, double offset) |
352 | 0 | { |
353 | 0 | unsigned i = 0; |
354 | 0 | while (i < knots.length && knots.arrayZ[i].z < z) i++; |
355 | 0 | if (i < knots.length && knots.arrayZ[i].z == z) return; |
356 | 0 | knots.push (avar2_offset_knot_t {0.0, 0.0}); |
357 | 0 | if (unlikely (knots.in_error ())) return; |
358 | 0 | for (unsigned j = knots.length - 1; j > i; j--) |
359 | 0 | knots.arrayZ[j] = knots.arrayZ[j - 1]; |
360 | 0 | knots.arrayZ[i] = avar2_offset_knot_t {z, offset}; |
361 | 0 | } Unexecuted instantiation: hb-font.cc:OT::_avar2_add_knot(hb_vector_t<OT::avar2_offset_knot_t, false>&, double, double) Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_add_knot(hb_vector_t<OT::avar2_offset_knot_t, false>&, double, double) Unexecuted instantiation: hb-style.cc:OT::_avar2_add_knot(hb_vector_t<OT::avar2_offset_knot_t, false>&, double, double) Unexecuted instantiation: hb-subset-plan-var.cc:OT::_avar2_add_knot(hb_vector_t<OT::avar2_offset_knot_t, false>&, double, double) Unexecuted instantiation: hb-subset-table-var.cc:OT::_avar2_add_knot(hb_vector_t<OT::avar2_offset_knot_t, false>&, double, double) |
362 | | |
363 | | /* Piecewise-linear evaluation over the sorted knots. Inputs are always |
364 | | * within [-1, +1] and anchor knots at -1/0/+1 always exist. */ |
365 | | static inline double |
366 | | _avar2_eval_offset (const hb_vector_t<avar2_offset_knot_t> &knots, double z) |
367 | 0 | { |
368 | 0 | unsigned len = knots.length; |
369 | 0 | for (unsigned i = 0; i < len; i++) |
370 | 0 | { |
371 | 0 | if (z == knots.arrayZ[i].z) return knots.arrayZ[i].offset; |
372 | 0 | if (z < knots.arrayZ[i].z) |
373 | 0 | { |
374 | 0 | if (!i) return knots.arrayZ[0].offset; |
375 | 0 | const auto &before = knots.arrayZ[i - 1]; |
376 | 0 | const auto &after = knots.arrayZ[i]; |
377 | 0 | double denom = after.z - before.z; |
378 | 0 | return before.offset + (after.offset - before.offset) * (z - before.z) / denom; |
379 | 0 | } |
380 | 0 | } |
381 | 0 | return len ? knots.arrayZ[len - 1].offset : 0.0; |
382 | 0 | } Unexecuted instantiation: hb-font.cc:OT::_avar2_eval_offset(hb_vector_t<OT::avar2_offset_knot_t, false> const&, double) Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_eval_offset(hb_vector_t<OT::avar2_offset_knot_t, false> const&, double) Unexecuted instantiation: hb-style.cc:OT::_avar2_eval_offset(hb_vector_t<OT::avar2_offset_knot_t, false> const&, double) Unexecuted instantiation: hb-subset-plan-var.cc:OT::_avar2_eval_offset(hb_vector_t<OT::avar2_offset_knot_t, false> const&, double) Unexecuted instantiation: hb-subset-table-var.cc:OT::_avar2_eval_offset(hb_vector_t<OT::avar2_offset_knot_t, false> const&, double) |
383 | | |
384 | | /* Piecewise-linear evaluation over instantiated avar v1 mappings, as |
385 | | * produced by SegmentMaps::subset (sorted, with -1/0/+1 anchors). |
386 | | * Matches SegmentMaps::map_float for such well-formed mappings. */ |
387 | | static inline double |
388 | | _avar2_map_new_mapping (const hb_vector_t<AxisValueMap> &mappings, double v, |
389 | | unsigned from_offset = 0, unsigned to_offset = 1) |
390 | 0 | { |
391 | 0 | unsigned len = mappings.length; |
392 | 0 | if (!len) return v; |
393 | | |
394 | 0 | for (unsigned i = 0; i < len; i++) |
395 | 0 | { |
396 | 0 | double from = (double) mappings.arrayZ[i].coords[from_offset].to_float (); |
397 | 0 | if (v == from) |
398 | 0 | return (double) mappings.arrayZ[i].coords[to_offset].to_float (); |
399 | 0 | if (v < from) |
400 | 0 | { |
401 | 0 | double to = (double) mappings.arrayZ[i].coords[to_offset].to_float (); |
402 | 0 | if (!i) return v - from + to; |
403 | 0 | double prev_from = (double) mappings.arrayZ[i - 1].coords[from_offset].to_float (); |
404 | 0 | double prev_to = (double) mappings.arrayZ[i - 1].coords[to_offset].to_float (); |
405 | 0 | double denom = from - prev_from; |
406 | 0 | if (denom == 0.0) return prev_to; |
407 | 0 | return prev_to + (to - prev_to) * (v - prev_from) / denom; |
408 | 0 | } |
409 | 0 | } |
410 | 0 | return v - (double) mappings.arrayZ[len - 1].coords[from_offset].to_float () |
411 | 0 | + (double) mappings.arrayZ[len - 1].coords[to_offset].to_float (); |
412 | 0 | } Unexecuted instantiation: hb-font.cc:OT::_avar2_map_new_mapping(hb_vector_t<OT::AxisValueMap, false> const&, double, unsigned int, unsigned int) Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_map_new_mapping(hb_vector_t<OT::AxisValueMap, false> const&, double, unsigned int, unsigned int) Unexecuted instantiation: hb-style.cc:OT::_avar2_map_new_mapping(hb_vector_t<OT::AxisValueMap, false> const&, double, unsigned int, unsigned int) Unexecuted instantiation: hb-subset-plan-var.cc:OT::_avar2_map_new_mapping(hb_vector_t<OT::AxisValueMap, false> const&, double, unsigned int, unsigned int) Unexecuted instantiation: hb-subset-table-var.cc:OT::_avar2_map_new_mapping(hb_vector_t<OT::AxisValueMap, false> const&, double, unsigned int, unsigned int) |
413 | | |
414 | | /* Inverse of _avar2_map_new_mapping: pull an output coordinate back to a |
415 | | * preimage input coordinate. For a non-strictly-monotone mapping this picks |
416 | | * one preimage, which is fine for its only use (augmenting the error |
417 | | * estimator's sample set). */ |
418 | | static inline double |
419 | | _avar2_unmap_new_mapping (const hb_vector_t<AxisValueMap> &mappings, double v) |
420 | 0 | { |
421 | 0 | return _avar2_map_new_mapping (mappings, v, 1, 0); |
422 | 0 | } Unexecuted instantiation: hb-font.cc:OT::_avar2_unmap_new_mapping(hb_vector_t<OT::AxisValueMap, false> const&, double) Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_unmap_new_mapping(hb_vector_t<OT::AxisValueMap, false> const&, double) Unexecuted instantiation: hb-style.cc:OT::_avar2_unmap_new_mapping(hb_vector_t<OT::AxisValueMap, false> const&, double) Unexecuted instantiation: hb-subset-plan-var.cc:OT::_avar2_unmap_new_mapping(hb_vector_t<OT::AxisValueMap, false> const&, double) Unexecuted instantiation: hb-subset-table-var.cc:OT::_avar2_unmap_new_mapping(hb_vector_t<OT::AxisValueMap, false> const&, double) |
423 | | |
424 | | /* Plain (non-avar) fvar-style normalization of a user value against a |
425 | | * user-space (min, default, max) triple. */ |
426 | | static inline double |
427 | | _avar2_normalize_value (double v, double min, double def, double max) |
428 | 0 | { |
429 | 0 | v = hb_clamp (v, min, max); |
430 | 0 | if (v == def) return 0.0; |
431 | 0 | if (v < def) return def == min ? 0.0 : (v - def) / (def - min); |
432 | 0 | return def == max ? 0.0 : (v - def) / (max - def); |
433 | 0 | } Unexecuted instantiation: hb-font.cc:OT::_avar2_normalize_value(double, double, double, double) Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_normalize_value(double, double, double, double) Unexecuted instantiation: hb-style.cc:OT::_avar2_normalize_value(double, double, double, double) Unexecuted instantiation: hb-subset-plan-var.cc:OT::_avar2_normalize_value(double, double, double, double) Unexecuted instantiation: hb-subset-table-var.cc:OT::_avar2_normalize_value(double, double, double, double) |
434 | | |
435 | | /* Inverse of the above: map a normalized value back to user space. */ |
436 | | static inline double |
437 | | _avar2_denormalize_value (double v, double min, double def, double max) |
438 | 0 | { |
439 | 0 | if (v == 0.0) return def; |
440 | 0 | return v < 0.0 ? def + v * (def - min) : def + v * (max - def); |
441 | 0 | } Unexecuted instantiation: hb-font.cc:OT::_avar2_denormalize_value(double, double, double, double) Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_denormalize_value(double, double, double, double) Unexecuted instantiation: hb-style.cc:OT::_avar2_denormalize_value(double, double, double, double) Unexecuted instantiation: hb-subset-plan-var.cc:OT::_avar2_denormalize_value(double, double, double, double) Unexecuted instantiation: hb-subset-table-var.cc:OT::_avar2_denormalize_value(double, double, double, double) |
442 | | |
443 | | /* Estimate the residual offset-compensation error for one restricted axis: |
444 | | * the max |old-avar1-final - (new-avar1 + offset)| over the retained user |
445 | | * range, in F2Dot14 units. offset(z) is the piecewise-linear function |
446 | | * through the knots. The residual is dominated by F2Dot14 requantization of |
447 | | * a steep retained avar v1 segment (e.g. a moved default compressing part |
448 | | * of the axis into a narrow z band); offset compensation cannot remove it. |
449 | | * Used only to pick the better knot set and decide whether to warn. |
450 | | * |
451 | | * Sampled on a uniform grid augmented with the user-space preimages of |
452 | | * every kink of the residual (old/new avar v1 breakpoints and offset(z) |
453 | | * knots), so the worst kink cannot fall between uniform samples. The |
454 | | * pointwise F2Dot14 rounding makes this an estimate rather than an exact |
455 | | * bound, but every piecewise-linear extremum is visited. */ |
456 | | static inline unsigned |
457 | | _avar2_estimate_offset_error (const SegmentMaps &old_seg, |
458 | | const hb_vector_t<AxisValueMap> &new_mapping, |
459 | | double old_min, double old_def, double old_max, |
460 | | double new_min, double new_def, double new_max, |
461 | | const hb_vector_t<avar2_offset_knot_t> &knots) |
462 | 0 | { |
463 | 0 | constexpr unsigned samples = 257; |
464 | 0 | hb_vector_t<double> us; |
465 | 0 | if (unlikely (!us.alloc (samples + old_seg.as_array ().length + |
466 | 0 | new_mapping.length + knots.length))) |
467 | 0 | return UINT_MAX; /* estimator only; fail towards "worse" */ |
468 | 0 | for (unsigned i = 0; i < samples; i++) |
469 | 0 | us.push (new_min + (new_max - new_min) * i / (samples - 1)); |
470 | 0 | for (const auto &_ : old_seg.as_array ()) |
471 | 0 | us.push (_avar2_denormalize_value ((double) _.coords[0].to_float (), |
472 | 0 | old_min, old_def, old_max)); |
473 | 0 | for (const auto &_ : new_mapping) |
474 | 0 | us.push (_avar2_denormalize_value ((double) _.coords[0].to_float (), |
475 | 0 | new_min, new_def, new_max)); |
476 | 0 | for (const auto &knot : knots) |
477 | 0 | us.push (_avar2_denormalize_value ( |
478 | 0 | _avar2_unmap_new_mapping (new_mapping, knot.z), |
479 | 0 | new_min, new_def, new_max)); |
480 | 0 | if (unlikely (us.in_error ())) return UINT_MAX; |
481 | | |
482 | 0 | unsigned max_err = 0; |
483 | 0 | for (double u : us) |
484 | 0 | { |
485 | 0 | if (u < new_min || u > new_max) continue; |
486 | 0 | double n_old = _avar2_normalize_value (u, old_min, old_def, old_max); |
487 | 0 | double old_final = (double) old_seg.map_float ((float) n_old); |
488 | 0 | double n_new = _avar2_normalize_value (u, new_min, new_def, new_max); |
489 | 0 | double z = _avar2_map_new_mapping (new_mapping, n_new); |
490 | 0 | double new_final = z + _avar2_eval_offset (knots, z); |
491 | 0 | int err = abs ((int) roundf ((float) (old_final * 16384.0)) - |
492 | 0 | (int) roundf ((float) (new_final * 16384.0))); |
493 | 0 | if ((unsigned) err > max_err) max_err = err; |
494 | 0 | } |
495 | 0 | return max_err; |
496 | 0 | } Unexecuted instantiation: hb-font.cc:OT::_avar2_estimate_offset_error(OT::SegmentMaps const&, hb_vector_t<OT::AxisValueMap, false> const&, double, double, double, double, double, double, hb_vector_t<OT::avar2_offset_knot_t, false> const&) Unexecuted instantiation: hb-ot-var.cc:OT::_avar2_estimate_offset_error(OT::SegmentMaps const&, hb_vector_t<OT::AxisValueMap, false> const&, double, double, double, double, double, double, hb_vector_t<OT::avar2_offset_knot_t, false> const&) Unexecuted instantiation: hb-style.cc:OT::_avar2_estimate_offset_error(OT::SegmentMaps const&, hb_vector_t<OT::AxisValueMap, false> const&, double, double, double, double, double, double, hb_vector_t<OT::avar2_offset_knot_t, false> const&) Unexecuted instantiation: hb-subset-plan-var.cc:OT::_avar2_estimate_offset_error(OT::SegmentMaps const&, hb_vector_t<OT::AxisValueMap, false> const&, double, double, double, double, double, double, hb_vector_t<OT::avar2_offset_knot_t, false> const&) Unexecuted instantiation: hb-subset-table-var.cc:OT::_avar2_estimate_offset_error(OT::SegmentMaps const&, hb_vector_t<OT::AxisValueMap, false> const&, double, double, double, double, double, double, hb_vector_t<OT::avar2_offset_knot_t, false> const&) |
497 | | |
498 | | struct avar |
499 | | { |
500 | | static constexpr hb_tag_t tableTag = HB_OT_TAG_avar; |
501 | | |
502 | 0 | bool has_data () const { return version.to_int (); } |
503 | | |
504 | | const SegmentMaps* get_segment_maps () const |
505 | 0 | { return &firstAxisSegmentMaps; } |
506 | | |
507 | | unsigned get_axis_count () const |
508 | 0 | { return axisCount; } |
509 | | |
510 | | bool sanitize (hb_sanitize_context_t *c) const |
511 | 0 | { |
512 | 0 | TRACE_SANITIZE (this); |
513 | 0 | if (!(version.sanitize (c) && |
514 | 0 | hb_barrier () && |
515 | 0 | (version.major == 1 |
516 | 0 | #ifndef HB_NO_AVAR2 |
517 | 0 | || version.major == 2 |
518 | 0 | #endif |
519 | 0 | ) && |
520 | 0 | c->check_struct (this))) |
521 | 0 | return_trace (false); |
522 | | |
523 | 0 | const SegmentMaps *map = &firstAxisSegmentMaps; |
524 | 0 | unsigned int count = axisCount; |
525 | 0 | for (unsigned int i = 0; i < count; i++) |
526 | 0 | { |
527 | 0 | if (unlikely (!map->sanitize (c))) |
528 | 0 | return_trace (false); |
529 | 0 | map = &StructAfter<SegmentMaps> (*map); |
530 | 0 | } |
531 | | |
532 | 0 | #ifndef HB_NO_AVAR2 |
533 | 0 | if (version.major < 2) |
534 | 0 | return_trace (true); |
535 | 0 | hb_barrier (); |
536 | |
|
537 | 0 | const auto &v2 = * (const avarV2Tail *) map; |
538 | 0 | if (unlikely (!v2.sanitize (c, this))) |
539 | 0 | return_trace (false); |
540 | 0 | #endif |
541 | | |
542 | 0 | return_trace (true); |
543 | 0 | } |
544 | | |
545 | | void map_coords_16_16 (int *coords, unsigned int coords_length) const |
546 | 218k | { |
547 | 218k | unsigned int count = hb_min (coords_length, axisCount); |
548 | | |
549 | 218k | const SegmentMaps *map = &firstAxisSegmentMaps; |
550 | 218k | for (unsigned int i = 0; i < count; i++) |
551 | 0 | { |
552 | 0 | coords[i] = roundf (map->map_float (coords[i] / 65536.f) * 65536.f); |
553 | 0 | map = &StructAfter<SegmentMaps> (*map); |
554 | 0 | } |
555 | | |
556 | 218k | #ifndef HB_NO_AVAR2 |
557 | 218k | if (version.major < 2) |
558 | 218k | return; |
559 | 0 | hb_barrier (); |
560 | |
|
561 | 0 | for (; count < axisCount; count++) |
562 | 0 | map = &StructAfter<SegmentMaps> (*map); |
563 | |
|
564 | 0 | const auto &v2 = * (const avarV2Tail *) map; |
565 | |
|
566 | 0 | const auto &varidx_map = this+v2.varIdxMap; |
567 | 0 | const auto &var_store = this+v2.varStore; |
568 | 0 | auto *var_store_cache = var_store.create_cache (); |
569 | |
|
570 | 0 | hb_vector_t<int> coords_2_14; |
571 | 0 | coords_2_14.resize (coords_length); |
572 | 0 | for (unsigned i = 0; i < coords_length; i++) |
573 | 0 | coords_2_14[i] = roundf (coords[i] / 4.f); // 16.16 -> 2.14 |
574 | |
|
575 | 0 | hb_vector_t<int> out; |
576 | 0 | out.alloc (coords_length); |
577 | 0 | for (unsigned i = 0; i < coords_length; i++) |
578 | 0 | { |
579 | 0 | int v = coords[i]; |
580 | 0 | uint32_t varidx = varidx_map.map (i); |
581 | 0 | float delta = var_store.get_delta (varidx, coords_2_14.arrayZ, coords_2_14.length, var_store_cache); |
582 | | /* Apply the delta unclamped and clamp only the result to [-1, +1], |
583 | | * matching fontTools. Since inputs and results are in [-1, +1], |
584 | | * deltas beyond ±2 are equivalent to ±2; clamp to that range only |
585 | | * to keep the float->int conversion safe. */ |
586 | 0 | float d = hb_clamp (delta * 4, -(float) (1<<17), +(float) (1<<17)); // 2.14 -> 16.16 |
587 | 0 | v += (int) roundf (d); |
588 | 0 | v = hb_clamp (v, -(1<<16), +(1<<16)); |
589 | 0 | out.push (v); |
590 | 0 | } |
591 | 0 | for (unsigned i = 0; i < coords_length; i++) |
592 | 0 | coords[i] = out[i]; |
593 | |
|
594 | 0 | OT::ItemVariationStore::destroy_cache (var_store_cache); |
595 | 0 | #endif |
596 | 0 | } |
597 | | |
598 | | /* An avar version 2 font is never downgraded to v1: even with a NULL |
599 | | * varStore offset (legal; maps like plain v1) the avar2 subsetting path |
600 | | * applies, with "no variation" semantics for rows that don't resolve — |
601 | | * offset compensation then creates delta rows on demand. */ |
602 | 0 | bool has_v2_data () const { return version.major > 1; } |
603 | | |
604 | | /* Resolve the avar2 VarStore and VarIdxMap, for the subset planner. |
605 | | * Either pointer may be to the Null object (nullable offsets). */ |
606 | | bool get_v2_store_and_map (const ItemVariationStore **store, |
607 | | const DeltaSetIndexMap **varidx_map) const |
608 | 0 | { |
609 | 0 | #ifndef HB_NO_AVAR2 |
610 | 0 | if (version.major < 2) return false; |
611 | 0 | const SegmentMaps *map = &firstAxisSegmentMaps; |
612 | 0 | for (unsigned i = 0; i < axisCount; i++) |
613 | 0 | map = &StructAfter<SegmentMaps> (*map); |
614 | 0 | const auto &v2 = * (const avarV2Tail *) map; |
615 | 0 | *store = &(this+v2.varStore); |
616 | 0 | *varidx_map = &(this+v2.varIdxMap); |
617 | 0 | return true; |
618 | | #else |
619 | | return false; |
620 | | #endif |
621 | 0 | } |
622 | | |
623 | | // axis normalization is done in 2.14 here |
624 | | // TODO: deprecate this API once fonttools is updated to use 16.16 normalization |
625 | | bool map_coords_2_14 (float *coords, unsigned int coords_length, |
626 | | bool v1_only = false) const |
627 | 0 | { |
628 | 0 | hb_vector_t<int> coords_2_14; |
629 | 0 | if (!v1_only && !coords_2_14.resize (coords_length)) return false; |
630 | 0 | unsigned int count = hb_min (coords_length, axisCount); |
631 | |
|
632 | 0 | const SegmentMaps *map = &firstAxisSegmentMaps; |
633 | 0 | for (unsigned int i = 0; i < count; i++) |
634 | 0 | { |
635 | 0 | int v = roundf (map->map_float (coords[i]) * 16384.f); |
636 | 0 | if (!v1_only) |
637 | 0 | coords_2_14[i] = v; |
638 | 0 | coords[i] = v / 16384.f; |
639 | 0 | map = &StructAfter<SegmentMaps> (*map); |
640 | 0 | } |
641 | |
|
642 | 0 | if (v1_only) |
643 | 0 | return true; |
644 | | |
645 | 0 | #ifndef HB_NO_AVAR2 |
646 | 0 | if (version.major < 2) |
647 | 0 | return true; |
648 | 0 | hb_barrier (); |
649 | |
|
650 | 0 | for (; count < axisCount; count++) |
651 | 0 | map = &StructAfter<SegmentMaps> (*map); |
652 | |
|
653 | 0 | const auto &v2 = * (const avarV2Tail *) map; |
654 | |
|
655 | 0 | const auto &varidx_map = this+v2.varIdxMap; |
656 | 0 | const auto &var_store = this+v2.varStore; |
657 | 0 | auto *var_store_cache = var_store.create_cache (); |
658 | |
|
659 | 0 | for (unsigned i = 0; i < coords_length; i++) |
660 | 0 | { |
661 | 0 | int v = coords_2_14[i]; |
662 | 0 | uint32_t varidx = varidx_map.map (i); |
663 | 0 | float delta = var_store.get_delta (varidx, coords_2_14.arrayZ, coords_2_14.length, var_store_cache); |
664 | | /* As above: apply the delta unclamped (±2 covers every useful case) |
665 | | * and clamp the result to [-1, +1], matching fontTools. */ |
666 | 0 | v += (int) hb_clamp (roundf (delta), -(float) (1<<15), +(float) (1<<15)); |
667 | 0 | v = hb_clamp (v, -(1<<14), +(1<<14)); |
668 | 0 | coords[i] = v / 16384.f; |
669 | 0 | } |
670 | |
|
671 | 0 | OT::ItemVariationStore::destroy_cache (var_store_cache); |
672 | 0 | return true; |
673 | | #else |
674 | | return version.major < 2; |
675 | | #endif |
676 | 0 | } |
677 | | |
678 | | bool subset (hb_subset_context_t *c) const |
679 | 0 | { |
680 | 0 | TRACE_SUBSET (this); |
681 | 0 | unsigned retained_axis_count = c->plan->axes_index_map.get_population (); |
682 | 0 | if (!retained_axis_count) //all axes are pinned/dropped |
683 | 0 | return_trace (false); |
684 | | |
685 | 0 | avar *out = c->serializer->allocate_min<avar> (); |
686 | 0 | if (unlikely (!out)) return_trace (false); |
687 | | |
688 | 0 | out->version.major = c->plan->has_avar2 ? 2 : 1; |
689 | 0 | out->version.minor = 0; |
690 | 0 | if (!c->serializer->check_assign (out->axisCount, retained_axis_count, HB_SERIALIZE_ERROR_INT_OVERFLOW)) |
691 | 0 | return_trace (false); |
692 | | |
693 | | /* For avar2, keep the instantiated v1 mappings around; offset |
694 | | * compensation needs them to locate the new mappings' kinks. */ |
695 | 0 | hb_vector_t<hb_vector_t<AxisValueMap>> new_mappings; |
696 | 0 | if (c->plan->has_avar2 && !new_mappings.resize (axisCount)) |
697 | 0 | return_trace (false); |
698 | | |
699 | 0 | const hb_map_t& axes_index_map = c->plan->axes_index_map; |
700 | 0 | const SegmentMaps *map = &firstAxisSegmentMaps; |
701 | 0 | unsigned count = axisCount; |
702 | 0 | for (unsigned int i = 0; i < count; i++) |
703 | 0 | { |
704 | 0 | if (axes_index_map.has (i)) |
705 | 0 | { |
706 | 0 | hb_tag_t *axis_tag; |
707 | 0 | if (!c->plan->axes_old_index_tag_map.has (i, &axis_tag)) |
708 | 0 | return_trace (false); |
709 | | |
710 | 0 | Triple *axis_location; |
711 | 0 | if (c->plan->has_avar2 && |
712 | 0 | c->plan->user_axes_location.has (*axis_tag, &axis_location) && |
713 | 0 | axis_location->is_point ()) |
714 | 0 | { |
715 | | /* Pinned axis in avar2 mode: serialize identity segment map |
716 | | * {-1->-1, 0->0, 1->1}. The axis is kept in fvar as hidden, |
717 | | * so avar needs a segment map entry for it. */ |
718 | 0 | auto *identity_map = c->serializer->start_embed<SegmentMaps> (); |
719 | 0 | if (unlikely (!c->serializer->extend_min (identity_map))) |
720 | 0 | return_trace (false); |
721 | 0 | AxisValueMap m; |
722 | 0 | m.set_mapping (-1.f, -1.f); |
723 | 0 | if (!m.serialize (c->serializer)) return_trace (false); |
724 | 0 | m.set_mapping (0.f, 0.f); |
725 | 0 | if (!m.serialize (c->serializer)) return_trace (false); |
726 | 0 | m.set_mapping (1.f, 1.f); |
727 | 0 | if (!m.serialize (c->serializer)) return_trace (false); |
728 | 0 | if (!c->serializer->check_assign (identity_map->len, 3u, |
729 | 0 | HB_SERIALIZE_ERROR_INT_OVERFLOW)) |
730 | 0 | return_trace (false); |
731 | 0 | } |
732 | 0 | else |
733 | 0 | { |
734 | | /* Restricted or free axis: use standard SegmentMaps::subset() */ |
735 | 0 | if (!map->subset (c, *axis_tag, |
736 | 0 | c->plan->has_avar2 ? &new_mappings[i] : nullptr)) |
737 | 0 | return_trace (false); |
738 | 0 | } |
739 | 0 | } |
740 | 0 | map = &StructAfter<SegmentMaps> (*map); |
741 | 0 | } |
742 | | |
743 | 0 | if (c->plan->has_avar2) |
744 | 0 | return_trace (_subset_avar2 (c, new_mappings)); |
745 | | |
746 | 0 | return_trace (true); |
747 | 0 | } |
748 | | |
749 | | private: |
750 | | struct avar2_index_map_plan_t |
751 | | { |
752 | | bool init (const hb_vector_t<uint32_t> &varidx_mapping, |
753 | | const hb_map_t &axes_index_map, |
754 | | unsigned axis_count) |
755 | 0 | { |
756 | 0 | if (!output_map.alloc (axes_index_map.get_population ())) |
757 | 0 | return false; |
758 | | |
759 | 0 | bool has_no_variation = false; |
760 | 0 | unsigned max_outer = 0, max_inner = 0; |
761 | 0 | for (unsigned i = 0; i < axis_count; i++) |
762 | 0 | { |
763 | 0 | if (!axes_index_map.has (i)) continue; |
764 | 0 | uint32_t varidx = varidx_mapping[i]; |
765 | 0 | output_map.push (varidx); |
766 | 0 | if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX) |
767 | 0 | { |
768 | 0 | has_no_variation = true; |
769 | 0 | continue; |
770 | 0 | } |
771 | 0 | max_outer = hb_max (max_outer, varidx >> 16); |
772 | 0 | max_inner = hb_max (max_inner, varidx & 0xFFFF); |
773 | 0 | } |
774 | 0 | if (output_map.in_error () || |
775 | 0 | output_map.length != axes_index_map.get_population ()) |
776 | 0 | return false; |
777 | | |
778 | 0 | if (has_no_variation) |
779 | 0 | { |
780 | 0 | width = 4; |
781 | 0 | inner_bit_count = 16; |
782 | 0 | } |
783 | 0 | else |
784 | 0 | { |
785 | 0 | inner_bit_count = hb_max (1u, hb_bit_storage (max_inner)); |
786 | 0 | unsigned outer_bit_count = hb_max (1u, hb_bit_storage (max_outer)); |
787 | 0 | width = hb_clamp ((inner_bit_count + outer_bit_count + 7) / 8, 1u, 4u); |
788 | 0 | if (inner_bit_count + outer_bit_count > width * 8) |
789 | 0 | inner_bit_count = width * 8 - outer_bit_count; |
790 | 0 | } |
791 | 0 | return true; |
792 | 0 | } |
793 | | |
794 | 0 | unsigned get_inner_bit_count () const { return inner_bit_count; } |
795 | 0 | unsigned get_width () const { return width; } |
796 | 0 | hb_array_t<const uint32_t> get_output_map () const { return output_map.as_array (); } |
797 | | |
798 | | unsigned inner_bit_count = 1; |
799 | | unsigned width = 1; |
800 | | hb_vector_t<uint32_t> output_map; |
801 | | }; |
802 | | |
803 | | bool _subset_avar2 (hb_subset_context_t *c, |
804 | | const hb_vector_t<hb_vector_t<AxisValueMap>> &new_mappings) const |
805 | 0 | { |
806 | | #if defined (HB_NO_VAR) || defined (HB_NO_AVAR2) |
807 | | /* Not reachable: the plan never sets has_avar2 in these configurations. */ |
808 | | return false; |
809 | | #else |
810 | | |
811 | | /* 1. Locate original avar2 data, keeping per-axis old segment maps */ |
812 | 0 | hb_vector_t<const SegmentMaps *> old_seg_maps; |
813 | 0 | if (!old_seg_maps.alloc (axisCount)) return false; |
814 | 0 | const SegmentMaps *map = &firstAxisSegmentMaps; |
815 | 0 | for (unsigned i = 0; i < axisCount; i++) |
816 | 0 | { |
817 | 0 | old_seg_maps.push (map); |
818 | 0 | map = &StructAfter<SegmentMaps> (*map); |
819 | 0 | } |
820 | |
|
821 | 0 | const auto &v2 = * (const avarV2Tail *) map; |
822 | 0 | const auto &varidx_map = this+v2.varIdxMap; |
823 | 0 | const auto &var_store = this+v2.varStore; |
824 | |
|
825 | 0 | auto fvar_axes = c->plan->source->table.fvar->get_axes (); |
826 | | |
827 | | /* 2. Compute default deltas by evaluating VarStore at old defaults */ |
828 | 0 | hb_vector_t<int> default_coords; |
829 | 0 | if (!default_coords.resize (axisCount)) return false; |
830 | 0 | for (unsigned i = 0; i < axisCount; i++) |
831 | 0 | { |
832 | 0 | hb_tag_t *axis_tag; |
833 | 0 | if (c->plan->axes_old_index_tag_map.has (i, &axis_tag) && |
834 | 0 | c->plan->old_intermediates.has (*axis_tag)) |
835 | 0 | { |
836 | 0 | float d_i = (float) c->plan->old_intermediates.get (*axis_tag).middle; |
837 | 0 | default_coords[i] = roundf (d_i * 16384.f); |
838 | 0 | } |
839 | 0 | else |
840 | 0 | default_coords[i] = 0; |
841 | 0 | } |
842 | |
|
843 | 0 | auto *store_cache = var_store.create_cache (); |
844 | 0 | hb_vector_t<float> default_deltas; |
845 | 0 | if (!default_deltas.resize (axisCount)) { |
846 | 0 | ItemVariationStore::destroy_cache (store_cache); |
847 | 0 | return false; |
848 | 0 | } |
849 | 0 | for (unsigned i = 0; i < axisCount; i++) |
850 | 0 | { |
851 | 0 | uint32_t varidx = varidx_map.map (i); |
852 | 0 | if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX) |
853 | 0 | default_deltas[i] = 0.f; |
854 | 0 | else |
855 | 0 | default_deltas[i] = var_store.get_delta (varidx, default_coords.arrayZ, |
856 | 0 | default_coords.length, store_cache); |
857 | 0 | } |
858 | 0 | ItemVariationStore::destroy_cache (store_cache); |
859 | | |
860 | | /* 3. Rebase IVS regions */ |
861 | 0 | item_variations_t item_vars; |
862 | 0 | if (!item_vars.create_from_item_varstore (var_store, c->plan->axes_old_index_tag_map)) |
863 | 0 | return false; |
864 | 0 | if (!item_vars.instantiate_tuple_vars (c->plan->old_intermediates, |
865 | 0 | c->plan->axes_triple_distances, |
866 | 0 | false)) |
867 | 0 | return false; |
868 | | |
869 | | /* 4. Self-contained pinned axes (whose final coordinate is constant over |
870 | | * the retained box) were detected at plan time |
871 | | * (_compute_avar2_reachable_ranges) and removed from axes_index_map. |
872 | | * They are skipped below; their constant contribution is baked into the |
873 | | * other variation tables by standard instancing at the plan's |
874 | | * axes_location/normalized_coords. */ |
875 | | |
876 | | /* 5. Build per-axis varIdx mapping (may create new VarDatas). |
877 | | * Entries (or the implicit identity mapping) that don't resolve to a |
878 | | * real store row behave as "no variation" at runtime; normalize them to |
879 | | * NO_VARIATIONS_INDEX so the offset loop creates fresh rows instead of |
880 | | * writing into nonexistent ones. This also covers a NULL VarStore |
881 | | * (never downgraded: rows are created on demand). */ |
882 | 0 | hb_vector_t<uint32_t> new_varidx_mapping; |
883 | 0 | if (!new_varidx_mapping.resize (axisCount)) return false; |
884 | 0 | for (unsigned i = 0; i < axisCount; i++) |
885 | 0 | { |
886 | 0 | uint32_t varidx = varidx_map.map (i); |
887 | 0 | if (varidx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX && |
888 | 0 | !var_store.has_delta_set (varidx)) |
889 | 0 | varidx = HB_OT_LAYOUT_NO_VARIATIONS_INDEX; |
890 | 0 | new_varidx_mapping[i] = varidx; |
891 | 0 | } |
892 | | |
893 | | /* 5.5. Privatize shared varIdx delta rows before adding offset |
894 | | * compensation. avar2's VarIdxMap may map several fvar axes to the SAME |
895 | | * IVS delta row. Writing one axis's offset-compensation deltas into a |
896 | | * shared row would corrupt every other axis that reads that row. So give |
897 | | * each offset-receiving axis whose row is shared its own private copy of |
898 | | * the row (identical contents, preserving the rebased deltas), then |
899 | | * repoint its varIdx. Sharers keep the clean row; the varstore |
900 | | * optimization pass re-merges identical rows afterwards. */ |
901 | 0 | hb_hashmap_t<uint32_t, unsigned> varidx_ref_count; |
902 | 0 | for (unsigned i = 0; i < axisCount; i++) |
903 | 0 | { |
904 | 0 | if (!c->plan->axes_index_map.has (i)) continue; /* self-contained: dropped */ |
905 | 0 | uint32_t varidx = new_varidx_mapping[i]; |
906 | 0 | if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX) continue; |
907 | 0 | unsigned *count; |
908 | 0 | if (varidx_ref_count.has (varidx, &count)) |
909 | 0 | (*count)++; |
910 | 0 | else if (!varidx_ref_count.set (varidx, 1)) |
911 | 0 | return false; |
912 | 0 | } |
913 | 0 | for (unsigned i = 0; i < axisCount; i++) |
914 | 0 | { |
915 | 0 | hb_tag_t *axis_tag_ptr; |
916 | 0 | if (!c->plan->axes_old_index_tag_map.has (i, &axis_tag_ptr)) |
917 | 0 | return false; |
918 | | /* Only axes that will receive offset compensation (restricted or |
919 | | * pinned) can contaminate a shared row. */ |
920 | 0 | if (!c->plan->axes_index_map.has (i) || |
921 | 0 | !c->plan->user_axes_location.has (*axis_tag_ptr)) |
922 | 0 | continue; |
923 | 0 | uint32_t varidx = new_varidx_mapping[i]; |
924 | 0 | if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX) |
925 | 0 | continue; /* gets a fresh, private VarData in the offset loop below */ |
926 | 0 | unsigned *count; |
927 | 0 | if (!varidx_ref_count.has (varidx, &count) || *count <= 1) |
928 | 0 | continue; /* sole owner: safe to write offsets in place */ |
929 | 0 | unsigned outer = varidx >> 16; |
930 | 0 | unsigned new_inner = item_vars.duplicate_row (outer, varidx & 0xFFFF); |
931 | 0 | if (unlikely (new_inner == (unsigned) -1)) return false; |
932 | 0 | new_varidx_mapping[i] = (outer << 16) | new_inner; |
933 | 0 | (*count)--; |
934 | 0 | } |
935 | | |
936 | | /* 6. Add offset compensation tuples. |
937 | | * Track processed (outer,inner) pairs to avoid adding duplicate biases |
938 | | * when multiple axes share the same varIdx. */ |
939 | 0 | hb_set_t processed_varidxes; |
940 | 0 | for (unsigned i = 0; i < axisCount; i++) |
941 | 0 | { |
942 | 0 | hb_tag_t *axis_tag_ptr; |
943 | 0 | if (!c->plan->axes_old_index_tag_map.has (i, &axis_tag_ptr)) |
944 | 0 | return false; |
945 | 0 | hb_tag_t axis_tag = *axis_tag_ptr; |
946 | | |
947 | | /* Self-contained pinned axes are removed from fvar/avar; their |
948 | | * contribution is baked into the variation tables instead. */ |
949 | 0 | if (!c->plan->axes_index_map.has (i)) |
950 | 0 | continue; |
951 | | |
952 | 0 | Triple *new_user; |
953 | 0 | if (c->plan->user_axes_location.has (axis_tag, &new_user)) |
954 | 0 | { |
955 | | /* This axis is being restricted or pinned */ |
956 | 0 | Triple *old_int; |
957 | 0 | if (!c->plan->old_intermediates.has (axis_tag, &old_int)) |
958 | 0 | return false; |
959 | | |
960 | 0 | float a_i = (float) old_int->minimum; |
961 | 0 | float d_i = (float) old_int->middle; |
962 | 0 | float b_i = (float) old_int->maximum; |
963 | |
|
964 | 0 | int d_int = roundf (d_i * 16384.f); |
965 | |
|
966 | 0 | bool is_pinned = new_user->is_point (); |
967 | |
|
968 | 0 | uint32_t varidx = new_varidx_mapping[i]; |
969 | 0 | unsigned outer, inner, item_count; |
970 | |
|
971 | 0 | if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX) |
972 | 0 | { |
973 | | /* No existing avar2 mapping. Create new VarData. */ |
974 | 0 | outer = item_vars.add_vardata (1); |
975 | 0 | inner = 0; |
976 | 0 | item_count = 1; |
977 | 0 | new_varidx_mapping[i] = (outer << 16) | inner; |
978 | 0 | default_deltas[i] = 0.f; /* no prior default delta */ |
979 | 0 | } |
980 | 0 | else |
981 | 0 | { |
982 | 0 | outer = varidx >> 16; |
983 | 0 | inner = varidx & 0xFFFF; |
984 | 0 | item_count = item_vars.get_item_count (outer); |
985 | 0 | } |
986 | | |
987 | | /* Empty-region bias: d_int + round(defaultDelta) */ |
988 | 0 | int bias = d_int + (int) roundf (default_deltas[i]); |
989 | 0 | if (bias != 0) |
990 | 0 | { |
991 | 0 | hb_hashmap_t<hb_tag_t, Triple> empty_region; |
992 | 0 | item_vars.add_tuple (outer, std::move (empty_region), |
993 | 0 | inner, bias, item_count); |
994 | 0 | } |
995 | |
|
996 | 0 | if (!is_pinned) |
997 | 0 | { |
998 | | /* Offset compensation encodes, as avar2 deltas on this axis, the |
999 | | * piecewise-linear function offset(z) = inv_renorm(z) - z, where |
1000 | | * inv_renorm maps a new intermediate coordinate z back to the old |
1001 | | * intermediate coordinate. It is known at these knots in the new |
1002 | | * intermediate space: |
1003 | | * z = -1 -> a_i + 1 (new minimum) |
1004 | | * z = 0 -> d_i (new default) |
1005 | | * z = +1 -> b_i - 1 (new maximum) |
1006 | | */ |
1007 | 0 | hb_vector_t<avar2_offset_knot_t> knots; |
1008 | 0 | _avar2_add_knot (knots, -1.0, (double) a_i + 1.0); |
1009 | 0 | _avar2_add_knot (knots, 0.0, (double) d_i); |
1010 | 0 | _avar2_add_knot (knots, 1.0, (double) b_i - 1.0); |
1011 | |
|
1012 | 0 | const hb_vector_t<AxisValueMap> &new_mapping = new_mappings[i]; |
1013 | |
|
1014 | 0 | float min_f = 0.f, def_f = 0.f, max_f = 0.f; |
1015 | 0 | if (likely (i < fvar_axes.length)) |
1016 | 0 | fvar_axes[i].get_coordinates (min_f, def_f, max_f); |
1017 | 0 | double old_min = (double) min_f; |
1018 | 0 | double old_def = (double) def_f; |
1019 | 0 | double old_max = (double) max_f; |
1020 | | |
1021 | | /* If the axis default MOVED, inv_renorm also kinks where the OLD |
1022 | | * default lands in the new space (the old intermediate coordinate |
1023 | | * crosses 0 there), at |
1024 | | * z = z_old -> -z_old |
1025 | | * Omitting that knot (as a plain two-tent encoding would) makes |
1026 | | * interior coordinates wrong. */ |
1027 | 0 | double z_old = _avar2_normalize_value ((double) old_def, |
1028 | 0 | new_user->minimum, |
1029 | 0 | new_user->middle, |
1030 | 0 | new_user->maximum); |
1031 | 0 | z_old = _avar2_map_new_mapping (new_mapping, z_old); |
1032 | 0 | z_old = (double) roundf ((float) (z_old * 16384.0)) / 16384.0; |
1033 | 0 | if (-1.0 < z_old && z_old < 1.0) |
1034 | 0 | _avar2_add_knot (knots, z_old, -z_old); |
1035 | | |
1036 | | /* Interior avar v1 breakpoints inside the retained range each put |
1037 | | * a kink in offset(z). Sampling only {-1, 0, +1, z_old} would |
1038 | | * linearly interpolate across those kinks. The instantiated |
1039 | | * mapping keeps exactly the in-range old breakpoints, and the new |
1040 | | * mapping kinks at each one's output coordinate; add that z with |
1041 | | * its old intermediate value so offset(z) is reproduced at every |
1042 | | * kink. */ |
1043 | 0 | hb_vector_t<avar2_offset_knot_t> with_breakpoints (knots); |
1044 | 0 | for (const auto &m : new_mapping) |
1045 | 0 | { |
1046 | 0 | double from = (double) m.coords[0].to_float (); |
1047 | 0 | if (from == -1.0 || from == 0.0 || from == 1.0) |
1048 | 0 | continue; /* anchors already seeded */ |
1049 | 0 | double z = (double) m.coords[1].to_float (); |
1050 | 0 | if (!(-1.0 < z && z < 1.0)) |
1051 | 0 | continue; |
1052 | 0 | double user = _avar2_denormalize_value (from, |
1053 | 0 | new_user->minimum, |
1054 | 0 | new_user->middle, |
1055 | 0 | new_user->maximum); |
1056 | 0 | double n_old = _avar2_normalize_value (user, old_min, old_def, old_max); |
1057 | 0 | double x_old = (double) old_seg_maps[i]->map_float ((float) n_old); |
1058 | 0 | x_old = (double) roundf ((float) (x_old * 16384.0)) / 16384.0; |
1059 | 0 | _avar2_add_knot (with_breakpoints, z, x_old - z); |
1060 | 0 | } |
1061 | |
|
1062 | 0 | if (unlikely (knots.in_error () || with_breakpoints.in_error ())) |
1063 | 0 | return false; |
1064 | | |
1065 | | /* Extra tents cost F2Dot14 rounding, so for a steep segment they |
1066 | | * can add more quantization noise than the structural error they |
1067 | | * remove. Keep the interior breakpoints only when they do not |
1068 | | * increase the estimated residual; this makes the collection a |
1069 | | * strict (never-worse) improvement over the {-1, 0, +1, z_old} |
1070 | | * anchors. Warn when even the better choice is not bit-exact (a |
1071 | | * steep retained segment that cannot be reproduced in F2Dot14). */ |
1072 | 0 | unsigned err = _avar2_estimate_offset_error (*old_seg_maps[i], new_mapping, |
1073 | 0 | old_min, old_def, old_max, |
1074 | 0 | new_user->minimum, |
1075 | 0 | new_user->middle, |
1076 | 0 | new_user->maximum, |
1077 | 0 | knots); |
1078 | 0 | if (with_breakpoints.length != knots.length) |
1079 | 0 | { |
1080 | 0 | unsigned err_with = _avar2_estimate_offset_error (*old_seg_maps[i], new_mapping, |
1081 | 0 | old_min, old_def, old_max, |
1082 | 0 | new_user->minimum, |
1083 | 0 | new_user->middle, |
1084 | 0 | new_user->maximum, |
1085 | 0 | with_breakpoints); |
1086 | 0 | if (err_with <= err) |
1087 | 0 | { |
1088 | 0 | knots = std::move (with_breakpoints); |
1089 | 0 | err = err_with; |
1090 | 0 | } |
1091 | 0 | } |
1092 | 0 | if (err > 8) |
1093 | 0 | DEBUG_MSG (SUBSET, nullptr, |
1094 | 0 | "avar2 offset compensation is approximate for axis %c%c%c%c: " |
1095 | 0 | "max residual %u F2Dot14 units", |
1096 | 0 | HB_UNTAG (axis_tag), err); |
1097 | | |
1098 | | /* Synthesize tents. Adjacent tents evaluate to zero at each |
1099 | | * other's peaks, so each knot's delta is offset(z) - offset(0); |
1100 | | * the base value offset(0) = d_i is carried by the empty-region |
1101 | | * bias above. This reduces to the classic pair of tents |
1102 | | * (-1,-1,0) / (0,+1,+1) when the default is unchanged and there |
1103 | | * are no interior knots. */ |
1104 | 0 | for (unsigned k = 0; k < knots.length; k++) |
1105 | 0 | { |
1106 | 0 | double z = knots.arrayZ[k].z; |
1107 | 0 | if (z == 0.0) continue; |
1108 | 0 | int delta = (int) roundf ((float) ((knots.arrayZ[k].offset - (double) d_i) * 16384.0)); |
1109 | 0 | if (!delta) continue; |
1110 | 0 | double lower, upper; |
1111 | 0 | if (z > 0.0) |
1112 | 0 | { |
1113 | 0 | lower = knots.arrayZ[k - 1].z; |
1114 | 0 | upper = k + 1 < knots.length ? knots.arrayZ[k + 1].z : z; |
1115 | 0 | } |
1116 | 0 | else |
1117 | 0 | { |
1118 | 0 | upper = knots.arrayZ[k + 1].z; |
1119 | 0 | lower = k > 0 ? knots.arrayZ[k - 1].z : z; |
1120 | 0 | } |
1121 | 0 | hb_hashmap_t<hb_tag_t, Triple> region; |
1122 | 0 | if (unlikely (!region.set (axis_tag, Triple (lower, z, upper)))) |
1123 | 0 | return false; |
1124 | 0 | item_vars.add_tuple (outer, std::move (region), |
1125 | 0 | inner, delta, item_count); |
1126 | 0 | } |
1127 | 0 | } |
1128 | 0 | } |
1129 | 0 | else |
1130 | 0 | { |
1131 | | /* Free or private axis — not being restricted. |
1132 | | * If it has a non-zero default delta, add it back as a bias. |
1133 | | * Skip if this (outer,inner) was already processed (shared varIdx). */ |
1134 | 0 | uint32_t varidx = new_varidx_mapping[i]; |
1135 | 0 | if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX) |
1136 | 0 | continue; |
1137 | | |
1138 | 0 | if (processed_varidxes.has (varidx)) |
1139 | 0 | continue; |
1140 | 0 | processed_varidxes.add (varidx); |
1141 | |
|
1142 | 0 | unsigned outer = varidx >> 16; |
1143 | 0 | unsigned inner = varidx & 0xFFFF; |
1144 | 0 | int dd = (int) roundf (default_deltas[i]); |
1145 | 0 | if (dd != 0) |
1146 | 0 | { |
1147 | 0 | unsigned item_count = item_vars.get_item_count (outer); |
1148 | 0 | hb_hashmap_t<hb_tag_t, Triple> empty_region; |
1149 | 0 | item_vars.add_tuple (outer, std::move (empty_region), |
1150 | 0 | inner, dd, item_count); |
1151 | 0 | } |
1152 | 0 | } |
1153 | 0 | } |
1154 | | |
1155 | | /* 7. Finalize: build region list + convert to varstore */ |
1156 | 0 | if (!item_vars.build_region_list ()) return false; |
1157 | 0 | if (!item_vars.as_item_varstore (true /* optimize */, |
1158 | 0 | false /* use_no_variation_idx */)) |
1159 | 0 | return false; |
1160 | | |
1161 | | /* 8. Apply varidx_map optimization remapping */ |
1162 | 0 | const auto &opt_varidx_map = item_vars.get_varidx_map (); |
1163 | 0 | for (unsigned i = 0; i < axisCount; i++) |
1164 | 0 | { |
1165 | 0 | uint32_t varidx = new_varidx_mapping[i]; |
1166 | 0 | if (varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX) |
1167 | 0 | continue; |
1168 | 0 | uint32_t *new_idx; |
1169 | 0 | if (opt_varidx_map.has (varidx, &new_idx)) |
1170 | 0 | new_varidx_mapping[i] = *new_idx; |
1171 | 0 | } |
1172 | | |
1173 | | /* 9. Serialize avarV2Tail. Entries cover the retained axes only; |
1174 | | * self-contained pinned axes are removed from fvar. */ |
1175 | 0 | avar2_index_map_plan_t index_map_plan; |
1176 | 0 | if (!index_map_plan.init (new_varidx_mapping, |
1177 | 0 | c->plan->axes_index_map, |
1178 | 0 | axisCount)) |
1179 | 0 | return false; |
1180 | | |
1181 | 0 | auto *tail = c->serializer->allocate_size<avarV2Tail> (avarV2Tail::static_size); |
1182 | 0 | if (unlikely (!tail)) return false; |
1183 | 0 | if (!tail->varIdxMap.serialize_serialize (c->serializer, index_map_plan)) |
1184 | 0 | return false; |
1185 | 0 | if (!tail->varStore.serialize_serialize (c->serializer, |
1186 | 0 | item_vars.has_long_word (), |
1187 | 0 | c->plan->axis_tags, |
1188 | 0 | item_vars.get_region_list (), |
1189 | 0 | item_vars.get_vardata_encodings ())) |
1190 | 0 | return false; |
1191 | | |
1192 | 0 | return true; |
1193 | 0 | #endif |
1194 | 0 | } |
1195 | | |
1196 | | public: |
1197 | | |
1198 | | protected: |
1199 | | FixedVersion<>version; /* Version of the avar table |
1200 | | * initially set to 0x00010000u */ |
1201 | | HBUINT16 reserved; /* This field is permanently reserved. Set to 0. */ |
1202 | | HBUINT16 axisCount; /* The number of variation axes in the font. This |
1203 | | * must be the same number as axisCount in the |
1204 | | * 'fvar' table. */ |
1205 | | SegmentMaps firstAxisSegmentMaps; |
1206 | | |
1207 | | public: |
1208 | | DEFINE_SIZE_MIN (8); |
1209 | | }; |
1210 | | |
1211 | | } /* namespace OT */ |
1212 | | |
1213 | | |
1214 | | #endif /* HB_OT_VAR_AVAR_TABLE_HH */ |