| /* |
| * 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); |
| } |
| } |
| |
| 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); |
| |
| 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; |
| } |
| |
| 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; |
| if (avar_table->has_v2_data () && !plan->all_axes_pinned) |
| { |
| DEBUG_MSG (SUBSET, nullptr, "Partial-instancing avar2 table is not supported."); |
| return false; |
| } |
| |
| unsigned coords_len = last_idx + 1; |
| 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; // Normalize -0 to 0 |
| if (normalized_default != 0.f) |
| plan->pinned_at_default = false; |
| |
| plan->normalized_coords[i] = roundf (normalized_default * 16384.f); |
| } |
| } |
| } |
| return true; |
| } |
| |
| 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); |
| } |
| |
| 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 |