From ac0e22fa8efae1fc581c2687f29f027fcb69a36c Mon Sep 17 00:00:00 2001 From: Qunxin Liu Date: Fri, 22 Jul 2022 09:37:09 -0700 Subject: [PATCH 1/3] [instance] update glyf/hmtx/vmtx tables --- src/OT/glyf/CompositeGlyph.hh | 56 +++++++++++++++ src/OT/glyf/Glyph.hh | 118 ++++++++++++++++++++++++++++++- src/OT/glyf/SimpleGlyph.hh | 126 ++++++++++++++++++++++++++++++++++ src/OT/glyf/SubsetGlyph.hh | 14 ++++ src/OT/glyf/glyf.hh | 42 +++++++++++- src/hb-ot-hmtx-table.hh | 45 +++++++++--- src/hb-subset-plan.cc | 26 +++---- src/hb-subset-plan.hh | 8 +++ src/hb-subset.cc | 40 ++++++++++- 9 files changed, 448 insertions(+), 27 deletions(-) diff --git a/src/OT/glyf/CompositeGlyph.hh b/src/OT/glyf/CompositeGlyph.hh index 98c2ee4e7..a2071a808 100644 --- a/src/OT/glyf/CompositeGlyph.hh +++ b/src/OT/glyf/CompositeGlyph.hh @@ -105,6 +105,29 @@ struct CompositeGlyphRecord } } + void apply_delta_to_offsets (const contour_point_t &p_delta) + { + HBINT8 *p = &StructAfter (flags); +#ifndef HB_NO_BEYOND_64K + if (flags & GID_IS_24BIT) + p += HBGlyphID24::static_size; + else +#endif + p += HBGlyphID16::static_size; + + if (flags & ARG_1_AND_2_ARE_WORDS) + { + HBINT16 *px = reinterpret_cast (p); + px[0] += roundf (p_delta.x); + px[1] += roundf (p_delta.y); + } + else + { + p[0] += roundf (p_delta.x); + p[1] += roundf (p_delta.y); + } + } + protected: bool scaled_offsets () const { return (flags & (SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET)) == SCALED_COMPONENT_OFFSET; } @@ -288,6 +311,39 @@ struct CompositeGlyph return; glyph_chain.set_overlaps_flag (); } + + bool compile_bytes_with_deltas (const hb_bytes_t &source_bytes, + const contour_point_vector_t &deltas, + hb_bytes_t &dest_bytes /* OUT */) + { + int len = source_bytes.length - GlyphHeader::static_size; + if (len <= 0 || header.numberOfContours != -1) + { + dest_bytes = hb_bytes_t (); + return true; + } + + char *p = (char *) hb_calloc (len, sizeof (char)); + if (unlikely (!p)) return false; + + memcpy (p, source_bytes.arrayZ + GlyphHeader::static_size, len); + dest_bytes = hb_bytes_t (p, len); + + auto it = composite_iter_t (dest_bytes, (CompositeGlyphRecord *)p); + + unsigned i = 0; + for (auto &component : it) + { + if (!component.is_anchored ()) + { + /* last 4 points in deltas are phantom points and should not be included*/ + if (i >= deltas.length - 4) return false; + const_cast (component).apply_delta_to_offsets (deltas[i]); + } + i++; + } + return true; + } }; diff --git a/src/OT/glyf/Glyph.hh b/src/OT/glyf/Glyph.hh index 3efe538f3..f5dc3d4ea 100644 --- a/src/OT/glyf/Glyph.hh +++ b/src/OT/glyf/Glyph.hh @@ -72,12 +72,110 @@ struct Glyph } } + void update_mtx (const hb_subset_plan_t *plan, + int xMin, int yMax, + const contour_point_vector_t &all_points) const + { + hb_codepoint_t new_gid = 0; + if (!plan->new_gid_for_old_gid (gid, &new_gid)) + return; + + unsigned len = all_points.length; + float leftSideX = all_points[len - 4].x; + float rightSideX = all_points[len - 3].x; + float topSideY = all_points[len - 2].y; + float bottomSideY = all_points[len - 1].y; + + int hori_aw = roundf (rightSideX - leftSideX); + if (hori_aw < 0) hori_aw = 0; + int lsb = roundf (xMin - leftSideX); + plan->hmtx_map->set (new_gid, hb_pair (hori_aw, lsb)); + + int vert_aw = roundf (topSideY - bottomSideY); + if (vert_aw < 0) vert_aw = 0; + int tsb = roundf (topSideY - yMax); + plan->vmtx_map->set (new_gid, hb_pair (vert_aw, tsb)); + } + + bool compile_header_bytes (const hb_subset_plan_t *plan, + const contour_point_vector_t &all_points, + hb_bytes_t &dest_bytes /* OUT */) const + { + if (all_points.length == 4) //Empty glyph + { + dest_bytes = hb_bytes_t (); + return true; + } + + GlyphHeader *glyph_header = (GlyphHeader *) hb_calloc (1, GlyphHeader::static_size); + if (unlikely (!glyph_header)) return false; + + int xMin, xMax; + xMin = xMax = roundf (all_points[0].x); + + int yMin, yMax; + yMin = yMax = roundf (all_points[0].y); + + for (unsigned i = 1; i < all_points.length - 4; i++) + { + float rounded_x = roundf (all_points[i].x); + float rounded_y = roundf (all_points[i].y); + xMin = hb_min (xMin, rounded_x); + xMax = hb_max (xMax, rounded_x); + yMin = hb_min (yMin, rounded_y); + yMax = hb_max (yMax, rounded_y); + } + + update_mtx (plan, xMin, yMax, all_points); + + glyph_header->numberOfContours = header->numberOfContours; + glyph_header->xMin = xMin; + glyph_header->yMin = yMin; + glyph_header->xMax = xMax; + glyph_header->yMax = yMax; + + dest_bytes = hb_bytes_t ((const char *)glyph_header, GlyphHeader::static_size); + return true; + } + + bool compile_bytes_with_deltas (const hb_subset_plan_t *plan, + hb_font_t *font, + const glyf_accelerator_t &glyf, + hb_bytes_t &dest_start, /* IN/OUT */ + hb_bytes_t &dest_end /* OUT */) const + { + contour_point_vector_t all_points, deltas; + get_points (font, glyf, all_points, &deltas); + + switch (type) { + case COMPOSITE: + if (!CompositeGlyph (*header, bytes).compile_bytes_with_deltas (dest_start, + deltas, + dest_end)) + return false; + break; + case SIMPLE: + if (!SimpleGlyph (*header, bytes).compile_bytes_with_deltas (all_points, + plan->flags & HB_SUBSET_FLAGS_NO_HINTING, + dest_end)) + return false; + break; + default: + //no need to compile empty glyph (.notdef) + return true; + } + + return compile_header_bytes (plan, all_points, dest_start); + } + + /* Note: Recursively calls itself. * all_points includes phantom points */ template bool get_points (hb_font_t *font, const accelerator_t &glyf_accelerator, contour_point_vector_t &all_points /* OUT */, + contour_point_vector_t *deltas = nullptr, /* OUT */ bool phantom_only = false, unsigned int depth = 0) const { @@ -130,10 +228,28 @@ struct Glyph phantoms[PHANTOM_BOTTOM].y = v_orig - (int) v_adv; } + if (deltas != nullptr && depth == 0 && type == COMPOSITE) + { + if (unlikely (!deltas->resize (points.length))) return false; + for (unsigned i = 0 ; i < points.length; i++) + deltas->arrayZ[i] = points.arrayZ[i]; + } + #ifndef HB_NO_VAR glyf_accelerator.gvar->apply_deltas_to_points (gid, font, points.as_array ()); #endif + // mainly used by CompositeGlyph calculating new X/Y offset value so no need to extend it + // with child glyphs' points + if (deltas != nullptr && depth == 0 && type == COMPOSITE) + { + for (unsigned i = 0 ; i < points.length; i++) + { + deltas->arrayZ[i].x = points.arrayZ[i].x - deltas->arrayZ[i].x; + deltas->arrayZ[i].y = points.arrayZ[i].y - deltas->arrayZ[i].y; + } + } + switch (type) { case SIMPLE: if (!inplace) @@ -148,7 +264,7 @@ struct Glyph comp_points.reset (); if (unlikely (!glyf_accelerator.glyph_for_gid (item.get_gid ()) .get_points (font, glyf_accelerator, comp_points, - phantom_only, depth + 1))) + deltas, phantom_only, depth + 1))) return false; /* Copy phantom points from component if USE_MY_METRICS flag set */ diff --git a/src/OT/glyf/SimpleGlyph.hh b/src/OT/glyf/SimpleGlyph.hh index 6df978cf1..11e4e9c0e 100644 --- a/src/OT/glyf/SimpleGlyph.hh +++ b/src/OT/glyf/SimpleGlyph.hh @@ -206,6 +206,132 @@ struct SimpleGlyph && read_points (p, points_, end, &contour_point_t::y, FLAG_Y_SHORT, FLAG_Y_SAME); } + + static void encode_coord (int value, + uint8_t &flag, + const simple_glyph_flag_t short_flag, + const simple_glyph_flag_t same_flag, + hb_vector_t &coords /* OUT */) + { + if (value == 0) + { + flag |= same_flag; + } + else if (value >= -255 && value <= 255) + { + flag |= short_flag; + if (value > 0) flag |= same_flag; + else value = -value; + + coords.push ((uint8_t)value); + } + else + { + int16_t val = value; + coords.push (val >> 8); + coords.push (val & 0xff); + } + } + + static void encode_flag (uint8_t &flag, + uint8_t &repeat, + uint8_t &lastflag, + hb_vector_t &flags /* OUT */) + { + if (flag == lastflag && repeat != 255) + { + repeat = repeat + 1; + if (repeat == 1) + { + flags.push(flag); + } + else + { + unsigned len = flags.length; + flags[len-2] = flag | FLAG_REPEAT; + flags[len-1] = repeat; + } + } + else + { + repeat = 0; + flags.push (flag); + } + lastflag = flag; + } + + bool compile_bytes_with_deltas (const contour_point_vector_t &all_points, + bool no_hinting, + hb_bytes_t &dest_bytes /* OUT */) + { + if (header.numberOfContours == 0 || all_points.length <= 4) + { + dest_bytes = hb_bytes_t (); + return true; + } + //convert absolute values to relative values + unsigned num_points = all_points.length - 4; + hb_vector_t> deltas; + deltas.resize (num_points); + + for (unsigned i = 0; i < num_points; i++) + { + deltas[i].first = i == 0 ? roundf (all_points[i].x) : roundf (all_points[i].x) - roundf (all_points[i-1].x); + deltas[i].second = i == 0 ? roundf (all_points[i].y) : roundf (all_points[i].y) - roundf (all_points[i-1].y); + } + + hb_vector_t flags, x_coords, y_coords; + flags.alloc (num_points); + x_coords.alloc (2*num_points); + y_coords.alloc (2*num_points); + + uint8_t lastflag, repeat = 0; + + for (unsigned i = 0; i < num_points; i++) + { + uint8_t flag = all_points[i].flag; + flag &= FLAG_ON_CURVE + FLAG_OVERLAP_SIMPLE; + + encode_coord (deltas[i].first, flag, FLAG_X_SHORT, FLAG_X_SAME, x_coords); + encode_coord (deltas[i].second, flag, FLAG_Y_SHORT, FLAG_Y_SAME, y_coords); + if (i == 0) lastflag = flag + 1; //make lastflag != flag for the first point + encode_flag (flag, repeat, lastflag, flags); + } + + unsigned len_before_instrs = 2 * header.numberOfContours + 2; + unsigned len_instrs = instructions_length (); + unsigned total_len = len_before_instrs + flags.length + x_coords.length + y_coords.length; + + if (!no_hinting) + total_len += len_instrs; + + char *p = (char *) hb_calloc (total_len, sizeof (char)); + if (unlikely (!p)) return false; + + const char *src = bytes.arrayZ + GlyphHeader::static_size; + char *cur = p; + memcpy (p, src, len_before_instrs); + + cur += len_before_instrs; + src += len_before_instrs; + + if (!no_hinting) + { + memcpy (cur, src, len_instrs); + cur += len_instrs; + } + + memcpy (cur, flags.arrayZ, flags.length); + cur += flags.length; + + memcpy (cur, x_coords.arrayZ, x_coords.length); + cur += x_coords.length; + + memcpy (cur, y_coords.arrayZ, y_coords.length); + + dest_bytes = hb_bytes_t (p, total_len); + return true; + } }; diff --git a/src/OT/glyf/SubsetGlyph.hh b/src/OT/glyf/SubsetGlyph.hh index 7ae8fe307..7ddefc5a9 100644 --- a/src/OT/glyf/SubsetGlyph.hh +++ b/src/OT/glyf/SubsetGlyph.hh @@ -6,6 +6,9 @@ namespace OT { + +struct glyf_accelerator_t; + namespace glyf_impl { @@ -55,6 +58,17 @@ struct SubsetGlyph return_trace (true); } + bool compile_bytes_with_deltas (const hb_subset_plan_t *plan, + hb_font_t *font, + const glyf_accelerator_t &glyf) + { return source_glyph.compile_bytes_with_deltas (plan, font, glyf, dest_start, dest_end); } + + void free_compiled_bytes () + { + dest_start.fini (); + dest_end.fini (); + } + void drop_hints_bytes () { source_glyph.drop_hints_bytes (dest_start, dest_end); } diff --git a/src/OT/glyf/glyf.hh b/src/OT/glyf/glyf.hh index bcaf44fc1..7ba2b8eb0 100644 --- a/src/OT/glyf/glyf.hh +++ b/src/OT/glyf/glyf.hh @@ -24,7 +24,6 @@ namespace OT { */ #define HB_OT_TAG_glyf HB_TAG('g','l','y','f') - struct glyf { friend struct glyf_accelerator_t; @@ -75,6 +74,9 @@ struct glyf hb_vector_t glyphs; _populate_subset_glyphs (c->plan, &glyphs); + if (!c->plan->pinned_at_default) + _compile_subset_glyphs_with_deltas (c->plan, &glyphs); + auto padded_offsets = + hb_iter (glyphs) | hb_map (&glyf_impl::SubsetGlyph::padded_size) @@ -93,6 +95,8 @@ struct glyf } + if (!c->plan->pinned_at_default) + _free_compiled_subset_glyphs (&glyphs); if (unlikely (c->serializer->in_error ())) return_trace (false); return_trace (c->serializer->check_success (glyf_impl::_add_loca_and_head (c->plan, padded_offsets, @@ -102,6 +106,16 @@ struct glyf void _populate_subset_glyphs (const hb_subset_plan_t *plan, hb_vector_t *glyphs /* OUT */) const; + + void + _compile_subset_glyphs_with_deltas (const hb_subset_plan_t *plan, + hb_vector_t *glyphs /* OUT */) const; + + void _free_compiled_subset_glyphs (hb_vector_t *glyphs) const + { + for (auto _ : *glyphs) + _.free_compiled_bytes (); + } protected: UnsizedArrayOf @@ -166,7 +180,7 @@ struct glyf_accelerator_t contour_point_vector_t all_points; bool phantom_only = !consumer.is_consuming_contour_points (); - if (unlikely (!glyph_for_gid (gid).get_points (font, *this, all_points, phantom_only))) + if (unlikely (!glyph_for_gid (gid).get_points (font, *this, all_points, nullptr, phantom_only))) return false; if (consumer.is_consuming_contour_points ()) @@ -389,6 +403,30 @@ glyf::_populate_subset_glyphs (const hb_subset_plan_t *plan, ; } +inline void +glyf::_compile_subset_glyphs_with_deltas (const hb_subset_plan_t *plan, + hb_vector_t *glyphs /* OUT */) const +{ + OT::glyf_accelerator_t glyf (plan->source); + hb_font_t *font = hb_font_create (plan->source); + + hb_vector_t vars; + vars.alloc (plan->user_axes_location->get_population ()); + + for (auto _ : *plan->user_axes_location) + { + hb_variation_t var; + var.tag = _.first; + var.value = _.second; + vars.push (var); + } + + hb_font_set_variations (font, vars.arrayZ, plan->user_axes_location->get_population ()); + for (auto& subset_glyph : *glyphs) + const_cast (subset_glyph).compile_bytes_with_deltas (plan, font, glyf); + + hb_font_destroy (font); +} } /* namespace OT */ diff --git a/src/hb-ot-hmtx-table.hh b/src/hb-ot-hmtx-table.hh index 50e4b54fd..96a394ba4 100644 --- a/src/hb-ot-hmtx-table.hh +++ b/src/hb-ot-hmtx-table.hh @@ -73,6 +73,8 @@ struct hmtxvmtx return_trace (true); } + const hb_hashmap_t>* get_mtx_map (const hb_subset_plan_t *plan) const + { return T::is_horizontal ? plan->hmtx_map : plan->vmtx_map; } bool subset_update_header (hb_subset_plan_t *plan, unsigned int num_hmetrics) const @@ -130,14 +132,15 @@ struct hmtxvmtx accelerator_t _mtx (c->plan->source); unsigned num_long_metrics; + const hb_hashmap_t> *mtx_map = get_mtx_map (c->plan); { /* Determine num_long_metrics to encode. */ auto& plan = c->plan; + num_long_metrics = plan->num_output_glyphs (); - hb_codepoint_t old_gid = 0; - unsigned int last_advance = plan->old_gid_for_new_gid (num_long_metrics - 1, &old_gid) ? _mtx.get_advance_without_var_unscaled (old_gid) : 0; + unsigned int last_advance = get_new_gid_advance_unscaled (plan, mtx_map, num_long_metrics - 1, _mtx); while (num_long_metrics > 1 && - last_advance == (plan->old_gid_for_new_gid (num_long_metrics - 2, &old_gid) ? _mtx.get_advance_without_var_unscaled (old_gid) : 0)) + last_advance == get_new_gid_advance_unscaled (plan, mtx_map, num_long_metrics - 2, _mtx)) { num_long_metrics--; } @@ -145,14 +148,18 @@ struct hmtxvmtx auto it = + hb_range (c->plan->num_output_glyphs ()) - | hb_map ([c, &_mtx] (unsigned _) + | hb_map ([c, &_mtx, mtx_map] (unsigned _) { - hb_codepoint_t old_gid; - if (!c->plan->old_gid_for_new_gid (_, &old_gid)) - return hb_pair (0u, 0); - int lsb = 0; - (void) _mtx.get_leading_bearing_without_var_unscaled (old_gid, &lsb); - return hb_pair (_mtx.get_advance_without_var_unscaled (old_gid), +lsb); + if (!mtx_map->has (_)) + { + hb_codepoint_t old_gid; + if (!c->plan->old_gid_for_new_gid (_, &old_gid)) + return hb_pair (0u, 0); + int lsb = 0; + (void) _mtx.get_leading_bearing_without_var_unscaled (old_gid, &lsb); + return hb_pair (_mtx.get_advance_without_var_unscaled (old_gid), +lsb); + } + return mtx_map->get (_); }) ; @@ -330,6 +337,24 @@ struct hmtxvmtx hb_blob_ptr_t var_table; }; + /* get advance: when no variations, call get_advance_without_var_unscaled. + * when there're variations, get advance value from mtx_map in subset_plan*/ + unsigned get_new_gid_advance_unscaled (const hb_subset_plan_t *plan, + const hb_hashmap_t> *mtx_map, + unsigned new_gid, + const accelerator_t &_mtx) const + { + if (mtx_map->is_empty () || + (new_gid == 0 && !mtx_map->has (new_gid))) + { + hb_codepoint_t old_gid = 0; + return plan->old_gid_for_new_gid (new_gid, &old_gid) ? + _mtx.get_advance_without_var_unscaled (old_gid) : 0; + } + else + { return mtx_map->get (new_gid).first; } + } + protected: UnsizedArrayOf longMetricZ; /* Paired advance width and leading diff --git a/src/hb-subset-plan.cc b/src/hb-subset-plan.cc index 9123ba7be..146900da9 100644 --- a/src/hb-subset-plan.cc +++ b/src/hb-subset-plan.cc @@ -585,12 +585,9 @@ _nameid_closure (hb_face_t *face, #ifndef HB_NO_VAR static void -_normalize_axes_location (hb_face_t *face, - const hb_hashmap_t *user_axes_location, - hb_hashmap_t *normalized_axes_location, /* OUT */ - bool &all_axes_pinned) +_normalize_axes_location (hb_face_t *face, hb_subset_plan_t *plan) { - if (user_axes_location->is_empty ()) + if (plan->user_axes_location->is_empty ()) return; hb_array_t axes = face->table.fvar->get_axes (); @@ -605,25 +602,27 @@ _normalize_axes_location (hb_face_t *face, for (const auto& axis : axes) { hb_tag_t axis_tag = axis.get_axis_tag (); - if (!user_axes_location->has (axis_tag)) + if (!plan->user_axes_location->has (axis_tag)) { axis_not_pinned = true; } else { - int normalized_v = axis.normalize_axis_value (user_axes_location->get (axis_tag)); + int normalized_v = axis.normalize_axis_value (plan->user_axes_location->get (axis_tag)); if (has_avar && axis_count < face->table.avar->get_axis_count ()) { normalized_v = seg_maps->map (normalized_v); } - normalized_axes_location->set (axis_tag, normalized_v); + plan->axes_location->set (axis_tag, normalized_v); + if (normalized_v != 0) + plan->pinned_at_default = false; } if (has_avar) seg_maps = &StructAfter (*seg_maps); axis_count++; } - all_axes_pinned = !axis_not_pinned; + plan->all_axes_pinned = !axis_not_pinned; } #endif /** @@ -692,6 +691,10 @@ hb_subset_plan_create_or_fail (hb_face_t *face, if (plan->user_axes_location && input->axes_location) *plan->user_axes_location = *input->axes_location; plan->all_axes_pinned = false; + plan->pinned_at_default = true; + + plan->check_success (plan->vmtx_map = hb_hashmap_create> ()); + plan->check_success (plan->hmtx_map = hb_hashmap_create> ()); if (unlikely (plan->in_error ())) { hb_subset_plan_destroy (plan); @@ -726,10 +729,7 @@ hb_subset_plan_create_or_fail (hb_face_t *face, } #ifndef HB_NO_VAR - _normalize_axes_location (face, - input->axes_location, - plan->axes_location, - plan->all_axes_pinned); + _normalize_axes_location (face, plan); #endif _nameid_closure (face, plan->name_ids, plan->all_axes_pinned, plan->user_axes_location); diff --git a/src/hb-subset-plan.hh b/src/hb-subset-plan.hh index 874d12540..0dc020525 100644 --- a/src/hb-subset-plan.hh +++ b/src/hb-subset-plan.hh @@ -73,6 +73,8 @@ struct hb_subset_plan_t hb_hashmap_destroy (gpos_langsys); hb_hashmap_destroy (axes_location); hb_hashmap_destroy (sanitized_table_cache); + hb_hashmap_destroy (hmtx_map); + hb_hashmap_destroy (vmtx_map); if (user_axes_location) { @@ -156,6 +158,12 @@ struct hb_subset_plan_t //user specified axes location map hb_hashmap_t *user_axes_location; bool all_axes_pinned; + bool pinned_at_default; + + //hmtx metrics map: new gid->(advance, lsb) + hb_hashmap_t> *hmtx_map; + //vmtx metrics map: new gid->(advance, lsb) + hb_hashmap_t> *vmtx_map; public: diff --git a/src/hb-subset.cc b/src/hb-subset.cc index f62e7e895..1a0bcbd1f 100644 --- a/src/hb-subset.cc +++ b/src/hb-subset.cc @@ -405,6 +405,27 @@ _passthrough (hb_subset_plan_t *plan, hb_tag_t tag) return result; } +static bool +_dependencies_satisfied (hb_subset_plan_t *plan, hb_tag_t tag, + hb_set_t &visited_set, hb_set_t &revisit_set) +{ + switch (tag) + { + case HB_OT_TAG_hmtx: + case HB_OT_TAG_vmtx: + if (!plan->pinned_at_default && + !visited_set.has (HB_OT_TAG_glyf)) + { + revisit_set.add (tag); + return false; + } + return true; + + default: + return true; + } +} + static bool _subset_table (hb_subset_plan_t *plan, hb_vector_t &buf, @@ -514,7 +535,7 @@ hb_subset_plan_execute_or_fail (hb_subset_plan_t *plan) return nullptr; } - hb_set_t tags_set; + hb_set_t tags_set, revisit_set; bool success = true; hb_tag_t table_tags[32]; unsigned offset = 0, num_tables = ARRAY_LENGTH (table_tags); @@ -527,10 +548,27 @@ hb_subset_plan_execute_or_fail (hb_subset_plan_t *plan) { hb_tag_t tag = table_tags[i]; if (_should_drop_table (plan, tag) && !tags_set.has (tag)) continue; + if (!_dependencies_satisfied (plan, tag, tags_set, revisit_set)) continue; tags_set.add (tag); success = _subset_table (plan, buf, tag); if (unlikely (!success)) goto end; } + + /*delayed subsetting for some tables since they might have dependency on other tables in some cases: + e.g: during instantiating glyf tables, hmetrics/vmetrics are updated and saved in subset plan, + hmtx/vmtx subsetting need to use these updated metrics values*/ + while (!revisit_set.is_empty ()) + { + hb_set_t revisit_temp; + for (hb_tag_t tag : revisit_set) + { + if (!_dependencies_satisfied (plan, tag, tags_set, revisit_temp)) continue; + tags_set.add (tag); + success = _subset_table (plan, buf, tag); + if (unlikely (!success)) goto end; + } + revisit_set = revisit_temp; + } offset += num_tables; } From 4882c717b5bc6f2cfe45c5c84bb5ddc060f8ee2e Mon Sep 17 00:00:00 2001 From: Qunxin Liu Date: Wed, 27 Jul 2022 12:54:33 -0700 Subject: [PATCH 2/3] [instance] update OS/2.usWeightClass and OS/2.usWidthClass --- src/hb-ot-os2-table.hh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/hb-ot-os2-table.hh b/src/hb-ot-os2-table.hh index 3473afef5..780d9627c 100644 --- a/src/hb-ot-os2-table.hh +++ b/src/hb-ot-os2-table.hh @@ -166,6 +166,21 @@ struct OS2 } } + float map_wdth_to_widthclass(float width) const + { + if (width < 50) return 1.0f; + if (width > 200) return 9.0f; + + float ratio = (width - 50) / 12.5f; + int a = (int) floorf (ratio); + int b = (int) ceilf (ratio); + + float va = 50 + a * 12.5f; + float vb = 50 + b * 12.5f; + + return a + 1.0f + (float) (b - a) * (width - va) / (vb - va); + } + bool subset (hb_subset_context_t *c) const { TRACE_SUBSET (this); @@ -183,6 +198,26 @@ struct OS2 _update_unicode_ranges (c->plan->unicodes, os2_prime->ulUnicodeRange); + if (c->plan->user_axes_location->has (HB_TAG ('w','g','h','t')) && + !c->plan->pinned_at_default) + { + float weight_class = c->plan->user_axes_location->get (HB_TAG ('w','g','h','t')); + if (!c->serializer->check_assign (os2_prime->usWeightClass, + roundf (hb_clamp (weight_class, 1.0f, 1000.0f)), + HB_SERIALIZE_ERROR_INT_OVERFLOW)) + return_trace (false); + } + + if (c->plan->user_axes_location->has (HB_TAG ('w','d','t','h')) && + !c->plan->pinned_at_default) + { + float width = c->plan->user_axes_location->get (HB_TAG ('w','d','t','h')); + if (!c->serializer->check_assign (os2_prime->usWidthClass, + roundf (map_wdth_to_widthclass (width)), + HB_SERIALIZE_ERROR_INT_OVERFLOW)) + return_trace (false); + } + return_trace (true); } From f887ee0c675b92a203133b98353a1820ad89af69 Mon Sep 17 00:00:00 2001 From: Qunxin Liu Date: Wed, 27 Jul 2022 13:18:21 -0700 Subject: [PATCH 3/3] [instance] update post.italicAngle Add tests for instancing glyf/hmtx --- src/hb-ot-post-table.hh | 8 ++++++++ ...ult.retain-all-codepoint.wght=200,wdth=90.ttf | Bin 0 -> 6540 bytes ...ult.retain-all-codepoint.wght=650,wdth=85.ttf | Bin 0 -> 6492 bytes ...ult.retain-all-codepoint.wght=200,wdth=90.ttf | Bin 0 -> 6332 bytes ...ult.retain-all-codepoint.wght=650,wdth=85.ttf | Bin 0 -> 6284 bytes .../data/fonts/Roboto-Variable.composite.ttf | Bin 0 -> 9576 bytes test/subset/data/tests/instantiate_glyf.tests | 13 +++++++++++++ test/subset/meson.build | 3 +++ 8 files changed, 24 insertions(+) create mode 100644 test/subset/data/expected/instantiate_glyf/Roboto-Variable.ABC.default.retain-all-codepoint.wght=200,wdth=90.ttf create mode 100644 test/subset/data/expected/instantiate_glyf/Roboto-Variable.ABC.default.retain-all-codepoint.wght=650,wdth=85.ttf create mode 100644 test/subset/data/expected/instantiate_glyf/Roboto-Variable.composite.default.retain-all-codepoint.wght=200,wdth=90.ttf create mode 100644 test/subset/data/expected/instantiate_glyf/Roboto-Variable.composite.default.retain-all-codepoint.wght=650,wdth=85.ttf create mode 100644 test/subset/data/fonts/Roboto-Variable.composite.ttf create mode 100644 test/subset/data/tests/instantiate_glyf.tests diff --git a/src/hb-ot-post-table.hh b/src/hb-ot-post-table.hh index f9cfd7f16..80d02ffba 100644 --- a/src/hb-ot-post-table.hh +++ b/src/hb-ot-post-table.hh @@ -102,6 +102,14 @@ struct post if (!serialize (c->serializer, glyph_names)) return_trace (false); + if (c->plan->user_axes_location->has (HB_TAG ('s','l','n','t')) && + !c->plan->pinned_at_default) + { + float italic_angle = c->plan->user_axes_location->get (HB_TAG ('s','l','n','t')); + italic_angle = hb_max (-90.f, hb_min (italic_angle, 90.f)); + post_prime->italicAngle.set_float (italic_angle); + } + if (glyph_names && version.major == 2) return_trace (v2X.subset (c)); diff --git a/test/subset/data/expected/instantiate_glyf/Roboto-Variable.ABC.default.retain-all-codepoint.wght=200,wdth=90.ttf b/test/subset/data/expected/instantiate_glyf/Roboto-Variable.ABC.default.retain-all-codepoint.wght=200,wdth=90.ttf new file mode 100644 index 0000000000000000000000000000000000000000..65fbb613a1a72e1a76c07a1ed6a202c0096537d3 GIT binary patch literal 6540 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 Nw{gdztLzZb{{V)}mm>fG literal 0 HcmV?d00001 diff --git a/test/subset/data/expected/instantiate_glyf/Roboto-Variable.composite.default.retain-all-codepoint.wght=200,wdth=90.ttf b/test/subset/data/expected/instantiate_glyf/Roboto-Variable.composite.default.retain-all-codepoint.wght=200,wdth=90.ttf new file mode 100644 index 0000000000000000000000000000000000000000..69682fed867202b132e3d4c0d844d4f23093a96f GIT binary patch literal 6332 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 E0JE}KQUCw| literal 0 HcmV?d00001 diff --git a/test/subset/data/fonts/Roboto-Variable.composite.ttf b/test/subset/data/fonts/Roboto-Variable.composite.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d0e12c0cc278e9196e9bb4780b7d4cc00890c84e GIT binary patch literal 9576 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