text: /* SPDX-License-Identifier: MIT */
  
  #include <graphene.h>
  #include <mutest.h>
  
  static void
  rect_init (mutest_spec_t *spec)
  {
    graphene_rect_t zero;
    graphene_rect_t *r;
    graphene_rect_t r2;
  
    r = graphene_rect_init (graphene_rect_alloc (), 0.f, 0.f, 10.f, 10.f);
    mutest_expect ("init to initialize the origin",
                   mutest_bool_value (graphene_point_equal (&r->origin, &GRAPHENE_POINT_INIT (0.f, 0.f))),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("init to initialize the size",
                   mutest_bool_value (graphene_size_equal (&r->size, &GRAPHENE_SIZE_INIT (10.f, 10.f))),
                   mutest_to_be_true,
                   NULL);
  
    graphene_rect_init_from_rect (&r2, r);
    mutest_expect ("initializing from a rectangle creates an equivalent rectangle",
                   mutest_bool_value (graphene_rect_equal (&r2, r)),
                   mutest_to_be_true,
                   NULL);
  
    zero = GRAPHENE_RECT_INIT_ZERO;
    mutest_expect ("GRAPHENE_RECT_INIT_ZERO initializes a degenerate rectangle",
                   mutest_bool_value (graphene_rect_equal (&zero, graphene_rect_zero ())),
                   mutest_to_be_true,
                   NULL);
  
    graphene_rect_free (r);
  }
  
  static void
  rect_normalize (mutest_spec_t *spec)
  {
    graphene_rect_t r = GRAPHENE_RECT_INIT (10.f, 10.f, -10.f, -10.f);
    graphene_rect_t s = GRAPHENE_RECT_INIT ( 0.f,  0.f,  10.f,  10.f);
    graphene_rect_t t;
  
    graphene_rect_normalize_r (&r, &t);
  
    mutest_expect ("normalizing(10, 10, -10, -10) will put the origin in (0, 0)",
                   mutest_bool_value (graphene_point_equal (&t.origin, &GRAPHENE_POINT_INIT (0, 0))),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("normalizing(10, 10, -10, -10) will make the width positive",
                   mutest_float_value (t.size.width),
                   mutest_to_be_greater_than, 0.0,
                   mutest_to_be_close_to, 10.0, 0.0001,
                   NULL);
    mutest_expect ("normalizing(10, 10, -10, -10) will make the height positive",
                   mutest_float_value (t.size.height),
                   mutest_to_be_greater_than, 0.0,
                   mutest_to_be_close_to, 10.0, 0.0001,
                   NULL);
    mutest_expect ("equality to operate on normalized rectangles",
                   mutest_bool_value (graphene_rect_equal (&r, &s)),
                   mutest_to_be_true,
                   NULL);
  }
  
  static void
  rect_equal (mutest_spec_t *spec)
  {
    graphene_rect_t *r, *s;
  
    r = graphene_rect_init (graphene_rect_alloc (), 0.f, 0.f, 10.f, 10.f);
    s = graphene_rect_init (graphene_rect_alloc (), 1.f, 1.f,  9.f,  9.f);
  
    mutest_expect ("a rectangle to be equal it itself",
                   mutest_bool_value (graphene_rect_equal (r, r)),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("a rectangle to not be equal to null",
                   mutest_bool_value (graphene_rect_equal (r, NULL)),
                   mutest_to_be_false,
                   NULL);
    mutest_expect ("null to not be equal to a rectangle",
                   mutest_bool_value (graphene_rect_equal (NULL, r)),
                   mutest_to_be_false,
                   NULL);
    mutest_expect ("two rectangles with different values to not be equal",
                   mutest_bool_value (graphene_rect_equal (r, s)),
                   mutest_to_be_false,
                   NULL);
  
    graphene_rect_free (r);
    graphene_rect_free (s);
  }
  
  static void
  rect_contains_point (mutest_spec_t *spec)
  {
    graphene_rect_t r = GRAPHENE_RECT_INIT (0.f, 0.f, 10.f, 10.f);
    graphene_point_t p;
  
    graphene_rect_get_top_left (&r, &p);
    mutest_expect ("a rectangle to contain its top left corner",
                   mutest_bool_value (graphene_rect_contains_point (&r, &p)),
                   mutest_to_be_true,
                   NULL);
  
    graphene_rect_get_top_right (&r, &p);
    mutest_expect ("a rectangle to contain its top right corner",
                   mutest_bool_value (graphene_rect_contains_point (&r, &p)),
                   mutest_to_be_true,
                   NULL);
  
    graphene_rect_get_bottom_right (&r, &p);
    mutest_expect ("a rectangle to contain its bottom right corner",
                   mutest_bool_value (graphene_rect_contains_point (&r, &p)),
                   mutest_to_be_true,
                   NULL);
  
    graphene_rect_get_bottom_left (&r, &p);
    mutest_expect ("a rectangle to contain its bottom left corner",
                   mutest_bool_value (graphene_rect_contains_point (&r, &p)),
                   mutest_to_be_true,
                   NULL);
  
    graphene_rect_get_center (&r, &p);
    mutest_expect ("a rectangle to contain its center",
                   mutest_bool_value (graphene_rect_contains_point (&r, &p)),
                   mutest_to_be_true,
                   NULL);
  
    graphene_point_init (&p, -1.f, 11.f);
    mutest_expect ("a rectangle to not contain a point outside its boundaries",
                   mutest_bool_value (graphene_rect_contains_point (&r, &p)),
                   mutest_to_be_false,
                   NULL);
  }
  
  static void
  rect_contains_rect (mutest_spec_t *spec)
  {
    graphene_rect_t *r, *s;
  
    r = graphene_rect_init (graphene_rect_alloc (), 0.f, 0.f, 10.f, 10.f);
    s = graphene_rect_init (graphene_rect_alloc (), 1.f, 1.f,  9.f,  9.f);
  
    mutest_expect ("a rectangle to contain itself",
                   mutest_bool_value (graphene_rect_contains_rect (r, r)),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("the smaller rectangle to not contain the larger one",
                   mutest_bool_value (graphene_rect_contains_rect (s, r)),
                   mutest_to_be_false,
                   NULL);
    mutest_expect ("the larger rectangle to contain the smaller one",
                   mutest_bool_value (graphene_rect_contains_rect (r, s)),
                   mutest_to_be_true,
                   NULL);
  
    graphene_rect_free (r);
    graphene_rect_free (s);
  }
  
  static void
  rect_intersect (mutest_spec_t *spec)
  {
    graphene_rect_t r = GRAPHENE_RECT_INIT (0.f, 0.f, 10.f, 10.f);
    graphene_rect_t s = GRAPHENE_RECT_INIT (5.f, 5.f, 15.f, 15.f);
    graphene_rect_t q = GRAPHENE_RECT_INIT (11.f, 11.f, 2.f, 2.f);
    graphene_rect_t i, j;
  
    mutest_expect ("intersection between (0, 0, 10, 10) and (5, 5, 15, 15) not be empty",
                   mutest_bool_value (graphene_rect_intersection (&r, &s, &i)),
                   mutest_not, mutest_to_be_false,
                   NULL);
  
    mutest_expect ("origin of intersection to be at (5, 5)",
                   mutest_bool_value (graphene_point_equal (&i.origin, &GRAPHENE_POINT_INIT (5, 5))),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("size of intersection to be (5, 5)",
                   mutest_bool_value (graphene_size_equal (&i.size, &GRAPHENE_SIZE_INIT (5, 5))),
                   mutest_to_be_true,
                   NULL);
  
    mutest_expect ("intersection between (5, 5, 5, 5) and (11, 11, 2, 2) to be empty",
                   mutest_bool_value (graphene_rect_intersection (&i, &q, &j)),
                   mutest_not, mutest_to_be_true,
                   NULL);
    mutest_expect ("empty intersection to be a degenerate rectangle",
                   mutest_bool_value (graphene_rect_equal (&j, graphene_rect_zero ())),
                   mutest_to_be_true,
                   NULL);
  }
  
  static void
  rect_union (mutest_spec_t *spec)
  {
    graphene_rect_t r = GRAPHENE_RECT_INIT (0.f, 0.f, 10.f, 10.f);
    graphene_rect_t s = GRAPHENE_RECT_INIT (5.f, 5.f, 15.f, 15.f);
    graphene_rect_t u;
  
    graphene_rect_union (&r, &s, &u);
    mutest_expect ("union to take the minimum normalize origin",
                   mutest_bool_value (graphene_point_equal (&u.origin, &r.origin)),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("union to take the maximum normalized size",
                   mutest_bool_value (graphene_size_equal (&u.size, &GRAPHENE_SIZE_INIT (20.f, 20.f))),
                   mutest_to_be_true,
                   NULL);
  
    mutest_expect ("union rectangle to contain original rectangle A",
                   mutest_bool_value (graphene_rect_contains_rect (&u, &r)),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("union rectangle to contain original rectangle B",
                   mutest_bool_value (graphene_rect_contains_rect (&u, &s)),
                   mutest_to_be_true,
                   NULL);
  }
  
  static void
  rect_offset (mutest_spec_t *spec)
  {
    graphene_rect_t r = GRAPHENE_RECT_INIT (0.f, 0.f, 10.f, 10.f);
  
    graphene_rect_offset (&r, 5.f, 5.f);
    mutest_expect ("offset to move the origin",
                   mutest_bool_value (graphene_point_equal (&r.origin, &GRAPHENE_POINT_INIT (5.f, 5.f))),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("offset to keep the size",
                   mutest_bool_value (graphene_size_equal (&r.size, &GRAPHENE_SIZE_INIT (10.f, 10.f))),
                   mutest_to_be_true,
                   NULL);
  }
  
  static void
  rect_inset (mutest_spec_t *spec)
  {
    graphene_rect_t r = GRAPHENE_RECT_INIT (0.f, 0.f, 10.f, 10.f);
  
    graphene_rect_inset (&r, 2.f, 2.f);
    mutest_expect ("inset(2, 2) to move the origin",
                   mutest_bool_value (graphene_point_equal (&r.origin, &GRAPHENE_POINT_INIT (2.f, 2.f))),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("inset(2, 2) to shrink the size",
                   mutest_bool_value (graphene_size_equal (&r.size, &GRAPHENE_SIZE_INIT (6.f, 6.f))),
                   mutest_to_be_true,
                   NULL);
  
    graphene_rect_inset (&r, 2.f, -2.f);
    mutest_expect ("inset(2, -2) to move back the origin",
                   mutest_float_value (r.origin.y),
                   mutest_to_be_close_to, 0.0, 0.0001,
                   NULL);
    mutest_expect ("inset(2, -2) to shrink the width",
                   mutest_float_value (r.size.width),
                   mutest_to_be_close_to, 2.0, 0.0001,
                   NULL);
    mutest_expect ("inset(2, -2) to expand the height",
                   mutest_float_value (r.size.height),
                   mutest_to_be_close_to, 10.0, 0.0001,
                   NULL);
  }
  
  static void
  rect_area (void)
  {
    graphene_rect_t r = GRAPHENE_RECT_INIT (0.5f, 1.9f,  9.3f, 8.7f);
    graphene_rect_t n;
  
    graphene_rect_normalize_r (&r, &n);
  
    mutest_expect ("area to be width × height",
                   mutest_float_value (graphene_rect_get_area (&r)),
                   mutest_to_be_close_to, (n.size.width * n.size.height), 0.1,
                   NULL);
  }
  
  static void
  rect_round (mutest_spec_t *spec)
  {
    graphene_rect_t r = GRAPHENE_RECT_INIT (0.5f, 1.9f,  9.3f, 8.7f);
    graphene_rect_t s = GRAPHENE_RECT_INIT (0.0f, 1.0f, 10.0f, 9.0f);
    graphene_rect_t tmp;
  
    graphene_rect_round (&r, &tmp);
    mutest_expect ("round() area is larger than or equal to the original rectangle's area",
                   mutest_float_value (graphene_rect_get_area (&tmp)),
                   mutest_to_be_greater_than_or_equal, graphene_rect_get_area (&r),
                   NULL);
    mutest_expect ("round() may not contain the original rectangle",
                   mutest_bool_value (graphene_rect_contains_rect (&tmp, &r)),
                   mutest_not, mutest_to_be, true,
                   NULL);
  
    /* round_to_pixel() operates in place */
    graphene_rect_init_from_rect (&tmp, &r);
    graphene_rect_round_to_pixel (&tmp);
    mutest_expect ("round_to_pixel() may not contain the original rectangle",
                   mutest_bool_value (graphene_rect_contains_rect (&tmp, &r)),
                   mutest_not, mutest_to_be, true,
                   NULL);
    mutest_expect ("rounding is stable",
                   mutest_bool_value (graphene_rect_equal (&s, &tmp)),
                   mutest_to_be, true,
                   NULL);
  }
  
  static void
  rect_round_extents (void)
  {
    graphene_rect_t r = GRAPHENE_RECT_INIT (2.5f, 1.9f,  9.3f, 8.7f);
    graphene_rect_t s = GRAPHENE_RECT_INIT (2.0f, 1.0f, 10.0f, 10.0f);
    graphene_rect_t rounded;
    graphene_point_t p;
  
    graphene_rect_round_extents (&r, &rounded);
  
    graphene_rect_get_top_left (&r, &p);
    mutest_expect ("round_extents()'s result to contain top-left corner of the original rectangle",
                   mutest_bool_value (graphene_rect_contains_point (&rounded, &p)),
                   mutest_to_be, true,
                   NULL);
  
    graphene_rect_get_top_right (&r, &p);
    mutest_expect ("round_extents()'s result to contain top-right corner of the original rectangle",
                   mutest_bool_value (graphene_rect_contains_point (&rounded, &p)),
                   mutest_to_be, true,
                   NULL);
  
    graphene_rect_get_bottom_left (&r, &p);
    mutest_expect ("round_extents()'s result to contain bottom-left corner of the original rectangle",
                   mutest_bool_value (graphene_rect_contains_point (&rounded, &p)),
                   mutest_to_be, true,
                   NULL);
  
    graphene_rect_get_bottom_right (&r, &p);
    mutest_expect ("round_extents()'s result to contain bottom-right corner of the original rectangle",
                   mutest_bool_value (graphene_rect_contains_point (&rounded, &p)),
                   mutest_to_be, true,
                   NULL);
  
    mutest_expect ("round_extents()'s result to contain the original rectangle",
                   mutest_bool_value (graphene_rect_contains_rect (&rounded, &r)),
                   mutest_to_be, true,
                   NULL);
  
    mutest_expect ("round_extents()'s rounding to be stable",
                   mutest_bool_value (graphene_rect_equal (&rounded, &s)),
                   mutest_to_be, true,
                   NULL);
  }
  
  static void
  rect_expand (mutest_spec_t *spec)
  {
    graphene_rect_t r = GRAPHENE_RECT_INIT (0.f, 0.f, 100.f, 100.f);
    graphene_point_t p;
    graphene_size_t s;
    graphene_rect_t check;
  
    graphene_rect_expand (&r, graphene_point_init (&p, -10.f, -10.f), &check);
    graphene_size_init (&s, 110.f, 110.f);
    mutest_expect ("expanding behind the origin changes the origin",
                   mutest_bool_value (graphene_point_equal (&p, &(check.origin))),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("expanding behind the origin does not change the size",
                   mutest_bool_value (graphene_size_equal (&s, &(check.size))),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("expanded rectangle contains original rectangle",
                   mutest_bool_value (graphene_rect_contains_rect (&check, &r)),
                   mutest_to_be_true,
                   NULL);
  
    graphene_rect_expand (&r, graphene_point_init (&p, 150.f, 150.f), &check);
    graphene_size_init (&s, 150.f, 150.f);
    mutest_expect ("expanding after the anti-origin does not change the origin",
                   mutest_bool_value (graphene_point_equal (&r.origin, &check.origin)),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("expanding after the anti-origin changes the size",
                   mutest_bool_value (graphene_size_equal (&s, &(check.size))),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("expanded rectangle contains original rectangle",
                   mutest_bool_value (graphene_rect_contains_rect (&check, &r)),
                   mutest_to_be_true,
                   NULL);
  
    graphene_rect_expand (&r, graphene_point_init (&p, 50.f, 150.f), &check);
    mutest_expect ("expanding half in/half out does not change the origin",
                   mutest_bool_value (graphene_point_equal (&r.origin, &(check.origin))),
                   mutest_to_be_true,
                   NULL);
    mutest_expect ("expanding horizontally inside the rectangle to not change width",
                   mutest_float_value (check.size.width),
                   mutest_to_be_close_to, r.size.width, 0.0001,
                   NULL);
    mutest_expect ("expanding vertically outside the rectangle to change the height",
                   mutest_float_value (check.size.height),
                   mutest_to_be_close_to, p.y, 0.0001,
                   NULL);
    mutest_expect ("expanded rectangle contains original rectangle",
                   mutest_bool_value (graphene_rect_contains_rect (&check, &r)),
                   mutest_to_be_true,
                   NULL);
  }
  
  static void
  rect_interpolate (mutest_spec_t *spec)
  {
    graphene_rect_t a = GRAPHENE_RECT_INIT ( 0.f, 0.f, 10.f, 10.f);
    graphene_rect_t b = GRAPHENE_RECT_INIT (10.f, 5.f, 30.f, 20.f);
    graphene_rect_t c;
    graphene_rect_t res;
  
    graphene_rect_interpolate (&a, &b, 0.0, &res);
    mutest_expect ("lerp(0) to produce the initial rectangle",
                   mutest_bool_value (graphene_rect_equal (&a, &res)),
                   mutest_to_be_true,
                   NULL);
  
    graphene_rect_interpolate (&a, &b, 1.0, &res);
    mutest_expect ("lerp(1) to produce the final rectangle",
                   mutest_bool_value (graphene_rect_equal (&b, &res)),
                   mutest_to_be_true,
                   NULL);
  
    c.origin.x = a.origin.x + (b.origin.x - a.origin.x) * 0.25f;
    c.origin.y = a.origin.y + (b.origin.y - a.origin.y) * 0.25f;
    c.size.width = a.size.width + (b.size.width - a.size.width) * 0.25f;
    c.size.height = a.size.height + (b.size.height - a.size.height) * 0.25f;
    graphene_rect_interpolate (&a, &b, 0.25, &res);
    mutest_expect ("lerp(0.25) to produce the expect rectangle",
                   mutest_bool_value (graphene_rect_equal (&c, &res)),
                   mutest_to_be_true,
                   NULL);
  }
  
  static void
  rect_scale (void)
  {
    struct {
      graphene_rect_t unscaled;
      graphene_rect_t scaled;
      float scales[2];
      const char *desc;
    } rects[] = {
      {
        GRAPHENE_RECT_INIT (0.f, 0.f, 10.f, 10.f),
        GRAPHENE_RECT_INIT (0.f, 0.f, 10.f, 10.f),
        { 1.f, 1.f },
        "Scaling by 1 does not change the rectangle",
      },
      {
        GRAPHENE_RECT_INIT (-2.f, -2.f, -2.f, -2.f),
        GRAPHENE_RECT_INIT (-8.f, -20.f, 4.f, 10.f),
        { 2.f, 5.f },
        "Scaling by (2, 5) preserves the origin sign but changes the size sign",
      },
      {
        GRAPHENE_RECT_INIT (-4.f, 2.f, 10.f,  10.f),
        GRAPHENE_RECT_INIT (-1.f, 1.f,  2.5f,  5.f),
        { 0.25f, 0.5f },
        "Scaling by a fraction moves the origin and reduces the size",
      },
      {
        GRAPHENE_RECT_INIT (1.f, 0.f, 10.f, -1.f),
        GRAPHENE_RECT_INIT (0.f, 0.f,  0.f, 10.f),
        { 0.f, -10.f, },
        "Scaling by (0, -10) moves the origin in (0, 0) and flips the negative sign of the size",
      },
    };
    unsigned int n_rects = sizeof (rects) / sizeof (rects[0]);
  
    for (unsigned int i = 0; i < n_rects; i++)
      {
        graphene_rect_t res;
  
        graphene_rect_scale (&rects[i].unscaled,
                             rects[i].scales[0],
                             rects[i].scales[1],
                             &res);
  
        mutest_expect (rects[i].desc,
                       mutest_bool_value (graphene_rect_equal (&res, &rects[i].scaled)),
                       mutest_to_be_true,
                       NULL);
      }
  }
  
  static void
  rect_suite (mutest_suite_t *suite)
  {
    mutest_it ("initializes origin and size", rect_init);
    mutest_it ("normalizes origin and size", rect_normalize);
    mutest_it ("can check for equality", rect_equal);
    mutest_it ("can check for contained points", rect_contains_point);
    mutest_it ("can check for contained rectangles", rect_contains_rect);
    mutest_it ("can compute intersections", rect_intersect);
    mutest_it ("can compute unions", rect_union);
    mutest_it ("can compute the area", rect_area);
    mutest_it ("can offset origin and size", rect_offset);
    mutest_it ("can inset origin and size", rect_inset);
    mutest_it ("can round to the nearest integer", rect_round);
    mutest_it ("can round the extents of a rectangle to the nearest integer", rect_round_extents);
    mutest_it ("can expand", rect_expand);
    mutest_it ("can interpolate", rect_interpolate);
    mutest_it ("can scale", rect_scale);
  }
  
  MUTEST_MAIN (
    mutest_describe ("graphene_rect_t", rect_suite);
  )
  