/*
 * 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 ();
  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];
}

#if !defined (HB_NO_SVG) && defined (HAVE_ZLIB)
static hb_raster_image_t *
render_svg_glyph (hb_face_t      *face,
		  hb_codepoint_t  glyph)
{
  g_assert_nonnull (face);

  hb_font_t *font = hb_font_create (face);
  g_assert_nonnull (font);
  hb_font_set_scale (font, 128, 128);

  hb_raster_paint_t *paint = hb_raster_paint_create_or_fail ();
  g_assert_nonnull (paint);
  hb_raster_paint_set_foreground (paint, HB_COLOR (0, 0, 0, 255));

  hb_raster_extents_t extents = {-32, -192, 256, 256, 0};
  hb_raster_paint_set_extents (paint, &extents);
  g_assert_true (hb_raster_paint_glyph (paint, font, glyph, 0.f, 0.f, 0, HB_COLOR (0, 0, 0, 255)));

  hb_raster_image_t *img = hb_raster_paint_render (paint);
  g_assert_nonnull (img);

  hb_raster_paint_destroy (paint);
  hb_font_destroy (font);
  return img;
}

static hb_bool_t
paint_svg_glyph_direct_or_fail (hb_face_t      *face,
			        hb_codepoint_t  glyph)
{
  g_assert_nonnull (face);

  hb_font_t *font = hb_font_create (face);
  g_assert_nonnull (font);
  hb_font_set_scale (font, 128, 128);

  hb_raster_paint_t *paint = hb_raster_paint_create_or_fail ();
  g_assert_nonnull (paint);
  hb_raster_paint_set_foreground (paint, HB_COLOR (0, 0, 0, 255));

  hb_raster_extents_t extents = {-32, -192, 256, 256, 0};
  hb_raster_paint_set_extents (paint, &extents);

  hb_bool_t ret = hb_font_paint_glyph_or_fail (font, glyph,
					       hb_raster_paint_get_funcs (), paint,
					       0, HB_COLOR (0, 0, 0, 255));
  hb_raster_image_t *img = hb_raster_paint_render (paint);
  g_assert_nonnull (img);

  hb_raster_image_destroy (img);
  hb_raster_paint_destroy (paint);
  hb_font_destroy (font);
  return ret;
}
#endif


/* ── 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: gzip-compressed SVG documents ──────────────────────── */

static void
test_svgz_render_matches_svg (void)
{
#if !defined (HB_NO_SVG) && defined (HAVE_ZLIB)
  hb_face_t *svg_face = hb_test_open_font_file ("../fuzzing/fonts/noto_handwriting-untouchedsvg.ttf");
  hb_face_t *svgz_face = hb_test_open_font_file ("../fuzzing/fonts/noto_handwriting-untouchedsvgz.ttf");

  hb_raster_image_t *svg = render_svg_glyph (svg_face, 7);
  hb_raster_image_t *svgz = render_svg_glyph (svgz_face, 7);

  hb_raster_extents_t svg_extents;
  hb_raster_extents_t svgz_extents;
  hb_raster_image_get_extents (svg, &svg_extents);
  hb_raster_image_get_extents (svgz, &svgz_extents);

  g_assert_cmpint (hb_raster_image_get_format (svg), ==, hb_raster_image_get_format (svgz));
  g_assert_cmpint (svg_extents.x_origin, ==, svgz_extents.x_origin);
  g_assert_cmpint (svg_extents.y_origin, ==, svgz_extents.y_origin);
  g_assert_cmpuint (svg_extents.width, ==, svgz_extents.width);
  g_assert_cmpuint (svg_extents.height, ==, svgz_extents.height);
  g_assert_cmpuint (svg_extents.stride, ==, svgz_extents.stride);
  g_assert_cmpuint (svg_extents.width, >, 0);
  g_assert_cmpuint (svg_extents.height, >, 0);

  size_t len = (size_t) svg_extents.stride * svg_extents.height;
  g_assert_cmpint (memcmp (hb_raster_image_get_buffer (svg),
			   hb_raster_image_get_buffer (svgz),
			   len), ==, 0);

  hb_raster_image_destroy (svg);
  hb_raster_image_destroy (svgz);
  hb_face_destroy (svg_face);
  hb_face_destroy (svgz_face);
#endif
}

static void
test_direct_svg_paint_fails_cleanly (void)
{
#if !defined (HB_NO_SVG) && defined (HAVE_ZLIB)
  hb_face_t *svg_face = hb_test_open_font_file ("../fuzzing/fonts/noto_handwriting-untouchedsvg.ttf");

  g_assert_false (paint_svg_glyph_direct_or_fail (svg_face, 7));
  hb_face_destroy (svg_face);
#endif
}


/* ── 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_svgz_render_matches_svg);
  hb_test_add (test_direct_svg_paint_fails_cleanly);

  return hb_test_run ();
}
