| /* |
| * Copyright © 2023 Google, Inc. |
| * |
| * This is part of HarfBuzz, a text shaping library. |
| * |
| * Permission is hereby granted, without written agreement and without |
| * license or royalty fees, to use, copy, modify, and distribute this |
| * software and its documentation for any purpose, provided that the |
| * above copyright notice and the following two paragraphs appear in |
| * all copies of this software. |
| * |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR |
| * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES |
| * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN |
| * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| * |
| * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, |
| * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS |
| * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO |
| * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
| * |
| * Google Author(s): Garret Rieger, Qunxin Liu, Roderick Sheeter |
| */ |
| |
| #include "hb-ot-layout-common.hh" |
| #include "hb-subset-plan.hh" |
| |
| #include "hb-ot-var-common.hh" |
| #include "hb-ot-layout-base-table.hh" |
| #include "hb-ot-glyf-table.hh" |
| #include "hb-ot-var-fvar-table.hh" |
| #include "hb-ot-var-avar-table.hh" |
| #include "hb-ot-cff2-table.hh" |
| |
| #ifndef HB_NO_VAR |
| |
| void |
| generate_varstore_inner_maps (const hb_set_t& varidx_set, |
| unsigned subtable_count, |
| hb_vector_t<hb_inc_bimap_t> &inner_maps /* OUT */) |
| { |
| if (varidx_set.is_empty () || subtable_count == 0) return; |
| |
| if (unlikely (!inner_maps.resize (subtable_count))) return; |
| for (unsigned idx : varidx_set) |
| { |
| uint16_t major = idx >> 16; |
| uint16_t minor = idx & 0xFFFF; |
| |
| if (major >= subtable_count) |
| continue; |
| inner_maps[major].add (minor); |
| } |
| } |
| |
| /* Evaluate a variation-region tent at v, mirroring VarRegionAxis::evaluate |
| * (invalid regions evaluate as constant 1). */ |
| static double |
| _tent_eval (const Triple &tent, double v) |
| { |
| double start = tent.minimum, peak = tent.middle, end = tent.maximum; |
| if (unlikely (start > peak || peak > end)) return 1.0; |
| if (unlikely (start < 0.0 && end > 0.0 && peak != 0.0)) return 1.0; |
| if (peak == 0.0 || v == peak) return 1.0; |
| if (v <= start || end <= v) return 0.0; |
| if (v < peak) return (v - start) / (peak - start); |
| return (end - v) / (end - peak); |
| } |
| |
| /* Range of a tent's scalar over an input interval [lo, hi]. The tent is |
| * unimodal, so the extremes are at the interval endpoints, plus the peak |
| * when it falls inside. */ |
| static void |
| _tent_value_range (const Triple &tent, double lo, double hi, |
| double *tmin, double *tmax) |
| { |
| double a = _tent_eval (tent, lo); |
| double b = _tent_eval (tent, hi); |
| *tmin = hb_min (a, b); |
| *tmax = hb_max (a, b); |
| if (lo <= tent.middle && tent.middle <= hi) |
| *tmax = hb_max (*tmax, 1.0); |
| } |
| |
| /* Compute conservative reachable old-space final-coord ranges per axis for |
| * avar2 partial instancing. With offset compensation, the instanced font's |
| * final coordinates equal the original font's over the retained user box, |
| * so the reachable range of axis i's final coordinate is the range of |
| * final_i = intermediate_i + delta_i(intermediates) |
| * over the box of retained old-intermediate ranges: restricted axes span |
| * their retained [a, b], free public axes [-1, +1], pinned axes sit at |
| * their d_i, and hidden (private) axes at 0. Interval arithmetic over the |
| * avar2 VarStore regions bounds delta_i. Mirrors fontTools' |
| * _computeReachableRangesForAvar2, but computed from the original store at |
| * plan time (the same quantity fontTools bounds via getExtremes on the |
| * instanced store). Only constraining ranges (narrower than [-1, +1]) are |
| * recorded. */ |
| static bool |
| _compute_avar2_reachable_ranges (hb_subset_plan_t *plan, |
| hb_array_t<const OT::AxisRecord> axes, |
| const OT::avar *avar_table, |
| bool detect_self_contained) |
| { |
| const OT::ItemVariationStore *var_store; |
| const OT::DeltaSetIndexMap *varidx_map; |
| if (!avar_table->get_v2_store_and_map (&var_store, &varidx_map)) |
| return false; |
| |
| /* Old-intermediate input box per axis. */ |
| hb_hashmap_t<hb_tag_t, hb_pair_t<double, double>> box; |
| for (const auto &axis : axes) |
| { |
| hb_tag_t tag = axis.get_axis_tag (); |
| double lo = -1.0, hi = +1.0; |
| Triple *old_int; |
| if (plan->user_axes_location.has (tag)) |
| { |
| if (!plan->old_intermediates.has (tag, &old_int)) return false; |
| lo = old_int->minimum; |
| hi = old_int->maximum; |
| } |
| else if (axis.is_hidden ()) |
| lo = hi = 0.0; /* private axis: intermediate is always 0 */ |
| if (!box.set (tag, hb_pair (lo, hi))) return false; |
| } |
| |
| hb_vector_t<hb_hashmap_t<hb_tag_t, Triple>> regions; |
| if (!var_store->get_region_list ().get_var_regions (plan->axes_old_index_tag_map, regions)) |
| return false; |
| |
| for (unsigned i = 0; i < axes.length; i++) |
| { |
| hb_tag_t tag = axes[i].get_axis_tag (); |
| hb_pair_t<double, double> *identity; |
| if (!box.has (tag, &identity)) return false; |
| |
| double dmin = 0.0, dmax = 0.0; |
| uint32_t varidx = varidx_map->map (i); |
| if (varidx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX) |
| { |
| unsigned outer = varidx >> 16; |
| unsigned inner = varidx & 0xFFFF; |
| if (outer >= var_store->get_sub_table_count ()) return false; |
| else |
| { |
| const OT::VarData &vd = var_store->get_sub_table (outer); |
| if (inner >= vd.get_item_count ()) return false; |
| else |
| { |
| const OT::HBUINT8 *delta_bytes = vd.get_delta_bytes (); |
| unsigned row_size = vd.get_row_size (); |
| unsigned region_count = vd.get_region_index_count (); |
| for (unsigned r = 0; r < region_count; r++) |
| { |
| int delta = vd.get_item_delta_fast (inner, r, delta_bytes, row_size); |
| if (!delta) continue; |
| unsigned region_idx = vd.get_region_index (r); |
| if (region_idx >= regions.length) return false; |
| double smin = 1.0, smax = 1.0; |
| for (const auto &_ : regions[region_idx]) |
| { |
| hb_pair_t<double, double> *input; |
| double blo = -1.0, bhi = +1.0; |
| if (box.has (_.first, &input)) { blo = input->first; bhi = input->second; } |
| double tmin, tmax; |
| _tent_value_range (_.second, blo, bhi, &tmin, &tmax); |
| smin *= tmin; |
| smax *= tmax; |
| if (smax == 0.0) break; |
| } |
| if (delta > 0) { dmin += smin * delta; dmax += smax * delta; } |
| else { dmin += smax * delta; dmax += smin * delta; } |
| } |
| } |
| } |
| } |
| /* A pinned axis whose delta is constant over the box is self-contained: |
| * its final coordinate is a constant. It can be removed from fvar/avar |
| * and its contribution baked into the variation tables like an ordinary |
| * pin at that coordinate (in old final space). Round the delta alone |
| * and add it to the exact quantized intermediate, matching the runtime |
| * (map_coords_2_14) and fontTools. */ |
| Triple *user; |
| if (detect_self_contained && dmin == dmax && |
| plan->user_axes_location.has (tag, &user) && user->is_point ()) |
| { |
| int v_int = (int) roundf ((float) identity->first * 16384.f) + |
| (int) roundf ((float) dmin); |
| v_int = hb_clamp (v_int, -(1 << 14), +(1 << 14)); |
| if (!plan->avar2_self_contained.set (tag, v_int / 16384.0)) return false; |
| continue; /* axis is dropped; no region can reference it afterwards */ |
| } |
| |
| /* Pad by one F2Dot14 unit against rounding differences with the |
| * runtime's fixed-point evaluation. */ |
| double lo = hb_clamp (identity->first + dmin / 16384.0 - 1.0 / 16384.0, -1.0, +1.0); |
| double hi = hb_clamp (identity->second + dmax / 16384.0 + 1.0 / 16384.0, -1.0, +1.0); |
| lo = floor (lo * 16384.0) / 16384.0; |
| hi = ceil (hi * 16384.0) / 16384.0; |
| if (lo <= -1.0 && hi >= +1.0) |
| continue; /* not constraining */ |
| if (!plan->avar2_reachable_ranges.set (tag, Triple (lo, hb_clamp (0.0, lo, hi), hi))) |
| return false; |
| } |
| return true; |
| } |
| |
| #ifndef HB_NO_OT_FONT_CFF |
| static inline hb_font_t* |
| _get_hb_font_with_variations (const hb_subset_plan_t *plan) |
| { |
| hb_font_t *font = hb_font_create (plan->source); |
| |
| if (plan->has_avar2) |
| { |
| /* Under avar2, instancing applies only to the self-contained pins, |
| * whose constant final coordinates the plan holds in normalized_coords. |
| * Setting user-space variations would run the full avar2 mapping and |
| * bake contributions that remain live in the instance's variations. */ |
| hb_font_set_var_coords_normalized (font, plan->normalized_coords.arrayZ, |
| plan->normalized_coords.length); |
| return font; |
| } |
| |
| hb_vector_t<hb_variation_t> vars; |
| if (!vars.alloc (plan->user_axes_location.get_population ())) { |
| hb_font_destroy (font); |
| return nullptr; |
| } |
| |
| for (auto _ : plan->user_axes_location) |
| { |
| hb_variation_t var; |
| var.tag = _.first; |
| var.value = _.second.middle; |
| vars.push (var); |
| } |
| |
| hb_font_set_variations (font, vars.arrayZ, plan->user_axes_location.get_population ()); |
| return font; |
| } |
| #endif |
| |
| template<typename ItemVarStore> |
| void |
| remap_variation_indices (const ItemVarStore &var_store, |
| const hb_set_t &variation_indices, |
| const hb_vector_t<int>& normalized_coords, |
| bool calculate_delta, /* not pinned at default */ |
| bool no_variations, /* all axes pinned */ |
| hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map /* OUT */) |
| { |
| if (&var_store == &Null (OT::ItemVariationStore)) return; |
| unsigned subtable_count = var_store.get_sub_table_count (); |
| auto *store_cache = var_store.create_cache (); |
| |
| unsigned new_major = 0, new_minor = 0; |
| unsigned last_major = (variation_indices.get_min ()) >> 16; |
| for (unsigned idx : variation_indices) |
| { |
| int delta = 0; |
| if (calculate_delta) |
| delta = roundf (var_store.get_delta (idx, normalized_coords.arrayZ, |
| normalized_coords.length, store_cache)); |
| |
| if (no_variations) |
| { |
| variation_idx_delta_map.set (idx, hb_pair_t<unsigned, int> (HB_OT_LAYOUT_NO_VARIATIONS_INDEX, delta)); |
| continue; |
| } |
| |
| uint16_t major = idx >> 16; |
| if (major >= subtable_count) break; |
| if (major != last_major) |
| { |
| new_minor = 0; |
| ++new_major; |
| } |
| |
| unsigned new_idx = (new_major << 16) + new_minor; |
| variation_idx_delta_map.set (idx, hb_pair_t<unsigned, int> (new_idx, delta)); |
| ++new_minor; |
| last_major = major; |
| } |
| var_store.destroy_cache (store_cache); |
| } |
| |
| template |
| void |
| remap_variation_indices<OT::ItemVariationStore> (const OT::ItemVariationStore &var_store, |
| const hb_set_t &variation_indices, |
| const hb_vector_t<int>& normalized_coords, |
| bool calculate_delta, /* not pinned at default */ |
| bool no_variations, /* all axes pinned */ |
| hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map /* OUT */); |
| |
| #ifndef HB_NO_BASE |
| void |
| collect_base_variation_indices (hb_subset_plan_t* plan) |
| { |
| hb_blob_ptr_t<OT::BASE> base = plan->source_table<OT::BASE> (); |
| if (!base->has_var_store ()) |
| { |
| base.destroy (); |
| return; |
| } |
| |
| hb_set_t varidx_set; |
| base->collect_variation_indices (plan, varidx_set); |
| const OT::ItemVariationStore &var_store = base->get_var_store (); |
| unsigned subtable_count = var_store.get_sub_table_count (); |
| |
| |
| remap_variation_indices (var_store, varidx_set, |
| plan->normalized_coords, |
| !plan->pinned_at_default, |
| plan->all_axes_pinned, |
| plan->base_variation_idx_map); |
| generate_varstore_inner_maps (varidx_set, subtable_count, plan->base_varstore_inner_maps); |
| |
| base.destroy (); |
| } |
| |
| #endif |
| |
| bool |
| normalize_axes_location (hb_face_t *face, hb_subset_plan_t *plan) |
| { |
| if (plan->user_axes_location.is_empty ()) |
| return true; |
| |
| hb_array_t<const OT::AxisRecord> axes = face->table.fvar->get_axes (); |
| if (!plan->check_success (plan->normalized_coords.resize (axes.length))) |
| return false; |
| |
| bool has_avar = face->table.avar->has_data (); |
| hb_vector_t<float> normalized_mins; |
| hb_vector_t<float> normalized_defaults; |
| hb_vector_t<float> normalized_maxs; |
| if (has_avar) |
| { |
| if (!plan->check_success (normalized_mins.resize (axes.length)) || |
| !plan->check_success (normalized_defaults.resize (axes.length)) || |
| !plan->check_success (normalized_maxs.resize (axes.length))) |
| return false; |
| } |
| |
| bool axis_not_pinned = false; |
| unsigned new_axis_idx = 0; |
| unsigned last_idx = 0; |
| for (const auto& _ : + hb_enumerate (axes)) |
| { |
| unsigned i = _.first; |
| const OT::AxisRecord &axis = _.second; |
| hb_tag_t axis_tag = axis.get_axis_tag (); |
| plan->axes_old_index_tag_map.set (i, axis_tag); |
| |
| if (!plan->user_axes_location.has (axis_tag) || |
| !plan->user_axes_location.get (axis_tag).is_point ()) |
| { |
| axis_not_pinned = true; |
| plan->axes_index_map.set (i, new_axis_idx); |
| plan->axis_tags.push (axis_tag); |
| new_axis_idx++; |
| } |
| |
| Triple *axis_range; |
| if (plan->user_axes_location.has (axis_tag, &axis_range)) |
| { |
| plan->axes_triple_distances.set (axis_tag, axis.get_triple_distances ()); |
| |
| float normalized_min = axis.normalize_axis_value (axis_range->minimum); |
| float normalized_default = axis.normalize_axis_value (axis_range->middle); |
| float normalized_max = axis.normalize_axis_value (axis_range->maximum); |
| |
| // TODO(behdad): Spec says axis normalization should be done in 16.16; |
| // We used to do it in 2.14, but that's not correct. I fixed this in |
| // the fvar/avar code, but keeping 2.14 here for now to keep tests |
| // happy. We might need to adjust fonttools as well. |
| // I'm only fairly confident in the above statement. Anyway, |
| // we should look deeper into this, and also update fonttools if |
| // needed. |
| |
| // Round to 2.14 |
| normalized_min = roundf (normalized_min * 16384.f) / 16384.f; |
| normalized_default = roundf (normalized_default * 16384.f) / 16384.f; |
| normalized_max = roundf (normalized_max * 16384.f) / 16384.f; |
| |
| if (has_avar) |
| { |
| normalized_mins[i] = normalized_min; |
| normalized_defaults[i] = normalized_default; |
| normalized_maxs[i] = normalized_max; |
| last_idx = i; |
| } |
| else |
| { |
| plan->axes_location.set (axis_tag, Triple ((double) normalized_min, |
| (double) normalized_default, |
| (double) normalized_max)); |
| if (normalized_default == -0.f) |
| normalized_default = 0.f; // Normalize -0 to 0 |
| if (normalized_default != 0.f) |
| plan->pinned_at_default = false; |
| |
| plan->normalized_coords[i] = roundf (normalized_default * 16384.f); |
| } |
| } |
| } |
| plan->all_axes_pinned = !axis_not_pinned; |
| |
| // TODO: use avar map_coords_16_16() when normalization is changed to 16.16 |
| // in fonttools |
| if (has_avar) |
| { |
| const OT::avar* avar_table = face->table.avar; |
| unsigned coords_len = last_idx + 1; |
| |
| if (avar_table->has_v2_data () && !plan->all_axes_pinned) |
| { |
| /* avar2 partial instancing: use v1-only mapping to get intermediate-space |
| * coords. These are used for IVS rebasing and offset compensation. */ |
| plan->has_avar2 = true; |
| |
| if (!plan->check_success (avar_table->map_coords_2_14 (normalized_mins.arrayZ, coords_len, true)) || |
| !plan->check_success (avar_table->map_coords_2_14 (normalized_defaults.arrayZ, coords_len, true)) || |
| !plan->check_success (avar_table->map_coords_2_14 (normalized_maxs.arrayZ, coords_len, true))) |
| return false; |
| |
| for (const auto& _ : + hb_enumerate (axes)) |
| { |
| unsigned i = _.first; |
| hb_tag_t axis_tag = _.second.get_axis_tag (); |
| if (plan->user_axes_location.has (axis_tag)) |
| { |
| /* Store intermediate-space coords for offset compensation, |
| * segment-map renormalization, and IVS rebasing. */ |
| plan->old_intermediates.set (axis_tag, Triple ((double) normalized_mins[i], |
| (double) normalized_defaults[i], |
| (double) normalized_maxs[i])); |
| } |
| } |
| |
| /* Reachable-range computation also detects self-contained pinned |
| * axes: pinned axes whose final coordinate is constant. Those are |
| * removed from fvar, so suppress the detection when the face has |
| * tables that cannot follow: CFF2 has no partial-pin instancing path |
| * (its 'pinned' path flattens ALL blends; lift this once |
| * https://github.com/harfbuzz/harfbuzz/pull/4710 lands, porting |
| * fontTools' instantiateCFF2 from |
| * https://github.com/fonttools/fonttools/pull/3506), and VARC passes |
| * through verbatim with explicit fvar axis indices that renumbering |
| * would desynchronize. Such axes stay in fvar as ordinary hidden |
| * pins. */ |
| bool detect_self_contained = true; |
| for (hb_tag_t table_tag : { HB_TAG ('C','F','F','2'), HB_TAG ('V','A','R','C') }) |
| { |
| hb_blob_t *blob = hb_face_reference_table (face, table_tag); |
| if (hb_blob_get_length (blob)) |
| detect_self_contained = false; |
| hb_blob_destroy (blob); |
| } |
| if (!_compute_avar2_reachable_ranges (plan, axes, avar_table, |
| detect_self_contained)) |
| return false; |
| |
| /* Keep all axes in fvar (pinned ones as hidden), EXCEPT self-contained |
| * pinned axes, which are removed entirely. */ |
| plan->axes_index_map.reset (); |
| plan->axis_tags.reset (); |
| unsigned retained_axis_idx = 0; |
| for (const auto& _ : + hb_enumerate (axes)) |
| { |
| unsigned i = _.first; |
| hb_tag_t axis_tag = _.second.get_axis_tag (); |
| if (plan->avar2_self_contained.has (axis_tag)) |
| continue; |
| plan->axes_index_map.set (i, retained_axis_idx++); |
| plan->axis_tags.push (axis_tag); |
| } |
| /* The other tables see ONLY the self-contained pins, as an ordinary |
| * partial instancing in old final-coordinate space; the standard |
| * instancing machinery bakes those axes' contributions in. The |
| * remaining restriction is carried entirely by avar. */ |
| plan->axes_location.reset (); |
| if (plan->avar2_self_contained.get_population ()) |
| { |
| bool sc_pinned_at_default = true; |
| for (auto _ : plan->avar2_self_contained) |
| { |
| plan->axes_location.set (_.first, Triple (_.second, _.second, _.second)); |
| if (_.second != 0.0) |
| sc_pinned_at_default = false; |
| } |
| for (const auto& _ : + hb_enumerate (axes)) |
| { |
| double *v; |
| plan->normalized_coords[_.first] = |
| plan->avar2_self_contained.has (_.second.get_axis_tag (), &v) |
| ? (int) roundf ((float) (*v * 16384.0)) : 0; |
| } |
| plan->pinned_at_default = sc_pinned_at_default; |
| } |
| else |
| plan->normalized_coords.shrink (0); |
| } |
| else |
| { |
| /* Standard avar v1 (or v1+v2 with all axes pinned) */ |
| if (!plan->check_success (avar_table->map_coords_2_14 (normalized_mins.arrayZ, coords_len)) || |
| !plan->check_success (avar_table->map_coords_2_14 (normalized_defaults.arrayZ, coords_len)) || |
| !plan->check_success (avar_table->map_coords_2_14 (normalized_maxs.arrayZ, coords_len))) |
| return false; |
| |
| for (const auto& _ : + hb_enumerate (axes)) |
| { |
| unsigned i = _.first; |
| hb_tag_t axis_tag = _.second.get_axis_tag (); |
| if (plan->user_axes_location.has (axis_tag)) |
| { |
| plan->axes_location.set (axis_tag, Triple ((double) normalized_mins[i], |
| (double) normalized_defaults[i], |
| (double) normalized_maxs[i])); |
| float normalized_default = normalized_defaults[i]; |
| if (normalized_default == -0.f) |
| normalized_default = 0.f; |
| if (normalized_default != 0.f) |
| plan->pinned_at_default = false; |
| |
| plan->normalized_coords[i] = roundf (normalized_default * 16384.f); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| #ifndef HB_NO_OT_FONT_CFF |
| void |
| update_instance_metrics_map_from_cff2 (hb_subset_plan_t *plan) |
| { |
| if (!plan->normalized_coords) return; |
| OT::cff2::accelerator_t cff2 (plan->source); |
| if (!cff2.is_valid ()) return; |
| |
| hb_font_t *font = _get_hb_font_with_variations (plan); |
| if (unlikely (!plan->check_success (font != nullptr))) |
| { |
| hb_font_destroy (font); |
| return; |
| } |
| |
| hb_glyph_extents_t extents = {0x7FFF, -0x7FFF}; |
| OT::hmtx_accelerator_t _hmtx (plan->source); |
| OT::hb_scalar_cache_t *hvar_store_cache = nullptr; |
| if (_hmtx.has_data () && _hmtx.var_table.get_length ()) |
| hvar_store_cache = _hmtx.var_table->get_var_store ().create_cache (); |
| |
| OT::vmtx_accelerator_t _vmtx (plan->source); |
| OT::hb_scalar_cache_t *vvar_store_cache = nullptr; |
| if (_vmtx.has_data () && _vmtx.var_table.get_length ()) |
| vvar_store_cache = _vmtx.var_table->get_var_store ().create_cache (); |
| |
| for (auto p : *plan->glyph_map) |
| { |
| hb_codepoint_t old_gid = p.first; |
| hb_codepoint_t new_gid = p.second; |
| if (!cff2.get_extents (font, old_gid, &extents)) continue; |
| bool has_bounds_info = true; |
| if (extents.x_bearing == 0 && extents.width == 0 && |
| extents.height == 0 && extents.y_bearing == 0) |
| has_bounds_info = false; |
| |
| if (has_bounds_info) |
| { |
| plan->head_maxp_info.xMin = hb_min (plan->head_maxp_info.xMin, extents.x_bearing); |
| plan->head_maxp_info.xMax = hb_max (plan->head_maxp_info.xMax, extents.x_bearing + extents.width); |
| plan->head_maxp_info.yMax = hb_max (plan->head_maxp_info.yMax, extents.y_bearing); |
| plan->head_maxp_info.yMin = hb_min (plan->head_maxp_info.yMin, extents.y_bearing + extents.height); |
| } |
| |
| if (_hmtx.has_data ()) |
| { |
| int hori_aw = _hmtx.get_advance_without_var_unscaled (old_gid); |
| if (_hmtx.var_table.get_length ()) |
| hori_aw += (int) roundf (_hmtx.var_table->get_advance_delta_unscaled (old_gid, font->coords, font->num_coords, |
| hvar_store_cache)); |
| int lsb = extents.x_bearing; |
| if (!has_bounds_info) |
| { |
| _hmtx.get_leading_bearing_without_var_unscaled (old_gid, &lsb); |
| } |
| plan->hmtx_map.set (new_gid, hb_pair ((unsigned) hori_aw, lsb)); |
| plan->bounds_width_vec[new_gid] = extents.width; |
| } |
| |
| if (_vmtx.has_data ()) |
| { |
| int vert_aw = _vmtx.get_advance_without_var_unscaled (old_gid); |
| if (_vmtx.var_table.get_length ()) |
| vert_aw += (int) roundf (_vmtx.var_table->get_advance_delta_unscaled (old_gid, font->coords, font->num_coords, |
| vvar_store_cache)); |
| hb_position_t vorg_x = 0; |
| hb_position_t vorg_y = 0; |
| int tsb = 0; |
| if (has_bounds_info && |
| hb_font_get_glyph_v_origin (font, old_gid, &vorg_x, &vorg_y)) |
| { |
| tsb = vorg_y - extents.y_bearing; |
| } else { |
| _vmtx.get_leading_bearing_without_var_unscaled (old_gid, &tsb); |
| } |
| |
| plan->vmtx_map.set (new_gid, hb_pair ((unsigned) vert_aw, tsb)); |
| plan->bounds_height_vec[new_gid] = extents.height; |
| } |
| } |
| hb_font_destroy (font); |
| if (hvar_store_cache) |
| _hmtx.var_table->get_var_store ().destroy_cache (hvar_store_cache); |
| if (vvar_store_cache) |
| _vmtx.var_table->get_var_store ().destroy_cache (vvar_store_cache); |
| } |
| #endif |
| |
| bool |
| get_instance_glyphs_contour_points (hb_subset_plan_t *plan) |
| { |
| /* contour_points vector only needed for updating gvar table (infer delta and |
| * iup delta optimization) during partial instancing */ |
| if (plan->user_axes_location.is_empty () || plan->all_axes_pinned) |
| return true; |
| |
| OT::glyf_accelerator_t glyf (plan->source); |
| |
| for (auto &_ : plan->new_to_old_gid_list) |
| { |
| hb_codepoint_t new_gid = _.first; |
| contour_point_vector_t all_points; |
| if (new_gid == 0 && !(plan->flags & HB_SUBSET_FLAGS_NOTDEF_OUTLINE)) |
| { |
| if (unlikely (!plan->new_gid_contour_points_map.set (new_gid, all_points))) |
| return false; |
| continue; |
| } |
| |
| hb_codepoint_t old_gid = _.second; |
| auto glyph = glyf.glyph_for_gid (old_gid); |
| if (unlikely (!glyph.get_all_points_without_var (plan->source, all_points))) |
| return false; |
| if (unlikely (!plan->new_gid_contour_points_map.set (new_gid, all_points))) |
| return false; |
| |
| /* composite new gids are only needed by iup delta optimization */ |
| if ((plan->flags & HB_SUBSET_FLAGS_OPTIMIZE_IUP_DELTAS) && glyph.is_composite ()) |
| plan->composite_new_gids.add (new_gid); |
| } |
| return true; |
| } |
| |
| template<typename DeltaSetIndexMap> |
| void |
| remap_colrv1_delta_set_index_indices (const DeltaSetIndexMap &index_map, |
| const hb_set_t &delta_set_idxes, |
| hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map, /* IN/OUT */ |
| hb_map_t &new_deltaset_idx_varidx_map /* OUT */) |
| { |
| if (!index_map.get_map_count ()) |
| return; |
| |
| hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> delta_set_idx_delta_map; |
| unsigned new_delta_set_idx = 0; |
| for (unsigned delta_set_idx : delta_set_idxes) |
| { |
| unsigned var_idx = index_map.map (delta_set_idx); |
| unsigned new_varidx = HB_OT_LAYOUT_NO_VARIATIONS_INDEX; |
| int delta = 0; |
| |
| if (var_idx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX) |
| { |
| hb_pair_t<unsigned, int> *new_varidx_delta; |
| if (!variation_idx_delta_map.has (var_idx, &new_varidx_delta)) continue; |
| |
| new_varidx = hb_first (*new_varidx_delta); |
| delta = hb_second (*new_varidx_delta); |
| } |
| |
| new_deltaset_idx_varidx_map.set (new_delta_set_idx, new_varidx); |
| delta_set_idx_delta_map.set (delta_set_idx, hb_pair_t<unsigned, int> (new_delta_set_idx, delta)); |
| new_delta_set_idx++; |
| } |
| variation_idx_delta_map = std::move (delta_set_idx_delta_map); |
| } |
| |
| template void |
| remap_colrv1_delta_set_index_indices<OT::DeltaSetIndexMap> (const OT::DeltaSetIndexMap &index_map, |
| const hb_set_t &delta_set_idxes, |
| hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> &variation_idx_delta_map, /* IN/OUT */ |
| hb_map_t &new_deltaset_idx_varidx_map /* OUT */); |
| |
| #endif |