/*
 * Copyright © 2026  Behdad Esfahbod
 *
 *  This is part of HarfBuzz, a text shaping library.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 *
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 *
 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */

#include "hb-test.h"

#include <hb-ot.h>
#include <hb-subset.h>

/* Differential tests for avar2 partial instancing.
 *
 * A partial instance of an avar2 font must produce the same final
 * normalized coordinates (fvar normalization + avar v1 + avar v2) as the
 * original font at every retained user-space location; the subsetter
 * encodes the difference between the old and the new intermediate
 * coordinate spaces as avar2 deltas ("offset compensation").
 *
 * TestAvar2Instance.ttf axes: wght 100/400/900, wdth 50/100/200,
 * GRAD -200/0/150, XOPQ 10/88/160, opsz 8/14/144. avar v1 has interior
 * breakpoints on wght (at ±0.5), XOPQ and opsz (at -0.45). The avar2
 * VarStore drives wght's final coordinate from wdth, and GRAD's and
 * XOPQ's from wght through a single SHARED delta row (the VarIdxMap maps
 * GRAD and XOPQ to the same varIdx); wdth and opsz map to
 * NO_VARIATION_INDEX. opsz's default lies near its minimum, so a
 * far-moved default makes the retained avar v1 segment steep in the new
 * space -- the regime where offset compensation is quantization-limited.
 */

#define WGHT HB_TAG ('w','g','h','t')
#define WDTH HB_TAG ('w','d','t','h')
#define GRAD HB_TAG ('G','R','A','D')
#define XOPQ HB_TAG ('X','O','P','Q')
#define OPSZ HB_TAG ('o','p','s','z')

/* Compare final normalized coords of two faces at the same user location,
 * in F2Dot14 units. Compared by axis tag: self-contained pinned axes are
 * removed from the instance entirely (their contribution is baked into the
 * variation tables, which check_same_rendering validates). */
static void
check_same_coords (hb_face_t *orig_face, hb_face_t *inst_face,
		   const hb_variation_t *vars, unsigned n_vars,
		   int tolerance)
{
  hb_font_t *orig_font = hb_font_create (orig_face);
  hb_font_t *inst_font = hb_font_create (inst_face);
  hb_font_set_variations (orig_font, vars, n_vars);
  hb_font_set_variations (inst_font, vars, n_vars);

  unsigned orig_len = 0, inst_len = 0;
  const int *orig_coords = hb_font_get_var_coords_normalized (orig_font, &orig_len);
  const int *inst_coords = hb_font_get_var_coords_normalized (inst_font, &inst_len);

  hb_ot_var_axis_info_t orig_axes[8], inst_axes[8];
  unsigned orig_axis_count = 8, inst_axis_count = 8;
  hb_ot_var_get_axis_infos (orig_face, 0, &orig_axis_count, orig_axes);
  hb_ot_var_get_axis_infos (inst_face, 0, &inst_axis_count, inst_axes);

  g_assert_cmpuint (orig_len, ==, 6); /* not vacuous */
  g_assert_cmpuint (orig_len, ==, orig_axis_count);
  g_assert_cmpuint (inst_len, ==, inst_axis_count);

  for (unsigned j = 0; j < inst_axis_count; j++)
  {
    unsigned i = 0;
    for (; i < orig_axis_count; i++)
      if (orig_axes[i].tag == inst_axes[j].tag)
	break;
    g_assert_cmpuint (i, <, orig_axis_count); /* tag must exist in original */
    g_assert_cmpint (ABS (orig_coords[i] - inst_coords[j]), <=, tolerance);
  }

  hb_font_destroy (orig_font);
  hb_font_destroy (inst_font);
}

/* Compare rendering-level outputs (advance width and extents of 'A') at
 * the same user location; validates that culled gvar/HVAR variations were
 * truly unreachable. */
static void
check_same_rendering (hb_face_t *orig_face, hb_face_t *inst_face,
		      const hb_variation_t *vars, unsigned n_vars)
{
  hb_font_t *orig_font = hb_font_create (orig_face);
  hb_font_t *inst_font = hb_font_create (inst_face);
  hb_font_set_variations (orig_font, vars, n_vars);
  hb_font_set_variations (inst_font, vars, n_vars);

  hb_codepoint_t orig_gid = 0, inst_gid = 0;
  g_assert_true (hb_font_get_nominal_glyph (orig_font, 'A', &orig_gid));
  g_assert_true (hb_font_get_nominal_glyph (inst_font, 'A', &inst_gid));

  hb_position_t orig_adv = hb_font_get_glyph_h_advance (orig_font, orig_gid);
  hb_position_t inst_adv = hb_font_get_glyph_h_advance (inst_font, inst_gid);
  g_assert_cmpint (ABS (orig_adv - inst_adv), <=, 1);

  hb_glyph_extents_t orig_ext, inst_ext;
  g_assert_true (hb_font_get_glyph_extents (orig_font, orig_gid, &orig_ext));
  g_assert_true (hb_font_get_glyph_extents (inst_font, inst_gid, &inst_ext));
  g_assert_cmpint (ABS (orig_ext.x_bearing - inst_ext.x_bearing), <=, 1);
  g_assert_cmpint (ABS (orig_ext.y_bearing - inst_ext.y_bearing), <=, 1);
  g_assert_cmpint (ABS (orig_ext.width - inst_ext.width), <=, 1);
  g_assert_cmpint (ABS (orig_ext.height - inst_ext.height), <=, 1);

  /* MVAR-driven metric (underline offset varies with wght). */
  hb_position_t orig_undo = 0, inst_undo = 0;
  g_assert_true (hb_ot_metrics_get_position (orig_font, HB_OT_METRICS_TAG_UNDERLINE_OFFSET, &orig_undo));
  g_assert_true (hb_ot_metrics_get_position (inst_font, HB_OT_METRICS_TAG_UNDERLINE_OFFSET, &inst_undo));
  g_assert_cmpint (ABS (orig_undo - inst_undo), <=, 1);

  hb_font_destroy (orig_font);
  hb_font_destroy (inst_font);
}

static hb_face_t *
open_original (void)
{
  hb_face_t *face = hb_test_open_font_file ("fonts/TestAvar2Instance.ttf");
  g_assert_cmpuint (hb_ot_var_get_axis_count (face), ==, 6);
  return face;
}

static unsigned
read_uint16_be (const unsigned char *p)
{
  return ((unsigned) p[0] << 8) | p[1];
}

static unsigned
read_uint32_be (const unsigned char *p)
{
  return ((unsigned) p[0] << 24) | ((unsigned) p[1] << 16) |
	 ((unsigned) p[2] << 8) | p[3];
}

/* Replace the test font's avar2 VarIdxMap with the implicit map and empty
 * its non-NULL VarStore. The resulting implicit delta-set indices are out
 * of range and must contribute zero, matching runtime evaluation. */
static hb_blob_t *
reference_table_with_empty_avar2_store (hb_face_t *face HB_UNUSED,
					hb_tag_t tag,
					void *user_data)
{
  hb_blob_t *table = hb_face_reference_table ((hb_face_t *) user_data, tag);
  if (tag != HB_TAG ('a','v','a','r'))
    return table;

  hb_blob_t *copy = hb_blob_copy_writable_or_fail (table);
  hb_blob_destroy (table);
  g_assert_nonnull (copy);

  unsigned length;
  unsigned char *data = (unsigned char *) hb_blob_get_data_writable (copy, &length);
  g_assert_nonnull (data);
  g_assert_cmpuint (length, >=, 8);
  g_assert_cmpuint (read_uint16_be (data), ==, 2); /* major version */

  unsigned offset = 8;
  unsigned axis_count = read_uint16_be (data + 6);
  for (unsigned i = 0; i < axis_count; i++)
  {
    g_assert_cmpuint (offset + 2, <=, length);
    unsigned map_count = read_uint16_be (data + offset);
    g_assert_cmpuint (map_count, <=, (length - offset - 2) / 4);
    offset += 2 + map_count * 4;
  }

  g_assert_cmpuint (offset + 8, <=, length);
  unsigned var_store_offset = read_uint32_be (data + offset + 4);
  g_assert_cmpuint (var_store_offset, <=, length - 8);
  g_assert_cmpuint (read_uint16_be (data + var_store_offset), ==, 1);
  g_assert_cmpuint (read_uint16_be (data + var_store_offset + 6), >, 0);

  data[offset + 0] = data[offset + 1] = 0;
  data[offset + 2] = data[offset + 3] = 0; /* NULL VarIdxMap: implicit map */
  data[var_store_offset + 6] = 0;
  data[var_store_offset + 7] = 0; /* VarDataCount = 0 */
  return copy;
}

static hb_face_t *
instance_n (hb_face_t *face, hb_subset_input_t *input, unsigned expected_axes)
{
  hb_face_t *inst = hb_subset_or_fail (face, input);
  hb_subset_input_destroy (input);
  g_assert_nonnull (inst);
  /* All axes are kept (pinned ones as hidden), except self-contained
   * pinned axes, which are removed entirely. */
  g_assert_cmpuint (hb_ot_var_get_axis_count (inst), ==, expected_axes);
  return inst;
}

static hb_face_t *
instance (hb_face_t *face, hb_subset_input_t *input)
{
  return instance_n (face, input, 6);
}

static hb_subset_input_t *
create_input (void)
{
  hb_subset_input_t *input = hb_subset_input_create_or_fail ();
  g_assert_nonnull (input);
  hb_subset_input_keep_everything (input);
  return input;
}

static float
lerp (float lo, float hi, unsigned i, unsigned n)
{
  return lo + (hi - lo) * i / (float) (n - 1);
}

/* Restrict wght without moving its default: the classic two-tent offset
 * compensation. */
static void
test_avar2_restrict_same_default (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_set_axis_range (input, face, WGHT, 200.f, 700.f, 400.f));
  hb_face_t *inst = instance (face, input);

  static const float wdths[] = {50.f, 100.f, 150.f, 200.f};
  for (unsigned i = 0; i < 9; i++)
    for (unsigned j = 0; j < G_N_ELEMENTS (wdths); j++)
    {
      hb_variation_t vars[2] = {{WGHT, lerp (200.f, 700.f, i, 9)},
				{WDTH, wdths[j]}};
      check_same_coords (face, inst, vars, 2, 2);
    }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

/* Restrict wght MOVING its default: the offset function kinks where the
 * old default lands in the new space; a plain two-tent encoding gets
 * interior coordinates wrong. */
static void
test_avar2_restrict_moved_default (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_set_axis_range (input, face, WGHT, 190.f, 900.f, 550.f));
  hb_face_t *inst = instance (face, input);

  /* Include the exact kink locations: the avar v1 breakpoints (user 250
   * and 650) and the old default (user 400, the z_old kink). */
  static const float wdths[] = {50.f, 100.f, 200.f};
  static const float kinks[] = {250.f, 400.f, 650.f};
  for (unsigned i = 0; i < 9 + G_N_ELEMENTS (kinks); i++)
    for (unsigned j = 0; j < G_N_ELEMENTS (wdths); j++)
    {
      float w = i < 9 ? lerp (190.f, 900.f, i, 9) : kinks[i - 9];
      hb_variation_t vars[2] = {{WGHT, w},
				{WDTH, wdths[j]}};
      check_same_coords (face, inst, vars, 2, 2);
      check_same_rendering (face, inst, vars, 2);
    }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

/* Restrict wght moving its default the other way: the old default lands
 * ABOVE the new one, putting the z_old kink on the positive side. */
static void
test_avar2_restrict_moved_default_positive (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_set_axis_range (input, face, WGHT, 100.f, 500.f, 250.f));
  hb_face_t *inst = instance (face, input);

  static const float wdths[] = {50.f, 100.f, 200.f};
  for (unsigned i = 0; i < 9; i++)
    for (unsigned j = 0; j < G_N_ELEMENTS (wdths); j++)
    {
      hb_variation_t vars[2] = {{WGHT, lerp (100.f, 500.f, i, 9)},
				{WDTH, wdths[j]}};
      check_same_coords (face, inst, vars, 2, 2);
    }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

/* Restrict wdth, which has no avar2 delta row (NO_VARIATION_INDEX): its
 * offset compensation needs a freshly created VarData. Moved default. */
static void
test_avar2_restrict_no_varidx_axis (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_set_axis_range (input, face, WDTH, 60.f, 200.f, 120.f));
  hb_face_t *inst = instance (face, input);

  static const float wghts[] = {100.f, 400.f, 900.f};
  for (unsigned i = 0; i < 9; i++)
    for (unsigned k = 0; k < G_N_ELEMENTS (wghts); k++)
    {
      hb_variation_t vars[2] = {{WDTH, lerp (60.f, 200.f, i, 9)},
				{WGHT, wghts[k]}};
      check_same_coords (face, inst, vars, 2, 2);
    }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

/* Restrict XOPQ (moved default; interior avar v1 breakpoint in range).
 * XOPQ shares its avar2 delta row with GRAD: without privatizing the
 * shared row, XOPQ's offset-compensation deltas corrupt free GRAD. */
static void
test_avar2_restrict_shared_row (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_set_axis_range (input, face, XOPQ, 20.f, 160.f, 120.f));
  hb_face_t *inst = instance (face, input);

  /* Include the exact kink locations: the avar v1 breakpoint (user 52.9)
   * and the old default (user 88, the z_old kink). */
  static const float grads[] = {-200.f, -50.f, 0.f, 100.f, 150.f};
  static const float wghts[] = {100.f, 400.f, 900.f};
  static const float kinks[] = {52.9f, 88.f};
  for (unsigned i = 0; i < 9 + G_N_ELEMENTS (kinks); i++)
    for (unsigned j = 0; j < G_N_ELEMENTS (grads); j++)
      for (unsigned k = 0; k < G_N_ELEMENTS (wghts); k++)
      {
	float x = i < 9 ? lerp (20.f, 160.f, i, 9) : kinks[i - 9];
	hb_variation_t vars[3] = {{XOPQ, x},
				  {GRAD, grads[j]},
				  {WGHT, wghts[k]}};
	check_same_coords (face, inst, vars, 3, 2);
      }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

/* Pin XOPQ off-default: only its bias is written; free GRAD, sharing the
 * row, must stay uncorrupted. */
static void
test_avar2_pin_shared_row (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_pin_axis_location (input, face, XOPQ, 120.f));
  hb_face_t *inst = instance (face, input);

  static const float grads[] = {-200.f, -50.f, 0.f, 100.f, 150.f};
  static const float wghts[] = {100.f, 400.f, 700.f, 900.f};
  for (unsigned j = 0; j < G_N_ELEMENTS (grads); j++)
    for (unsigned k = 0; k < G_N_ELEMENTS (wghts); k++)
    {
      /* Evaluate the original at the pinned XOPQ value. */
      hb_variation_t vars[3] = {{XOPQ, 120.f},
				{GRAD, grads[j]},
				{WGHT, wghts[k]}};
      check_same_coords (face, inst, vars, 3, 2);
    }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

/* Pin wght off-default: its final coordinate still varies with free wdth
 * through the (rebased) avar2 delta row. */
static void
test_avar2_pin_driven_axis (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_pin_axis_location (input, face, WGHT, 600.f));
  hb_face_t *inst = instance (face, input);

  static const float wdths[] = {50.f, 100.f, 150.f, 200.f};
  static const float grads[] = {0.f, 100.f};
  for (unsigned j = 0; j < G_N_ELEMENTS (wdths); j++)
    for (unsigned k = 0; k < G_N_ELEMENTS (grads); k++)
    {
      hb_variation_t vars[3] = {{WGHT, 600.f},
				{WDTH, wdths[j]},
				{GRAD, grads[k]}};
      check_same_coords (face, inst, vars, 3, 2);
    }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

/* Pin axes AT their old defaults (the most common instancing operation):
 * d_i = 0 and the bias vanishes; the axes just become hidden. XOPQ covers
 * the shared-row path, wdth the NO_VARIATION_INDEX path. */
static void
test_avar2_pin_at_default (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_pin_axis_location (input, face, XOPQ, 88.f));
  g_assert_true (hb_subset_input_pin_axis_location (input, face, WDTH, 100.f));
  /* wdth has no avar2 row: pinning it is self-contained, so it is removed
   * from fvar entirely. XOPQ's row varies with wght, so it stays hidden. */
  hb_face_t *inst = instance_n (face, input, 5);

  static const float grads[] = {-200.f, -50.f, 0.f, 100.f, 150.f};
  static const float wghts[] = {100.f, 250.f, 400.f, 650.f, 900.f};
  for (unsigned j = 0; j < G_N_ELEMENTS (grads); j++)
    for (unsigned k = 0; k < G_N_ELEMENTS (wghts); k++)
    {
      hb_variation_t vars[4] = {{XOPQ, 88.f},
				{WDTH, 100.f},
				{GRAD, grads[j]},
				{WGHT, wghts[k]}};
      check_same_coords (face, inst, vars, 4, 2);
    }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

/* Restrict both axes sharing the delta row: each needs a private copy. */
static void
test_avar2_restrict_both_shared (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_set_axis_range (input, face, GRAD, -100.f, 100.f, 0.f));
  g_assert_true (hb_subset_input_set_axis_range (input, face, XOPQ, 30.f, 140.f, 88.f));
  hb_face_t *inst = instance (face, input);

  static const float wghts[] = {100.f, 400.f, 900.f};
  for (unsigned i = 0; i < 7; i++)
    for (unsigned j = 0; j < 7; j++)
      for (unsigned k = 0; k < G_N_ELEMENTS (wghts); k++)
      {
	hb_variation_t vars[3] = {{GRAD, lerp (-100.f, 100.f, i, 7)},
				  {XOPQ, lerp (30.f, 140.f, j, 7)},
				  {WGHT, wghts[k]}};
	check_same_coords (face, inst, vars, 3, 2);
      }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

/* Move XOPQ's default near the axis edge: the retained avar v1 segment
 * becomes steep in the new space; interior-breakpoint collection keeps
 * the residual within quantization noise. */
static void
test_avar2_restrict_steep (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_set_axis_range (input, face, XOPQ, 10.f, 160.f, 140.f));
  hb_face_t *inst = instance (face, input);

  static const float wghts[] = {100.f, 400.f, 900.f};
  static const float kinks[] = {52.9f, 88.f};
  for (unsigned i = 0; i < 17 + G_N_ELEMENTS (kinks); i++)
    for (unsigned k = 0; k < G_N_ELEMENTS (wghts); k++)
    {
      float x = i < 17 ? lerp (10.f, 160.f, i, 17) : kinks[i - 17];
      hb_variation_t vars[2] = {{XOPQ, x},
				{WGHT, wghts[k]}};
      check_same_coords (face, inst, vars, 2, 2);
    }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

/* Pin opsz (NO_VARIATION_INDEX) off its default: self-contained, so the
 * axis is removed from fvar entirely and its constant final coordinate is
 * baked into the variation tables by standard instancing. */
static void
test_avar2_self_contained_pin (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_pin_axis_location (input, face, OPSZ, 40.f));
  hb_face_t *inst = instance_n (face, input, 5);

  static const float grads[] = {-200.f, 0.f, 150.f};
  for (unsigned i = 0; i < 9; i++)
    for (unsigned j = 0; j < G_N_ELEMENTS (grads); j++)
    {
      hb_variation_t vars[3] = {{OPSZ, 40.f},
				{WGHT, lerp (100.f, 900.f, i, 9)},
				{GRAD, grads[j]}};
      check_same_coords (face, inst, vars, 3, 2);
      check_same_rendering (face, inst, vars, 3);
    }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

/* Self-contained pin combined with a range restriction: wdth folds into
 * the tables (including its constant push on wght's final coordinate,
 * re-added as a free-axis bias) while wght keeps offset compensation. */
static void
test_avar2_self_contained_plus_range (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_pin_axis_location (input, face, WDTH, 150.f));
  g_assert_true (hb_subset_input_set_axis_range (input, face, WGHT, 250.f, 700.f, 400.f));
  hb_face_t *inst = instance_n (face, input, 5);

  static const float grads[] = {-200.f, 0.f, 150.f};
  for (unsigned i = 0; i < 9; i++)
    for (unsigned j = 0; j < G_N_ELEMENTS (grads); j++)
    {
      hb_variation_t vars[3] = {{WDTH, 150.f},
				{WGHT, lerp (250.f, 700.f, i, 9)},
				{GRAD, grads[j]}};
      check_same_coords (face, inst, vars, 3, 2);
      check_same_rendering (face, inst, vars, 3);
    }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

/* Culling: restricting wght narrows the reachable range of the hidden
 * XTRA axis (driven by wght through avar2), killing the gvar/HVAR
 * variations that peak outside it. Rendering must be unchanged over the
 * retained space, and the instance's variation tables must shrink. */
static void
test_avar2_culling (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_set_axis_range (input, face, WGHT, 300.f, 500.f, 400.f));
  hb_face_t *inst = instance (face, input);

  static const float grads[] = {-200.f, 0.f, 150.f};
  static const float wdths[] = {50.f, 100.f, 200.f};
  for (unsigned i = 0; i < 9; i++)
    for (unsigned j = 0; j < G_N_ELEMENTS (grads); j++)
      for (unsigned k = 0; k < G_N_ELEMENTS (wdths); k++)
      {
	hb_variation_t vars[3] = {{WGHT, lerp (300.f, 500.f, i, 9)},
				  {GRAD, grads[j]},
				  {WDTH, wdths[k]}};
	check_same_coords (face, inst, vars, 3, 2);
	check_same_rendering (face, inst, vars, 3);
      }

  /* Unreachable gvar/HVAR variations must be gone. */
  hb_blob_t *orig_gvar = hb_face_reference_table (face, HB_TAG ('g','v','a','r'));
  hb_blob_t *inst_gvar = hb_face_reference_table (inst, HB_TAG ('g','v','a','r'));
  g_assert_cmpuint (hb_blob_get_length (inst_gvar), <, hb_blob_get_length (orig_gvar));
  hb_blob_destroy (orig_gvar);
  hb_blob_destroy (inst_gvar);

  hb_blob_t *orig_hvar = hb_face_reference_table (face, HB_TAG ('H','V','A','R'));
  hb_blob_t *inst_hvar = hb_face_reference_table (inst, HB_TAG ('H','V','A','R'));
  g_assert_cmpuint (hb_blob_get_length (inst_hvar), <, hb_blob_get_length (orig_hvar));
  hb_blob_destroy (orig_hvar);
  hb_blob_destroy (inst_hvar);

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

static void
test_avar2_empty_varstore (void)
{
  hb_face_t *source = open_original ();
  hb_face_t *face = hb_face_create_for_tables (reference_table_with_empty_avar2_store,
					       source, NULL);
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_set_axis_range (input, face, WGHT, 300.f, 500.f, 400.f));
  hb_face_t *inst = instance (face, input);

  for (unsigned i = 0; i < 9; i++)
  {
    hb_variation_t vars[1] = {{WGHT, lerp (300.f, 500.f, i, 9)}};
    check_same_coords (face, inst, vars, 1, 2);
  }

  hb_face_destroy (inst);
  hb_face_destroy (face);
  hb_face_destroy (source);
}

/* Move opsz's default from near the axis minimum (14) far up the axis:
 * the retained negative-side avar v1 segment becomes very steep in the
 * new space and offset compensation is quantization-limited. The
 * tolerance sits one F2Dot14 unit above the residual achieved WITH
 * interior-breakpoint collection; dropping the collection (or the whole
 * knot machinery) exceeds it. */
static void
test_avar2_restrict_steep_no_varidx (void)
{
  hb_face_t *face = open_original ();
  hb_subset_input_t *input = create_input ();
  g_assert_true (hb_subset_input_set_axis_range (input, face, OPSZ, 8.f, 144.f, 120.f));
  hb_face_t *inst = instance (face, input);

  for (unsigned i = 0; i < 33; i++)
  {
    hb_variation_t vars[1] = {{OPSZ, lerp (8.f, 144.f, i, 33)}};
    check_same_coords (face, inst, vars, 1, 5);
  }

  hb_face_destroy (inst);
  hb_face_destroy (face);
}

int
main (int argc, char **argv)
{
  hb_test_init (&argc, &argv);

  hb_test_add (test_avar2_restrict_same_default);
  hb_test_add (test_avar2_restrict_moved_default);
  hb_test_add (test_avar2_restrict_moved_default_positive);
  hb_test_add (test_avar2_restrict_no_varidx_axis);
  hb_test_add (test_avar2_restrict_shared_row);
  hb_test_add (test_avar2_pin_shared_row);
  hb_test_add (test_avar2_pin_at_default);
  hb_test_add (test_avar2_pin_driven_axis);
  hb_test_add (test_avar2_self_contained_pin);
  hb_test_add (test_avar2_self_contained_plus_range);
  hb_test_add (test_avar2_restrict_both_shared);
  hb_test_add (test_avar2_culling);
  hb_test_add (test_avar2_empty_varstore);
  hb_test_add (test_avar2_restrict_steep);
  hb_test_add (test_avar2_restrict_steep_no_varidx);

  return hb_test_run ();
}
