[instance] update glyf/hmtx/vmtx tables

This commit is contained in:
Qunxin Liu 2022-07-22 09:37:09 -07:00
parent 23461b7502
commit ac0e22fa8e
9 changed files with 448 additions and 27 deletions

View File

@ -105,6 +105,29 @@ struct CompositeGlyphRecord
}
}
void apply_delta_to_offsets (const contour_point_t &p_delta)
{
HBINT8 *p = &StructAfter<HBINT8> (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<HBINT16 *> (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<CompositeGlyphRecord &> (component).apply_delta_to_offsets (deltas[i]);
}
i++;
}
return true;
}
};

View File

@ -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 <typename accelerator_t>
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 */

View File

@ -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<uint8_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<uint8_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<hb_pair_t<int, int>> 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<uint8_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;
}
};

View File

@ -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); }

View File

@ -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<glyf_impl::SubsetGlyph> 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<glyf_impl::SubsetGlyph> *glyphs /* OUT */) const;
void
_compile_subset_glyphs_with_deltas (const hb_subset_plan_t *plan,
hb_vector_t<glyf_impl::SubsetGlyph> *glyphs /* OUT */) const;
void _free_compiled_subset_glyphs (hb_vector_t<glyf_impl::SubsetGlyph> *glyphs) const
{
for (auto _ : *glyphs)
_.free_compiled_bytes ();
}
protected:
UnsizedArrayOf<HBUINT8>
@ -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<glyf_impl::SubsetGlyph> *glyphs /* OUT */) const
{
OT::glyf_accelerator_t glyf (plan->source);
hb_font_t *font = hb_font_create (plan->source);
hb_vector_t<hb_variation_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<glyf_impl::SubsetGlyph &> (subset_glyph).compile_bytes_with_deltas (plan, font, glyf);
hb_font_destroy (font);
}
} /* namespace OT */

View File

@ -73,6 +73,8 @@ struct hmtxvmtx
return_trace (true);
}
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>>* 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<unsigned, hb_pair_t<unsigned, int>> *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<V> 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<unsigned, hb_pair_t<unsigned, int>> *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<LongMetric>
longMetricZ; /* Paired advance width and leading

View File

@ -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<hb_tag_t, float> *user_axes_location,
hb_hashmap_t<hb_tag_t, int> *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<const OT::AxisRecord> 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<OT::SegmentMaps> (*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<unsigned, hb_pair_t<unsigned, int>> ());
plan->check_success (plan->hmtx_map = hb_hashmap_create<unsigned, hb_pair_t<unsigned, int>> ());
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);

View File

@ -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<hb_tag_t, float> *user_axes_location;
bool all_axes_pinned;
bool pinned_at_default;
//hmtx metrics map: new gid->(advance, lsb)
hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *hmtx_map;
//vmtx metrics map: new gid->(advance, lsb)
hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *vmtx_map;
public:

View File

@ -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<char> &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;
}