Merge pull request #2958 from googlefonts/layout_by_default_gr

[subset] Enable layout subsetting by default.
This commit is contained in:
Behdad Esfahbod 2021-04-30 11:39:47 -06:00 committed by GitHub
commit 46362a954c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
617 changed files with 293 additions and 135 deletions

View File

@ -91,7 +91,7 @@ static void ClassDef_remap_and_serialize (hb_serialize_context_t *c,
const hb_map_t &gid_klass_map,
hb_sorted_vector_t<HBGlyphID> &glyphs,
const hb_set_t &klasses,
bool use_class_zero,
bool use_class_zero,
hb_map_t *klass_map /*INOUT*/);
@ -1894,6 +1894,7 @@ struct ClassDefFormat1
if (unlikely (!it))
{
classFormat = 1;
startGlyph = 0;
classValue.len = 0;
return_trace (true);
@ -1917,6 +1918,7 @@ struct ClassDefFormat1
bool subset (hb_subset_context_t *c,
hb_map_t *klass_map = nullptr /*OUT*/,
bool keep_empty_table = true,
bool use_class_zero = true,
const Coverage* glyph_filter = nullptr) const
{
@ -1950,7 +1952,7 @@ struct ClassDefFormat1
use_class_zero = use_class_zero && glyph_count <= gid_org_klass_map.get_population ();
ClassDef_remap_and_serialize (c->serializer, gid_org_klass_map,
glyphs, orig_klasses, use_class_zero, klass_map);
return_trace ((bool) glyphs);
return_trace (keep_empty_table || (bool) glyphs);
}
bool sanitize (hb_sanitize_context_t *c) const
@ -2071,6 +2073,7 @@ struct ClassDefFormat2
if (unlikely (!it))
{
classFormat = 2;
rangeRecord.len = 0;
return_trace (true);
}
@ -2117,6 +2120,7 @@ struct ClassDefFormat2
bool subset (hb_subset_context_t *c,
hb_map_t *klass_map = nullptr /*OUT*/,
bool keep_empty_table = true,
bool use_class_zero = true,
const Coverage* glyph_filter = nullptr) const
{
@ -2151,7 +2155,7 @@ struct ClassDefFormat2
use_class_zero = use_class_zero && glyph_count <= gid_org_klass_map.get_population ();
ClassDef_remap_and_serialize (c->serializer, gid_org_klass_map,
glyphs, orig_klasses, use_class_zero, klass_map);
return_trace ((bool) glyphs);
return_trace (keep_empty_table || (bool) glyphs);
}
bool sanitize (hb_sanitize_context_t *c) const
@ -2350,13 +2354,14 @@ struct ClassDef
bool subset (hb_subset_context_t *c,
hb_map_t *klass_map = nullptr /*OUT*/,
bool keep_empty_table = true,
bool use_class_zero = true,
const Coverage* glyph_filter = nullptr) const
{
TRACE_SUBSET (this);
switch (u.format) {
case 1: return_trace (u.format1.subset (c, klass_map, use_class_zero, glyph_filter));
case 2: return_trace (u.format2.subset (c, klass_map, use_class_zero, glyph_filter));
case 1: return_trace (u.format1.subset (c, klass_map, keep_empty_table, use_class_zero, glyph_filter));
case 2: return_trace (u.format2.subset (c, klass_map, keep_empty_table, use_class_zero, glyph_filter));
default:return_trace (false);
}
}

View File

@ -643,10 +643,10 @@ struct GDEF
auto *out = c->serializer->embed (*this);
if (unlikely (!out)) return_trace (false);
bool subset_glyphclassdef = out->glyphClassDef.serialize_subset (c, glyphClassDef, this);
bool subset_glyphclassdef = out->glyphClassDef.serialize_subset (c, glyphClassDef, this, nullptr, false, true);
bool subset_attachlist = out->attachList.serialize_subset (c, attachList, this);
bool subset_ligcaretlist = out->ligCaretList.serialize_subset (c, ligCaretList, this);
bool subset_markattachclassdef = out->markAttachClassDef.serialize_subset (c, markAttachClassDef, this);
bool subset_markattachclassdef = out->markAttachClassDef.serialize_subset (c, markAttachClassDef, this, nullptr, false, true);
bool subset_markglyphsetsdef = true;
if (version.to_int () >= 0x00010002u)

View File

@ -103,6 +103,8 @@ struct ValueFormat : HBUINT16
* PosTable (may be NULL) */
#endif
IntType& operator = (uint16_t i) { v = i; return *this; }
unsigned int get_len () const { return hb_popcount ((unsigned int) *this); }
unsigned int get_size () const { return get_len () * Value::static_size; }
@ -160,16 +162,40 @@ struct ValueFormat : HBUINT16
return ret;
}
void serialize_copy (hb_serialize_context_t *c, const void *base,
const Value *values, const hb_map_t *layout_variation_idx_map) const
unsigned int get_effective_format (const Value *values) const
{
unsigned int format = *this;
for (unsigned flag = xPlacement; flag <= yAdvDevice; flag = flag << 1) {
if (format & flag) should_drop (*values++, (Flags) flag, &format);
}
return format;
}
template<typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
unsigned int get_effective_format (Iterator it) const {
unsigned int new_format = 0;
for (const hb_array_t<const Value>& values : it)
new_format = new_format | get_effective_format (&values);
return new_format;
}
void copy_values (hb_serialize_context_t *c,
unsigned int new_format,
const void *base,
const Value *values,
const hb_map_t *layout_variation_idx_map) const
{
unsigned int format = *this;
if (!format) return;
if (format & xPlacement) c->copy (*values++);
if (format & yPlacement) c->copy (*values++);
if (format & xAdvance) c->copy (*values++);
if (format & yAdvance) c->copy (*values++);
if (format & xPlacement) copy_value (c, new_format, xPlacement, *values++);
if (format & yPlacement) copy_value (c, new_format, yPlacement, *values++);
if (format & xAdvance) copy_value (c, new_format, xAdvance, *values++);
if (format & yAdvance) copy_value (c, new_format, yAdvance, *values++);
if (format & xPlaDevice) copy_device (c, base, values++, layout_variation_idx_map);
if (format & yPlaDevice) copy_device (c, base, values++, layout_variation_idx_map);
@ -177,6 +203,16 @@ struct ValueFormat : HBUINT16
if (format & yAdvDevice) copy_device (c, base, values++, layout_variation_idx_map);
}
void copy_value (hb_serialize_context_t *c,
unsigned int new_format,
Flags flag,
Value value) const
{
// Filter by new format.
if (!(new_format & flag)) return;
c->copy (value);
}
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
const void *base,
const hb_array_t<const Value>& values) const
@ -317,13 +353,21 @@ struct ValueFormat : HBUINT16
return_trace (true);
}
private:
void should_drop (Value value, Flags flag, unsigned int* format) const
{
if (value) return;
*format = *format & ~flag;
}
};
template<typename Iterator>
template<typename Iterator, typename SrcLookup>
static void SinglePos_serialize (hb_serialize_context_t *c,
const void *src,
const SrcLookup *src,
Iterator it,
ValueFormat valFormat,
const hb_map_t *layout_variation_idx_map);
@ -491,20 +535,18 @@ struct Anchor
bool subset (hb_subset_context_t *c) const
{
TRACE_SUBSET (this);
if (c->plan->drop_hints)
{
// AnchorFormat 2 and 3 just containing extra hinting information, so
// if hints are being dropped convert to format 1.
if (u.format != 1 && u.format != 2 && u.format != 3)
return_trace (false);
return_trace (bool (reinterpret_cast<Anchor *> (u.format1.copy (c->serializer))));
}
switch (u.format) {
case 1: return_trace (bool (reinterpret_cast<Anchor *> (u.format1.copy (c->serializer))));
case 2: return_trace (bool (reinterpret_cast<Anchor *> (u.format2.copy (c->serializer))));
case 2:
if (c->plan->drop_hints)
{
// AnchorFormat 2 just containins extra hinting information, so
// if hints are being dropped convert to format 1.
return_trace (bool (reinterpret_cast<Anchor *> (u.format1.copy (c->serializer))));
}
return_trace (bool (reinterpret_cast<Anchor *> (u.format2.copy (c->serializer))));
case 3: return_trace (bool (reinterpret_cast<Anchor *> (u.format3.copy (c->serializer,
c->plan->layout_variation_idx_map))));
c->plan->layout_variation_idx_map))));
default:return_trace (false);
}
}
@ -739,6 +781,8 @@ struct SinglePosFormat1
const Coverage &get_coverage () const { return this+coverage; }
ValueFormat get_value_format () const { return valueFormat; }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
@ -753,28 +797,33 @@ struct SinglePosFormat1
}
template<typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
typename SrcLookup,
hb_requires (hb_is_iterator (Iterator))>
void serialize (hb_serialize_context_t *c,
const void *src,
const SrcLookup *src,
Iterator it,
ValueFormat valFormat,
ValueFormat newFormat,
const hb_map_t *layout_variation_idx_map)
{
auto out = c->extend_min (*this);
if (unlikely (!out)) return;
if (unlikely (!c->check_assign (valueFormat, valFormat, HB_SERIALIZE_ERROR_INT_OVERFLOW))) return;
if (unlikely (!c->extend_min (*this))) return;
if (unlikely (!c->check_assign (valueFormat,
newFormat,
HB_SERIALIZE_ERROR_INT_OVERFLOW))) return;
+ it
| hb_map (hb_second)
| hb_apply ([&] (hb_array_t<const Value> _)
{ valFormat.serialize_copy (c, src, &_, layout_variation_idx_map); })
;
for (const hb_array_t<const Value>& _ : + it | hb_map (hb_second))
{
src->get_value_format ().copy_values (c, newFormat, src, &_, layout_variation_idx_map);
// Only serialize the first entry in the iterator, the rest are assumed to
// be the same.
break;
}
auto glyphs =
+ it
| hb_map_retains_sorting (hb_first)
;
// TODO(garretrieger): serialize_subset this.
coverage.serialize (c, this).serialize (c, glyphs);
}
@ -792,7 +841,7 @@ struct SinglePosFormat1
;
bool ret = bool (it);
SinglePos_serialize (c->serializer, this, it, valueFormat, c->plan->layout_variation_idx_map);
SinglePos_serialize (c->serializer, this, it, c->plan->layout_variation_idx_map);
return_trace (ret);
}
@ -849,6 +898,8 @@ struct SinglePosFormat2
const Coverage &get_coverage () const { return this+coverage; }
ValueFormat get_value_format () const { return valueFormat; }
bool apply (hb_ot_apply_context_t *c) const
{
TRACE_APPLY (this);
@ -867,22 +918,23 @@ struct SinglePosFormat2
}
template<typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
typename SrcLookup,
hb_requires (hb_is_iterator (Iterator))>
void serialize (hb_serialize_context_t *c,
const void *src,
const SrcLookup *src,
Iterator it,
ValueFormat valFormat,
ValueFormat newFormat,
const hb_map_t *layout_variation_idx_map)
{
auto out = c->extend_min (*this);
if (unlikely (!out)) return;
if (unlikely (!c->check_assign (valueFormat, valFormat, HB_SERIALIZE_ERROR_INT_OVERFLOW))) return;
if (unlikely (!c->check_assign (valueFormat, newFormat, HB_SERIALIZE_ERROR_INT_OVERFLOW))) return;
if (unlikely (!c->check_assign (valueCount, it.len (), HB_SERIALIZE_ERROR_ARRAY_OVERFLOW))) return;
+ it
| hb_map (hb_second)
| hb_apply ([&] (hb_array_t<const Value> _)
{ valFormat.serialize_copy (c, src, &_, layout_variation_idx_map); })
{ src->get_value_format ().copy_values (c, newFormat, src, &_, layout_variation_idx_map); })
;
auto glyphs =
@ -914,7 +966,7 @@ struct SinglePosFormat2
;
bool ret = bool (it);
SinglePos_serialize (c->serializer, this, it, valueFormat, c->plan->layout_variation_idx_map);
SinglePos_serialize (c->serializer, this, it, c->plan->layout_variation_idx_map);
return_trace (ret);
}
@ -958,24 +1010,37 @@ struct SinglePos
template<typename Iterator,
hb_requires (hb_is_iterator (Iterator))>
typename SrcLookup,
hb_requires (hb_is_iterator (Iterator))>
void serialize (hb_serialize_context_t *c,
const void *src,
const SrcLookup* src,
Iterator glyph_val_iter_pairs,
ValueFormat valFormat,
const hb_map_t *layout_variation_idx_map)
{
if (unlikely (!c->extend_min (u.format))) return;
unsigned format = 2;
ValueFormat new_format = src->get_value_format ();
if (glyph_val_iter_pairs) format = get_format (glyph_val_iter_pairs);
if (glyph_val_iter_pairs)
{
format = get_format (glyph_val_iter_pairs);
new_format = src->get_value_format ().get_effective_format (+ glyph_val_iter_pairs | hb_map (hb_second));
}
u.format = format;
switch (u.format) {
case 1: u.format1.serialize (c, src, glyph_val_iter_pairs, valFormat, layout_variation_idx_map);
return;
case 2: u.format2.serialize (c, src, glyph_val_iter_pairs, valFormat, layout_variation_idx_map);
return;
case 1: u.format1.serialize (c,
src,
glyph_val_iter_pairs,
new_format,
layout_variation_idx_map);
return;
case 2: u.format2.serialize (c,
src,
glyph_val_iter_pairs,
new_format,
layout_variation_idx_map);
return;
default:return;
}
}
@ -1000,14 +1065,13 @@ struct SinglePos
} u;
};
template<typename Iterator>
template<typename Iterator, typename SrcLookup>
static void
SinglePos_serialize (hb_serialize_context_t *c,
const void *src,
const SrcLookup *src,
Iterator it,
ValueFormat valFormat,
const hb_map_t *layout_variation_idx_map)
{ c->start_embed<SinglePos> ()->serialize (c, src, it, valFormat, layout_variation_idx_map); }
{ c->start_embed<SinglePos> ()->serialize (c, src, it, layout_variation_idx_map); }
struct PairValueRecord
@ -1017,26 +1081,35 @@ struct PairValueRecord
int cmp (hb_codepoint_t k) const
{ return secondGlyph.cmp (k); }
struct serialize_closure_t
struct context_t
{
const void *base;
const ValueFormat *valueFormats;
const ValueFormat *newFormats;
unsigned len1; /* valueFormats[0].get_len() */
const hb_map_t *glyph_map;
const hb_map_t *layout_variation_idx_map;
};
bool serialize (hb_serialize_context_t *c,
serialize_closure_t *closure) const
bool subset (hb_subset_context_t *c,
context_t *closure) const
{
TRACE_SERIALIZE (this);
auto *out = c->start_embed (*this);
if (unlikely (!c->extend_min (out))) return_trace (false);
auto *s = c->serializer;
auto *out = s->start_embed (*this);
if (unlikely (!s->extend_min (out))) return_trace (false);
out->secondGlyph = (*closure->glyph_map)[secondGlyph];
closure->valueFormats[0].serialize_copy (c, closure->base, &values[0], closure->layout_variation_idx_map);
closure->valueFormats[1].serialize_copy (c, closure->base, &values[closure->len1], closure->layout_variation_idx_map);
closure->valueFormats[0].copy_values (s,
closure->newFormats[0],
closure->base, &values[0],
closure->layout_variation_idx_map);
closure->valueFormats[1].copy_values (s,
closure->newFormats[1],
closure->base,
&values[closure->len1],
closure->layout_variation_idx_map);
return_trace (true);
}
@ -1056,6 +1129,21 @@ struct PairValueRecord
valueFormats[1].collect_variation_indices (c, base, values_array.sub_array (record1_len, record2_len));
}
bool intersects (const hb_set_t& glyphset) const
{
return glyphset.has(secondGlyph);
}
const Value* get_values_1 () const
{
return &values[0];
}
const Value* get_values_2 (ValueFormat format1) const
{
return &values[format1.get_len ()];
}
protected:
HBGlyphID secondGlyph; /* GlyphID of second glyph in the
* pair--first glyph is listed in the
@ -1146,7 +1234,8 @@ struct PairSet
}
bool subset (hb_subset_context_t *c,
const ValueFormat valueFormats[2]) const
const ValueFormat valueFormats[2],
const ValueFormat newFormats[2]) const
{
TRACE_SUBSET (this);
auto snap = c->serializer->snapshot ();
@ -1162,10 +1251,11 @@ struct PairSet
unsigned len2 = valueFormats[1].get_len ();
unsigned record_size = HBUINT16::static_size + Value::static_size * (len1 + len2);
PairValueRecord::serialize_closure_t closure =
PairValueRecord::context_t context =
{
this,
valueFormats,
newFormats,
len1,
&glyph_map,
c->plan->layout_variation_idx_map
@ -1176,7 +1266,7 @@ struct PairSet
for (unsigned i = 0; i < count; i++)
{
if (glyphset.has (record->secondGlyph)
&& record->serialize (c->serializer, &closure)) num++;
&& record->subset (c, &context)) num++;
record = &StructAtOffset<const PairValueRecord> (record, record_size);
}
@ -1284,6 +1374,12 @@ struct PairPosFormat1
out->format = format;
out->valueFormat[0] = valueFormat[0];
out->valueFormat[1] = valueFormat[1];
if (c->plan->drop_hints)
{
hb_pair_t<unsigned, unsigned> newFormats = compute_effective_value_formats (glyphset);
out->valueFormat[0] = newFormats.first;
out->valueFormat[1] = newFormats.second;
}
hb_sorted_vector_t<hb_codepoint_t> new_coverage;
@ -1294,7 +1390,7 @@ struct PairPosFormat1
auto *o = out->pairSet.serialize_append (c->serializer);
if (unlikely (!o)) return false;
auto snap = c->serializer->snapshot ();
bool ret = o->serialize_subset (c, _, this, valueFormat);
bool ret = o->serialize_subset (c, _, this, valueFormat, out->valueFormat);
if (!ret)
{
out->pairSet.pop ();
@ -1314,6 +1410,36 @@ struct PairPosFormat1
return_trace (bool (new_coverage));
}
hb_pair_t<unsigned, unsigned> compute_effective_value_formats (const hb_set_t& glyphset) const
{
unsigned len1 = valueFormat[0].get_len ();
unsigned len2 = valueFormat[1].get_len ();
unsigned record_size = HBUINT16::static_size + Value::static_size * (len1 + len2);
unsigned format1 = 0;
unsigned format2 = 0;
for (const Offset16To<PairSet>& _ :
+ hb_zip (this+coverage, pairSet) | hb_filter (glyphset, hb_first) | hb_map (hb_second))
{
const PairSet& set = (this + _);
const PairValueRecord *record = &set.firstPairValueRecord;
for (unsigned i = 0; i < set.len; i++)
{
if (record->intersects (glyphset))
{
format1 = format1 | valueFormat[0].get_effective_format (record->get_values_1 ());
format2 = format2 | valueFormat[1].get_effective_format (record->get_values_2 (valueFormat[0]));
}
record = &StructAtOffset<const PairValueRecord> (record, record_size);
}
}
return hb_pair (format1, format2);
}
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);
@ -1361,14 +1487,28 @@ struct PairPosFormat2
void closure_lookups (hb_closure_lookups_context_t *c) const {}
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
{
if (!intersects (c->glyph_set)) return;
if ((!valueFormat1.has_device ()) && (!valueFormat2.has_device ())) return;
hb_set_t klass1_glyphs, klass2_glyphs;
if (!(this+classDef1).collect_coverage (&klass1_glyphs)) return;
if (!(this+classDef2).collect_coverage (&klass2_glyphs)) return;
hb_set_t class1_set, class2_set;
for (const unsigned cp : + c->glyph_set->iter () | hb_filter (this + coverage))
{
unsigned klass1 = (this+classDef1).get (cp);
if (!klass1_glyphs.has (cp)) class1_set.add (0);
else
{
unsigned klass1 = (this+classDef1).get (cp);
class1_set.add (klass1);
}
}
class2_set.add (0);
for (const unsigned cp : + c->glyph_set->iter () | hb_filter (klass2_glyphs))
{
unsigned klass2 = (this+classDef2).get (cp);
class1_set.add (klass1);
class2_set.add (klass2);
}
@ -1440,35 +1580,34 @@ struct PairPosFormat2
auto *out = c->serializer->start_embed (*this);
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
out->format = format;
out->valueFormat1 = valueFormat1;
out->valueFormat2 = valueFormat2;
hb_map_t klass1_map;
out->classDef1.serialize_subset (c, classDef1, this, &klass1_map, true, &(this + coverage));
out->classDef1.serialize_subset (c, classDef1, this, &klass1_map, true, true, &(this + coverage));
out->class1Count = klass1_map.get_population ();
hb_map_t klass2_map;
out->classDef2.serialize_subset (c, classDef2, this, &klass2_map, false);
out->classDef2.serialize_subset (c, classDef2, this, &klass2_map, true, false);
out->class2Count = klass2_map.get_population ();
unsigned len1 = valueFormat1.get_len ();
unsigned len2 = valueFormat2.get_len ();
+ hb_range ((unsigned) class1Count)
| hb_filter (klass1_map)
| hb_apply ([&] (const unsigned class1_idx)
{
+ hb_range ((unsigned) class2Count)
| hb_filter (klass2_map)
| hb_apply ([&] (const unsigned class2_idx)
{
unsigned idx = (class1_idx * (unsigned) class2Count + class2_idx) * (len1 + len2);
valueFormat1.serialize_copy (c->serializer, this, &values[idx], c->plan->layout_variation_idx_map);
valueFormat2.serialize_copy (c->serializer, this, &values[idx + len1], c->plan->layout_variation_idx_map);
})
;
})
;
hb_pair_t<unsigned, unsigned> newFormats = hb_pair (valueFormat1, valueFormat2);
if (c->plan->drop_hints)
newFormats = compute_effective_value_formats (klass1_map, klass2_map);
out->valueFormat1 = newFormats.first;
out->valueFormat2 = newFormats.second;
for (unsigned class1_idx : + hb_range ((unsigned) class1Count) | hb_filter (klass1_map))
{
for (unsigned class2_idx : + hb_range ((unsigned) class2Count) | hb_filter (klass2_map))
{
unsigned idx = (class1_idx * (unsigned) class2Count + class2_idx) * (len1 + len2);
valueFormat1.copy_values (c->serializer, newFormats.first, this, &values[idx], c->plan->layout_variation_idx_map);
valueFormat2.copy_values (c->serializer, newFormats.second, this, &values[idx + len1], c->plan->layout_variation_idx_map);
}
}
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
const hb_map_t &glyph_map = *c->plan->glyph_map;
@ -1483,6 +1622,30 @@ struct PairPosFormat2
return_trace (out->class1Count && out->class2Count && bool (it));
}
hb_pair_t<unsigned, unsigned> compute_effective_value_formats (const hb_map_t& klass1_map,
const hb_map_t& klass2_map) const
{
unsigned len1 = valueFormat1.get_len ();
unsigned len2 = valueFormat2.get_len ();
unsigned format1 = 0;
unsigned format2 = 0;
for (unsigned class1_idx : + hb_range ((unsigned) class1Count) | hb_filter (klass1_map))
{
for (unsigned class2_idx : + hb_range ((unsigned) class2Count) | hb_filter (klass2_map))
{
unsigned idx = (class1_idx * (unsigned) class2Count + class2_idx) * (len1 + len2);
format1 = format1 | valueFormat1.get_effective_format (&values[idx]);
format2 = format2 | valueFormat2.get_effective_format (&values[idx + len1]);
}
}
return hb_pair (format1, format2);
}
bool sanitize (hb_sanitize_context_t *c) const
{
TRACE_SANITIZE (this);

View File

@ -56,9 +56,6 @@ hb_subset_input_create_or_fail ()
hb_tag_t default_drop_tables[] = {
// Layout disabled by default
HB_TAG ('G', 'S', 'U', 'B'),
HB_TAG ('G', 'P', 'O', 'S'),
HB_TAG ('G', 'D', 'E', 'F'),
HB_TAG ('m', 'o', 'r', 'x'),
HB_TAG ('m', 'o', 'r', 't'),
HB_TAG ('k', 'e', 'r', 'x'),

Some files were not shown because too many files have changed in this diff Show More