[aat] Remove deleted-glyhs after applying kerx/kern

Finally:  Fixes https://github.com/harfbuzz/harfbuzz/issues/1356

Test case:
$ ./hb-shape GeezaPro.ttc -u U+0628,U+064A,U+064E,U+0651,U+0629
[u0629.final.tehMarbuta=4+713|u064e_u0651.shaddaFatha=1@0,-200+0|u064a.medial.yeh=1+656|u0628.initial.beh=0+656]

The mark positioning (kern table CrossStream kerning) only works if deleted
glyph (as result of ligation) is still in stream and pushed through the
state machine.
This commit is contained in:
Behdad Esfahbod 2018-11-07 17:19:21 -05:00
parent 1909072235
commit 385f78b312
7 changed files with 142 additions and 107 deletions

View File

@ -1106,21 +1106,6 @@ struct mortmorx
} }
} }
inline static void remove_deleted_glyphs (hb_buffer_t *buffer)
{
if (unlikely (!buffer->successful)) return;
buffer->clear_output ();
for (buffer->idx = 0; buffer->idx < buffer->len && buffer->successful;)
{
if (unlikely (buffer->cur().codepoint == DELETED_GLYPH))
buffer->skip_glyph ();
else
buffer->next_glyph ();
}
buffer->swap_buffers ();
}
inline void apply (hb_aat_apply_context_t *c) const inline void apply (hb_aat_apply_context_t *c) const
{ {
if (unlikely (!c->buffer->successful)) return; if (unlikely (!c->buffer->successful)) return;
@ -1133,7 +1118,6 @@ struct mortmorx
if (unlikely (!c->buffer->successful)) return; if (unlikely (!c->buffer->successful)) return;
chain = &StructAfter<Chain<Types> > (*chain); chain = &StructAfter<Chain<Types> > (*chain);
} }
remove_deleted_glyphs (c->buffer);
} }
inline bool sanitize (hb_sanitize_context_t *c) const inline bool sanitize (hb_sanitize_context_t *c) const

View File

@ -193,7 +193,7 @@ hb_aat_layout_compile_map (const hb_aat_map_builder_t *mapper,
} }
hb_bool_t bool
hb_aat_layout_has_substitution (hb_face_t *face) hb_aat_layout_has_substitution (hb_face_t *face)
{ {
return face->table.morx->has_data () || return face->table.morx->has_data () ||
@ -224,8 +224,32 @@ hb_aat_layout_substitute (hb_ot_shape_plan_t *plan,
} }
} }
void
hb_aat_layout_zero_width_deleted_glyphs (hb_buffer_t *buffer)
{
unsigned int count = buffer->len;
hb_glyph_info_t *info = buffer->info;
hb_glyph_position_t *pos = buffer->pos;
for (unsigned int i = 0; i < count; i++)
if (unlikely (info[i].codepoint == AAT::DELETED_GLYPH))
pos[i].x_advance = pos[i].y_advance = 0;
}
hb_bool_t static bool
is_deleted_glyph (const hb_glyph_info_t *info)
{
return info->codepoint == AAT::DELETED_GLYPH;
}
void
hb_aat_layout_remove_deleted_glyphs (hb_buffer_t *buffer)
{
hb_ot_layout_delete_glyphs_inplace (buffer, is_deleted_glyph);
}
bool
hb_aat_layout_has_positioning (hb_face_t *face) hb_aat_layout_has_positioning (hb_face_t *face)
{ {
return face->table.kerx->has_data (); return face->table.kerx->has_data ();
@ -248,7 +272,7 @@ hb_aat_layout_position (hb_ot_shape_plan_t *plan,
} }
hb_bool_t bool
hb_aat_layout_has_tracking (hb_face_t *face) hb_aat_layout_has_tracking (hb_face_t *face)
{ {
return face->table.trak->has_data (); return face->table.trak->has_data ();

View File

@ -56,7 +56,7 @@ HB_INTERNAL void
hb_aat_layout_compile_map (const hb_aat_map_builder_t *mapper, hb_aat_layout_compile_map (const hb_aat_map_builder_t *mapper,
hb_aat_map_t *map); hb_aat_map_t *map);
HB_INTERNAL hb_bool_t HB_INTERNAL bool
hb_aat_layout_has_substitution (hb_face_t *face); hb_aat_layout_has_substitution (hb_face_t *face);
HB_INTERNAL void HB_INTERNAL void
@ -64,7 +64,13 @@ hb_aat_layout_substitute (hb_ot_shape_plan_t *plan,
hb_font_t *font, hb_font_t *font,
hb_buffer_t *buffer); hb_buffer_t *buffer);
HB_INTERNAL hb_bool_t HB_INTERNAL void
hb_aat_layout_zero_width_deleted_glyphs (hb_buffer_t *buffer);
HB_INTERNAL void
hb_aat_layout_remove_deleted_glyphs (hb_buffer_t *buffer);
HB_INTERNAL bool
hb_aat_layout_has_positioning (hb_face_t *face); hb_aat_layout_has_positioning (hb_face_t *face);
HB_INTERNAL void HB_INTERNAL void
@ -72,7 +78,7 @@ hb_aat_layout_position (hb_ot_shape_plan_t *plan,
hb_font_t *font, hb_font_t *font,
hb_buffer_t *buffer); hb_buffer_t *buffer);
HB_INTERNAL hb_bool_t HB_INTERNAL bool
hb_aat_layout_has_tracking (hb_face_t *face); hb_aat_layout_has_tracking (hb_face_t *face);
HB_INTERNAL void HB_INTERNAL void

View File

@ -113,7 +113,7 @@ struct ValueFormat : HBUINT16
if (!format) return ret; if (!format) return ret;
hb_font_t *font = c->font; hb_font_t *font = c->font;
hb_bool_t horizontal = HB_DIRECTION_IS_HORIZONTAL (c->direction); bool horizontal = HB_DIRECTION_IS_HORIZONTAL (c->direction);
if (format & xPlacement) glyph_pos.x_offset += font->em_scale_x (get_short (values++, &ret)); if (format & xPlacement) glyph_pos.x_offset += font->em_scale_x (get_short (values++, &ret));
if (format & yPlacement) glyph_pos.y_offset += font->em_scale_y (get_short (values++, &ret)); if (format & yPlacement) glyph_pos.y_offset += font->em_scale_y (get_short (values++, &ret));
@ -271,10 +271,10 @@ struct AnchorFormat2
unsigned int x_ppem = font->x_ppem; unsigned int x_ppem = font->x_ppem;
unsigned int y_ppem = font->y_ppem; unsigned int y_ppem = font->y_ppem;
hb_position_t cx = 0, cy = 0; hb_position_t cx = 0, cy = 0;
hb_bool_t ret; bool ret;
ret = (x_ppem || y_ppem) && ret = (x_ppem || y_ppem) &&
font->get_glyph_contour_point_for_origin (glyph_id, anchorPoint, HB_DIRECTION_LTR, &cx, &cy); font->get_glyph_contour_point_for_origin (glyph_id, anchorPoint, HB_DIRECTION_LTR, &cx, &cy);
*x = ret && x_ppem ? cx : font->em_fscale_x (xCoordinate); *x = ret && x_ppem ? cx : font->em_fscale_x (xCoordinate);
*y = ret && y_ppem ? cy : font->em_fscale_y (yCoordinate); *y = ret && y_ppem ? cy : font->em_fscale_y (yCoordinate);
} }

View File

@ -57,13 +57,13 @@
* kern * kern
*/ */
hb_bool_t bool
hb_ot_layout_has_kerning (hb_face_t *face) hb_ot_layout_has_kerning (hb_face_t *face)
{ {
return face->table.kern->has_data (); return face->table.kern->has_data ();
} }
hb_bool_t bool
hb_ot_layout_has_cross_kerning (hb_face_t *face) hb_ot_layout_has_cross_kerning (hb_face_t *face)
{ {
return face->table.kern->has_cross_stream (); return face->table.kern->has_cross_stream ();
@ -423,7 +423,7 @@ hb_ot_layout_table_get_feature_tags (hb_face_t *face,
return g.get_feature_tags (start_offset, feature_count, feature_tags); return g.get_feature_tags (start_offset, feature_count, feature_tags);
} }
hb_bool_t bool
hb_ot_layout_table_find_feature (hb_face_t *face, hb_ot_layout_table_find_feature (hb_face_t *face,
hb_tag_t table_tag, hb_tag_t table_tag,
hb_tag_t feature_tag, hb_tag_t feature_tag,
@ -933,12 +933,12 @@ hb_ot_layout_lookup_would_substitute (hb_face_t *face,
zero_context); zero_context);
} }
hb_bool_t bool
hb_ot_layout_lookup_would_substitute_fast (hb_face_t *face, hb_ot_layout_lookup_would_substitute_fast (hb_face_t *face,
unsigned int lookup_index, unsigned int lookup_index,
const hb_codepoint_t *glyphs, const hb_codepoint_t *glyphs,
unsigned int glyphs_length, unsigned int glyphs_length,
hb_bool_t zero_context) bool zero_context)
{ {
if (unlikely (lookup_index >= face->table.GSUB->lookup_count)) return false; if (unlikely (lookup_index >= face->table.GSUB->lookup_count)) return false;
OT::hb_would_apply_context_t c (face, glyphs, glyphs_length, (bool) zero_context); OT::hb_would_apply_context_t c (face, glyphs, glyphs_length, (bool) zero_context);
@ -955,6 +955,56 @@ hb_ot_layout_substitute_start (hb_font_t *font,
_hb_ot_layout_set_glyph_props (font, buffer); _hb_ot_layout_set_glyph_props (font, buffer);
} }
void
hb_ot_layout_delete_glyphs_inplace (hb_buffer_t *buffer,
bool (*filter) (const hb_glyph_info_t *info))
{
/* Merge clusters and delete filtered glyphs.
* NOTE! We can't use out-buffer as we have positioning data. */
unsigned int j = 0;
unsigned int count = buffer->len;
hb_glyph_info_t *info = buffer->info;
hb_glyph_position_t *pos = buffer->pos;
for (unsigned int i = 0; i < count; i++)
{
if (filter (&info[i]))
{
/* Merge clusters.
* Same logic as buffer->delete_glyph(), but for in-place removal. */
unsigned int cluster = info[i].cluster;
if (i + 1 < count && cluster == info[i + 1].cluster)
continue; /* Cluster survives; do nothing. */
if (j)
{
/* Merge cluster backward. */
if (cluster < info[j - 1].cluster)
{
unsigned int mask = info[i].mask;
unsigned int old_cluster = info[j - 1].cluster;
for (unsigned k = j; k && info[k - 1].cluster == old_cluster; k--)
buffer->set_cluster (info[k - 1], cluster, mask);
}
continue;
}
if (i + 1 < count)
buffer->merge_clusters (i, i + 2); /* Merge cluster forward. */
continue;
}
if (j != i)
{
info[j] = info[i];
pos[j] = pos[i];
}
j++;
}
buffer->len = j;
}
/** /**
* hb_ot_layout_lookup_substitute_closure: * hb_ot_layout_lookup_substitute_closure:
* *

View File

@ -45,10 +45,10 @@ struct hb_ot_shape_plan_t;
* kern * kern
*/ */
HB_INTERNAL hb_bool_t HB_INTERNAL bool
hb_ot_layout_has_kerning (hb_face_t *face); hb_ot_layout_has_kerning (hb_face_t *face);
HB_INTERNAL hb_bool_t HB_INTERNAL bool
hb_ot_layout_has_cross_kerning (hb_face_t *face); hb_ot_layout_has_cross_kerning (hb_face_t *face);
HB_INTERNAL void HB_INTERNAL void
@ -59,7 +59,7 @@ hb_ot_layout_kern (hb_ot_shape_plan_t *plan,
/* Private API corresponding to hb-ot-layout.h: */ /* Private API corresponding to hb-ot-layout.h: */
HB_INTERNAL hb_bool_t HB_INTERNAL bool
hb_ot_layout_table_find_feature (hb_face_t *face, hb_ot_layout_table_find_feature (hb_face_t *face,
hb_tag_t table_tag, hb_tag_t table_tag,
hb_tag_t feature_tag, hb_tag_t feature_tag,
@ -93,12 +93,12 @@ HB_MARK_AS_FLAG_T (hb_ot_layout_glyph_props_flags_t);
* GSUB/GPOS * GSUB/GPOS
*/ */
HB_INTERNAL hb_bool_t HB_INTERNAL bool
hb_ot_layout_lookup_would_substitute_fast (hb_face_t *face, hb_ot_layout_lookup_would_substitute_fast (hb_face_t *face,
unsigned int lookup_index, unsigned int lookup_index,
const hb_codepoint_t *glyphs, const hb_codepoint_t *glyphs,
unsigned int glyphs_length, unsigned int glyphs_length,
hb_bool_t zero_context); bool zero_context);
/* Should be called before all the substitute_lookup's are done. */ /* Should be called before all the substitute_lookup's are done. */
@ -106,6 +106,9 @@ HB_INTERNAL void
hb_ot_layout_substitute_start (hb_font_t *font, hb_ot_layout_substitute_start (hb_font_t *font,
hb_buffer_t *buffer); hb_buffer_t *buffer);
HB_INTERNAL void
hb_ot_layout_delete_glyphs_inplace (hb_buffer_t *buffer,
bool (*filter) (const hb_glyph_info_t *info));
namespace OT { namespace OT {
struct hb_ot_apply_context_t; struct hb_ot_apply_context_t;
@ -306,13 +309,13 @@ _hb_glyph_info_get_unicode_space_fallback_type (const hb_glyph_info_t *info)
static inline bool _hb_glyph_info_ligated (const hb_glyph_info_t *info); static inline bool _hb_glyph_info_ligated (const hb_glyph_info_t *info);
static inline hb_bool_t static inline bool
_hb_glyph_info_is_default_ignorable (const hb_glyph_info_t *info) _hb_glyph_info_is_default_ignorable (const hb_glyph_info_t *info)
{ {
return (info->unicode_props() & UPROPS_MASK_IGNORABLE) && return (info->unicode_props() & UPROPS_MASK_IGNORABLE) &&
!_hb_glyph_info_ligated (info); !_hb_glyph_info_ligated (info);
} }
static inline hb_bool_t static inline bool
_hb_glyph_info_is_default_ignorable_and_not_hidden (const hb_glyph_info_t *info) _hb_glyph_info_is_default_ignorable_and_not_hidden (const hb_glyph_info_t *info)
{ {
return ((info->unicode_props() & (UPROPS_MASK_IGNORABLE|UPROPS_MASK_HIDDEN)) return ((info->unicode_props() & (UPROPS_MASK_IGNORABLE|UPROPS_MASK_HIDDEN))
@ -366,17 +369,17 @@ _hb_glyph_info_is_unicode_format (const hb_glyph_info_t *info)
return _hb_glyph_info_get_general_category (info) == return _hb_glyph_info_get_general_category (info) ==
HB_UNICODE_GENERAL_CATEGORY_FORMAT; HB_UNICODE_GENERAL_CATEGORY_FORMAT;
} }
static inline hb_bool_t static inline bool
_hb_glyph_info_is_zwnj (const hb_glyph_info_t *info) _hb_glyph_info_is_zwnj (const hb_glyph_info_t *info)
{ {
return _hb_glyph_info_is_unicode_format (info) && (info->unicode_props() & UPROPS_MASK_Cf_ZWNJ); return _hb_glyph_info_is_unicode_format (info) && (info->unicode_props() & UPROPS_MASK_Cf_ZWNJ);
} }
static inline hb_bool_t static inline bool
_hb_glyph_info_is_zwj (const hb_glyph_info_t *info) _hb_glyph_info_is_zwj (const hb_glyph_info_t *info)
{ {
return _hb_glyph_info_is_unicode_format (info) && (info->unicode_props() & UPROPS_MASK_Cf_ZWJ); return _hb_glyph_info_is_unicode_format (info) && (info->unicode_props() & UPROPS_MASK_Cf_ZWJ);
} }
static inline hb_bool_t static inline bool
_hb_glyph_info_is_joiner (const hb_glyph_info_t *info) _hb_glyph_info_is_joiner (const hb_glyph_info_t *info)
{ {
return _hb_glyph_info_is_unicode_format (info) && (info->unicode_props() & (UPROPS_MASK_Cf_ZWNJ|UPROPS_MASK_Cf_ZWJ)); return _hb_glyph_info_is_unicode_format (info) && (info->unicode_props() & (UPROPS_MASK_Cf_ZWNJ|UPROPS_MASK_Cf_ZWJ));

View File

@ -476,7 +476,9 @@ hb_ensure_native_direction (hb_buffer_t *buffer)
} }
/* Substitute */ /*
* Substitute
*/
static inline void static inline void
hb_ot_mirror_chars (const hb_ot_shape_context_t *c) hb_ot_mirror_chars (const hb_ot_shape_context_t *c)
@ -582,10 +584,8 @@ hb_ot_shape_setup_masks (const hb_ot_shape_context_t *c)
} }
static void static void
hb_ot_zero_width_default_ignorables (const hb_ot_shape_context_t *c) hb_ot_zero_width_default_ignorables (const hb_buffer_t *buffer)
{ {
hb_buffer_t *buffer = c->buffer;
if (!(buffer->scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES) || if (!(buffer->scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES) ||
(buffer->flags & HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES) || (buffer->flags & HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES) ||
(buffer->flags & HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES)) (buffer->flags & HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES))
@ -601,21 +601,19 @@ hb_ot_zero_width_default_ignorables (const hb_ot_shape_context_t *c)
} }
static void static void
hb_ot_hide_default_ignorables (const hb_ot_shape_context_t *c) hb_ot_hide_default_ignorables (hb_buffer_t *buffer,
hb_font_t *font)
{ {
hb_buffer_t *buffer = c->buffer;
if (!(buffer->scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES) || if (!(buffer->scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES) ||
(buffer->flags & HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES)) (buffer->flags & HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES))
return; return;
unsigned int count = buffer->len; unsigned int count = buffer->len;
hb_glyph_info_t *info = buffer->info; hb_glyph_info_t *info = buffer->info;
hb_glyph_position_t *pos = buffer->pos;
hb_codepoint_t invisible = c->buffer->invisible; hb_codepoint_t invisible = buffer->invisible;
if (!(buffer->flags & HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && if (!(buffer->flags & HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) &&
(invisible || c->font->get_nominal_glyph (' ', &invisible))) (invisible || font->get_nominal_glyph (' ', &invisible)))
{ {
/* Replace default-ignorables with a zero-advance invisible glyph. */ /* Replace default-ignorables with a zero-advance invisible glyph. */
for (unsigned int i = 0; i < count; i++) for (unsigned int i = 0; i < count; i++)
@ -625,49 +623,7 @@ hb_ot_hide_default_ignorables (const hb_ot_shape_context_t *c)
} }
} }
else else
{ hb_ot_layout_delete_glyphs_inplace (buffer, _hb_glyph_info_is_default_ignorable);
/* Merge clusters and delete default-ignorables.
* NOTE! We can't use out-buffer as we have positioning data. */
unsigned int j = 0;
for (unsigned int i = 0; i < count; i++)
{
if (_hb_glyph_info_is_default_ignorable (&info[i]))
{
/* Merge clusters.
* Same logic as buffer->delete_glyph(), but for in-place removal. */
unsigned int cluster = info[i].cluster;
if (i + 1 < count && cluster == info[i + 1].cluster)
continue; /* Cluster survives; do nothing. */
if (j)
{
/* Merge cluster backward. */
if (cluster < info[j - 1].cluster)
{
unsigned int mask = info[i].mask;
unsigned int old_cluster = info[j - 1].cluster;
for (unsigned k = j; k && info[k - 1].cluster == old_cluster; k--)
buffer->set_cluster (info[k - 1], cluster, mask);
}
continue;
}
if (i + 1 < count)
buffer->merge_clusters (i, i + 2); /* Merge cluster forward. */
continue;
}
if (j != i)
{
info[j] = info[i];
pos[j] = pos[i];
}
j++;
}
buffer->len = j;
}
} }
@ -684,10 +640,10 @@ hb_ot_map_glyphs_fast (hb_buffer_t *buffer)
} }
static inline void static inline void
hb_synthesize_glyph_classes (const hb_ot_shape_context_t *c) hb_synthesize_glyph_classes (hb_buffer_t *buffer)
{ {
unsigned int count = c->buffer->len; unsigned int count = buffer->len;
hb_glyph_info_t *info = c->buffer->info; hb_glyph_info_t *info = buffer->info;
for (unsigned int i = 0; i < count; i++) for (unsigned int i = 0; i < count; i++)
{ {
hb_ot_layout_glyph_props_flags_t klass; hb_ot_layout_glyph_props_flags_t klass;
@ -739,7 +695,7 @@ hb_ot_substitute_complex (const hb_ot_shape_context_t *c)
hb_ot_layout_substitute_start (c->font, buffer); hb_ot_layout_substitute_start (c->font, buffer);
if (c->plan->fallback_glyph_classes) if (c->plan->fallback_glyph_classes)
hb_synthesize_glyph_classes (c); hb_synthesize_glyph_classes (c->buffer);
if (unlikely (c->plan->apply_morx)) if (unlikely (c->plan->apply_morx))
hb_aat_layout_substitute (c->plan, c->font, c->buffer); hb_aat_layout_substitute (c->plan, c->font, c->buffer);
@ -748,7 +704,7 @@ hb_ot_substitute_complex (const hb_ot_shape_context_t *c)
} }
static inline void static inline void
hb_ot_substitute (const hb_ot_shape_context_t *c) hb_ot_substitute_pre (const hb_ot_shape_context_t *c)
{ {
hb_ot_substitute_default (c); hb_ot_substitute_default (c);
@ -757,7 +713,21 @@ hb_ot_substitute (const hb_ot_shape_context_t *c)
hb_ot_substitute_complex (c); hb_ot_substitute_complex (c);
} }
/* Position */ static inline void
hb_ot_substitute_post (const hb_ot_shape_context_t *c)
{
hb_ot_hide_default_ignorables (c->buffer, c->font);
if (c->plan->apply_morx)
hb_aat_layout_remove_deleted_glyphs (c->buffer);
if (c->plan->shaper->postprocess_glyphs)
c->plan->shaper->postprocess_glyphs (c->plan, c->buffer, c->font);
}
/*
* Position
*/
static inline void static inline void
adjust_mark_offsets (hb_glyph_position_t *pos) adjust_mark_offsets (hb_glyph_position_t *pos)
@ -890,9 +860,11 @@ hb_ot_position_complex (const hb_ot_shape_context_t *c)
break; break;
} }
/* Finishing off GPOS has to follow a certain order. */ /* Finish off. Has to follow a certain order. */
hb_ot_layout_position_finish_advances (c->font, c->buffer); hb_ot_layout_position_finish_advances (c->font, c->buffer);
hb_ot_zero_width_default_ignorables (c); hb_ot_zero_width_default_ignorables (c->buffer);
if (c->plan->apply_morx)
hb_aat_layout_zero_width_deleted_glyphs (c->buffer);
hb_ot_layout_position_finish_offsets (c->font, c->buffer); hb_ot_layout_position_finish_offsets (c->font, c->buffer);
/* The nil glyph_h_origin() func returns 0, so no need to apply it. */ /* The nil glyph_h_origin() func returns 0, so no need to apply it. */
@ -983,13 +955,9 @@ hb_ot_shape_internal (hb_ot_shape_context_t *c)
if (c->plan->shaper->preprocess_text) if (c->plan->shaper->preprocess_text)
c->plan->shaper->preprocess_text (c->plan, c->buffer, c->font); c->plan->shaper->preprocess_text (c->plan, c->buffer, c->font);
hb_ot_substitute (c); hb_ot_substitute_pre (c);
hb_ot_position (c); hb_ot_position (c);
hb_ot_substitute_post (c);
hb_ot_hide_default_ignorables (c);
if (c->plan->shaper->postprocess_glyphs)
c->plan->shaper->postprocess_glyphs (c->plan, c->buffer, c->font);
hb_propagate_flags (c->buffer); hb_propagate_flags (c->buffer);