/*
 * 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.hh"

#include "hb-raster-paint.hh"
#include "hb-machinery.hh"
#include "hb-paint.hh"

#include <math.h>


/*
 * Pixel helpers (paint-specific)
 */

/* Convert unpremultiplied hb_color_t (BGRA order) to premultiplied BGRA32 pixel. */
static inline uint32_t
color_to_premul_pixel (hb_color_t color)
{
  uint8_t a = hb_color_get_alpha (color);
  uint8_t r = hb_raster_div255 (hb_color_get_red (color) * a);
  uint8_t g = hb_raster_div255 (hb_color_get_green (color) * a);
  uint8_t b = hb_raster_div255 (hb_color_get_blue (color) * a);
  return (uint32_t) b | ((uint32_t) g << 8) | ((uint32_t) r << 16) | ((uint32_t) a << 24);
}

/* Bilinear sample from a premultiplied BGRA32 image. */
static inline uint32_t
hb_raster_sample_bilinear_premul (const hb_packed_t<uint32_t> *src,
				  unsigned width,
				  unsigned height,
				  float x,
				  float y)
{
  int x0 = (int) floorf (x);
  int y0 = (int) floorf (y);
  int x1 = x0 + 1;
  int y1 = y0 + 1;
  if (x1 >= (int) width) x1 = (int) width - 1;
  if (y1 >= (int) height) y1 = (int) height - 1;

  float tx = x - x0;
  float ty = y - y0;
  float omtx = 1.f - tx;
  float omty = 1.f - ty;

  uint32_t p00 = (uint32_t) src[(size_t) y0 * width + (size_t) x0];
  uint32_t p10 = (uint32_t) src[(size_t) y0 * width + (size_t) x1];
  uint32_t p01 = (uint32_t) src[(size_t) y1 * width + (size_t) x0];
  uint32_t p11 = (uint32_t) src[(size_t) y1 * width + (size_t) x1];

  float w00 = omtx * omty;
  float w10 = tx * omty;
  float w01 = omtx * ty;
  float w11 = tx * ty;

  float b = ((p00 >>  0) & 0xff) * w00 + ((p10 >>  0) & 0xff) * w10 +
	    ((p01 >>  0) & 0xff) * w01 + ((p11 >>  0) & 0xff) * w11;
  float g = ((p00 >>  8) & 0xff) * w00 + ((p10 >>  8) & 0xff) * w10 +
	    ((p01 >>  8) & 0xff) * w01 + ((p11 >>  8) & 0xff) * w11;
  float r = ((p00 >> 16) & 0xff) * w00 + ((p10 >> 16) & 0xff) * w10 +
	    ((p01 >> 16) & 0xff) * w01 + ((p11 >> 16) & 0xff) * w11;
  float a = ((p00 >> 24) & 0xff) * w00 + ((p10 >> 24) & 0xff) * w10 +
	    ((p01 >> 24) & 0xff) * w01 + ((p11 >> 24) & 0xff) * w11;

  return (uint32_t) (b + 0.5f)
       | ((uint32_t) (g + 0.5f) << 8)
       | ((uint32_t) (r + 0.5f) << 16)
       | ((uint32_t) (a + 0.5f) << 24);
}


/*
 * Paint callbacks
 */

/* Lazy initialization: set up root surface, initial clip and transform.
 * Called from every paint callback that needs state.
 * hb_font_paint_glyph() does NOT wrap with push/pop_transform,
 * so the first callback could be push_clip_glyph or paint_color. */
static void
ensure_initialized (hb_raster_paint_t *c)
{
  if (c->surface_stack.length) return;

  /* A failed stack push (OOM) leaves the vector in a sticky error
   * state, so initialization can never succeed; bail out cheaply
   * instead of re-paying the surface acquire-and-clear below on
   * every subsequent paint callback. */
  if (unlikely (c->surface_stack.in_error () || c->clip_stack.in_error ()))
    return;

  /* acquire_surface() clears a full surface; charge its area, so
   * repeated failed initialization attempts cannot re-pay it
   * indefinitely.  Successful initialization re-arms the session
   * budget below, making the charge a no-op for normal sessions. */
  if (unlikely (!c->charge_work ((int64_t) c->fixed_extents.width *
				 c->fixed_extents.height)))
    return;

  /* Root surface */
  hb_raster_image_t *root = c->acquire_surface ();
  if (unlikely (!root)) return;

  if (hb_color_get_alpha (c->background))
  {
    uint32_t bg = HB_COLOR (hb_color_get_blue (c->background),
			    hb_color_get_green (c->background),
			    hb_color_get_red (c->background),
			    hb_color_get_alpha (c->background));
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-align"
    uint32_t *pixels = (uint32_t *) hb_raster_image_get_buffer (root);
#pragma GCC diagnostic pop
    hb_raster_extents_t ext;
    hb_raster_image_get_extents (root, &ext);
    unsigned count = ext.width * ext.height;
    for (unsigned i = 0; i < count; i++)
      pixels[i] = bg;
  }

  if (unlikely (!c->surface_stack.push_or_fail (root)))
  {
    c->release_surface (root);
    return;
  }

  /* Session work budget: flat floor, or a few passes over the (possibly
   * client-sized) surface, whichever is larger. */
  c->work_left = hb_max ((int64_t) HB_RASTER_MAX_PAINT_WORK,
			 (int64_t) HB_RASTER_MAX_PAINT_WORK_PASSES *
			 c->fixed_extents.width * c->fixed_extents.height);

  /* Initial clip: full coverage rectangle */
  hb_raster_clip_t clip;
  clip.init_full (c->fixed_extents.width, c->fixed_extents.height);
  if (unlikely (!c->clip_stack.push_or_fail (std::move (clip))))
  {
    c->release_surface (c->surface_stack.pop ());
    return;
  }
}

static void
hb_raster_paint_push_transform (hb_paint_funcs_t *pfuncs HB_UNUSED,
				void *paint_data,
				float xx, float yx,
				float xy, float yy,
				float dx, float dy,
				void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;

  ensure_initialized (c);

  hb_transform_t<> t = c->current_transform ();
  t.multiply ({xx, yx, xy, yy, dx, dy});
  (void) c->transform_stack.push (t);
}

static void
hb_raster_paint_pop_transform (hb_paint_funcs_t *pfuncs HB_UNUSED,
			       void *paint_data,
			       void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;
  if (c->transform_stack.length)
    c->transform_stack.pop ();
}

static hb_bool_t
hb_raster_paint_color_glyph (hb_paint_funcs_t *pfuncs HB_UNUSED,
			     void *paint_data HB_UNUSED,
			     hb_codepoint_t glyph HB_UNUSED,
			     hb_font_t *font HB_UNUSED,
			     void *user_data HB_UNUSED)
{
  return false;
}

typedef void (*hb_raster_paint_clip_mask_emit_t) (hb_raster_draw_t *rdr, void *user_data);

/* Attach the surface box and the session work budget to @rdr before
 * outlines are drawn into it: curves outside the surface collapse to
 * their chord, and flattening work is charged as it happens instead
 * of only after the whole outline has been emitted. */
static void
hb_raster_paint_attach_clip_rdr (hb_raster_paint_t *c,
				 hb_raster_draw_t *rdr,
				 const hb_raster_image_t *surf)
{
  hb_raster_draw_set_clip_box (rdr,
			       (float) surf->extents.x_origin,
			       (float) surf->extents.y_origin,
			       (float) surf->extents.x_origin + (float) surf->extents.width,
			       (float) surf->extents.y_origin + (float) surf->extents.height);
  hb_raster_draw_set_external_work (rdr, &c->work_left);
}

static void
hb_raster_paint_push_empty_clip (hb_raster_paint_t *c, unsigned w, unsigned h)
{
  hb_raster_clip_t new_clip = c->acquire_clip (w, h);
  new_clip.init_full (w, h);
  new_clip.is_rect = true;
  new_clip.rect_x0 = new_clip.rect_y0 = 0;
  new_clip.rect_x1 = new_clip.rect_y1 = 0;
  new_clip.min_x = new_clip.min_y = new_clip.max_x = new_clip.max_y = 0;
  (void) c->clip_stack.push (std::move (new_clip));
}

static bool
hb_raster_paint_intersect_mask (const hb_raster_image_t *surf,
				const hb_raster_extents_t &mask_ext,
				const hb_raster_clip_t &clip,
				unsigned w, unsigned h,
				int64_t *mask_x0, int64_t *mask_y0,
				unsigned *ix0, unsigned *iy0,
				unsigned *ix1, unsigned *iy1)
{
  /* Convert mask extents from surface coordinates to clip-buffer coordinates. */
  int64_t mx0 = (int64_t) mask_ext.x_origin - (int64_t) surf->extents.x_origin;
  int64_t my0 = (int64_t) mask_ext.y_origin - (int64_t) surf->extents.y_origin;

  int64_t x0 = hb_max ((int64_t) clip.min_x, hb_max (mx0, (int64_t) 0));
  int64_t y0 = hb_max ((int64_t) clip.min_y, hb_max (my0, (int64_t) 0));
  int64_t x1 = hb_min ((int64_t) clip.max_x, hb_min (mx0 + mask_ext.width, (int64_t) w));
  int64_t y1 = hb_min ((int64_t) clip.max_y, hb_min (my0 + mask_ext.height, (int64_t) h));

  if (x0 >= x1 || y0 >= y1) return false;

  *mask_x0 = mx0;
  *mask_y0 = my0;
  *ix0 = (unsigned) x0;
  *iy0 = (unsigned) y0;
  *ix1 = (unsigned) x1;
  *iy1 = (unsigned) y1;
  return true;
}

/* Render whatever edges have been accumulated into @rdr and
 * push the result as a new clip on the stack, intersecting
 * with the existing clip.  Used by the emitter-based clip
 * helpers (push_clip_glyph) and by push_clip_path_end once
 * the caller has drawn the path into @rdr. */
static void
hb_raster_paint_finalize_path_clip (hb_raster_paint_t *c,
				    hb_raster_draw_t *rdr,
				    hb_raster_image_t *surf,
				    unsigned w, unsigned h)
{
  /* Charge the scanline work of the accumulated outline before
   * rendering it. */
  c->work_left -= hb_raster_draw_get_edge_work (rdr, h);

  hb_raster_image_t *mask_img = hb_raster_draw_render (rdr);

  if (unlikely (!mask_img))
  {
    hb_raster_paint_push_empty_clip (c, w, h);
    return;
  }

  /* Charge the mask render (its own area; may exceed the surface) plus
   * the clip-buffer pass below. */
  hb_raster_extents_t mask_render_ext;
  hb_raster_image_get_extents (mask_img, &mask_render_ext);
  if (unlikely (!c->charge_work ((int64_t) mask_render_ext.width * mask_render_ext.height +
				 (int64_t) w * h)))
  {
    hb_raster_draw_recycle_image (rdr, mask_img);
    hb_raster_paint_push_empty_clip (c, w, h);
    return;
  }

  hb_raster_clip_t new_clip = c->acquire_clip (w, h);

  /* Allocate alpha buffer and intersect with previous clip */
  size_t clip_size = (size_t) new_clip.stride * h;
  if (unlikely (clip_size > HB_RASTER_MAX_BUFFER_SIZE ||
                !new_clip.alpha.resize ((unsigned) clip_size)))
  {
    hb_raster_draw_recycle_image (rdr, mask_img);
    hb_raster_paint_push_empty_clip (c, w, h);
    return;
  }
  hb_memset (new_clip.alpha.arrayZ, 0, (unsigned) clip_size);

  const uint8_t *mask_buf = hb_raster_image_get_buffer (mask_img);
  hb_raster_extents_t mask_ext;
  hb_raster_image_get_extents (mask_img, &mask_ext);
  const hb_raster_clip_t &old_clip = c->current_clip ();
  if (unlikely (!old_clip.has_valid_alpha_mask ()))
  {
    hb_raster_draw_recycle_image (rdr, mask_img);
    hb_raster_paint_push_empty_clip (c, w, h);
    return;
  }

  int64_t mask_x0, mask_y0;
  unsigned ix0, iy0, ix1, iy1;
  if (!hb_raster_paint_intersect_mask (surf, mask_ext, old_clip, w, h,
				       &mask_x0, &mask_y0,
				       &ix0, &iy0, &ix1, &iy1))
  {
    hb_raster_draw_recycle_image (rdr, mask_img);
    hb_raster_paint_push_empty_clip (c, w, h);
    return;
  }

  new_clip.min_x = w; new_clip.min_y = h;
  new_clip.max_x = 0; new_clip.max_y = 0;

  if (old_clip.is_rect)
  {
    for (unsigned y = iy0; y < iy1; y++)
    {
      const uint8_t *mask_row = mask_buf + (unsigned) ((int64_t) y - mask_y0) * mask_ext.stride;
      uint8_t *out_row = new_clip.alpha.arrayZ + y * new_clip.stride;
      unsigned row_min = ix1;
      unsigned row_max = ix0;
      unsigned mx = (unsigned) ((int64_t) ix0 - mask_x0);
      for (unsigned x = ix0; x < ix1; x++)
      {
	uint8_t a = mask_row[mx++];
	out_row[x] = a;
	if (a && row_min == ix1)
	{
	  row_min = x;
	  row_max = x + 1;
	}
	else if (a)
	  row_max = x + 1;
      }
      if (row_min < row_max)
      {
	new_clip.min_x = hb_min (new_clip.min_x, row_min);
	new_clip.min_y = hb_min (new_clip.min_y, y);
	new_clip.max_x = hb_max (new_clip.max_x, row_max);
	new_clip.max_y = hb_max (new_clip.max_y, y + 1);
      }
    }
  }
  else
  {
    for (unsigned y = iy0; y < iy1; y++)
    {
      const uint8_t *old_row = old_clip.alpha.arrayZ + y * old_clip.stride;
      const uint8_t *mask_row = mask_buf + (unsigned) ((int64_t) y - mask_y0) * mask_ext.stride;
      uint8_t *out_row = new_clip.alpha.arrayZ + y * new_clip.stride;
      unsigned row_min = ix1;
      unsigned row_max = ix0;
      for (unsigned x = ix0; x < ix1; x++)
      {
	unsigned mx = (unsigned) ((int64_t) x - mask_x0);
	uint8_t a = hb_raster_div255 (mask_row[mx] * old_row[x]);
	out_row[x] = a;
	if (a)
	{
	  row_min = hb_min (row_min, x);
	  row_max = x + 1;
	}
      }
      if (row_min < row_max)
      {
	new_clip.min_x = hb_min (new_clip.min_x, row_min);
	new_clip.min_y = hb_min (new_clip.min_y, y);
	new_clip.max_x = hb_max (new_clip.max_x, row_max);
	new_clip.max_y = hb_max (new_clip.max_y, y + 1);
      }
    }
  }

  hb_raster_draw_recycle_image (rdr, mask_img);
  if (unlikely (!c->clip_stack.push_or_fail (std::move (new_clip))))
    hb_raster_paint_push_empty_clip (c, w, h);
}

static void
hb_raster_paint_push_clip_from_emitter (hb_raster_paint_t *c,
					hb_raster_paint_clip_mask_emit_t emit,
					void *emit_data)
{
  ensure_initialized (c);

  hb_raster_image_t *surf = c->current_surface ();
  if (unlikely (!surf)) return;

  unsigned w = surf->extents.width;
  unsigned h = surf->extents.height;

  /* Out of budget: skip the glyph-outline extraction entirely, so
   * per-glyph outline limits cannot multiply with the caller's
   * paint-graph traversal limits. */
  if (unlikely (c->work_left <= 0))
  {
    hb_raster_paint_push_empty_clip (c, w, h);
    return;
  }

  hb_raster_draw_t *rdr = c->clip_rdr;
  hb_transform_t<> t = c->current_effective_transform ();
  hb_raster_draw_set_transform (rdr, t.xx, t.yx, t.xy, t.yy, t.x0, t.y0);
  hb_raster_paint_attach_clip_rdr (c, rdr, surf);
  emit (rdr, emit_data);

  hb_raster_paint_finalize_path_clip (c, rdr, surf, w, h);
}

struct hb_raster_paint_glyph_clip_data_t
{
  hb_codepoint_t glyph;
  hb_font_t *font;
};

static void
hb_raster_paint_emit_clip_glyph_mask (hb_raster_draw_t *rdr, void *user_data)
{
  hb_raster_paint_glyph_clip_data_t *data = (hb_raster_paint_glyph_clip_data_t *) user_data;
  /* Let draw-render choose tight glyph extents; we map by mask origin below. */
  hb_font_draw_glyph (data->font, data->glyph, hb_raster_draw_get_funcs (rdr), rdr);
}

static void
hb_raster_paint_push_clip_glyph (hb_paint_funcs_t *pfuncs HB_UNUSED,
				 void *paint_data,
				 hb_codepoint_t glyph,
				 hb_font_t *font,
				 void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;
  hb_raster_paint_glyph_clip_data_t data = {glyph, font};
  hb_raster_paint_push_clip_from_emitter (c, hb_raster_paint_emit_clip_glyph_mask, &data);
}

static void
hb_raster_paint_push_clip_rectangle (hb_paint_funcs_t *pfuncs HB_UNUSED,
				     void *paint_data,
				     float xmin, float ymin,
				     float xmax, float ymax,
				     void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;

  ensure_initialized (c);

  if (!c->surface_stack.length) return;

  hb_transform_t<> t = c->current_effective_transform ();

  hb_raster_image_t *surf = c->current_surface ();
  if (unlikely (!surf)) return;

  unsigned w = surf->extents.width;
  unsigned h = surf->extents.height;
  bool is_axis_aligned = (t.xy == 0.f && t.yx == 0.f);

  /* Transform the four corners to pixel space */
  float cx[4], cy[4];
  cx[0] = xmin; cy[0] = ymin;
  cx[1] = xmax; cy[1] = ymin;
  cx[2] = xmax; cy[2] = ymax;
  cx[3] = xmin; cy[3] = ymax;
  for (unsigned i = 0; i < 4; i++)
    t.transform_point (cx[i], cy[i]);

  /* Compute bounding box in pixel coords */
  float fmin_x = cx[0], fmin_y = cy[0], fmax_x = cx[0], fmax_y = cy[0];
  for (unsigned i = 1; i < 4; i++)
  {
    fmin_x = hb_min (fmin_x, cx[i]); fmin_y = hb_min (fmin_y, cy[i]);
    fmax_x = hb_max (fmax_x, cx[i]); fmax_y = hb_max (fmax_y, cy[i]);
  }

  /* A spec-legal but very large transform can push these bounds past the int
     range or to non-finite values; a bare float->int conversion would then be
     undefined.  Saturate through hb_clamp_to<> (which also maps NaN to
     INT32_MIN); the subtraction is done in double to avoid overflow. */
  int px0 = hb_clamp_to<int32_t> ((double) floorf (fmin_x) - surf->extents.x_origin);
  int py0 = hb_clamp_to<int32_t> ((double) floorf (fmin_y) - surf->extents.y_origin);
  int px1 = hb_clamp_to<int32_t> ((double) ceilf  (fmax_x) - surf->extents.x_origin);
  int py1 = hb_clamp_to<int32_t> ((double) ceilf  (fmax_y) - surf->extents.y_origin);

  /* Clamp to surface bounds */
  px0 = hb_max (px0, 0);
  py0 = hb_max (py0, 0);
  px1 = hb_min (px1, (int) w);
  py1 = hb_min (py1, (int) h);

  const hb_raster_clip_t &old_clip = c->current_clip ();
  if (unlikely (!old_clip.has_valid_alpha_mask ()))
  {
    hb_raster_paint_push_empty_clip (c, w, h);
    return;
  }

  /* All paths below except rect-on-rect run a full clip-buffer pass. */
  if (!(is_axis_aligned && old_clip.is_rect) &&
      unlikely (!c->charge_work ((int64_t) w * h)))
  {
    hb_raster_paint_push_empty_clip (c, w, h);
    return;
  }

  hb_raster_clip_t new_clip = c->acquire_clip (w, h);

  if (is_axis_aligned && old_clip.is_rect)
  {
    /* Fast path: axis-aligned rect-on-rect intersection */
    new_clip.is_rect = true;
    new_clip.rect_x0 = hb_max (px0, old_clip.rect_x0);
    new_clip.rect_y0 = hb_max (py0, old_clip.rect_y0);
    new_clip.rect_x1 = hb_min (px1, old_clip.rect_x1);
    new_clip.rect_y1 = hb_min (py1, old_clip.rect_y1);
    new_clip.update_bounds_from_rect ();
  }
  else if (is_axis_aligned)
  {
    /* Fast path: intersect an axis-aligned rect with an existing alpha clip. */
    new_clip.is_rect = false;
    size_t clip_size = (size_t) new_clip.stride * h;
    if (unlikely (clip_size > HB_RASTER_MAX_BUFFER_SIZE ||
                  !new_clip.alpha.resize ((unsigned) clip_size)))
    {
      hb_raster_paint_push_empty_clip (c, w, h);
      return;
    }
    hb_memset (new_clip.alpha.arrayZ, 0, (unsigned) clip_size);

    int iy0_i = hb_max (py0, (int) old_clip.min_y);
    int iy1_i = hb_min (py1, (int) old_clip.max_y);
    int ix0_i = hb_max (px0, (int) old_clip.min_x);
    int ix1_i = hb_min (px1, (int) old_clip.max_x);

    if (ix0_i < ix1_i && iy0_i < iy1_i)
    {
      unsigned iy0 = (unsigned) iy0_i;
      unsigned iy1 = (unsigned) iy1_i;
      unsigned ix0 = (unsigned) ix0_i;
      unsigned ix1 = (unsigned) ix1_i;

      for (unsigned y = iy0; y < iy1; y++)
      {
        const uint8_t *old_row = old_clip.alpha.arrayZ + y * old_clip.stride;
        uint8_t *new_row = new_clip.alpha.arrayZ + y * new_clip.stride;
        hb_memcpy (new_row + ix0, old_row + ix0, ix1 - ix0);
      }
      new_clip.min_x = ix0;
      new_clip.min_y = iy0;
      new_clip.max_x = ix1;
      new_clip.max_y = iy1;
    }
  }
  else
  {
    /* General case: rasterize transformed quad as alpha mask */
    new_clip.is_rect = false;
    size_t clip_size = (size_t) new_clip.stride * h;
    if (unlikely (clip_size > HB_RASTER_MAX_BUFFER_SIZE ||
                  !new_clip.alpha.resize ((unsigned) clip_size)))
    {
      hb_raster_paint_push_empty_clip (c, w, h);
      return;
    }
    hb_memset (new_clip.alpha.arrayZ, 0, (unsigned) clip_size);

    /* Convert quad corners to pixel-relative coords */
    float qx[4], qy[4];
    int ox = surf->extents.x_origin;
    int oy = surf->extents.y_origin;
    for (unsigned i = 0; i < 4; i++)
    {
      qx[i] = cx[i] - ox;
      qy[i] = cy[i] - oy;
    }

    /* For each pixel in the bounding box, test if inside the quad
     * using cross-product edge tests (winding order). */
    int iy0_i = hb_max (py0, (int) old_clip.min_y);
    int iy1_i = hb_min (py1, (int) old_clip.max_y);
    int ix0_i = hb_max (px0, (int) old_clip.min_x);
    int ix1_i = hb_min (px1, (int) old_clip.max_x);
    new_clip.min_x = w; new_clip.min_y = h;
    new_clip.max_x = 0; new_clip.max_y = 0;

    if (ix0_i < ix1_i && iy0_i < iy1_i)
    {
      unsigned iy0 = (unsigned) iy0_i;
      unsigned iy1 = (unsigned) iy1_i;
      unsigned ix0 = (unsigned) ix0_i;
      unsigned ix1 = (unsigned) ix1_i;

      /* Precompute edge normals for point-in-quad test.
       * Edge i goes from corner i to corner (i+1)%4.
       * Normal = (dy, -dx); inside test: dot(normal, p-corner) >= 0 */
	      float enx[4], eny[4], ed[4];
	      for (unsigned i = 0; i < 4; i++)
	      {
		unsigned j = (i + 1) & 3;
		float edx = qx[j] - qx[i], edy = qy[j] - qy[i];
		enx[i] = edy;       /* normal x */
		eny[i] = -edx;      /* normal y */
		ed[i] = enx[i] * qx[i] + eny[i] * qy[i]; /* distance threshold */
	      }
	      float area2 = 0.f;
	      for (unsigned i = 0; i < 4; i++)
	      {
		unsigned j = (i + 1) & 3;
		area2 += qx[i] * qy[j] - qx[j] * qy[i];
	      }
	      bool ccw = area2 >= 0.f;

      if (old_clip.is_rect)
      {
	for (unsigned y = iy0; y < iy1; y++)
	  for (unsigned x = ix0; x < ix1; x++)
	  {
	    float px_f = x + 0.5f, py_f = y + 0.5f;
	    /* Test if pixel center is inside the quad */
		    bool inside = true;
		    for (unsigned i = 0; i < 4; i++)
		    {
		      float d = enx[i] * px_f + eny[i] * py_f;
		      if (ccw ? d < ed[i] : d > ed[i])
		      {
			inside = false;
			break;
		      }
		    }
	    uint8_t a = inside ? 255 : 0;
	    new_clip.alpha[y * new_clip.stride + x] = a;
	    if (a)
	    {
	      new_clip.min_x = hb_min (new_clip.min_x, x);
	      new_clip.min_y = hb_min (new_clip.min_y, y);
	      new_clip.max_x = hb_max (new_clip.max_x, x + 1);
	      new_clip.max_y = hb_max (new_clip.max_y, y + 1);
	    }
	  }
      }
      else
      {
	for (unsigned y = iy0; y < iy1; y++)
	{
	  const uint8_t *old_row = old_clip.alpha.arrayZ + y * old_clip.stride;
	  for (unsigned x = ix0; x < ix1; x++)
	  {
	    float px_f = x + 0.5f, py_f = y + 0.5f;
	    /* Test if pixel center is inside the quad */
		    bool inside = true;
		    for (unsigned i = 0; i < 4; i++)
		    {
		      float d = enx[i] * px_f + eny[i] * py_f;
		      if (ccw ? d < ed[i] : d > ed[i])
		      {
			inside = false;
			break;
		      }
		    }
	    uint8_t a = inside ? old_row[x] : 0;
	    new_clip.alpha[y * new_clip.stride + x] = a;
	    if (a)
	    {
	      new_clip.min_x = hb_min (new_clip.min_x, x);
	      new_clip.min_y = hb_min (new_clip.min_y, y);
	      new_clip.max_x = hb_max (new_clip.max_x, x + 1);
	      new_clip.max_y = hb_max (new_clip.max_y, y + 1);
	    }
	  }
	}
      }
    }
  }

  if (unlikely (!c->clip_stack.push_or_fail (std::move (new_clip))))
    hb_raster_paint_push_empty_clip (c, surf->extents.width, surf->extents.height);
}

static hb_draw_funcs_t *
hb_raster_paint_push_clip_path_start (hb_paint_funcs_t *pfuncs HB_UNUSED,
				      void *paint_data,
				      void **draw_data,
				      void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;

  ensure_initialized (c);

  /* Prime clip_rdr with the current effective transform; the
   * caller then drives hb_draw_*() into it, and _end renders
   * the accumulated edges into a mask and intersects with the
   * current clip. */
  if (unlikely (!c->surface_stack.length || !c->clip_rdr))
  {
    *draw_data = nullptr;
    return nullptr;
  }

  hb_raster_draw_t *rdr = c->clip_rdr;
  hb_transform_t<> t = c->current_effective_transform ();
  hb_raster_draw_set_transform (rdr, t.xx, t.yx, t.xy, t.yy, t.x0, t.y0);
  hb_raster_image_t *surf = c->current_surface ();
  if (likely (surf))
    hb_raster_paint_attach_clip_rdr (c, rdr, surf);

  *draw_data = rdr;
  return hb_raster_draw_get_funcs (rdr);
}

static void
hb_raster_paint_push_clip_path_end (hb_paint_funcs_t *pfuncs HB_UNUSED,
				    void *paint_data,
				    void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;

  hb_raster_image_t *surf = c->current_surface ();
  if (unlikely (!surf || !c->clip_rdr)) return;

  unsigned w = surf->extents.width;
  unsigned h = surf->extents.height;

  if (unlikely (c->work_left <= 0))
  {
    hb_raster_draw_clear (c->clip_rdr);
    hb_raster_paint_push_empty_clip (c, w, h);
    return;
  }

  hb_raster_paint_finalize_path_clip (c, c->clip_rdr, surf, w, h);
}

static void
hb_raster_paint_pop_clip (hb_paint_funcs_t *pfuncs HB_UNUSED,
			  void *paint_data,
			  void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;
  if (!c->clip_stack.length) return;
  c->release_clip (c->clip_stack.pop ());
}

static void
hb_raster_paint_push_group (hb_paint_funcs_t *pfuncs HB_UNUSED,
			    void *paint_data,
			    void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;

  ensure_initialized (c);

  /* acquire_surface() clears a full surface; charge its area. */
  if (unlikely (!c->charge_work ((int64_t) c->fixed_extents.width * c->fixed_extents.height)))
    return;

  hb_raster_image_t *new_surf = c->acquire_surface ();
  if (unlikely (!new_surf)) return;
  if (unlikely (!c->surface_stack.push_or_fail (new_surf)))
    c->release_surface (new_surf);
}

static void
hb_raster_paint_pop_group (hb_paint_funcs_t *pfuncs HB_UNUSED,
			   void *paint_data,
			   hb_paint_composite_mode_t mode,
			   void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;

  if (c->surface_stack.length < 2) return;

  hb_raster_image_t *src = c->surface_stack.pop ();
  hb_raster_image_t *dst = c->current_surface ();

  if (dst && src &&
      likely (c->charge_work ((int64_t) dst->extents.width * dst->extents.height)))
    hb_raster_image_composite (dst, src, mode);

  c->release_surface (src);
}

static void
hb_raster_paint_solid (hb_raster_paint_t *c,
		       hb_raster_image_t *surf,
		       hb_color_t color,
		       hb_raster_image_t *mask_img)
{
  uint32_t premul = color_to_premul_pixel (color);
  uint8_t premul_a = (uint8_t) (premul >> 24);
  const hb_raster_clip_t &clip = c->current_clip ();

  unsigned stride = surf->extents.stride;
  if (clip.min_x >= clip.max_x || clip.min_y >= clip.max_y) return;
  if (premul_a == 0) return;

  unsigned ix0 = clip.min_x, iy0 = clip.min_y;
  unsigned ix1 = clip.max_x, iy1 = clip.max_y;
  const uint8_t *mask_buf = nullptr;
  hb_raster_extents_t mask_ext;
  int64_t mask_x0 = 0, mask_y0 = 0;
  if (mask_img)
  {
    mask_buf = hb_raster_image_get_buffer (mask_img);
    hb_raster_image_get_extents (mask_img, &mask_ext);
    if (!hb_raster_paint_intersect_mask (surf, mask_ext, clip,
					 surf->extents.width, surf->extents.height,
					 &mask_x0, &mask_y0,
					 &ix0, &iy0, &ix1, &iy1))
      return;
  }

  if (unlikely (!c->charge_work ((int64_t) (ix1 - ix0) * (iy1 - iy0))))
    return;

  if (likely (!clip.is_rect))
  {
    for (unsigned y = iy0; y < iy1; y++)
    {
      hb_packed_t<uint32_t> *__restrict row = (hb_packed_t<uint32_t> *) (surf->buffer.arrayZ + y * stride);
      const uint8_t *__restrict clip_row = clip.alpha.arrayZ + y * clip.stride;
      const uint8_t *__restrict mask_row = mask_buf ? mask_buf + (unsigned) ((int64_t) y - mask_y0) * mask_ext.stride
						    : nullptr;
      unsigned mx = (unsigned) ((int64_t) ix0 - mask_x0);
      for (unsigned x = ix0; x < ix1; x++)
      {
	uint8_t clip_alpha = clip_row[x];
	if (mask_row)
	  clip_alpha = hb_raster_div255 (mask_row[mx++] * clip_alpha);
	if (clip_alpha == 0) continue;
	if (clip_alpha == 255)
	{
	  if (premul_a == 255)
	    row[x] = hb_packed_t<uint32_t> (premul);
	  else
	    row[x] = hb_packed_t<uint32_t> (hb_raster_src_over (premul, (uint32_t) row[x]));
	}
	else
	{
	  uint32_t src = hb_raster_alpha_mul (premul, clip_alpha);
	  row[x] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[x]));
	}
      }
    }
  }
  else if (mask_buf)
  {
    for (unsigned y = iy0; y < iy1; y++)
    {
      hb_packed_t<uint32_t> *__restrict row = (hb_packed_t<uint32_t> *) (surf->buffer.arrayZ + y * stride);
      const uint8_t *__restrict mask_row = mask_buf + (unsigned) ((int64_t) y - mask_y0) * mask_ext.stride;
      unsigned mx = (unsigned) ((int64_t) ix0 - mask_x0);
      for (unsigned x = ix0; x < ix1; x++)
      {
	uint8_t mask_alpha = mask_row[mx++];
	if (mask_alpha == 0) continue;
	uint32_t src = hb_raster_alpha_mul (premul, mask_alpha);
	row[x] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[x]));
      }
    }
  }
  else
  {
    for (unsigned y = clip.min_y; y < clip.max_y; y++)
    {
      hb_packed_t<uint32_t> *row = (hb_packed_t<uint32_t> *) (surf->buffer.arrayZ + y * stride);
      for (unsigned x = clip.min_x; x < clip.max_x; x++)
	row[x] = hb_packed_t<uint32_t> (hb_raster_src_over (premul, (uint32_t) row[x]));
    }
  }
}

static void
hb_raster_paint_color (hb_paint_funcs_t *pfuncs HB_UNUSED,
		       void *paint_data,
		       hb_bool_t is_foreground HB_UNUSED,
		       hb_color_t color,
		       void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;

  ensure_initialized (c);

  hb_raster_image_t *surf = c->current_surface ();
  if (unlikely (!surf)) return;

  hb_raster_paint_solid (c, surf, color, nullptr);
}

static void
hb_raster_paint_fill_glyph (hb_paint_funcs_t *pfuncs HB_UNUSED,
			    void *paint_data,
			    hb_codepoint_t glyph,
			    hb_font_t *font,
			    hb_bool_t is_foreground HB_UNUSED,
			    hb_color_t color,
			    void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;

  ensure_initialized (c);

  hb_raster_image_t *surf = c->current_surface ();
  if (unlikely (!surf)) return;

  /* Out of budget: skip the glyph-outline extraction entirely. */
  if (unlikely (c->work_left <= 0)) return;

  hb_raster_draw_t *rdr = c->clip_rdr;
  hb_transform_t<> t = c->current_effective_transform ();
  hb_raster_draw_set_transform (rdr, t.xx, t.yx, t.xy, t.yy, t.x0, t.y0);
  hb_raster_paint_attach_clip_rdr (c, rdr, surf);

  hb_raster_paint_glyph_clip_data_t data = {glyph, font};
  hb_raster_paint_emit_clip_glyph_mask (rdr, &data);

  /* Charge the scanline work of the accumulated outline before
   * rendering it. */
  c->work_left -= hb_raster_draw_get_edge_work (rdr, surf->extents.height);

  hb_raster_image_t *mask_img = hb_raster_draw_render (rdr);
  if (unlikely (!mask_img)) return;

  hb_raster_extents_t mask_ext;
  hb_raster_image_get_extents (mask_img, &mask_ext);
  if (likely (c->charge_work ((int64_t) mask_ext.width * mask_ext.height)))
    hb_raster_paint_solid (c, surf, color, mask_img);

  hb_raster_draw_recycle_image (rdr, mask_img);
}

static hb_bool_t
hb_raster_paint_image (hb_paint_funcs_t *pfuncs HB_UNUSED,
		       void *paint_data,
		       hb_blob_t *blob,
		       unsigned width,
		       unsigned height,
		       hb_tag_t format,
		       float slant HB_UNUSED,
		       hb_glyph_extents_t *extents,
		       void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;

  ensure_initialized (c);

  /* Out of budget: skip, including the image decode below. */
  if (unlikely (c->work_left <= 0)) return false;

  unsigned src_width = width;
  unsigned src_height = height;
  const hb_packed_t<uint32_t> *src_data = nullptr;
  hb_raster_image_t decoded_png;

  if (format == HB_PAINT_IMAGE_FORMAT_BGRA)
  {
    if (src_width == 0 || src_height == 0)
      return false;
    if (src_width > (unsigned) INT_MAX || src_height > (unsigned) INT_MAX)
      return false;

    unsigned data_len;
    const uint8_t *data = (const uint8_t *) hb_blob_get_data (blob, &data_len);
    size_t pixel_count = (size_t) src_width * (size_t) src_height;
    if (src_width && pixel_count / src_width != src_height)
      return false;
    if (pixel_count > (size_t) -1 / 4u)
      return false;
    size_t required_size = pixel_count * 4u;
    if (!data || (size_t) data_len < required_size)
      return false;

    src_data = (const hb_packed_t<uint32_t> *) data;
  }
  else if (format == HB_PAINT_IMAGE_FORMAT_PNG)
  {
#ifdef HAVE_PNG
    if (!decoded_png.deserialize_from_png (blob))
      return false;
    src_width = decoded_png.extents.width;
    src_height = decoded_png.extents.height;
    src_data = (const hb_packed_t<uint32_t> *) decoded_png.buffer.arrayZ;
#else
    return false;
#endif
  }
  else
    return false;

  hb_raster_image_t *surf = c->current_surface ();
  if (unlikely (!surf)) return false;
  if (!extents) return false;

  const hb_raster_clip_t &clip = c->current_clip ();
  hb_transform_t<> t = c->current_effective_transform ();

  /* Compute inverse transform for sampling */
  float det = t.xx * t.yy - t.xy * t.yx;
  if (fabsf (det) < 1e-10f) return false;
  float inv_det = 1.f / det;
  float inv_xx =  t.yy * inv_det;
  float inv_xy = -t.xy * inv_det;
  float inv_yx = -t.yx * inv_det;
  float inv_yy =  t.xx * inv_det;
  float inv_x0 = (t.xy * t.y0 - t.yy * t.x0) * inv_det;
  float inv_y0 = (t.yx * t.x0 - t.xx * t.y0) * inv_det;

  unsigned surf_stride = surf->extents.stride;
  int ox = surf->extents.x_origin;
  int oy = surf->extents.y_origin;

  /* Image source rectangle in glyph space */
  float img_x = extents->x_bearing;
  float img_y = extents->y_bearing;
  float img_sx = (float) extents->width / src_width;
  float img_sy = (float) extents->height / src_height;
  if (fabsf (img_sx) < 1e-10f || fabsf (img_sy) < 1e-10f)
    return false;

  {
    unsigned clip_w = clip.max_x > clip.min_x ? clip.max_x - clip.min_x : 0;
    unsigned clip_h = clip.max_y > clip.min_y ? clip.max_y - clip.min_y : 0;
    if (unlikely (!c->charge_work ((int64_t) clip_w * clip_h +
				   (int64_t) src_width * src_height)))
      return false;
  }

  if (clip.is_rect)
  {
    for (unsigned py = clip.min_y; py < clip.max_y; py++)
    {
      hb_packed_t<uint32_t> *row = (hb_packed_t<uint32_t> *) (surf->buffer.arrayZ + py * surf_stride);
      float gx = inv_xx * (float) ((int) clip.min_x + ox) + inv_xy * (float) ((int) py + oy) + inv_x0;
      float gy = inv_yx * (float) ((int) clip.min_x + ox) + inv_yy * (float) ((int) py + oy) + inv_y0;
      for (unsigned px = clip.min_x; px < clip.max_x; px++)
      {
	/* Map glyph space to image texel; bilinear reconstruction. */
	float ix = (gx - img_x) / img_sx;
	float iy = (float) (src_height - 1) - (gy - img_y) / img_sy;

	if (unlikely (!std::isfinite (ix) || !std::isfinite (iy)) ||
	    ix < 0.f || iy < 0.f ||
	    ix > (float) (src_width - 1) || iy > (float) (src_height - 1))
	{
	  gx += inv_xx;
	  gy += inv_yx;
	  continue;
	}

	uint32_t src_px = hb_raster_sample_bilinear_premul (src_data, src_width, src_height,
							     ix, iy);
	row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src_px, (uint32_t) row[px]));
	gx += inv_xx;
	gy += inv_yx;
      }
    }
  }
  else
  {
    for (unsigned py = clip.min_y; py < clip.max_y; py++)
    {
      hb_packed_t<uint32_t> *row = (hb_packed_t<uint32_t> *) (surf->buffer.arrayZ + py * surf_stride);
      const uint8_t *clip_row = clip.alpha.arrayZ + py * clip.stride;
      float gx = inv_xx * (float) ((int) clip.min_x + ox) + inv_xy * (float) ((int) py + oy) + inv_x0;
      float gy = inv_yx * (float) ((int) clip.min_x + ox) + inv_yy * (float) ((int) py + oy) + inv_y0;
      for (unsigned px = clip.min_x; px < clip.max_x; px++)
      {
	uint8_t clip_alpha = clip_row[px];
	if (clip_alpha == 0)
	{
	  gx += inv_xx;
	  gy += inv_yx;
	  continue;
	}

	/* Map glyph space to image texel; bilinear reconstruction. */
	float ix = (gx - img_x) / img_sx;
	float iy = (float) (src_height - 1) - (gy - img_y) / img_sy;

	if (unlikely (!std::isfinite (ix) || !std::isfinite (iy)) ||
	    ix < 0.f || iy < 0.f ||
	    ix > (float) (src_width - 1) || iy > (float) (src_height - 1))
	{
	  gx += inv_xx;
	  gy += inv_yx;
	  continue;
	}

	uint32_t src_px = hb_raster_sample_bilinear_premul (src_data, src_width, src_height,
							     ix, iy);
	src_px = hb_raster_alpha_mul (src_px, clip_alpha);
	row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src_px, (uint32_t) row[px]));
	gx += inv_xx;
	gy += inv_yx;
      }
    }
  }

  return true;
}


/*
 * Gradient helpers
 */

#define GRADIENT_LUT_SIZE 256
#define GRADIENT_LUT_MIN_PIXELS (64u * 64u)

static HB_ALWAYS_INLINE float
reflect_gradient_t (float t)
{
  t = fmodf (fabsf (t), 2.f);
  return t > 1.f ? 2.f - t : t;
}

/* Evaluate color at normalized position t, interpolating in premultiplied space. */
static uint32_t
evaluate_color_line (const hb_color_stop_t *stops, unsigned len, float t,
		     hb_paint_extend_t extend)
{
  if (unlikely (!len))
    return 0;
  if (unlikely (len == 1))
    return color_to_premul_pixel (stops[0].color);
  if (unlikely (!std::isfinite (t)))
    t = 0.f;

  /* Apply extend mode */
  if (extend == HB_PAINT_EXTEND_PAD)
  {
    t = hb_clamp (t, 0.f, 1.f);
  }
  else if (extend == HB_PAINT_EXTEND_REPEAT)
  {
    t = t - floorf (t);
  }
  else /* REFLECT */
  {
    t = reflect_gradient_t (t);
  }

  /* Find bounding stops */
  if (t <= stops[0].offset)
    return color_to_premul_pixel (stops[0].color);
  if (t >= stops[len - 1].offset)
    return color_to_premul_pixel (stops[len - 1].color);

  unsigned i;
  for (i = 0; i < len - 1; i++)
    if (t < stops[i + 1].offset)
      break;

  float range = stops[i + 1].offset - stops[i].offset;
  float k = range > 0.f ? (t - stops[i].offset) / range : 0.f;

  /* Interpolate in premultiplied [0,255] space */
  hb_color_t c0 = stops[i].color;
  hb_color_t c1 = stops[i + 1].color;

  float a0 = hb_color_get_alpha (c0) / 255.f;
  float r0 = hb_color_get_red   (c0) / 255.f * a0;
  float g0 = hb_color_get_green (c0) / 255.f * a0;
  float b0 = hb_color_get_blue  (c0) / 255.f * a0;

  float a1 = hb_color_get_alpha (c1) / 255.f;
  float r1 = hb_color_get_red   (c1) / 255.f * a1;
  float g1 = hb_color_get_green (c1) / 255.f * a1;
  float b1 = hb_color_get_blue  (c1) / 255.f * a1;

  float a = a0 + k * (a1 - a0);
  float r = r0 + k * (r1 - r0);
  float g = g0 + k * (g1 - g0);
  float b = b0 + k * (b1 - b0);

  uint8_t pa = (uint8_t) (a * 255.f + 0.5f);
  uint8_t pr = (uint8_t) (r * 255.f + 0.5f);
  uint8_t pg = (uint8_t) (g * 255.f + 0.5f);
  uint8_t pb = (uint8_t) (b * 255.f + 0.5f);

  return (uint32_t) pb | ((uint32_t) pg << 8) | ((uint32_t) pr << 16) | ((uint32_t) pa << 24);
}

static HB_ALWAYS_INLINE float
normalize_gradient_t (float t, hb_paint_extend_t extend)
{
  if (unlikely (!std::isfinite (t)))
    return 0.f;

  if (extend == HB_PAINT_EXTEND_PAD)
    return hb_clamp (t, 0.f, 1.f);
  if (extend == HB_PAINT_EXTEND_REPEAT)
  {
    t = t - floorf (t);
    return t < 0.f ? t + 1.f : t;
  }

  /* REFLECT */
  return reflect_gradient_t (t);
}

static void
build_gradient_lut (const hb_color_stop_t *stops,
		    unsigned len,
		    uint32_t *lut)
{
  for (unsigned i = 0; i < GRADIENT_LUT_SIZE; i++)
  {
    float t = (float) i / (GRADIENT_LUT_SIZE - 1);
    lut[i] = evaluate_color_line (stops, len, t, HB_PAINT_EXTEND_PAD);
  }
}

static HB_ALWAYS_INLINE uint32_t
lookup_gradient_lut (const uint32_t *lut,
		     float t,
		     hb_paint_extend_t extend)
{
  float u = normalize_gradient_t (t, extend);
  if (unlikely (!std::isfinite (u)))
    u = 0.f;
  else
    u = hb_clamp (u, 0.f, 1.f);
  unsigned idx = (unsigned) (u * (GRADIENT_LUT_SIZE - 1) + 0.5f);
  return lut[idx];
}

/*
 * Gradient paint callbacks
 */

static void
hb_raster_paint_linear_gradient (hb_paint_funcs_t *pfuncs HB_UNUSED,
				 void *paint_data,
				 hb_color_line_t *color_line,
				 float x0, float y0,
				 float x1, float y1,
				 float x2, float y2,
				 void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;

  ensure_initialized (c);

  hb_raster_image_t *surf = c->current_surface ();
  if (unlikely (!surf)) return;

  if (unlikely (!c->fetch_color_stops (color_line)))
    return;
  hb_color_stop_t *stops = c->scratch_color_stops.arrayZ;
  unsigned len = c->scratch_color_stops.length;
  float mn, mx;
  hb_paint_normalize_color_line (stops, len, &mn, &mx);

  hb_paint_extend_t extend = hb_color_line_get_extend (color_line);
  const hb_raster_clip_t &clip = c->current_clip ();
  unsigned clip_w = clip.max_x > clip.min_x ? clip.max_x - clip.min_x : 0;
  unsigned clip_h = clip.max_y > clip.min_y ? clip.max_y - clip.min_y : 0;
  if (unlikely (!c->charge_work ((int64_t) clip_w * clip_h)))
    return;
  bool use_lut = (uint64_t) clip_w * clip_h >= GRADIENT_LUT_MIN_PIXELS;
  uint32_t lut[GRADIENT_LUT_SIZE];
  if (use_lut)
    build_gradient_lut (stops, len, lut);

  /* Reduce 3-point anchor to 2-point gradient axis */
  float lx0, ly0, lx1, ly1;
  hb_paint_reduce_linear_anchors (x0, y0, x1, y1, x2, y2,
				  &lx0, &ly0, &lx1, &ly1);

  /* Apply normalization to endpoints */
  float gx0 = lx0 + mn * (lx1 - lx0);
  float gy0 = ly0 + mn * (ly1 - ly0);
  float gx1 = lx0 + mx * (lx1 - lx0);
  float gy1 = ly0 + mx * (ly1 - ly0);

  /* Inverse transform: pixel → glyph space */
  hb_transform_t<> t = c->current_effective_transform ();
  float det = t.xx * t.yy - t.xy * t.yx;
  if (fabsf (det) < 1e-10f) return;

  {
    float inv_det = 1.f / det;
    float inv_xx =  t.yy * inv_det;
    float inv_xy = -t.xy * inv_det;
    float inv_yx = -t.yx * inv_det;
    float inv_yy =  t.xx * inv_det;
    float inv_x0 = (t.xy * t.y0 - t.yy * t.x0) * inv_det;
    float inv_y0 = (t.yx * t.x0 - t.xx * t.y0) * inv_det;

    /* Gradient direction vector and denominator for projection */
    float dx = gx1 - gx0, dy = gy1 - gy0;
    float denom = dx * dx + dy * dy;
    if (denom < 1e-10f) return;
    float inv_denom = 1.f / denom;

    unsigned stride = surf->extents.stride;
    int ox = surf->extents.x_origin;
    int oy = surf->extents.y_origin;

    if (clip.is_rect)
    {
      for (unsigned py = clip.min_y; py < clip.max_y; py++)
      {
	hb_packed_t<uint32_t> *row = (hb_packed_t<uint32_t> *) (surf->buffer.arrayZ + py * stride);
	float gx = inv_xx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_xy * ((float) ((int) py + oy) + 0.5f) + inv_x0;
	float gy = inv_yx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_yy * ((float) ((int) py + oy) + 0.5f) + inv_y0;
	if (use_lut)
	{
	  for (unsigned px = clip.min_x; px < clip.max_x; px++)
	  {
	    float proj_t = ((gx - gx0) * dx + (gy - gy0) * dy) * inv_denom;
	    uint32_t src = lookup_gradient_lut (lut, proj_t, extend);
	    row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[px]));
	    gx += inv_xx;
	    gy += inv_yx;
	  }
	}
	else
	{
	  for (unsigned px = clip.min_x; px < clip.max_x; px++)
	  {
	    float proj_t = ((gx - gx0) * dx + (gy - gy0) * dy) * inv_denom;
	    uint32_t src = evaluate_color_line (stops, len, proj_t, extend);
	    row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[px]));
	    gx += inv_xx;
	    gy += inv_yx;
	  }
	}
      }
    }
    else
    {
      for (unsigned py = clip.min_y; py < clip.max_y; py++)
      {
	hb_packed_t<uint32_t> *row = (hb_packed_t<uint32_t> *) (surf->buffer.arrayZ + py * stride);
	const uint8_t *clip_row = clip.alpha.arrayZ + py * clip.stride;
	float gx = inv_xx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_xy * ((float) ((int) py + oy) + 0.5f) + inv_x0;
	float gy = inv_yx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_yy * ((float) ((int) py + oy) + 0.5f) + inv_y0;
	if (use_lut)
	{
	  for (unsigned px = clip.min_x; px < clip.max_x; px++)
	  {
	    uint8_t clip_alpha = clip_row[px];
	    if (clip_alpha == 0)
	    {
	      gx += inv_xx;
	      gy += inv_yx;
	      continue;
	    }
	    float proj_t = ((gx - gx0) * dx + (gy - gy0) * dy) * inv_denom;
	    uint32_t src = lookup_gradient_lut (lut, proj_t, extend);
	    src = hb_raster_alpha_mul (src, clip_alpha);
	    row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[px]));
	    gx += inv_xx;
	    gy += inv_yx;
	  }
	}
	else
	{
	  for (unsigned px = clip.min_x; px < clip.max_x; px++)
	  {
	    uint8_t clip_alpha = clip_row[px];
	    if (clip_alpha == 0)
	    {
	      gx += inv_xx;
	      gy += inv_yx;
	      continue;
	    }
	    float proj_t = ((gx - gx0) * dx + (gy - gy0) * dy) * inv_denom;
	    uint32_t src = evaluate_color_line (stops, len, proj_t, extend);
	    src = hb_raster_alpha_mul (src, clip_alpha);
	    row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[px]));
	    gx += inv_xx;
	    gy += inv_yx;
	  }
	}
      }
    }
  }

}

static void
hb_raster_paint_radial_gradient (hb_paint_funcs_t *pfuncs HB_UNUSED,
				 void *paint_data,
				 hb_color_line_t *color_line,
				 float x0, float y0, float r0,
				 float x1, float y1, float r1,
				 void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;

  ensure_initialized (c);

  hb_raster_image_t *surf = c->current_surface ();
  if (unlikely (!surf)) return;

  if (unlikely (!c->fetch_color_stops (color_line)))
    return;
  hb_color_stop_t *stops = c->scratch_color_stops.arrayZ;
  unsigned len = c->scratch_color_stops.length;
  float mn, mx;
  hb_paint_normalize_color_line (stops, len, &mn, &mx);

  hb_paint_extend_t extend = hb_color_line_get_extend (color_line);
  const hb_raster_clip_t &clip = c->current_clip ();
  unsigned clip_w = clip.max_x > clip.min_x ? clip.max_x - clip.min_x : 0;
  unsigned clip_h = clip.max_y > clip.min_y ? clip.max_y - clip.min_y : 0;
  if (unlikely (!c->charge_work ((int64_t) clip_w * clip_h)))
    return;
  bool use_lut = (uint64_t) clip_w * clip_h >= GRADIENT_LUT_MIN_PIXELS;
  uint32_t lut[GRADIENT_LUT_SIZE];
  if (use_lut)
    build_gradient_lut (stops, len, lut);

  /* Apply normalization to circle parameters */
  float cx0 = x0 + mn * (x1 - x0);
  float cy0 = y0 + mn * (y1 - y0);
  float cr0 = r0 + mn * (r1 - r0);
  float cx1 = x0 + mx * (x1 - x0);
  float cy1 = y0 + mx * (y1 - y0);
  float cr1 = r0 + mx * (r1 - r0);

  /* Inverse transform */
  hb_transform_t<> t = c->current_effective_transform ();
  float det = t.xx * t.yy - t.xy * t.yx;
  if (fabsf (det) < 1e-10f) return;

  {
    float inv_det = 1.f / det;
    float inv_xx =  t.yy * inv_det;
    float inv_xy = -t.xy * inv_det;
    float inv_yx = -t.yx * inv_det;
    float inv_yy =  t.xx * inv_det;
    float inv_x0 = (t.xy * t.y0 - t.yy * t.x0) * inv_det;
    float inv_y0 = (t.yx * t.x0 - t.xx * t.y0) * inv_det;

    /* Precompute quadratic coefficients for radial gradient:
     * |p - c0 - t*(c1-c0)|^2 = (r0 + t*(r1-r0))^2
     *
     * Expanding gives At^2 + Bt + C = 0 where:
     *   cdx = c1.x - c0.x, cdy = c1.y - c0.y, dr = r1 - r0
     *   A = cdx^2 + cdy^2 - dr^2
     *   B = -2*(px-c0.x)*cdx - 2*(py-c0.y)*cdy - 2*r0*dr
     *   C = (px-c0.x)^2 + (py-c0.y)^2 - r0^2
     */
    float cdx = cx1 - cx0, cdy = cy1 - cy0;
    float dr = cr1 - cr0;
    float A = cdx * cdx + cdy * cdy - dr * dr;

    unsigned stride = surf->extents.stride;
    int ox = surf->extents.x_origin;
    int oy = surf->extents.y_origin;

    if (clip.is_rect)
    {
      for (unsigned py = clip.min_y; py < clip.max_y; py++)
      {
	hb_packed_t<uint32_t> *row = (hb_packed_t<uint32_t> *) (surf->buffer.arrayZ + py * stride);
	float gx = inv_xx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_xy * ((float) ((int) py + oy) + 0.5f) + inv_x0;
	float gy = inv_yx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_yy * ((float) ((int) py + oy) + 0.5f) + inv_y0;
	if (use_lut)
	{
	  for (unsigned px = clip.min_x; px < clip.max_x; px++)
	  {
	    float dpx = gx - cx0, dpy = gy - cy0;
	    float B = -2.f * (dpx * cdx + dpy * cdy + cr0 * dr);
	    float C = dpx * dpx + dpy * dpy - cr0 * cr0;

	    float grad_t;
	    if (fabsf (A) > 1e-10f)
	    {
	      float disc = B * B - 4.f * A * C;
	      if (disc < 0.f)
	      {
		gx += inv_xx;
		gy += inv_yx;
		continue;
	      }
	      float sq = sqrtf (disc);
	      /* Pick the larger root (t closer to 1 = outer circle) */
	      float t1 = (-B + sq) / (2.f * A);
	      float t2 = (-B - sq) / (2.f * A);
	      /* Choose the root that gives a positive radius */
	      if (cr0 + t1 * dr >= 0.f)
		grad_t = t1;
	      else
		grad_t = t2;
	    }
	    else
	    {
	      /* Linear case: Bt + C = 0 */
	      if (fabsf (B) < 1e-10f)
	      {
		gx += inv_xx;
		gy += inv_yx;
		continue;
	      }
	      grad_t = -C / B;
	    }

	    uint32_t src = lookup_gradient_lut (lut, grad_t, extend);
	    row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[px]));
	    gx += inv_xx;
	    gy += inv_yx;
	  }
	}
	else
	{
	  for (unsigned px = clip.min_x; px < clip.max_x; px++)
	  {
	    float dpx = gx - cx0, dpy = gy - cy0;
	    float B = -2.f * (dpx * cdx + dpy * cdy + cr0 * dr);
	    float C = dpx * dpx + dpy * dpy - cr0 * cr0;

	    float grad_t;
	    if (fabsf (A) > 1e-10f)
	    {
	      float disc = B * B - 4.f * A * C;
	      if (disc < 0.f)
	      {
		gx += inv_xx;
		gy += inv_yx;
		continue;
	      }
	      float sq = sqrtf (disc);
	      float t1 = (-B + sq) / (2.f * A);
	      float t2 = (-B - sq) / (2.f * A);
	      grad_t = (cr0 + t1 * dr >= 0.f) ? t1 : t2;
	    }
	    else
	    {
	      if (fabsf (B) < 1e-10f)
	      {
		gx += inv_xx;
		gy += inv_yx;
		continue;
	      }
	      grad_t = -C / B;
	    }

	    uint32_t src = evaluate_color_line (stops, len, grad_t, extend);
	    row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[px]));
	    gx += inv_xx;
	    gy += inv_yx;
	  }
	}
      }
    }
    else
    {
      for (unsigned py = clip.min_y; py < clip.max_y; py++)
      {
	hb_packed_t<uint32_t> *row = (hb_packed_t<uint32_t> *) (surf->buffer.arrayZ + py * stride);
	const uint8_t *clip_row = clip.alpha.arrayZ + py * clip.stride;
	float gx = inv_xx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_xy * ((float) ((int) py + oy) + 0.5f) + inv_x0;
	float gy = inv_yx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_yy * ((float) ((int) py + oy) + 0.5f) + inv_y0;
	if (use_lut)
	{
	  for (unsigned px = clip.min_x; px < clip.max_x; px++)
	  {
	    uint8_t clip_alpha = clip_row[px];
	    if (clip_alpha == 0)
	    {
	      gx += inv_xx;
	      gy += inv_yx;
	      continue;
	    }
	    float dpx = gx - cx0, dpy = gy - cy0;
	    float B = -2.f * (dpx * cdx + dpy * cdy + cr0 * dr);
	    float C = dpx * dpx + dpy * dpy - cr0 * cr0;

	    float grad_t;
	    if (fabsf (A) > 1e-10f)
	    {
	      float disc = B * B - 4.f * A * C;
	      if (disc < 0.f)
	      {
		gx += inv_xx;
		gy += inv_yx;
		continue;
	      }
	      float sq = sqrtf (disc);
	      float t1 = (-B + sq) / (2.f * A);
	      float t2 = (-B - sq) / (2.f * A);
	      grad_t = (cr0 + t1 * dr >= 0.f) ? t1 : t2;
	    }
	    else
	    {
	      if (fabsf (B) < 1e-10f)
	      {
		gx += inv_xx;
		gy += inv_yx;
		continue;
	      }
	      grad_t = -C / B;
	    }

	    uint32_t src = lookup_gradient_lut (lut, grad_t, extend);
	    src = hb_raster_alpha_mul (src, clip_alpha);
	    row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[px]));
	    gx += inv_xx;
	    gy += inv_yx;
	  }
	}
	else
	{
	  for (unsigned px = clip.min_x; px < clip.max_x; px++)
	  {
	    uint8_t clip_alpha = clip_row[px];
	    if (clip_alpha == 0)
	    {
	      gx += inv_xx;
	      gy += inv_yx;
	      continue;
	    }
	    float dpx = gx - cx0, dpy = gy - cy0;
	    float B = -2.f * (dpx * cdx + dpy * cdy + cr0 * dr);
	    float C = dpx * dpx + dpy * dpy - cr0 * cr0;

	    float grad_t;
	    if (fabsf (A) > 1e-10f)
	    {
	      float disc = B * B - 4.f * A * C;
	      if (disc < 0.f)
	      {
		gx += inv_xx;
		gy += inv_yx;
		continue;
	      }
	      float sq = sqrtf (disc);
	      float t1 = (-B + sq) / (2.f * A);
	      float t2 = (-B - sq) / (2.f * A);
	      grad_t = (cr0 + t1 * dr >= 0.f) ? t1 : t2;
	    }
	    else
	    {
	      if (fabsf (B) < 1e-10f)
	      {
		gx += inv_xx;
		gy += inv_yx;
		continue;
	      }
	      grad_t = -C / B;
	    }

	    uint32_t src = evaluate_color_line (stops, len, grad_t, extend);
	    src = hb_raster_alpha_mul (src, clip_alpha);
	    row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[px]));
	    gx += inv_xx;
	    gy += inv_yx;
	  }
	}
      }
    }
  }

}

static void
hb_raster_paint_sweep_gradient (hb_paint_funcs_t *pfuncs HB_UNUSED,
				void *paint_data,
				hb_color_line_t *color_line,
				float cx, float cy,
				float start_angle,
				float end_angle,
				void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;

  ensure_initialized (c);

  hb_raster_image_t *surf = c->current_surface ();
  if (unlikely (!surf)) return;

  if (unlikely (!c->fetch_color_stops (color_line)))
    return;
  hb_color_stop_t *stops = c->scratch_color_stops.arrayZ;
  unsigned len = c->scratch_color_stops.length;
  float mn, mx;
  hb_paint_normalize_color_line (stops, len, &mn, &mx);

  hb_paint_extend_t extend = hb_color_line_get_extend (color_line);
  const hb_raster_clip_t &clip = c->current_clip ();
  unsigned clip_w = clip.max_x > clip.min_x ? clip.max_x - clip.min_x : 0;
  unsigned clip_h = clip.max_y > clip.min_y ? clip.max_y - clip.min_y : 0;
  if (unlikely (!c->charge_work ((int64_t) clip_w * clip_h)))
    return;
  bool use_lut = (uint64_t) clip_w * clip_h >= GRADIENT_LUT_MIN_PIXELS;
  uint32_t lut[GRADIENT_LUT_SIZE];
  if (use_lut)
    build_gradient_lut (stops, len, lut);

  /* Apply normalization to angle range */
  float a0 = start_angle + mn * (end_angle - start_angle);
  float a1 = start_angle + mx * (end_angle - start_angle);
  float angle_range = a1 - a0;

  /* Inverse transform */
  hb_transform_t<> t = c->current_effective_transform ();
  float det = t.xx * t.yy - t.xy * t.yx;
  if (fabsf (det) < 1e-10f || fabsf (angle_range) < 1e-10f) return;

  {
    float inv_det = 1.f / det;
    float inv_xx =  t.yy * inv_det;
    float inv_xy = -t.xy * inv_det;
    float inv_yx = -t.yx * inv_det;
    float inv_yy =  t.xx * inv_det;
    float inv_x0 = (t.xy * t.y0 - t.yy * t.x0) * inv_det;
    float inv_y0 = (t.yx * t.x0 - t.xx * t.y0) * inv_det;

    float inv_angle_range = 1.f / angle_range;

    unsigned stride = surf->extents.stride;
    int ox = surf->extents.x_origin;
    int oy = surf->extents.y_origin;

    if (clip.is_rect)
    {
      for (unsigned py = clip.min_y; py < clip.max_y; py++)
      {
	hb_packed_t<uint32_t> *row = (hb_packed_t<uint32_t> *) (surf->buffer.arrayZ + py * stride);
	float gx = inv_xx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_xy * ((float) ((int) py + oy) + 0.5f) + inv_x0;
	float gy = inv_yx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_yy * ((float) ((int) py + oy) + 0.5f) + inv_y0;
	if (use_lut)
	{
	  for (unsigned px = clip.min_x; px < clip.max_x; px++)
	  {
	    float angle = atan2f (gy - cy, gx - cx);
	    if (angle < 0) angle += (float) HB_2_PI;
	    float grad_t = (angle - a0) * inv_angle_range;
	    uint32_t src = lookup_gradient_lut (lut, grad_t, extend);
	    row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[px]));
	    gx += inv_xx;
	    gy += inv_yx;
	  }
	}
	else
	{
	  for (unsigned px = clip.min_x; px < clip.max_x; px++)
	  {
	    float angle = atan2f (gy - cy, gx - cx);
	    if (angle < 0) angle += (float) HB_2_PI;
	    float grad_t = (angle - a0) * inv_angle_range;
	    uint32_t src = evaluate_color_line (stops, len, grad_t, extend);
	    row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[px]));
	    gx += inv_xx;
	    gy += inv_yx;
	  }
	}
      }
    }
    else
    {
      for (unsigned py = clip.min_y; py < clip.max_y; py++)
      {
	hb_packed_t<uint32_t> *row = (hb_packed_t<uint32_t> *) (surf->buffer.arrayZ + py * stride);
	const uint8_t *clip_row = clip.alpha.arrayZ + py * clip.stride;
	float gx = inv_xx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_xy * ((float) ((int) py + oy) + 0.5f) + inv_x0;
	float gy = inv_yx * ((float) ((int) clip.min_x + ox) + 0.5f) + inv_yy * ((float) ((int) py + oy) + 0.5f) + inv_y0;
	if (use_lut)
	{
	  for (unsigned px = clip.min_x; px < clip.max_x; px++)
	  {
	    uint8_t clip_alpha = clip_row[px];
	    if (clip_alpha == 0)
	    {
	      gx += inv_xx;
	      gy += inv_yx;
	      continue;
	    }
	    float angle = atan2f (gy - cy, gx - cx);
	    if (angle < 0) angle += (float) HB_2_PI;
	    float grad_t = (angle - a0) * inv_angle_range;
	    uint32_t src = lookup_gradient_lut (lut, grad_t, extend);
	    src = hb_raster_alpha_mul (src, clip_alpha);
	    row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[px]));
	    gx += inv_xx;
	    gy += inv_yx;
	  }
	}
	else
	{
	  for (unsigned px = clip.min_x; px < clip.max_x; px++)
	  {
	    uint8_t clip_alpha = clip_row[px];
	    if (clip_alpha == 0)
	    {
	      gx += inv_xx;
	      gy += inv_yx;
	      continue;
	    }
	    float angle = atan2f (gy - cy, gx - cx);
	    if (angle < 0) angle += (float) HB_2_PI;
	    float grad_t = (angle - a0) * inv_angle_range;
	    uint32_t src = evaluate_color_line (stops, len, grad_t, extend);
	    src = hb_raster_alpha_mul (src, clip_alpha);
	    row[px] = hb_packed_t<uint32_t> (hb_raster_src_over (src, (uint32_t) row[px]));
	    gx += inv_xx;
	    gy += inv_yx;
	  }
	}
      }
    }
  }

}

static hb_bool_t
hb_raster_paint_custom_palette_color (hb_paint_funcs_t *funcs HB_UNUSED,
				      void *paint_data,
				      unsigned int color_index,
				      hb_color_t *color,
				      void *user_data HB_UNUSED)
{
  hb_raster_paint_t *c = (hb_raster_paint_t *) paint_data;
  if (likely (c->custom_palette && hb_map_has (c->custom_palette, color_index)))
  {
    *color = hb_map_get (c->custom_palette, color_index);
    return true;
  }
  return false;
}


/*
 * Lazy-loader singleton for paint funcs
 */

static inline void free_static_raster_paint_funcs ();

static struct hb_raster_paint_funcs_lazy_loader_t : hb_paint_funcs_lazy_loader_t<hb_raster_paint_funcs_lazy_loader_t>
{
  static hb_paint_funcs_t *create ()
  {
    hb_paint_funcs_t *funcs = hb_paint_funcs_create ();

    hb_paint_funcs_set_push_transform_func (funcs, hb_raster_paint_push_transform, nullptr, nullptr);
    hb_paint_funcs_set_pop_transform_func (funcs, hb_raster_paint_pop_transform, nullptr, nullptr);
    hb_paint_funcs_set_fill_glyph_func (funcs, hb_raster_paint_fill_glyph, nullptr, nullptr);
    hb_paint_funcs_set_color_glyph_func (funcs, hb_raster_paint_color_glyph, nullptr, nullptr);
    hb_paint_funcs_set_push_clip_glyph_func (funcs, hb_raster_paint_push_clip_glyph, nullptr, nullptr);
    hb_paint_funcs_set_push_clip_rectangle_func (funcs, hb_raster_paint_push_clip_rectangle, nullptr, nullptr);
    hb_paint_funcs_set_push_clip_path_start_func (funcs, hb_raster_paint_push_clip_path_start, nullptr, nullptr);
    hb_paint_funcs_set_push_clip_path_end_func (funcs, hb_raster_paint_push_clip_path_end, nullptr, nullptr);
    hb_paint_funcs_set_pop_clip_func (funcs, hb_raster_paint_pop_clip, nullptr, nullptr);
    hb_paint_funcs_set_push_group_func (funcs, hb_raster_paint_push_group, nullptr, nullptr);
    hb_paint_funcs_set_pop_group_func (funcs, hb_raster_paint_pop_group, nullptr, nullptr);
    hb_paint_funcs_set_color_func (funcs, hb_raster_paint_color, nullptr, nullptr);
    hb_paint_funcs_set_image_func (funcs, hb_raster_paint_image, nullptr, nullptr);
    hb_paint_funcs_set_linear_gradient_func (funcs, hb_raster_paint_linear_gradient, nullptr, nullptr);
    hb_paint_funcs_set_radial_gradient_func (funcs, hb_raster_paint_radial_gradient, nullptr, nullptr);
    hb_paint_funcs_set_sweep_gradient_func (funcs, hb_raster_paint_sweep_gradient, nullptr, nullptr);
    hb_paint_funcs_set_custom_palette_color_func (funcs, hb_raster_paint_custom_palette_color, nullptr, nullptr);

    hb_paint_funcs_make_immutable (funcs);

    hb_atexit (free_static_raster_paint_funcs);

    return funcs;
  }
} static_raster_paint_funcs;

static inline void
free_static_raster_paint_funcs ()
{
  static_raster_paint_funcs.free_instance ();
}


/*
 * Public API
 */

/**
 * hb_raster_paint_create_or_fail:
 *
 * Creates a new color-glyph paint context.
 *
 * Return value: (transfer full):
 * A newly allocated #hb_raster_paint_t, or `NULL` on allocation failure.
 *
 * Since: 13.0.0
 **/
hb_raster_paint_t *
hb_raster_paint_create_or_fail (void)
{
  hb_raster_paint_t *paint = hb_object_create<hb_raster_paint_t> ();
  if (unlikely (!paint))
    return nullptr;

  paint->clip_rdr = hb_raster_draw_create_or_fail ();
  if (unlikely (!paint->clip_rdr))
  {
    hb_free (paint);
    return nullptr;
  }

  return paint;
}

/**
 * hb_raster_paint_reference: (skip)
 * @paint: a paint context
 *
 * Increases the reference count on @paint by one.
 *
 * Return value: (transfer full):
 * The referenced #hb_raster_paint_t.
 *
 * Since: 13.0.0
 **/
hb_raster_paint_t *
hb_raster_paint_reference (hb_raster_paint_t *paint)
{
  return hb_object_reference (paint);
}

/**
 * hb_raster_paint_destroy: (skip)
 * @paint: a paint context
 *
 * Decreases the reference count on @paint by one. When the
 * reference count reaches zero, the paint context is freed.
 *
 * Since: 13.0.0
 **/
void
hb_raster_paint_destroy (hb_raster_paint_t *paint)
{
  if (!hb_object_should_destroy (paint))
    return;

  hb_map_destroy (paint->custom_palette);
  hb_raster_draw_destroy (paint->clip_rdr);
  for (auto *s : paint->surface_stack)
    hb_raster_image_destroy (s);
  for (auto *s : paint->surface_cache)
    hb_raster_image_destroy (s);
  hb_object_actually_destroy (paint);
  hb_free (paint);
}

/**
 * hb_raster_paint_set_user_data: (skip)
 * @paint: a paint context
 * @key: the user-data key
 * @data: a pointer to the user data
 * @destroy: (nullable): a callback to call when @data is not needed anymore
 * @replace: whether to replace an existing data with the same key
 *
 * Attaches a user-data key/data pair to the specified paint context.
 *
 * Return value: `true` if success, `false` otherwise
 *
 * Since: 13.0.0
 **/
hb_bool_t
hb_raster_paint_set_user_data (hb_raster_paint_t  *paint,
			       hb_user_data_key_t *key,
			       void               *data,
			       hb_destroy_func_t   destroy,
			       hb_bool_t           replace)
{
  return hb_object_set_user_data (paint, key, data, destroy, replace);
}

/**
 * hb_raster_paint_get_user_data: (skip)
 * @paint: a paint context
 * @key: the user-data key
 *
 * Fetches the user-data associated with the specified key,
 * attached to the specified paint context.
 *
 * Return value: (transfer none):
 * A pointer to the user data
 *
 * Since: 13.0.0
 **/
void *
hb_raster_paint_get_user_data (const hb_raster_paint_t  *paint,
			       hb_user_data_key_t *key)
{
  return hb_object_get_user_data (paint, key);
}

/**
 * hb_raster_paint_set_transform:
 * @paint: a paint context
 * @xx: xx component of the transform matrix
 * @yx: yx component of the transform matrix
 * @xy: xy component of the transform matrix
 * @yy: yy component of the transform matrix
 * @dx: x translation
 * @dy: y translation
 *
 * Sets the base 2×3 affine transform that maps from glyph-space
 * coordinates to pixel-space coordinates.
 *
 * Since: 13.0.0
 **/
void
hb_raster_paint_set_transform (hb_raster_paint_t *paint,
			       float xx, float yx,
			       float xy, float yy,
			       float dx, float dy)
{
  paint->base_transform = {xx, yx, xy, yy, dx, dy};
}

/**
 * hb_raster_paint_get_transform:
 * @paint: a paint context
 * @xx: (out) (nullable): xx component of the transform matrix
 * @yx: (out) (nullable): yx component of the transform matrix
 * @xy: (out) (nullable): xy component of the transform matrix
 * @yy: (out) (nullable): yy component of the transform matrix
 * @dx: (out) (nullable): x translation
 * @dy: (out) (nullable): y translation
 *
 * Gets the current base 2x3 affine transform.
 *
 * Since: 13.0.0
 **/
void
hb_raster_paint_get_transform (const hb_raster_paint_t *paint,
			       float *xx, float *yx,
			       float *xy, float *yy,
			       float *dx, float *dy)
{
  if (xx) *xx = paint->base_transform.xx;
  if (yx) *yx = paint->base_transform.yx;
  if (xy) *xy = paint->base_transform.xy;
  if (yy) *yy = paint->base_transform.yy;
  if (dx) *dx = paint->base_transform.x0;
  if (dy) *dy = paint->base_transform.y0;
}

/**
 * hb_raster_paint_set_scale_factor:
 * @paint: a paint context
 * @x_scale_factor: x-axis minification factor
 * @y_scale_factor: y-axis minification factor
 *
 * Sets post-transform minification factors applied during painting.
 * Factors larger than 1 shrink the output in pixels. The default is 1.
 *
 * Since: 13.0.0
 **/
void
hb_raster_paint_set_scale_factor (hb_raster_paint_t *paint,
				  float x_scale_factor,
				  float y_scale_factor)
{
  paint->x_scale_factor = x_scale_factor > 0.f ? x_scale_factor : 1.f;
  paint->y_scale_factor = y_scale_factor > 0.f ? y_scale_factor : 1.f;
}

/**
 * hb_raster_paint_get_scale_factor:
 * @paint: a paint context
 * @x_scale_factor: (out) (nullable): x-axis minification factor
 * @y_scale_factor: (out) (nullable): y-axis minification factor
 *
 * Fetches the current post-transform minification factors.
 *
 * Since: 13.0.0
 **/
void
hb_raster_paint_get_scale_factor (const hb_raster_paint_t *paint,
				  float *x_scale_factor,
				  float *y_scale_factor)
{
  if (x_scale_factor) *x_scale_factor = paint->x_scale_factor;
  if (y_scale_factor) *y_scale_factor = paint->y_scale_factor;
}

/**
 * hb_raster_paint_set_extents:
 * @paint: a paint context
 * @extents: the desired output extents
 *
 * Sets the output image extents (pixel rectangle).
 *
 * Call this before hb_font_paint_glyph() for each render.
 * A common pattern is:
 * |[<!-- language="plain" -->
 * hb_glyph_extents_t gext;
 * if (hb_font_get_glyph_extents (font, gid, &gext))
 *   hb_raster_paint_set_glyph_extents (paint, &gext);
 * ]|
 *
 * Since: 13.0.0
 **/
void
hb_raster_paint_set_extents (hb_raster_paint_t         *paint,
			     const hb_raster_extents_t *extents)
{
  paint->fixed_extents = *extents;
  paint->has_extents = true;
  if (paint->fixed_extents.stride == 0)
    paint->fixed_extents.stride = paint->fixed_extents.width * 4;
}

/**
 * hb_raster_paint_get_extents:
 * @paint: a paint context
 * @extents: (out) (nullable): where to write current extents
 *
 * Gets currently configured output extents.
 *
 * Return value: `true` if extents are set, `false` otherwise.
 *
 * Since: 13.0.0
 **/
hb_bool_t
hb_raster_paint_get_extents (const hb_raster_paint_t   *paint,
			     hb_raster_extents_t *extents)
{
  if (!paint->has_extents)
    return false;

  if (extents)
    *extents = paint->fixed_extents;
  return true;
}

/**
 * hb_raster_paint_set_glyph_extents:
 * @paint: a paint context
 * @glyph_extents: glyph extents from hb_font_get_glyph_extents()
 *
 * Transforms @glyph_extents with the paint context's base transform and
 * sets the resulting output image extents.
 *
 * This is equivalent to computing a transformed bounding box in pixel
 * space and calling hb_raster_paint_set_extents().
 *
 * The resulting dimensions are capped at 4096 pixels per side.
 *
 * Return value: `true` if transformed extents are non-empty and set;
 * `false` otherwise.
 *
 * Since: 13.0.0
 **/
hb_bool_t
hb_raster_paint_set_glyph_extents (hb_raster_paint_t        *paint,
				   const hb_glyph_extents_t *glyph_extents)
{
  float x0 = (float) glyph_extents->x_bearing;
  float y0 = (float) glyph_extents->y_bearing;
  float x1 = (float) glyph_extents->x_bearing + glyph_extents->width;
  float y1 = (float) glyph_extents->y_bearing + glyph_extents->height;

  float xmin = hb_min (x0, x1);
  float xmax = hb_max (x0, x1);
  float ymin = hb_min (y0, y1);
  float ymax = hb_max (y0, y1);

  float px[4] = {xmin, xmin, xmax, xmax};
  float py[4] = {ymin, ymax, ymin, ymax};

  hb_transform_t<> t = paint->base_transform;
  paint->apply_scale_factor (t);

  float tx, ty;
  t.transform_point (px[0], py[0]);
  tx = px[0]; ty = py[0];
  float tx_min = tx, tx_max = tx;
  float ty_min = ty, ty_max = ty;

  for (unsigned i = 1; i < 4; i++)
  {
    t.transform_point (px[i], py[i]);
    tx_min = hb_min (tx_min, px[i]);
    tx_max = hb_max (tx_max, px[i]);
    ty_min = hb_min (ty_min, py[i]);
    ty_max = hb_max (ty_max, py[i]);
  }

  int32_t ex0 = hb_clamp_to<int32_t> (floorf (tx_min));
  int32_t ey0 = hb_clamp_to<int32_t> (floorf (ty_min));
  int32_t ex1 = hb_clamp_to<int32_t> (ceilf  (tx_max));
  int32_t ey1 = hb_clamp_to<int32_t> (ceilf  (ty_max));

  if (ex1 <= ex0 || ey1 <= ey0)
  {
    paint->fixed_extents = {};
    paint->has_extents = false;
    return false;
  }

  paint->fixed_extents = {
    ex0, ey0,
    (unsigned) hb_min ((int64_t) ex1 - ex0, (int64_t) HB_RASTER_MAX_AUTO_DIMENSION),
    (unsigned) hb_min ((int64_t) ey1 - ey0, (int64_t) HB_RASTER_MAX_AUTO_DIMENSION),
    0
  };
  paint->has_extents = true;
  if (paint->fixed_extents.stride == 0)
    paint->fixed_extents.stride = paint->fixed_extents.width * 4;
  return true;
}

/**
 * hb_raster_paint_set_foreground:
 * @paint: a paint context
 * @foreground: the foreground color
 *
 * Sets the foreground color used when paint callbacks request it
 * (e.g. `is_foreground` in color stops or solid fills).
 *
 * Since: 13.0.0
 **/
void
hb_raster_paint_set_foreground (hb_raster_paint_t *paint,
				hb_color_t         foreground)
{
  paint->foreground = foreground;
}

/**
 * hb_raster_paint_get_foreground:
 * @paint: a paint context
 *
 * Returns the foreground color previously set on @paint, or the
 * default opaque black if none was set.
 *
 * Return value: the foreground color.
 *
 * Since: 14.2.0
 **/
hb_color_t
hb_raster_paint_get_foreground (const hb_raster_paint_t *paint)
{
  return paint->foreground;
}

/**
 * hb_raster_paint_set_background:
 * @paint: a paint context
 * @background: the background color
 *
 * Sets the background color for @paint.  If set to a non-transparent
 * value, the rendered image is pre-filled with this color before
 * glyph content is composited on top.  Default is transparent.
 *
 * Since: 14.2.0
 **/
void
hb_raster_paint_set_background (hb_raster_paint_t *paint,
				hb_color_t         background)
{
  paint->background = background;
}

/**
 * hb_raster_paint_get_background:
 * @paint: a paint context
 *
 * Returns the background color previously set on @paint, or
 * transparent if none was set.
 *
 * Return value: the background color.
 *
 * Since: 14.2.0
 **/
hb_color_t
hb_raster_paint_get_background (const hb_raster_paint_t *paint)
{
  return paint->background;
}

/**
 * hb_raster_paint_set_palette:
 * @paint: a paint context
 * @palette: palette index
 *
 * Selects which font palette is used when paint callbacks look up
 * indexed colors.  Default is palette 0.
 *
 * Since: 14.2.0
 **/
void
hb_raster_paint_set_palette (hb_raster_paint_t *paint,
			     unsigned           palette)
{
  paint->palette = palette;
}

/**
 * hb_raster_paint_get_palette:
 * @paint: a paint context
 *
 * Returns the palette index previously set on @paint, or 0 if none
 * was set.
 *
 * Return value: the palette index.
 *
 * Since: 14.2.0
 **/
unsigned
hb_raster_paint_get_palette (const hb_raster_paint_t *paint)
{
  return paint->palette;
}

/**
 * hb_raster_paint_clear_custom_palette_colors:
 * @paint: a paint context.
 *
 * Clears all custom palette color overrides previously set on @paint.
 *
 * After this call, palette lookups use the selected font palette without
 * custom override entries.
 *
 * Since: 13.0.0
 **/
void
hb_raster_paint_clear_custom_palette_colors (hb_raster_paint_t *paint)
{
  if (paint->custom_palette)
    hb_map_clear (paint->custom_palette);
}

/**
 * hb_raster_paint_set_custom_palette_color:
 * @paint: a paint context.
 * @color_index: color index to override.
 * @color: replacement color.
 *
 * Overrides one font palette color entry for subsequent paint operations.
 * Overrides are keyed by @color_index and persist on @paint until cleared
 * (or replaced for the same index).
 *
 * These overrides are consulted by paint operations that resolve CPAL
 * entries.
 *
 * Return value: `true` if the override was set; `false` on allocation failure.
 *
 * Since: 13.0.0
 **/
hb_bool_t
hb_raster_paint_set_custom_palette_color (hb_raster_paint_t *paint,
					  unsigned int       color_index,
					  hb_color_t         color)
{
  if (unlikely (!paint->custom_palette))
  {
    paint->custom_palette = hb_map_create ();
    if (unlikely (!paint->custom_palette))
      return false;
  }
  hb_map_set (paint->custom_palette, color_index, color);
  return hb_map_allocation_successful (paint->custom_palette);
}

/**
 * hb_raster_paint_get_funcs:
 * @paint: a rasterizer paint context.
 *
 * Fetches the #hb_paint_funcs_t that renders color glyphs into
 * @paint.  Pass @paint as the @paint_data argument when calling
 * hb_font_paint_glyph().
 *
 * Return value: (transfer none):
 * The rasterizer paint functions
 *
 * Since: 14.2.0
 **/
hb_paint_funcs_t *
hb_raster_paint_get_funcs (const hb_raster_paint_t *paint HB_UNUSED)
{
  return static_raster_paint_funcs.get_unconst ();
}

static hb_bool_t
hb_raster_paint_glyph_impl (hb_raster_paint_t *paint,
			    hb_font_t        *font,
			    hb_codepoint_t    glyph,
			    hb_bool_t         fallible)
{
  if (!paint->has_extents)
  {
    hb_glyph_extents_t ge;
    if (hb_font_get_glyph_extents (font, glyph, &ge))
      hb_raster_paint_set_glyph_extents (paint, &ge);
  }

  hb_paint_funcs_t *funcs = hb_raster_paint_get_funcs (paint);
  hb_paint_push_transform (funcs, paint,
			   paint->base_transform.xx, paint->base_transform.yx,
			   paint->base_transform.xy, paint->base_transform.yy,
			   paint->base_transform.x0, paint->base_transform.y0);

  hb_bool_t ret = true;
  if (fallible)
    ret = hb_font_paint_glyph_or_fail (font, glyph,
				       funcs, paint,
				       paint->palette, paint->foreground);
  else
    hb_font_paint_glyph (font, glyph,
			 funcs, paint,
			 paint->palette, paint->foreground);

  hb_paint_pop_transform (funcs, paint);
  return ret;
}

/**
 * hb_raster_paint_glyph_or_fail:
 * @paint: a paint context
 * @font: font to paint from
 * @glyph: glyph ID to paint
 *
 * Convenience to paint one color glyph.  Equivalent to:
 *
 * |[<!-- language="plain" -->
 * hb_font_paint_glyph_or_fail (font, glyph,
 *   hb_raster_paint_get_funcs (paint), paint,
 *   palette, foreground);
 * ]|
 *
 * Return value: `true` if painting succeeded, `false` otherwise.
 *
 * Since: 14.2.0
 **/
hb_bool_t
hb_raster_paint_glyph_or_fail (hb_raster_paint_t *paint,
			       hb_font_t        *font,
			       hb_codepoint_t    glyph)
{
  return hb_raster_paint_glyph_impl (paint, font, glyph, true);
}

/**
 * hb_raster_paint_glyph:
 * @paint: a paint context
 * @font: font to paint from
 * @glyph: glyph ID to paint
 *
 * Paints one glyph into @paint.  Unlike
 * hb_raster_paint_glyph_or_fail(), glyphs with no color paint data
 * fall back to a synthesized foreground-colored outline, so any
 * glyph with an outline or bitmap image produces output.
 *
 * Since: 14.2.0
 **/
void
hb_raster_paint_glyph (hb_raster_paint_t *paint,
		       hb_font_t        *font,
		       hb_codepoint_t    glyph)
{
  hb_raster_paint_glyph_impl (paint, font, glyph, false);
}

/**
 * hb_raster_paint_render:
 * @paint: a paint context
 *
 * Extracts the rendered image after hb_font_paint_glyph() has
 * completed.  The paint context's surface stack is consumed and
 * the result returned as a new #hb_raster_image_t. Output format is
 * always @HB_RASTER_FORMAT_BGRA32.
 *
 * Call hb_font_paint_glyph() before calling this function.
 * hb_raster_paint_set_extents() or hb_raster_paint_set_glyph_extents()
 * must be called before painting; otherwise this function returns `NULL`.
 * Internal drawing state is cleared here so the same object can
 * be reused without client-side clearing.
 *
 * Return value: (transfer full):
 * A rendered #hb_raster_image_t. Returns `NULL` if extents were not set
 * or if allocation/configuration fails. If extents were set but nothing
 * was painted, returns an empty image.
 *
 * Since: 13.0.0
 **/
hb_raster_image_t *
hb_raster_paint_render (hb_raster_paint_t *paint)
{
  /* Common per-exit state reset.  Runs on every return path,
   * including the early-return failure cases below. */
  HB_SCOPE_GUARD (hb_raster_paint_clear (paint));

  if (unlikely (!paint->has_extents))
    return nullptr;

  hb_raster_image_t *result;
  if (paint->surface_stack.length)
  {
    result = paint->surface_stack[0];
    /* Release any remaining group surfaces (shouldn't happen with
     * well-formed paint calls, but be safe). */
    for (unsigned i = 1; i < paint->surface_stack.length; i++)
      paint->release_surface (paint->surface_stack[i]);
    paint->surface_stack.clear ();
  }
  else
  {
    result = paint->acquire_surface ();
    if (unlikely (!result))
      return nullptr;
  }

  return result;
}

/**
 * hb_raster_paint_clear:
 * @paint: a paint context
 *
 * Discards accumulated paint output so @paint can be reused for
 * another render.  User configuration (base transform, scale
 * factors, foreground, custom palette colors) is preserved.  Call
 * hb_raster_paint_reset() to also reset user configuration to
 * defaults.
 *
 * Since: 14.2.0
 **/
void
hb_raster_paint_clear (hb_raster_paint_t *paint)
{
  paint->fixed_extents = {};
  paint->has_extents = false;
  paint->work_left = HB_RASTER_MAX_PAINT_WORK;
  paint->transform_stack.clear ();
  paint->release_all_clips ();
  for (auto *s : paint->surface_stack)
    paint->release_surface (s);
  paint->surface_stack.clear ();
  hb_raster_draw_reset (paint->clip_rdr);
}

/**
 * hb_raster_paint_reset:
 * @paint: a paint context
 *
 * Resets the paint context to its initial state, clearing all
 * configuration while preserving internal image caches.
 *
 * Since: 13.0.0
 **/
void
hb_raster_paint_reset (hb_raster_paint_t *paint)
{
  paint->base_transform = {1, 0, 0, 1, 0, 0};
  paint->x_scale_factor = 1.f;
  paint->y_scale_factor = 1.f;
  paint->foreground = HB_COLOR (0, 0, 0, 255);
  hb_raster_paint_clear_custom_palette_colors (paint);
  hb_raster_paint_clear (paint);
}

/**
 * hb_raster_paint_recycle_image:
 * @paint: a paint context
 * @image: a raster image to recycle
 *
 * Recycles @image for reuse by subsequent render calls.
 * The caller transfers ownership of @image to @paint.
 *
 * Since: 13.0.0
 **/
void
hb_raster_paint_recycle_image (hb_raster_paint_t  *paint,
			       hb_raster_image_t  *image)
{
  paint->release_surface (image);
}
