/*
 * 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.
 *
 * Author(s): Behdad Esfahbod
 */

#include "hb-test.h"
#include "hb-raster.h"
#include <hb-ot.h>


/* ── Helpers ─────────────────────────────────────────────────────── */

/* Draw a closed rectangle by emitting raw draw callbacks. */
static void
draw_rect (hb_raster_draw_t *rdr,
	   float x0, float y0, float x1, float y1)
{
  hb_draw_funcs_t *df = hb_raster_draw_get_funcs (rdr);
  hb_draw_state_t  st = HB_DRAW_STATE_DEFAULT;

  hb_draw_move_to   (df, rdr, &st, x0, y0);
  hb_draw_line_to   (df, rdr, &st, x1, y0);
  hb_draw_line_to   (df, rdr, &st, x1, y1);
  hb_draw_line_to   (df, rdr, &st, x0, y1);
  hb_draw_close_path (df, rdr, &st);  /* emits closing line_to(x0,y0) */
}

/* Read one A8 pixel value; returns 0 for out-of-bounds coordinates. */
static uint8_t
pixel_at (hb_raster_image_t *img, int x, int y)
{
  hb_raster_extents_t ext;
  hb_raster_image_get_extents (img, &ext);

  int col = x - ext.x_origin;
  int row = y - ext.y_origin;

  if (col < 0 || row < 0 ||
      (unsigned) col >= ext.width || (unsigned) row >= ext.height)
    return 0;

  return hb_raster_image_get_buffer (img)[row * ext.stride + col];
}


/* ── Test 1: rectangle geometry ──────────────────────────────────── */

static void
test_rectangle (void)
{
  hb_raster_draw_t *rdr = hb_raster_draw_create_or_fail ();

  draw_rect (rdr, 2.f, 2.f, 30.f, 30.f);

  hb_raster_image_t *img = hb_raster_draw_render (rdr);
  g_assert_nonnull (img);

  hb_raster_extents_t ext;
  hb_raster_image_get_extents (img, &ext);
  g_assert_cmpint (ext.x_origin, ==, 2);
  g_assert_cmpint (ext.y_origin, ==, 2);
  g_assert_cmpuint (ext.width, ==, 28);
  g_assert_cmpuint (ext.height, ==, 28);

  /* Center: fully inside → full coverage */
  g_assert_cmpint (pixel_at (img, 16, 16), ==, 255);

  /* Well outside the box → zero */
  g_assert_cmpint (pixel_at (img, 0, 0), ==, 0);   /* out of image */
  g_assert_cmpint (pixel_at (img, 31, 31), ==, 0);  /* out of image */

  /* Left-most and right-most pixel columns: still fully inside
     because integer-coordinate box edges align with pixel boundaries */
  g_assert_cmpint (pixel_at (img, 2, 16), ==, 255);
  g_assert_cmpint (pixel_at (img, 29, 16), ==, 255);

  /* One pixel beyond the box right edge: out of image */
  g_assert_cmpint (pixel_at (img, 30, 16), ==, 0);

  hb_raster_image_destroy (img);

  /* render() with no accumulated edges → empty (0×0) image */
  img = hb_raster_draw_render (rdr);
  g_assert_nonnull (img);
  hb_raster_image_get_extents (img, &ext);
  g_assert_cmpuint (ext.width, ==, 0);
  g_assert_cmpuint (ext.height, ==, 0);
  hb_raster_image_destroy (img);

  hb_raster_draw_destroy (rdr);
}


/* ── Test 2: multi-glyph accumulation ────────────────────────────── */

static void
test_accumulate (void)
{
  hb_raster_draw_t *rdr = hb_raster_draw_create_or_fail ();

  /* Two non-overlapping boxes accumulated before a single render() */
  draw_rect (rdr, 0.f, 0.f, 10.f, 10.f);
  draw_rect (rdr, 20.f, 0.f, 30.f, 10.f);

  hb_raster_image_t *img = hb_raster_draw_render (rdr);
  g_assert_nonnull (img);

  /* Both interiors fully covered */
  g_assert_cmpint (pixel_at (img, 5,  5), ==, 255);
  g_assert_cmpint (pixel_at (img, 25, 5), ==, 255);

  /* Gap between the two boxes is empty */
  g_assert_cmpint (pixel_at (img, 15, 5), ==, 0);

  hb_raster_image_destroy (img);
  hb_raster_draw_destroy (rdr);
}


/* ── Test 3: sub-pixel edge coverage ─────────────────────────────── */

static void
test_subpixel_edge (void)
{
  hb_raster_draw_t *rdr = hb_raster_draw_create_or_fail ();

  /* Box with a half-pixel offset so the leftmost pixel column is split */
  draw_rect (rdr, 2.5f, 2.f, 30.f, 30.f);

  hb_raster_image_t *img = hb_raster_draw_render (rdr);
  g_assert_nonnull (img);

  /* The pixel at x=2 straddles the left edge → partial coverage */
  int v = pixel_at (img, 2, 16);
  g_assert_cmpint (v, >, 0);
  g_assert_cmpint (v, <, 255);

  /* The pixel at x=3 is fully inside */
  g_assert_cmpint (pixel_at (img, 3, 16), ==, 255);

  hb_raster_image_destroy (img);
  hb_raster_draw_destroy (rdr);
}


/* ── Test 4: transform ───────────────────────────────────────────── */

static void
test_transform (void)
{
  hb_raster_draw_t *rdr = hb_raster_draw_create_or_fail ();

  /* Scale by 2 — same unit-square box maps to (0,0)→(20,20) */
  hb_raster_draw_set_transform (rdr, 2.f, 0.f, 0.f, 2.f, 0.f, 0.f);
  draw_rect (rdr, 0.f, 0.f, 10.f, 10.f);

  hb_raster_image_t *img = hb_raster_draw_render (rdr);
  g_assert_nonnull (img);

  hb_raster_extents_t ext;
  hb_raster_image_get_extents (img, &ext);
  g_assert_cmpuint (ext.width, ==, 20);
  g_assert_cmpuint (ext.height, ==, 20);
  g_assert_cmpint (pixel_at (img, 10, 10), ==, 255); /* center of scaled box */

  hb_raster_image_destroy (img);
  hb_raster_draw_destroy (rdr);
}

/* ── Test 5: transformed glyph extents helper ───────────────────── */

static void
test_set_glyph_extents_with_transform (void)
{
  hb_raster_draw_t *rdr = hb_raster_draw_create_or_fail ();

  hb_raster_draw_set_transform (rdr, 2.f, 0.f, 0.f, 3.f, 5.f, 7.f);

  hb_glyph_extents_t gext = {
    1,   /* x_bearing */
    4,   /* y_bearing */
    10,  /* width */
    -6   /* height */
  };

  g_assert_true (hb_raster_draw_set_glyph_extents (rdr, &gext));

  draw_rect (rdr, (float) gext.x_bearing,
	     (float) gext.y_bearing + gext.height,
	     (float) gext.x_bearing + gext.width,
	     (float) gext.y_bearing);

  hb_raster_image_t *img = hb_raster_draw_render (rdr);
  g_assert_nonnull (img);

  hb_raster_extents_t ext;
  hb_raster_image_get_extents (img, &ext);
  g_assert_cmpint (ext.x_origin, ==, 7);   /* 2*1 + 5 */
  g_assert_cmpint (ext.y_origin, ==, 1);   /* 3*(-2) + 7 */
  g_assert_cmpuint (ext.width, ==, 20);    /* 2*10 */
  g_assert_cmpuint (ext.height, ==, 18);   /* 3*6 */

  hb_raster_image_destroy (img);
  hb_raster_draw_destroy (rdr);
}

/* ── Test 6: image paint under an overflowing transform ──────────── */

/* Nested scales, each representable, whose product overflows to infinity.
 * The inverse transform then carries NaN into the image sampler's texel
 * coordinates.  Exercised for the float-to-int conversion, so this needs a
 * sanitizer build to catch a regression. */
static void
test_image_nonfinite_transform (void)
{
  hb_raster_paint_t *paint = hb_raster_paint_create_or_fail ();
  g_assert_nonnull (paint);

  hb_raster_extents_t ext = {0, 0, 32, 32, 0};
  hb_raster_paint_set_extents (paint, &ext);

  hb_paint_funcs_t *funcs = hb_raster_paint_get_funcs (paint);

  hb_paint_push_transform (funcs, paint, 1e30f, 0.f, 0.f, 1e30f, 0.f, 0.f);
  hb_paint_push_transform (funcs, paint, 1e30f, 0.f, 0.f, 1e30f, 0.f, 0.f);

  const unsigned w = 4, h = 4;
  char pixels[w * h * 4];
  memset (pixels, 0, sizeof (pixels));
  hb_blob_t *blob = hb_blob_create (pixels, sizeof (pixels),
				    HB_MEMORY_MODE_READONLY, nullptr, nullptr);

  hb_glyph_extents_t gext = {0, 32, 32, -32};
  hb_paint_image (funcs, paint, blob, w, h,
		  HB_PAINT_IMAGE_FORMAT_BGRA, 0.f, &gext);

  hb_paint_pop_transform (funcs, paint);
  hb_paint_pop_transform (funcs, paint);

  hb_blob_destroy (blob);
  hb_raster_paint_destroy (paint);
}

/* ── Test 7: glyph extents that overflow the int grid ────────────── */

/* Glyph extents are int32, so a transform that scales them up puts the
 * corner coordinates far outside the int range before they are floored
 * back to the integer pixel grid.  Exercised for the conversion itself,
 * so this needs a sanitizer build to catch a regression. */
static void
test_set_glyph_extents_overflow (void)
{
  hb_glyph_extents_t gext = {-2000000000, 2000000000, 2000000000, -2000000000};

  hb_raster_draw_t *rdr = hb_raster_draw_create_or_fail ();
  g_assert_nonnull (rdr);
  hb_raster_draw_set_transform (rdr, 1000.f, 0.f, 0.f, 1000.f, 0.f, 0.f);
  hb_raster_draw_set_glyph_extents (rdr, &gext);
  hb_raster_draw_destroy (rdr);

  hb_raster_paint_t *paint = hb_raster_paint_create_or_fail ();
  g_assert_nonnull (paint);
  hb_raster_paint_set_transform (paint, 1000.f, 0.f, 0.f, 1000.f, 0.f, 0.f);
  hb_raster_paint_set_glyph_extents (paint, &gext);
  hb_raster_paint_destroy (paint);
}

/* ── main ────────────────────────────────────────────────────────── */

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

  hb_test_add (test_rectangle);
  hb_test_add (test_accumulate);
  hb_test_add (test_subpixel_edge);
  hb_test_add (test_transform);
  hb_test_add (test_set_glyph_extents_with_transform);
  hb_test_add (test_image_nonfinite_transform);
  hb_test_add (test_set_glyph_extents_overflow);

  return hb_test_run ();
}
