Merge pull request #4368 from googlefonts/support_gvar

Initial implementation for gvar partial instancing
diff --git a/src/OT/glyf/Glyph.hh b/src/OT/glyf/Glyph.hh
index 1aed3ae..5ea6119 100644
--- a/src/OT/glyf/Glyph.hh
+++ b/src/OT/glyf/Glyph.hh
@@ -103,6 +103,63 @@
     }
   }
 
+  bool get_all_points_without_var (const hb_face_t *face,
+                                   contour_point_vector_t &points /* OUT */) const
+  {
+    switch (type) {
+    case SIMPLE:
+      if (unlikely (!SimpleGlyph (*header, bytes).get_contour_points (points)))
+        return false;
+      break;
+    case COMPOSITE:
+    {
+      for (auto &item : get_composite_iterator ())
+        if (unlikely (!item.get_points (points))) return false;
+      break;
+    }
+#ifndef HB_NO_VAR_COMPOSITES
+    case VAR_COMPOSITE:
+    {
+      for (auto &item : get_var_composite_iterator ())
+        if (unlikely (!item.get_points (points))) return false;
+      break;
+    }
+#endif
+    case EMPTY:
+      break;
+    }
+
+    /* Init phantom points */
+    if (unlikely (!points.resize (points.length + PHANTOM_COUNT))) return false;
+    hb_array_t<contour_point_t> phantoms = points.as_array ().sub_array (points.length - PHANTOM_COUNT, PHANTOM_COUNT);
+    {
+      int lsb = 0;
+      int h_delta = face->table.hmtx->get_leading_bearing_without_var_unscaled (gid, &lsb) ?
+                    (int) header->xMin - lsb : 0;
+      HB_UNUSED int tsb = 0;
+      int v_orig  = (int) header->yMax +
+#ifndef HB_NO_VERTICAL
+                    ((void) face->table.vmtx->get_leading_bearing_without_var_unscaled (gid, &tsb), tsb)
+#else
+                    0
+#endif
+                    ;
+      unsigned h_adv = face->table.hmtx->get_advance_without_var_unscaled (gid);
+      unsigned v_adv =
+#ifndef HB_NO_VERTICAL
+                       face->table.vmtx->get_advance_without_var_unscaled (gid)
+#else
+                       - face->get_upem ()
+#endif
+                       ;
+      phantoms[PHANTOM_LEFT].x = h_delta;
+      phantoms[PHANTOM_RIGHT].x = (int) h_adv + h_delta;
+      phantoms[PHANTOM_TOP].y = v_orig;
+      phantoms[PHANTOM_BOTTOM].y = v_orig - (int) v_adv;
+    }
+    return true;
+  }
+
   void update_mtx (const hb_subset_plan_t *plan,
                    int xMin, int xMax,
                    int yMin, int yMax,
diff --git a/src/hb-algs.hh b/src/hb-algs.hh
index 6cabc7f..ea97057 100644
--- a/src/hb-algs.hh
+++ b/src/hb-algs.hh
@@ -367,6 +367,10 @@
 	    hb_enable_if (std::is_integral<T>::value && sizeof (T) > sizeof (uint32_t))> constexpr auto
   impl (const T& v, hb_priority<1>) const HB_RETURN (uint32_t, (uint32_t) (v ^ (v >> 32)) * 2654435761u /* Knuth's multiplicative hash */)
 
+  template <typename T,
+	    hb_enable_if (std::is_floating_point<T>::value)> constexpr auto
+  impl (const T& v, hb_priority<1>) const HB_RETURN (uint32_t, fasthash32 (std::addressof (v), sizeof (T), 0xf437ffe6))
+
   template <typename T> constexpr auto
   impl (const T& v, hb_priority<0>) const HB_RETURN (uint32_t, std::hash<hb_decay<decltype (hb_deref (v))>>{} (hb_deref (v)))
 
diff --git a/src/hb-ot-var-common.hh b/src/hb-ot-var-common.hh
index fd3b640..d1c617e 100644
--- a/src/hb-ot-var-common.hh
+++ b/src/hb-ot-var-common.hh
@@ -449,17 +449,26 @@
   hb_vector_t<char> compiled_tuple_header;
   hb_vector_t<char> compiled_deltas;
 
+  /* compiled peak coords, empty for non-gvar tuples */
+  hb_vector_t<char> compiled_peak_coords;
+
   tuple_delta_t () = default;
   tuple_delta_t (const tuple_delta_t& o) = default;
 
-  tuple_delta_t (tuple_delta_t&& o) : tuple_delta_t ()
+  friend void swap (tuple_delta_t& a, tuple_delta_t& b)
   {
-    axis_tuples = std::move (o.axis_tuples);
-    indices = std::move (o.indices);
-    deltas_x = std::move (o.deltas_x);
-    deltas_y = std::move (o.deltas_y);
+    hb_swap (a.axis_tuples, b.axis_tuples);
+    hb_swap (a.indices, b.indices);
+    hb_swap (a.deltas_x, b.deltas_x);
+    hb_swap (a.deltas_y, b.deltas_y);
+    hb_swap (a.compiled_tuple_header, b.compiled_tuple_header);
+    hb_swap (a.compiled_deltas, b.compiled_deltas);
+    hb_swap (a.compiled_peak_coords, b.compiled_peak_coords);
   }
 
+  tuple_delta_t (tuple_delta_t&& o) : tuple_delta_t ()
+  { hb_swap (*this, o); }
+
   tuple_delta_t& operator = (tuple_delta_t&& o)
   {
     hb_swap (*this, o);
@@ -552,13 +561,43 @@
     return out;
   }
 
+  bool compile_peak_coords (const hb_map_t& axes_index_map,
+                            const hb_map_t& axes_old_index_tag_map)
+  {
+    unsigned axis_count = axes_index_map.get_population ();
+    if (unlikely (!compiled_peak_coords.alloc (axis_count * F2DOT14::static_size)))
+      return false;
+
+    unsigned orig_axis_count = axes_old_index_tag_map.get_population ();
+    for (unsigned i = 0; i < orig_axis_count; i++)
+    {
+      if (!axes_index_map.has (i))
+        continue;
+
+      hb_tag_t axis_tag = axes_old_index_tag_map.get (i);
+      Triple *coords;
+      F2DOT14 peak_coord;
+      if (axis_tuples.has (axis_tag, &coords))
+        peak_coord.set_float (coords->middle);
+      else
+        peak_coord.set_int (0);
+
+      /* push F2DOT14 value into char vector */
+      int16_t val = peak_coord.to_int ();
+      compiled_peak_coords.push (static_cast<char> (val >> 8));
+      compiled_peak_coords.push (static_cast<char> (val & 0xFF));
+    }
+
+    return !compiled_peak_coords.in_error ();
+  }
+
   /* deltas should be compiled already before we compile tuple
    * variation header cause we need to fill in the size of the
    * serialized data for this tuple variation */
-  //TODO(qxliu):add option to use sharedTuples in gvar
   bool compile_tuple_var_header (const hb_map_t& axes_index_map,
                                  unsigned points_data_length,
-                                 const hb_map_t& axes_old_index_tag_map)
+                                 const hb_map_t& axes_old_index_tag_map,
+                                 const hb_hashmap_t<const hb_vector_t<char>*, unsigned>* shared_tuples_idx_map)
   {
     if (!compiled_deltas) return false;
 
@@ -574,14 +613,25 @@
     hb_array_t<F2DOT14> coords (p, end - p);
 
     /* encode peak coords */
-    unsigned peak_count = encode_peak_coords(coords, flag, axes_index_map, axes_old_index_tag_map);
-    if (!peak_count) return false;
+    unsigned peak_count = 0;
+    unsigned *shared_tuple_idx;
+    if (shared_tuples_idx_map &&
+        shared_tuples_idx_map->has (&compiled_peak_coords, &shared_tuple_idx))
+    {
+      flag = *shared_tuple_idx;
+    }
+    else
+    {
+      peak_count = encode_peak_coords(coords, flag, axes_index_map, axes_old_index_tag_map);
+      if (!peak_count) return false;
+    }
 
     /* encode interim coords, it's optional so returned num could be 0 */
     unsigned interim_count = encode_interm_coords (coords.sub_array (peak_count), flag, axes_index_map, axes_old_index_tag_map);
 
-    //TODO(qxliu): add option to use shared_points in gvar
-    flag |= TupleVariationHeader::TuppleIndex::PrivatePointNumbers;
+    /* pointdata length = 0 implies "use shared points" */
+    if (points_data_length)
+      flag |= TupleVariationHeader::TuppleIndex::PrivatePointNumbers;
 
     unsigned serialized_data_size = points_data_length + compiled_deltas.length;
     TupleVariationHeader *o = reinterpret_cast<TupleVariationHeader *> (compiled_tuple_header.begin ());
@@ -870,6 +920,111 @@
     }
     return encoded_len;
   }
+
+  bool calc_inferred_deltas (const contour_point_vector_t& orig_points)
+  {
+    unsigned point_count = orig_points.length;
+    if (point_count != indices.length)
+      return false;
+
+    unsigned ref_count = 0;
+    hb_vector_t<unsigned> end_points;
+
+    for (unsigned i = 0; i < point_count; i++)
+    {
+      if (indices.arrayZ[i])
+        ref_count++;
+      if (orig_points.arrayZ[i].is_end_point)
+        end_points.push (i);
+    }
+    /* all points are referenced, nothing to do */
+    if (ref_count == point_count)
+      return true;
+    if (unlikely (end_points.in_error ())) return false;
+
+    hb_set_t inferred_idxes;
+    unsigned start_point = 0;
+    for (unsigned end_point : end_points)
+    {
+      /* Check the number of unreferenced points in a contour. If no unref points or no ref points, nothing to do. */
+      unsigned unref_count = 0;
+      for (unsigned i = start_point; i < end_point + 1; i++)
+        unref_count += indices.arrayZ[i];
+      unref_count = (end_point - start_point + 1) - unref_count;
+
+      unsigned j = start_point;
+      if (unref_count == 0 || unref_count > end_point - start_point)
+        goto no_more_gaps;
+      for (;;)
+      {
+        /* Locate the next gap of unreferenced points between two referenced points prev and next.
+         * Note that a gap may wrap around at left (start_point) and/or at right (end_point).
+         */
+        unsigned int prev, next, i;
+        for (;;)
+        {
+          i = j;
+          j = next_index (i, start_point, end_point);
+          if (indices.arrayZ[i] && !indices.arrayZ[j]) break;
+        }
+        prev = j = i;
+        for (;;)
+        {
+          i = j;
+          j = next_index (i, start_point, end_point);
+          if (!indices.arrayZ[i] && indices.arrayZ[j]) break;
+        }
+        next = j;
+       /* Infer deltas for all unref points in the gap between prev and next */
+        i = prev;
+        for (;;)
+        {
+          i = next_index (i, start_point, end_point);
+          if (i == next) break;
+          deltas_x.arrayZ[i] = infer_delta (orig_points.arrayZ[i].x, orig_points.arrayZ[prev].x, orig_points.arrayZ[next].x,
+                                            deltas_x.arrayZ[prev], deltas_x.arrayZ[next]);
+          deltas_y.arrayZ[i] = infer_delta (orig_points.arrayZ[i].y, orig_points.arrayZ[prev].y, orig_points.arrayZ[next].y,
+                                            deltas_y.arrayZ[prev], deltas_y.arrayZ[next]);
+          inferred_idxes.add (i);
+          if (--unref_count == 0) goto no_more_gaps;
+        }
+      }
+    no_more_gaps:
+      start_point = end_point + 1;
+    }
+
+    for (unsigned i = 0; i < point_count; i++)
+    {
+      /* if points are not referenced and deltas are not inferred, set to 0.
+       * reference all points for gvar */
+      if ( !indices[i])
+      {
+        if (!inferred_idxes.has (i))
+        {
+          deltas_x.arrayZ[i] = 0.f;
+          deltas_y.arrayZ[i] = 0.f;
+        }
+        indices[i] = true;
+      }
+    }
+    return true;
+  }
+
+  static float infer_delta (float target_val, float prev_val, float next_val, float prev_delta, float next_delta)
+  {
+    if (prev_val == next_val)
+      return (prev_delta == next_delta) ? prev_delta : 0.f;
+    else if (target_val <= hb_min (prev_val, next_val))
+      return (prev_val < next_val) ? prev_delta : next_delta;
+    else if (target_val >= hb_max (prev_val, next_val))
+      return (prev_val > next_val) ? prev_delta : next_delta;
+
+    float r = (target_val - prev_val) / (next_val - prev_val);
+    return prev_delta + r * (next_delta - prev_delta);
+  }
+
+  static unsigned int next_index (unsigned int i, unsigned int start, unsigned int end)
+  { return (i >= end) ? start : (i + 1); }
 };
 
 struct TupleVariationData
@@ -910,6 +1065,16 @@
     /* referenced point set-> count map, used in finding shared points */
     hb_hashmap_t<const hb_vector_t<bool>*, unsigned> point_set_count_map;
 
+    /* empty for non-gvar tuples.
+     * shared_points_bytes is just a copy of some value in the point_data_map,
+     * which will be freed during map destruction. Save it for serialization, so
+     * no need to do find_shared_points () again */
+    hb_bytes_t shared_points_bytes;
+
+    /* total compiled byte size as TupleVariationData format, initialized to its
+     * min_size: 4 */
+    unsigned compiled_byte_size = 4;
+
     public:
     ~tuple_variations_t () { fini (); }
     void fini ()
@@ -921,8 +1086,17 @@
       tuple_vars.fini ();
     }
 
+    explicit operator bool () const { return bool (tuple_vars); }
     unsigned get_var_count () const
-    { return tuple_vars.length; }
+    {
+      unsigned count = tuple_vars.length;
+      if (shared_points_bytes.length)
+        count |= TupleVarCount::SharedPointNumbers;
+      return count;
+    }
+
+    unsigned get_compiled_byte_size () const
+    { return compiled_byte_size; }
 
     bool create_from_tuple_var_data (tuple_iterator_t iterator,
                                      unsigned tuple_var_count,
@@ -992,6 +1166,7 @@
       return true;
     }
 
+    private:
     void change_tuple_variations_axis_limits (const hb_hashmap_t<hb_tag_t, Triple>& normalized_axes_location,
                                               const hb_hashmap_t<hb_tag_t, TripleDistances>& axes_triple_distances)
     {
@@ -1025,7 +1200,7 @@
     void merge_tuple_variations ()
     {
       hb_vector_t<tuple_delta_t> new_vars;
-      hb_hashmap_t<hb_hashmap_t<hb_tag_t, Triple>, unsigned> m;
+      hb_hashmap_t<const hb_hashmap_t<hb_tag_t, Triple>*, unsigned> m;
       unsigned i = 0;
       for (const tuple_delta_t& var : tuple_vars)
       {
@@ -1033,14 +1208,14 @@
         if (var.axis_tuples.is_empty ()) continue;
 
         unsigned *idx;
-        if (m.has (var.axis_tuples, &idx))
+        if (m.has (&(var.axis_tuples), &idx))
         {
           new_vars[*idx] += var;
         }
         else
         {
           new_vars.push (var);
-          m.set (var.axis_tuples, i);
+          m.set (&(var.axis_tuples), i);
           i++;
         }
       }
@@ -1187,19 +1362,45 @@
       return res;
     }
 
-    void instantiate (const hb_hashmap_t<hb_tag_t, Triple>& normalized_axes_location,
-                      const hb_hashmap_t<hb_tag_t, TripleDistances>& axes_triple_distances)
+    bool calc_inferred_deltas (contour_point_vector_t& contour_points)
     {
+      for (tuple_delta_t& var : tuple_vars)
+        if (!var.calc_inferred_deltas (contour_points))
+          return false;
+      
+      return true;
+    }
+
+    public:
+    bool instantiate (const hb_hashmap_t<hb_tag_t, Triple>& normalized_axes_location,
+                      const hb_hashmap_t<hb_tag_t, TripleDistances>& axes_triple_distances,
+                      contour_point_vector_t* contour_points = nullptr)
+    {
+      if (!tuple_vars) return true;
       change_tuple_variations_axis_limits (normalized_axes_location, axes_triple_distances);
+      /* compute inferred deltas only for gvar */
+      if (contour_points)
+        if (!calc_inferred_deltas (*contour_points))
+          return false;
+
       merge_tuple_variations ();
+      return !tuple_vars.in_error ();
     }
 
     bool compile_bytes (const hb_map_t& axes_index_map,
-                        const hb_map_t& axes_old_index_tag_map)
+                        const hb_map_t& axes_old_index_tag_map,
+                        bool use_shared_points,
+                        const hb_hashmap_t<const hb_vector_t<char>*, unsigned>* shared_tuples_idx_map = nullptr)
     {
       // compile points set and store data in hashmap
       if (!compile_all_point_sets ())
         return false;
+
+      if (use_shared_points)
+      {
+        shared_points_bytes = find_shared_points ();
+        compiled_byte_size += shared_points_bytes.length;
+      }
       // compile delta and tuple var header for each tuple variation
       for (auto& tuple: tuple_vars)
       {
@@ -1211,8 +1412,11 @@
         if (!tuple.compile_deltas ())
           return false;
 
-        if (!tuple.compile_tuple_var_header (axes_index_map, points_data->length, axes_old_index_tag_map))
+        unsigned points_data_length = (*points_data != shared_points_bytes) ? points_data->length : 0;
+        if (!tuple.compile_tuple_var_header (axes_index_map, points_data_length, axes_old_index_tag_map,
+                                             shared_tuples_idx_map))
           return false;
+        compiled_byte_size += tuple.compiled_tuple_header.length + points_data_length + tuple.compiled_deltas.length;
       }
       return true;
     }
@@ -1229,9 +1433,12 @@
       return_trace (true);
     }
 
-    bool serialize_var_data (hb_serialize_context_t *c) const
+    bool serialize_var_data (hb_serialize_context_t *c, bool is_gvar) const
     {
       TRACE_SERIALIZE (this);
+      if (is_gvar)
+        shared_points_bytes.copy (c);
+
       for (const auto& tuple: tuple_vars)
       {
         const hb_vector_t<bool>* points_set = &(tuple.indices);
@@ -1239,10 +1446,20 @@
         if (!point_data_map.has (points_set, &point_data))
           return_trace (false);
 
-        point_data->copy (c);
+        if (!is_gvar || *point_data != shared_points_bytes)
+          point_data->copy (c);
+
         tuple.compiled_deltas.as_array ().copy (c);
         if (c->in_error ()) return_trace (false);
       }
+
+      /* padding for gvar */
+      if (is_gvar && (compiled_byte_size % 2))
+      {
+        HBUINT8 pad;
+        pad = 0;
+        if (!c->embed (pad)) return_trace (false);
+      }
       return_trace (true);
     }
   };
@@ -1428,9 +1645,12 @@
 
   bool serialize (hb_serialize_context_t *c,
                   bool is_gvar,
-                  tuple_variations_t& tuple_variations) const
+                  const tuple_variations_t& tuple_variations) const
   {
     TRACE_SERIALIZE (this);
+    /* empty tuple variations, just return and skip serialization. */
+    if (!tuple_variations) return_trace (true);
+
     auto *out = c->start_embed (this);
     if (unlikely (!c->extend_min (out))) return_trace (false);
 
@@ -1446,15 +1666,17 @@
     if (!is_gvar) data_offset += 4;
     if (!c->check_assign (out->data, data_offset, HB_SERIALIZE_ERROR_INT_OVERFLOW)) return_trace (false);
 
-    return tuple_variations.serialize_var_data (c);
+    return tuple_variations.serialize_var_data (c, is_gvar);
   }
 
   protected:
   struct TupleVarCount : HBUINT16
   {
+    friend struct tuple_variations_t;
     bool has_shared_point_numbers () const { return ((*this) & SharedPointNumbers); }
     unsigned int get_count () const { return (*this) & CountMask; }
     TupleVarCount& operator = (uint16_t i) { HBUINT16::operator= (i); return *this; }
+    explicit operator bool () const { return get_count (); }
 
     protected:
     enum Flags
diff --git a/src/hb-ot-var-cvar-table.hh b/src/hb-ot-var-cvar-table.hh
index fee39ef..adb8176 100644
--- a/src/hb-ot-var-cvar-table.hh
+++ b/src/hb-ot-var-cvar-table.hh
@@ -131,6 +131,7 @@
                   TupleVariationData::tuple_variations_t& tuple_variations) const
   {
     TRACE_SERIALIZE (this);
+    if (!tuple_variations) return_trace (false);
     if (unlikely (!c->embed (version))) return_trace (false);
 
     return_trace (tupleVariationData.serialize (c, false, tuple_variations));
@@ -168,8 +169,11 @@
                                      tuple_variations))
       return_trace (false);
 
-    tuple_variations.instantiate (c->plan->axes_location, c->plan->axes_triple_distances);
-    if (!tuple_variations.compile_bytes (c->plan->axes_index_map, c->plan->axes_old_index_tag_map))
+    if (!tuple_variations.instantiate (c->plan->axes_location, c->plan->axes_triple_distances))
+      return_trace (false);
+
+    if (!tuple_variations.compile_bytes (c->plan->axes_index_map, c->plan->axes_old_index_tag_map,
+                                         false /* do not use shared points */))
       return_trace (false);
 
     return_trace (serialize (c->serializer, tuple_variations));
diff --git a/src/hb-ot-var-gvar-table.hh b/src/hb-ot-var-gvar-table.hh
index b5099ac..3f9e106 100644
--- a/src/hb-ot-var-gvar-table.hh
+++ b/src/hb-ot-var-gvar-table.hh
@@ -39,43 +39,256 @@
 
 namespace OT {
 
-struct contour_point_t
-{
-  void init (float x_ = 0.f, float y_ = 0.f, bool is_end_point_ = false)
-  { flag = 0; x = x_; y = y_; is_end_point = is_end_point_; }
-
-  void transform (const float (&matrix)[4])
-  {
-    float x_ = x * matrix[0] + y * matrix[2];
-	  y  = x * matrix[1] + y * matrix[3];
-    x  = x_;
-  }
-  HB_ALWAYS_INLINE
-  void translate (const contour_point_t &p) { x += p.x; y += p.y; }
-
-
-  float x;
-  float y;
-  uint8_t flag;
-  bool is_end_point;
-};
-
-struct contour_point_vector_t : hb_vector_t<contour_point_t>
-{
-  void extend (const hb_array_t<contour_point_t> &a)
-  {
-    unsigned int old_len = length;
-    if (unlikely (!resize (old_len + a.length, false)))
-      return;
-    auto arrayZ = this->arrayZ + old_len;
-    unsigned count = a.length;
-    hb_memcpy (arrayZ, a.arrayZ, count * sizeof (arrayZ[0]));
-  }
-};
-
 struct GlyphVariationData : TupleVariationData
 {};
 
+struct glyph_variations_t
+{
+  using tuple_variations_t = TupleVariationData::tuple_variations_t;
+  hb_vector_t<tuple_variations_t> glyph_variations;
+
+  hb_vector_t<char> compiled_shared_tuples;
+  private:
+  unsigned shared_tuples_count = 0;
+
+  /* shared coords-> index map after instantiation */
+  hb_hashmap_t<const hb_vector_t<char>*, unsigned> shared_tuples_idx_map;
+
+  public:
+  unsigned compiled_shared_tuples_count () const
+  { return shared_tuples_count; }
+
+  unsigned compiled_byte_size () const
+  {
+    unsigned byte_size = 0;
+    for (const auto& _ : glyph_variations)
+      byte_size += _.get_compiled_byte_size ();
+
+    return byte_size;
+  }
+
+  bool create_from_glyphs_var_data (unsigned axis_count,
+                                    const hb_array_t<const F2DOT14> shared_tuples,
+                                    const hb_subset_plan_t *plan,
+                                    const hb_hashmap_t<hb_codepoint_t, hb_bytes_t>& new_gid_var_data_map)
+  {
+    if (unlikely (!glyph_variations.alloc (plan->new_to_old_gid_list.length, true)))
+      return false;
+
+    auto it = hb_iter (plan->new_to_old_gid_list);
+    for (auto &_ : it)
+    {
+      hb_codepoint_t new_gid = _.first;
+      contour_point_vector_t *all_contour_points;
+      if (!new_gid_var_data_map.has (new_gid) ||
+          !plan->new_gid_contour_points_map.has (new_gid, &all_contour_points))
+        return false;
+      hb_bytes_t var_data = new_gid_var_data_map.get (new_gid);
+
+      const GlyphVariationData* p = reinterpret_cast<const GlyphVariationData*> (var_data.arrayZ);
+      hb_vector_t<unsigned> shared_indices;
+      GlyphVariationData::tuple_iterator_t iterator;
+      tuple_variations_t tuple_vars;
+
+      /* in case variation data is empty, push an empty struct into the vector,
+       * keep the vector in sync with the new_to_old_gid_list */
+      if (!var_data || ! p->has_data () || !all_contour_points->length ||
+          !GlyphVariationData::get_tuple_iterator (var_data, axis_count,
+                                                   var_data.arrayZ,
+                                                   shared_indices, &iterator))
+      {
+        glyph_variations.push (std::move (tuple_vars));
+        continue;
+      }
+
+      if (!p->decompile_tuple_variations (all_contour_points->length, true /* is_gvar */,
+                                          iterator, &(plan->axes_old_index_tag_map),
+                                          shared_indices, shared_tuples,
+                                          tuple_vars /* OUT */))
+        return false;
+      glyph_variations.push (std::move (tuple_vars));
+    }
+    return !glyph_variations.in_error () && glyph_variations.length == plan->new_to_old_gid_list.length;
+  }
+
+  bool instantiate (const hb_subset_plan_t *plan)
+  {
+    unsigned count = plan->new_to_old_gid_list.length;
+    for (unsigned i = 0; i < count; i++)
+    {
+      hb_codepoint_t new_gid = plan->new_to_old_gid_list[i].first;
+      contour_point_vector_t *all_points;
+      if (!plan->new_gid_contour_points_map.has (new_gid, &all_points))
+        return false;
+      if (!glyph_variations[i].instantiate (plan->axes_location, plan->axes_triple_distances, all_points))
+        return false;
+    }
+    return true;
+  }
+
+  bool compile_bytes (const hb_map_t& axes_index_map,
+                      const hb_map_t& axes_old_index_tag_map)
+  {
+    if (!compile_shared_tuples (axes_index_map, axes_old_index_tag_map))
+      return false;
+    for (tuple_variations_t& vars: glyph_variations)
+      if (!vars.compile_bytes (axes_index_map, axes_old_index_tag_map,
+                               true, /* use shared points*/
+                               &shared_tuples_idx_map))
+        return false;
+
+    return true;
+  }
+
+  bool compile_shared_tuples (const hb_map_t& axes_index_map,
+                              const hb_map_t& axes_old_index_tag_map)
+  {
+    /* key is pointer to compiled_peak_coords inside each tuple, hashing
+     * function will always deref pointers first */
+    hb_hashmap_t<const hb_vector_t<char>*, unsigned> coords_count_map;
+
+    /* count the num of shared coords */
+    for (tuple_variations_t& vars: glyph_variations)
+    {
+      for (tuple_delta_t& var : vars.tuple_vars)
+      {
+        if (!var.compile_peak_coords (axes_index_map, axes_old_index_tag_map))
+          return false;
+        unsigned* count;
+        if (coords_count_map.has (&(var.compiled_peak_coords), &count))
+          coords_count_map.set (&(var.compiled_peak_coords), *count + 1);
+        else
+          coords_count_map.set (&(var.compiled_peak_coords), 1);
+      }
+    }
+
+    if (!coords_count_map || coords_count_map.in_error ())
+      return false;
+
+    /* add only those coords that are used more than once into the vector and sort */
+    hb_vector_t<const hb_vector_t<char>*> shared_coords;
+    if (unlikely (!shared_coords.alloc (coords_count_map.get_population ())))
+      return false;
+
+    for (const auto _ : coords_count_map.iter ())
+    {
+      if (_.second == 1) continue;
+      shared_coords.push (_.first);
+    }
+
+    /* no shared tuples: no coords are used more than once */
+    if (!shared_coords) return true;
+    /* sorting based on the coords frequency first (high to low), then compare
+     * the coords bytes */
+    hb_qsort (shared_coords.arrayZ, shared_coords.length, sizeof (hb_vector_t<char>*), _cmp_coords, (void *) (&coords_count_map));
+
+    /* build shared_coords->idx map and shared tuples byte array */
+
+    shared_tuples_count = hb_min (0xFFFu + 1, shared_coords.length);
+    unsigned len = shared_tuples_count * (shared_coords[0]->length);
+    if (unlikely (!compiled_shared_tuples.alloc (len)))
+      return false;
+
+    for (unsigned i = 0; i < shared_tuples_count; i++)
+    {
+      shared_tuples_idx_map.set (shared_coords[i], i);
+      /* add a concat() in hb_vector_t? */
+      for (char c : shared_coords[i]->iter ())
+        compiled_shared_tuples.push (c);
+    }
+
+    return true;
+  }
+
+  static int _cmp_coords (const void *pa, const void *pb, void *arg)
+  {
+    const hb_hashmap_t<const hb_vector_t<char>*, unsigned>* coords_count_map =
+        reinterpret_cast<const hb_hashmap_t<const hb_vector_t<char>*, unsigned>*> (arg);
+
+    /* shared_coords is hb_vector_t<const hb_vector_t<char>*> so casting pa/pb
+     * to be a pointer to a pointer */
+    const hb_vector_t<char>** a = reinterpret_cast<const hb_vector_t<char>**> (const_cast<void*>(pa));
+    const hb_vector_t<char>** b = reinterpret_cast<const hb_vector_t<char>**> (const_cast<void*>(pb));
+
+    bool has_a = coords_count_map->has (*a);
+    bool has_b = coords_count_map->has (*b);
+
+    if (has_a && has_b)
+    {
+      unsigned a_num = coords_count_map->get (*a);
+      unsigned b_num = coords_count_map->get (*b);
+
+      if (a_num != b_num)
+        return b_num - a_num;
+
+      return (*b)->as_array().cmp ((*a)->as_array ());
+    }
+    else if (has_a) return -1;
+    else if (has_b) return 1;
+    else return 0;
+  }
+
+  template<typename Iterator,
+           hb_requires (hb_is_iterator (Iterator))>
+  bool serialize_glyph_var_data (hb_serialize_context_t *c,
+                                 Iterator it,
+                                 bool long_offset,
+                                 unsigned num_glyphs,
+                                 char* glyph_var_data_offsets /* OUT: glyph var data offsets array */) const
+  {
+    TRACE_SERIALIZE (this);
+
+    if (long_offset)
+    {
+      ((HBUINT32 *) glyph_var_data_offsets)[0] = 0;
+      glyph_var_data_offsets += 4;
+    }
+    else
+    {
+      ((HBUINT16 *) glyph_var_data_offsets)[0] = 0;
+      glyph_var_data_offsets += 2;
+    }
+    unsigned glyph_offset = 0;
+    hb_codepoint_t last_gid = 0;
+    unsigned idx = 0;
+
+    TupleVariationData* cur_glyph = c->start_embed<TupleVariationData> ();
+    if (!cur_glyph) return_trace (false);
+    for (auto &_ : it)
+    {
+      hb_codepoint_t gid = _.first;
+      if (long_offset)
+        for (; last_gid < gid; last_gid++)
+          ((HBUINT32 *) glyph_var_data_offsets)[last_gid] = glyph_offset;
+      else
+        for (; last_gid < gid; last_gid++)
+          ((HBUINT16 *) glyph_var_data_offsets)[last_gid] = glyph_offset / 2;
+
+      if (idx >= glyph_variations.length) return_trace (false);
+      if (!cur_glyph->serialize (c, true, glyph_variations[idx])) return_trace (false);
+      TupleVariationData* next_glyph = c->start_embed<TupleVariationData> ();
+      glyph_offset += (char *) next_glyph - (char *) cur_glyph;
+
+      if (long_offset)
+        ((HBUINT32 *) glyph_var_data_offsets)[gid] = glyph_offset;
+      else
+        ((HBUINT16 *) glyph_var_data_offsets)[gid] = glyph_offset / 2;
+
+      last_gid++;
+      idx++;
+      cur_glyph = next_glyph;
+    }
+
+    if (long_offset)
+      for (; last_gid < num_glyphs; last_gid++)
+        ((HBUINT32 *) glyph_var_data_offsets)[last_gid] = glyph_offset;
+    else
+      for (; last_gid < num_glyphs; last_gid++)
+        ((HBUINT16 *) glyph_var_data_offsets)[last_gid] = glyph_offset / 2;
+    return_trace (true);
+  }
+};
+
 struct gvar
 {
   static constexpr hb_tag_t tableTag = HB_OT_TAG_gvar;
@@ -94,9 +307,102 @@
   bool sanitize (hb_sanitize_context_t *c) const
   { return sanitize_shallow (c); }
 
+  bool decompile_glyph_variations (const hb_subset_plan_t *plan,
+                                   glyph_variations_t& glyph_vars /* OUT */) const
+  {
+
+    hb_hashmap_t<hb_codepoint_t, hb_bytes_t> new_gid_var_data_map;
+    auto it = hb_iter (plan->new_to_old_gid_list);
+    if (it->first == 0 && !(plan->flags & HB_SUBSET_FLAGS_NOTDEF_OUTLINE))
+    {
+      new_gid_var_data_map.set (0, hb_bytes_t ());
+      it++;
+    }
+
+    for (auto &_ : it)
+    {
+      hb_codepoint_t new_gid = _.first;
+      hb_codepoint_t old_gid = _.second;
+      hb_bytes_t var_data_bytes = get_glyph_var_data_bytes (old_gid);
+      new_gid_var_data_map.set (new_gid, var_data_bytes);
+    }
+
+    if (new_gid_var_data_map.in_error ()) return false;
+
+    hb_array_t<const F2DOT14> shared_tuples = (this+sharedTuples).as_array ((unsigned) sharedTupleCount * (unsigned) axisCount);
+    return glyph_vars.create_from_glyphs_var_data (axisCount, shared_tuples, plan, new_gid_var_data_map);
+  }
+
+  template<typename Iterator,
+           hb_requires (hb_is_iterator (Iterator))>
+  bool serialize (hb_serialize_context_t *c,
+                  const glyph_variations_t& glyph_vars,
+                  Iterator it,
+                  unsigned axis_count,
+                  unsigned num_glyphs) const
+  {
+    TRACE_SERIALIZE (this);
+    gvar *out = c->allocate_min<gvar> ();
+    if (unlikely (!out)) return_trace (false);
+
+    out->version.major = 1;
+    out->version.minor = 0;
+    out->axisCount = axis_count;
+    out->glyphCountX = hb_min (0xFFFFu, num_glyphs);
+
+    unsigned glyph_var_data_size = glyph_vars.compiled_byte_size ();
+    bool long_offset = glyph_var_data_size & ~0xFFFFu;
+    out->flags = long_offset ? 1 : 0;
+
+    HBUINT8 *glyph_var_data_offsets = c->allocate_size<HBUINT8> ((long_offset ? 4 : 2) * (num_glyphs + 1), false);
+    if (!glyph_var_data_offsets) return_trace (false);
+
+    /* shared tuples */
+    unsigned shared_tuple_count = glyph_vars.compiled_shared_tuples_count ();
+    out->sharedTupleCount = shared_tuple_count;
+
+    if (!shared_tuple_count)
+      out->sharedTuples = 0;
+    else
+    {
+      hb_array_t<const char> shared_tuples = glyph_vars.compiled_shared_tuples.as_array ().copy (c);
+      if (!shared_tuples.arrayZ) return_trace (false);
+      out->sharedTuples = shared_tuples.arrayZ - (char *) out;
+    }
+
+    char *glyph_var_data = c->start_embed<char> ();
+    if (!glyph_var_data) return_trace (false);
+    out->dataZ = glyph_var_data - (char *) out;
+
+    return_trace (glyph_vars.serialize_glyph_var_data (c, it, long_offset, num_glyphs,
+                                                       (char *) glyph_var_data_offsets));
+  }
+
+  bool instantiate (hb_subset_context_t *c) const
+  {
+    TRACE_SUBSET (this);
+    glyph_variations_t glyph_vars;
+    if (!decompile_glyph_variations (c->plan, glyph_vars))
+      return_trace (false);
+
+    if (!glyph_vars.instantiate (c->plan)) return_trace (false);
+    if (!glyph_vars.compile_bytes (c->plan->axes_index_map, c->plan->axes_old_index_tag_map))
+      return_trace (false);
+
+    unsigned axis_count = c->plan->axes_index_map.get_population ();
+    unsigned num_glyphs = c->plan->num_output_glyphs ();
+    auto it = hb_iter (c->plan->new_to_old_gid_list);
+    return_trace (serialize (c->serializer, glyph_vars, it, axis_count, num_glyphs));
+  }
+
   bool subset (hb_subset_context_t *c) const
   {
     TRACE_SUBSET (this);
+    if (c->plan->all_axes_pinned)
+      return_trace (false);
+
+    if (c->plan->normalized_coords)
+      return_trace (instantiate (c));
 
     unsigned glyph_count = version.to_int () ? c->plan->source->get_num_glyphs () : 0;
 
@@ -211,6 +517,17 @@
     return likely (var_data.length >= GlyphVariationData::min_size) ? var_data : hb_bytes_t ();
   }
 
+  const hb_bytes_t get_glyph_var_data_bytes (hb_codepoint_t gid) const
+  {
+    unsigned start_offset = get_offset (glyphCountX, gid);
+    unsigned end_offset = get_offset (glyphCountX, gid+1);
+    if (unlikely (end_offset < start_offset)) return hb_bytes_t ();
+    unsigned length = end_offset - start_offset;
+    const char *p = (const char*) this + (unsigned) dataZ + start_offset;
+    hb_bytes_t var_data{p, length};
+    return likely (length >= GlyphVariationData::min_size) ? var_data : hb_bytes_t ();
+  }
+
   bool is_long_offset () const { return flags & 1; }
 
   unsigned get_offset (unsigned glyph_count, unsigned i) const
diff --git a/src/hb-subset-input.cc b/src/hb-subset-input.cc
index 93f961f..aade558 100644
--- a/src/hb-subset-input.cc
+++ b/src/hb-subset-input.cc
@@ -75,7 +75,6 @@
     HB_TAG ('V', 'D', 'M', 'X'),
     HB_TAG ('D', 'S', 'I', 'G'),
     HB_TAG ('M', 'V', 'A', 'R'),
-    HB_TAG ('c', 'v', 'a', 'r'),
   };
   sets.no_subset_tables->add_array (default_no_subset_tables,
 					 ARRAY_LENGTH (default_no_subset_tables));
@@ -479,16 +478,21 @@
  * @axis_tag: Tag of the axis
  * @axis_min_value: Minimum value of the axis variation range to set
  * @axis_max_value: Maximum value of the axis variation range to set
+ * @axis_def_value: Default value of the axis variation range to set, in case of
+ * null, it'll be determined automatically
  *
  * Restricting the range of variation on an axis in the given subset input object.
- * New min/max values will be clamped if they're not within the fvar axis range.
+ * New min/default/max values will be clamped if they're not within the fvar axis range.
+ * If the new default value is null:
+ * If the fvar axis default value is within the new range, then new default
+ * value is the same as original default value.
  * If the fvar axis default value is not within the new range, the new default
  * value will be changed to the new min or max value, whichever is closer to the fvar
  * axis default.
  *
- * Note: input min value can not be bigger than input max value
- * Note: currently this API does not support changing axis limits yet.It'd be only
- * used internally for setting axis limits in the internal data structures
+ * Note: input min value can not be bigger than input max value. If the input
+ * default value is not within the new min/max range, it'll be clamped.
+ * Note: currently it supports gvar and cvar tables only.
  *
  * Return value: `true` if success, `false` otherwise
  *
@@ -499,7 +503,8 @@
                                 hb_face_t          *face,
                                 hb_tag_t            axis_tag,
                                 float               axis_min_value,
-                                float               axis_max_value)
+                                float               axis_max_value,
+                                float              *axis_def_value /* IN, maybe NULL */)
 {
   if (axis_min_value > axis_max_value)
     return false;
@@ -510,7 +515,8 @@
 
   float new_min_val = hb_clamp(axis_min_value, axis_info.min_value, axis_info.max_value);
   float new_max_val = hb_clamp(axis_max_value, axis_info.min_value, axis_info.max_value);
-  float new_default_val = hb_clamp(axis_info.default_value, new_min_val, new_max_val);
+  float new_default_val = axis_def_value ? *axis_def_value : axis_info.default_value;
+  new_default_val = hb_clamp(new_default_val, new_min_val, new_max_val);
   return input->axes_location.set (axis_tag, Triple (new_min_val, new_default_val, new_max_val));
 }
 #endif
diff --git a/src/hb-subset-plan-member-list.hh b/src/hb-subset-plan-member-list.hh
index 8e61055..01d8b0f 100644
--- a/src/hb-subset-plan-member-list.hh
+++ b/src/hb-subset-plan-member-list.hh
@@ -123,6 +123,9 @@
 //boundsHeight map: new gid->boundsHeight, boundsHeight=yMax - yMin
 HB_SUBSET_PLAN_MEMBER (mutable hb_vector_t<unsigned>, bounds_height_vec)
 
+//map: new_gid -> contour points vector
+HB_SUBSET_PLAN_MEMBER (mutable hb_hashmap_t E(<hb_codepoint_t, contour_point_vector_t>), new_gid_contour_points_map)
+
 #ifdef HB_EXPERIMENTAL_API
 // name table overrides map: hb_ot_name_record_ids_t-> name string new value or
 // None to indicate should remove
diff --git a/src/hb-subset-plan.cc b/src/hb-subset-plan.cc
index a2090b7..c293ba3 100644
--- a/src/hb-subset-plan.cc
+++ b/src/hb-subset-plan.cc
@@ -1045,6 +1045,36 @@
   if (vvar_store_cache)
     _vmtx.var_table->get_var_store ().destroy_cache (vvar_store_cache);
 }
+
+static bool
+_get_instance_glyphs_contour_points (hb_subset_plan_t *plan)
+{
+  /* contour_points vector only needed for updating gvar table (infer delta)
+   * 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;
+    if (unlikely (!glyf.glyph_for_gid (old_gid).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;
+  }
+  return true;
+}
 #endif
 
 hb_subset_plan_t::hb_subset_plan_t (hb_face_t *face,
@@ -1148,6 +1178,8 @@
 
 #ifndef HB_NO_VAR
   _update_instance_metrics_map_from_cff2 (this);
+  if (!check_success (_get_instance_glyphs_contour_points (this)))
+      return;
 #endif
 
   if (attach_accelerator_data)
diff --git a/src/hb-subset-plan.hh b/src/hb-subset-plan.hh
index d156de0..a05d1d1 100644
--- a/src/hb-subset-plan.hh
+++ b/src/hb-subset-plan.hh
@@ -67,6 +67,40 @@
 
 typedef struct head_maxp_info_t head_maxp_info_t;
 
+struct contour_point_t
+{
+  void init (float x_ = 0.f, float y_ = 0.f, bool is_end_point_ = false)
+  { flag = 0; x = x_; y = y_; is_end_point = is_end_point_; }
+
+  void transform (const float (&matrix)[4])
+  {
+    float x_ = x * matrix[0] + y * matrix[2];
+	  y  = x * matrix[1] + y * matrix[3];
+    x  = x_;
+  }
+  HB_ALWAYS_INLINE
+  void translate (const contour_point_t &p) { x += p.x; y += p.y; }
+
+
+  float x;
+  float y;
+  uint8_t flag;
+  bool is_end_point;
+};
+
+struct contour_point_vector_t : hb_vector_t<contour_point_t>
+{
+  void extend (const hb_array_t<contour_point_t> &a)
+  {
+    unsigned int old_len = length;
+    if (unlikely (!resize (old_len + a.length, false)))
+      return;
+    auto arrayZ = this->arrayZ + old_len;
+    unsigned count = a.length;
+    hb_memcpy (arrayZ, a.arrayZ, count * sizeof (arrayZ[0]));
+  }
+};
+
 namespace OT {
   struct cff1_subset_accelerator_t;
   struct cff2_subset_accelerator_t;
diff --git a/src/hb-subset.cc b/src/hb-subset.cc
index 1f97dbe..100ce87 100644
--- a/src/hb-subset.cc
+++ b/src/hb-subset.cc
@@ -520,6 +520,9 @@
   case HB_OT_TAG_avar:
     if (plan->user_axes_location.is_empty ()) return _passthrough (plan, tag);
     return _subset<const OT::avar> (plan, buf);
+  case HB_OT_TAG_cvar:
+    if (plan->user_axes_location.is_empty ()) return _passthrough (plan, tag);
+    return _subset<const OT::cvar> (plan, buf);
   case HB_OT_TAG_STAT:
     if (!plan->user_axes_location.is_empty ()) return _subset<const OT::STAT> (plan, buf);
     else return _passthrough (plan, tag);
diff --git a/src/hb-subset.h b/src/hb-subset.h
index 93f1f7f..4c35699 100644
--- a/src/hb-subset.h
+++ b/src/hb-subset.h
@@ -181,7 +181,8 @@
 				hb_face_t          *face,
 				hb_tag_t            axis_tag,
 				float               axis_min_value,
-				float               axis_max_value);
+				float               axis_max_value,
+				float              *axis_def_value);
 
 HB_EXTERN hb_bool_t
 hb_subset_input_override_name_table (hb_subset_input_t  *input,
diff --git a/src/test-tuple-varstore.cc b/src/test-tuple-varstore.cc
index 32d2ea4..f1286e7 100644
--- a/src/test-tuple-varstore.cc
+++ b/src/test-tuple-varstore.cc
@@ -83,8 +83,7 @@
   hb_hashmap_t<hb_tag_t, TripleDistances> axes_triple_distances;
   axes_triple_distances.set (axis_tag, TripleDistances (1.f, 1.f));
 
-  tuple_variations.change_tuple_variations_axis_limits (normalized_axes_location, axes_triple_distances);
-  tuple_variations.merge_tuple_variations ();
+  tuple_variations.instantiate (normalized_axes_location, axes_triple_distances);
 
   assert (tuple_variations.tuple_vars[0].indices.length == 65);
   assert (tuple_variations.tuple_vars[1].indices.length == 65);
@@ -115,7 +114,7 @@
 
   hb_map_t axes_index_map;
   axes_index_map.set (0, 0);
-  bool res = tuple_variations.compile_bytes (axes_index_map, axis_idx_tag_map);
+  bool res = tuple_variations.compile_bytes (axes_index_map, axis_idx_tag_map, false);
   assert (res);
   assert (tuple_variations.tuple_vars[0].compiled_tuple_header.length == 6);
   const char tuple_var_header_1[] = "\x0\x51\xa0\x0\xc0\x0";
diff --git a/test/subset/data/expected/glyf_partial_instancing/Roboto-Variable.ABC.no-tables-with-item-variations.retain-all-codepoint.wght=200-300-500,wdth=80-90.ttf b/test/subset/data/expected/glyf_partial_instancing/Roboto-Variable.ABC.no-tables-with-item-variations.retain-all-codepoint.wght=200-300-500,wdth=80-90.ttf
new file mode 100644
index 0000000..92b190b
--- /dev/null
+++ b/test/subset/data/expected/glyf_partial_instancing/Roboto-Variable.ABC.no-tables-with-item-variations.retain-all-codepoint.wght=200-300-500,wdth=80-90.ttf
Binary files differ
diff --git a/test/subset/data/expected/glyf_partial_instancing/Roboto-Variable.ABC.no-tables-with-item-variations.retain-all-codepoint.wght=300-600,wdth=85.ttf b/test/subset/data/expected/glyf_partial_instancing/Roboto-Variable.ABC.no-tables-with-item-variations.retain-all-codepoint.wght=300-600,wdth=85.ttf
new file mode 100644
index 0000000..7fa2452
--- /dev/null
+++ b/test/subset/data/expected/glyf_partial_instancing/Roboto-Variable.ABC.no-tables-with-item-variations.retain-all-codepoint.wght=300-600,wdth=85.ttf
Binary files differ
diff --git a/test/subset/data/expected/glyf_partial_instancing/Roboto-Variable.composite.no-tables-with-item-variations.retain-all-codepoint.wght=200-300-500,wdth=80-90.ttf b/test/subset/data/expected/glyf_partial_instancing/Roboto-Variable.composite.no-tables-with-item-variations.retain-all-codepoint.wght=200-300-500,wdth=80-90.ttf
new file mode 100644
index 0000000..2e55653
--- /dev/null
+++ b/test/subset/data/expected/glyf_partial_instancing/Roboto-Variable.composite.no-tables-with-item-variations.retain-all-codepoint.wght=200-300-500,wdth=80-90.ttf
Binary files differ
diff --git a/test/subset/data/expected/glyf_partial_instancing/Roboto-Variable.composite.no-tables-with-item-variations.retain-all-codepoint.wght=300-600,wdth=85.ttf b/test/subset/data/expected/glyf_partial_instancing/Roboto-Variable.composite.no-tables-with-item-variations.retain-all-codepoint.wght=300-600,wdth=85.ttf
new file mode 100644
index 0000000..fc890fb
--- /dev/null
+++ b/test/subset/data/expected/glyf_partial_instancing/Roboto-Variable.composite.no-tables-with-item-variations.retain-all-codepoint.wght=300-600,wdth=85.ttf
Binary files differ
diff --git a/test/subset/data/profiles/no-tables-with-item-variations.txt b/test/subset/data/profiles/no-tables-with-item-variations.txt
new file mode 100644
index 0000000..df2b423
--- /dev/null
+++ b/test/subset/data/profiles/no-tables-with-item-variations.txt
@@ -0,0 +1 @@
+--drop-tables+=MVAR,HVAR,VVAR,GDEF,COLR,GPOS
diff --git a/test/subset/data/tests/glyf_partial_instancing.tests b/test/subset/data/tests/glyf_partial_instancing.tests
new file mode 100644
index 0000000..c0cb129
--- /dev/null
+++ b/test/subset/data/tests/glyf_partial_instancing.tests
@@ -0,0 +1,13 @@
+FONTS:
+Roboto-Variable.ABC.ttf
+Roboto-Variable.composite.ttf
+
+PROFILES:
+no-tables-with-item-variations.txt
+
+SUBSETS:
+*
+
+INSTANCES:
+wght=300:600,wdth=85
+wght=200:300:500,wdth=80:90
diff --git a/test/subset/generate-expected-outputs.py b/test/subset/generate-expected-outputs.py
index 5f21f80..4e37562 100755
--- a/test/subset/generate-expected-outputs.py
+++ b/test/subset/generate-expected-outputs.py
@@ -34,6 +34,7 @@
 		args = ["fonttools", "varLib.instancer",
 			"--no-overlap-flag",
 			"--no-recalc-timestamp",
+			"--no-optimize",
 			"--output=%s" % instance_path,
 			input_file]
 		args.extend(instance_flags)
diff --git a/test/subset/meson.build b/test/subset/meson.build
index 45cd70e..be4f69b 100644
--- a/test/subset/meson.build
+++ b/test/subset/meson.build
@@ -67,6 +67,10 @@
   'instantiate_cff2_update_metrics',
 ]
 
+if get_option('experimental_api')
+  tests += 'glyf_partial_instancing'
+endif
+
 repack_tests = [
   'basic',
   'prioritization',
diff --git a/util/hb-subset.cc b/util/hb-subset.cc
index 599c7dc..bd3df8f 100644
--- a/util/hb-subset.cc
+++ b/util/hb-subset.cc
@@ -707,34 +707,88 @@
       return false;
     }
 
-    if (strcmp (s, "drop") == 0)
-    {
-      if (!hb_subset_input_pin_axis_to_default (subset_main->input, subset_main->face, axis_tag))
-      {
-        g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
-                     "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag));
-        return false;
-      }
-    }
-    else
+#ifdef HB_EXPERIMENTAL_API
+    char *pp = s;
+    pp = strpbrk (pp, ":");
+    if (pp) // partial instancing
     {
       errno = 0;
-      char *p;
-      float axis_value = strtof (s, &p);
-      if (errno || s == p)
+      char *pend;
+      float min_val = strtof (s, &pend);
+      if (errno || s == pend || pend != pp)
       {
         g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
                      "Failed parsing axis value at: '%s'", s);
         return false;
       }
-
-      if (!hb_subset_input_pin_axis_location (subset_main->input, subset_main->face, axis_tag, axis_value))
+      pp++;
+      float max_val = strtof (pp, &pend);
+      /* we need to specify 2 values or 3 values for partial instancing:
+       * at least new min and max values, new default is optional */
+      if (errno || pp == pend || (*pend != ':' && *pend != '\0'))
       {
         g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
-                     "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag));
+                     "Failed parsing axis value at: '%s'", s);
         return false;
       }
+      /* 3 values are specified */
+      float *def_val_p = nullptr;
+      float def_val;
+      if (*pend == ':')
+      {
+        def_val = max_val;
+        def_val_p = &def_val;
+        pp = pend + 1;
+        max_val = strtof (pp, &pend);
+        if (errno || pp == pend || *pend != '\0')
+        {
+          g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                     "Failed parsing axis value at: '%s'", s);
+          return false;
+        }
+      }
+      if (!hb_subset_input_set_axis_range (subset_main->input, subset_main->face, axis_tag, min_val, max_val, def_val_p))
+        {
+          g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                       "Error: axis: '%c%c%c%c', not present in fvar or invalid range with min:%.6f max:%.6f",
+                       HB_UNTAG (axis_tag), min_val, max_val);
+          return false;
+        }
     }
+    else
+    {
+#endif
+      if (strcmp (s, "drop") == 0)
+      {
+        if (!hb_subset_input_pin_axis_to_default (subset_main->input, subset_main->face, axis_tag))
+        {
+          g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                       "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag));
+          return false;
+        }
+      }
+      else
+      {
+        errno = 0;
+        char *p;
+        float axis_value = strtof (s, &p);
+        if (errno || s == p)
+        {
+          g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                       "Failed parsing axis value at: '%s'", s);
+          return false;
+        }
+  
+        if (!hb_subset_input_pin_axis_location (subset_main->input, subset_main->face, axis_tag, axis_value))
+        {
+          g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
+                       "Cannot pin axis: '%c%c%c%c', not present in fvar", HB_UNTAG (axis_tag));
+          return false;
+        }
+      }
+#ifdef HB_EXPERIMENTAL_API
+    }
+#endif
     s = strtok(nullptr, "=");
   }