/*
 * 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
 */

#ifndef RASTER_OUTPUT_HH
#define RASTER_OUTPUT_HH

#include "options.hh"
#include "output-options.hh"
#include "view-options.hh"
#include "helper-extents-overlay.hh"
#include "helper-image-output.hh"
#include "hb-raster.h"
#include "hb-ot.h"

#include <math.h>
#include <vector>

static const char *raster_supported_formats[] = {
#ifdef HAVE_PNG
  "png",
#endif
  "ppm",
  "ansi",
  nullptr
};

#ifdef HAVE_PNG
#define HB_RASTER_DEFAULT_OUTPUT_FORMAT "png"
#else
#define HB_RASTER_DEFAULT_OUTPUT_FORMAT "ppm"
#endif

struct raster_output_t : output_options_t<true>, view_options_t
{
  static const bool repeat_shape = false;

  ~raster_output_t ()
  {
    hb_raster_draw_destroy (rdr);
    hb_raster_paint_destroy (pnt);
    hb_font_destroy (font);
  }

  void add_options (option_parser_t *parser)
  {
    parser->set_summary ("Rasterize text with given font.");
    parser->set_description ("Renders shaped text as a raster image.");
    output_options_t::add_options (parser, raster_supported_formats);
    view_options_t::add_options (parser);
  }

  template <typename app_t>
  void init (hb_buffer_t *buffer HB_UNUSED, const app_t *font_opts)
  {
    lines.clear ();
    direction = HB_DIRECTION_INVALID;

    font = hb_font_reference (font_opts->font);
    subpixel_bits = font_opts->subpixel_bits;

    hb_face_t *face = hb_font_get_face (font);
    bool font_has_color = hb_ot_color_has_paint (face) ||
			  hb_ot_color_has_layers (face) ||
			  hb_ot_color_has_png (face);
    /* --paint wins over --draw; otherwise auto-detect.
     * hb_raster_paint_glyph synthesizes paint from outlines
     * for mono fonts, so --paint works even without color. */
    has_color = force_paint ? true
	      : force_draw  ? false
	      : font_has_color;

    fg_color = HB_COLOR ((uint8_t) foreground_color.b,
			 (uint8_t) foreground_color.g,
			 (uint8_t) foreground_color.r,
			 (uint8_t) foreground_color.a);
    bg_r = (uint8_t) background_color.r;
    bg_g = (uint8_t) background_color.g;
    bg_b = (uint8_t) background_color.b;
    bg_a = (uint8_t) background_color.a;

    if (output_format &&
	0 != g_ascii_strcasecmp (output_format, "ansi") &&
	0 != g_ascii_strcasecmp (output_format, "ppm")
#ifdef HAVE_PNG
	     && 0 != g_ascii_strcasecmp (output_format, "png")
#endif
	     )
      fail (false, "Unknown output format `%s'; supported formats are: %s",
	    output_format,
	    g_strjoinv ("/", const_cast<char **> (raster_supported_formats)));

    rdr = hb_raster_draw_create_or_fail ();
    if (has_color)
    {
      pnt = hb_raster_paint_create_or_fail ();
      hb_raster_paint_set_foreground (pnt, fg_color);
      hb_raster_paint_clear_custom_palette_colors (pnt);
      if (custom_palette_entries)
      {
	for (unsigned i = 0; i < custom_palette_entries->len; i++)
	{
	  auto &entry =
	    g_array_index (custom_palette_entries,
			   typename view_options_t::custom_palette_entry_t, i);
	  hb_raster_paint_set_custom_palette_color (pnt, entry.index,
						    HB_COLOR ((uint8_t) entry.color.b,
							      (uint8_t) entry.color.g,
							      (uint8_t) entry.color.r,
							      (uint8_t) entry.color.a));
	}
      }
    }
  }

  void new_line () { lines.emplace_back (); }

  void consume_text (hb_buffer_t  * HB_UNUSED,
		     const char   * HB_UNUSED,
		     unsigned int   HB_UNUSED,
		     hb_bool_t      HB_UNUSED) {}

  void error (const char *message)
  { g_printerr ("%s: %s\n", g_get_prgname (), message); }

  void consume_glyphs (hb_buffer_t  *buffer,
		       const char   * HB_UNUSED,
		       unsigned int   HB_UNUSED,
		       hb_bool_t      HB_UNUSED)
  {
    if (lines.empty ()) lines.emplace_back ();
    line_t &line = lines.back ();

    hb_direction_t line_dir = hb_buffer_get_direction (buffer);
    if (line_dir == HB_DIRECTION_INVALID)
      line_dir = HB_DIRECTION_LTR;
    if (direction == HB_DIRECTION_INVALID)
      direction = line_dir;

    unsigned count;
    const hb_glyph_info_t     *infos     = hb_buffer_get_glyph_infos     (buffer, &count);
    const hb_glyph_position_t *positions = hb_buffer_get_glyph_positions (buffer, &count);

    hb_position_t pen_x = 0, pen_y = 0;
    line.glyphs.reserve (line.glyphs.size () + count);
    for (unsigned i = 0; i < count; i++)
    {
      line.glyphs.push_back ({
	infos[i].codepoint,
	(float) (pen_x + positions[i].x_offset),
	(float) (pen_y + positions[i].y_offset),
      });
      pen_x += positions[i].x_advance;
      pen_y += positions[i].y_advance;
    }
    line.advance_x = (float) pen_x;
    line.advance_y = (float) pen_y;
  }

  template <typename app_t>
  void finish (hb_buffer_t *buffer HB_UNUSED, const app_t *app)
  {
    unsigned int num_iterations = app->num_iterations ? app->num_iterations : 1;

    /* pixels per font unit */
    float sx = scalbnf (1.f, -(int) subpixel_bits);
    float sy = scalbnf (1.f, -(int) subpixel_bits);
    float scale_factor = scalbnf (1.f, (int) subpixel_bits);

    hb_raster_draw_set_scale_factor (rdr, scale_factor, scale_factor);
    if (pnt)
      hb_raster_paint_set_scale_factor (pnt, scale_factor, scale_factor);

    /* line step in font units */
    hb_direction_t dir = direction;
    if (dir == HB_DIRECTION_INVALID) dir = HB_DIRECTION_LTR;
    bool vertical = HB_DIRECTION_IS_VERTICAL (dir);

    float step = 0.f;
    if (have_font_extents)
      step = fabsf (scalbnf ((float) (font_extents.ascent + font_extents.descent + font_extents.line_gap),
			     (int) subpixel_bits));
    else
    {
      hb_font_extents_t extents = {};
      hb_font_get_extents_for_direction (font, dir, &extents);
      step = fabsf ((float) (extents.ascender - extents.descender + extents.line_gap));
    }
    step += scalbnf ((float) line_space, (int) subpixel_bits);
    if (step < 0.f)
      step = 0.f;
    if (!(step > 0.f))
    {
      int x_scale = 0, y_scale = 0;
      hb_font_get_scale (font, &x_scale, &y_scale);
      int axis_scale = vertical ? x_scale : y_scale;
      step = axis_scale ? fabsf ((float) axis_scale) : 1.f;
    }

    if (has_color && pnt)
    {
      finish_paint (sx, sy, step, vertical, num_iterations);
    }
    else if (foreground_use_palette && foreground_palette && foreground_palette->len > 1)
    {
      finish_draw_palette (sx, sy, step, vertical, num_iterations);
    }
    else
    {
      hb_raster_extents_t ext;
      if (!compute_extents (sx, sy, step, vertical, &ext))
      {
	cleanup ();
	return;
      }

      for (unsigned int iter = 0; iter < num_iterations; iter++)
      {
	hb_raster_draw_set_extents (rdr, &ext);
	for (unsigned li = 0; li < lines.size (); li++)
	{
	  float off_x = vertical ? -(step * (float) li) : 0.f;
	  float off_y = vertical ?  0.f                 : -(step * (float) li);

	  for (const auto &g : lines[li].glyphs)
	  {
	    float pen_x = g.x + off_x;
	    float pen_y = g.y + off_y;
	    hb_raster_draw_set_transform (rdr, 1.f, 0.f, 0.f, 1.f, pen_x, pen_y);
	    hb_raster_draw_glyph (rdr, font, g.gid);
	    if (show_extents)
	    {
	      hb_glyph_extents_t ge;
	      if (hb_font_get_glyph_extents (font, g.gid, &ge))
		util_emit_extents_overlay_into_draw (
		  hb_raster_draw_get_funcs (rdr), rdr,
		  &ge, pen_x, pen_y, extents_stroke_width ());
	    }
	  }
	}

	hb_raster_image_t *img = hb_raster_draw_render (rdr);
	if (img)
	{
	  if (iter + 1 == num_iterations)
	    write_a8_image (img, sx, sy, step, vertical, ext);
	  hb_raster_draw_recycle_image (rdr, img);
	}
      }
    }

    cleanup ();
  }

  private:

  struct glyph_instance_t { hb_codepoint_t gid; float x, y; };
  struct line_t
  {
    float advance_x = 0.f, advance_y = 0.f;
    std::vector<glyph_instance_t> glyphs;
  };

  float extents_stroke_width () const
  {
    return scalbnf (1.f, (int) subpixel_bits);
  }

  void finish_paint (float sx, float sy, float step, bool vertical, unsigned int num_iterations)
  {
    hb_raster_extents_t ext;
    if (!compute_extents (sx, sy, step, vertical, &ext))
      return;
    unsigned w = ext.width;
    unsigned h = ext.height;
    unsigned stride = w * 4;
    ext.stride = stride;
    hb_raster_image_t *out_img = hb_raster_image_create_or_fail ();
    if (!out_img) return;
    if (!hb_raster_image_configure (out_img, HB_RASTER_FORMAT_BGRA32, &ext))
    {
      hb_raster_image_destroy (out_img);
      return;
    }
    uint8_t *out_buf = const_cast<uint8_t *> (hb_raster_image_get_buffer (out_img));
    if (!out_buf)
    {
      hb_raster_image_destroy (out_img);
      return;
    }

    for (unsigned int iter = 0; iter < num_iterations; iter++)
    {
      hb_raster_image_clear (out_img);
      /* Second pass: paint each glyph */
      unsigned glyph_index = 0;
      for (unsigned li = 0; li < lines.size (); li++)
      {
	float off_x = vertical ? -(step * (float) li) : 0.f;
	float off_y = vertical ?  0.f                 : -(step * (float) li);

	for (const auto &g : lines[li].glyphs)
	{
	  hb_color_t glyph_fg = foreground_for_glyph (glyph_index++);
	  float pen_x = g.x + off_x;
	  float pen_y = g.y + off_y;

	  hb_raster_paint_set_transform (pnt, 1.f, 0.f, 0.f, 1.f, pen_x, pen_y);
	  hb_raster_paint_set_extents (pnt, &ext);
	  hb_raster_paint_set_palette (pnt, palette);
	  hb_raster_paint_set_foreground (pnt, glyph_fg);

	  hb_raster_paint_glyph (pnt, font, g.gid);

	  if (show_extents)
	  {
	    hb_glyph_extents_t ge;
	    if (hb_font_get_glyph_extents (font, g.gid, &ge))
	      util_emit_extents_overlay_into_paint (
		hb_raster_paint_get_funcs (pnt), pnt,
		&ge, pen_x, pen_y, extents_stroke_width ());
	  }

	  hb_raster_image_t *img = hb_raster_paint_render (pnt);
	  if (img)
	  {
	    /* Composite onto output buffer (SRC_OVER) */
	    const uint8_t *src = hb_raster_image_get_buffer (img);
	    hb_raster_extents_t img_ext;
	    hb_raster_image_get_extents (img, &img_ext);

	    for (unsigned y = 0; y < h; y++)
	      for (unsigned x = 0; x < w; x++)
	      {
		uint32_t s;
		hb_memcpy (&s, src + y * img_ext.stride + x * 4, 4);
		if (!s) continue;
		uint8_t sa = (uint8_t) (s >> 24);
		uint32_t d;
		hb_memcpy (&d, out_buf + y * stride + x * 4, 4);
		if (sa == 255) { d = s; }
		else
		{
		  unsigned inv_sa = 255 - sa;
		  uint8_t rb = (uint8_t) (((d & 0xFF) * inv_sa + 128 + (((d & 0xFF) * inv_sa + 128) >> 8)) >> 8) + (uint8_t) (s & 0xFF);
		  uint8_t rg = (uint8_t) ((((d >> 8) & 0xFF) * inv_sa + 128 + ((((d >> 8) & 0xFF) * inv_sa + 128) >> 8)) >> 8) + (uint8_t) ((s >> 8) & 0xFF);
		  uint8_t rr = (uint8_t) ((((d >> 16) & 0xFF) * inv_sa + 128 + ((((d >> 16) & 0xFF) * inv_sa + 128) >> 8)) >> 8) + (uint8_t) ((s >> 16) & 0xFF);
		  uint8_t ra = (uint8_t) ((((d >> 24) & 0xFF) * inv_sa + 128 + ((((d >> 24) & 0xFF) * inv_sa + 128) >> 8)) >> 8) + sa;
		  d = (uint32_t) rb | ((uint32_t) rg << 8) | ((uint32_t) rr << 16) | ((uint32_t) ra << 24);
		}
		hb_memcpy (out_buf + y * stride + x * 4, &d, 4);
	      }

	    hb_raster_paint_recycle_image (pnt, img);
	  }
	}
      }

      if (iter + 1 == num_iterations)
	write_bgra_image (out_img);
    }
    hb_raster_image_destroy (out_img);
  }

  void finish_draw_palette (float sx, float sy, float step, bool vertical, unsigned int num_iterations)
  {
    hb_raster_extents_t ext;
    if (!compute_extents (sx, sy, step, vertical, &ext))
      return;
    unsigned w = ext.width;
    unsigned h = ext.height;
    unsigned stride = w * 4;
    ext.stride = stride;

    hb_raster_image_t *out_img = hb_raster_image_create_or_fail ();
    if (!out_img) return;
    if (!hb_raster_image_configure (out_img, HB_RASTER_FORMAT_BGRA32, &ext))
    {
      hb_raster_image_destroy (out_img);
      return;
    }
    uint8_t *out_buf = const_cast<uint8_t *> (hb_raster_image_get_buffer (out_img));
    if (!out_buf)
    {
      hb_raster_image_destroy (out_img);
      return;
    }

    for (unsigned int iter = 0; iter < num_iterations; iter++)
    {
      hb_raster_image_clear (out_img);
      unsigned glyph_index = 0;
      for (unsigned li = 0; li < lines.size (); li++)
      {
	float off_x = vertical ? -(step * (float) li) : 0.f;
	float off_y = vertical ?  0.f                 : -(step * (float) li);

	for (const auto &g : lines[li].glyphs)
	{
	  hb_raster_draw_reset (rdr);
	  hb_raster_draw_set_scale_factor (rdr, scalbnf (1.f, (int) subpixel_bits),
					       scalbnf (1.f, (int) subpixel_bits));
	  hb_raster_draw_set_extents (rdr, &ext);
	  hb_raster_draw_set_transform (rdr, 1.f, 0.f, 0.f, 1.f, g.x + off_x, g.y + off_y);
	  hb_raster_draw_glyph (rdr, font, g.gid);
	  if (show_extents)
	  {
	    hb_glyph_extents_t ge;
	    if (hb_font_get_glyph_extents (font, g.gid, &ge))
	      util_emit_extents_overlay_into_draw (
		hb_raster_draw_get_funcs (rdr), rdr,
		&ge, g.x + off_x, g.y + off_y, extents_stroke_width ());
	  }

	  hb_raster_image_t *img = hb_raster_draw_render (rdr);
	  if (!img) { glyph_index++; continue; }

	  hb_color_t glyph_fg = foreground_for_glyph (glyph_index++);
	  uint8_t fr = hb_color_get_red (glyph_fg);
	  uint8_t fg = hb_color_get_green (glyph_fg);
	  uint8_t fb = hb_color_get_blue (glyph_fg);
	  uint8_t fa = hb_color_get_alpha (glyph_fg);

	  const uint8_t *src = hb_raster_image_get_buffer (img);
	  hb_raster_extents_t img_ext;
	  hb_raster_image_get_extents (img, &img_ext);
	  for (unsigned y = 0; y < h; y++)
	    for (unsigned x = 0; x < w; x++)
	    {
	      uint8_t cov = src[y * img_ext.stride + x];
	      if (!cov) continue;

	      uint8_t sa = (uint8_t) ((cov * fa + 127) / 255);
	      uint8_t sb = (uint8_t) ((fb * sa + 127) / 255);
	      uint8_t sg = (uint8_t) ((fg * sa + 127) / 255);
	      uint8_t sr = (uint8_t) ((fr * sa + 127) / 255);

	      uint32_t s = (uint32_t) sb |
			   ((uint32_t) sg << 8) |
			   ((uint32_t) sr << 16) |
			   ((uint32_t) sa << 24);

	      uint32_t d;
	      hb_memcpy (&d, out_buf + y * stride + x * 4, 4);
	      if (sa == 255) d = s;
	      else
	      {
		unsigned inv_sa = 255 - sa;
		uint8_t rb = (uint8_t) (((d & 0xFF) * inv_sa + 128 + (((d & 0xFF) * inv_sa + 128) >> 8)) >> 8) + sb;
		uint8_t rg = (uint8_t) ((((d >> 8) & 0xFF) * inv_sa + 128 + ((((d >> 8) & 0xFF) * inv_sa + 128) >> 8)) >> 8) + sg;
		uint8_t rr = (uint8_t) ((((d >> 16) & 0xFF) * inv_sa + 128 + ((((d >> 16) & 0xFF) * inv_sa + 128) >> 8)) >> 8) + sr;
		uint8_t ra = (uint8_t) ((((d >> 24) & 0xFF) * inv_sa + 128 + ((((d >> 24) & 0xFF) * inv_sa + 128) >> 8)) >> 8) + sa;
		d = (uint32_t) rb | ((uint32_t) rg << 8) | ((uint32_t) rr << 16) | ((uint32_t) ra << 24);
	      }
	      hb_memcpy (out_buf + y * stride + x * 4, &d, 4);
	    }

	  hb_raster_draw_recycle_image (rdr, img);
	}
      }

      if (iter + 1 == num_iterations)
	write_bgra_image (out_img);
    }

    hb_raster_draw_reset (rdr);
    hb_raster_image_destroy (out_img);
  }

  void cleanup ()
  {
    hb_raster_draw_destroy (rdr);  rdr = nullptr;
    hb_raster_paint_destroy (pnt); pnt = nullptr;
    hb_font_destroy (font);        font = nullptr;
    lines.clear ();
    direction = HB_DIRECTION_INVALID;
  }

  bool compute_extents (float sx, float sy, float step, bool vertical, hb_raster_extents_t *ext)
  {
    /* Compute bounding box in pixel space from logical and/or ink extents. */
    const bool include_logical = include_logical_extents ();
    const bool include_ink = include_ink_extents ();

    float pmin_x = 1e30f, pmin_y = 1e30f;
    float pmax_x = -1e30f, pmax_y = -1e30f;
    bool have_extents = false;

    if (include_logical)
    {
      float asc = 0.f, desc = 0.f;
      if (have_font_extents)
      {
	asc  = scalbnf ((float) font_extents.ascent, (int) subpixel_bits);
	desc = -scalbnf ((float) font_extents.descent, (int) subpixel_bits);
      }
      else
      {
	hb_font_extents_t fext = {};
	hb_font_get_extents_for_direction (font, direction, &fext);
	asc = (float) fext.ascender;
	desc = (float) fext.descender;
      }

      for (unsigned li = 0; li < lines.size (); li++)
      {
	float off_x = vertical ? -(step * (float) li) : 0.f;
	float off_y = vertical ?  0.f                 : -(step * (float) li);

	float ax0 = off_x;
	float ay0 = off_y;
	float ax1 = off_x + lines[li].advance_x;
	float ay1 = off_y + lines[li].advance_y;

	float lx0, ly0, lx1, ly1;
	if (vertical)
	{
	  lx0 = off_x + desc;
	  lx1 = off_x + asc;
	  ly0 = hb_min (ay0, ay1);
	  ly1 = hb_max (ay0, ay1);
	}
	else
	{
	  lx0 = hb_min (ax0, ax1);
	  lx1 = hb_max (ax0, ax1);
	  ly0 = off_y + desc;
	  ly1 = off_y + asc;
	}

	lx0 *= sx; lx1 *= sx;
	ly0 *= sy; ly1 *= sy;

	pmin_x = hb_min (pmin_x, hb_min (lx0, lx1));
	pmin_y = hb_min (pmin_y, hb_min (ly0, ly1));
	pmax_x = hb_max (pmax_x, hb_max (lx0, lx1));
	pmax_y = hb_max (pmax_y, hb_max (ly0, ly1));
	have_extents = true;
      }
    }

    if (include_ink)
    {
    for (unsigned li = 0; li < lines.size (); li++)
    {
      float off_x = vertical ? -(step * (float) li) : 0.f;
      float off_y = vertical ?  0.f                 : -(step * (float) li);

      for (const auto &g : lines[li].glyphs)
      {
	hb_glyph_extents_t gext;
	if (!hb_font_get_glyph_extents (font, g.gid, &gext))
	  continue;

	float gx = (g.x + off_x) * sx;
	float gy = (g.y + off_y) * sy;

	float x0 = gx + gext.x_bearing * sx;
	float y0 = gy + (gext.y_bearing + gext.height) * sy;
	float x1 = gx + (gext.x_bearing + gext.width) * sx;
	float y1 = gy + gext.y_bearing * sy;

	pmin_x = hb_min (pmin_x, hb_min (x0, x1));
	pmin_y = hb_min (pmin_y, hb_min (y0, y1));
	pmax_x = hb_max (pmax_x, hb_max (x0, x1));
	pmax_y = hb_max (pmax_y, hb_max (y0, y1));
	have_extents = true;
      }
    }
    }

    if (!have_extents || pmin_x >= pmax_x || pmin_y >= pmax_y)
      return false;

    int ix0 = (int) floorf (pmin_x) - (int) floor (margin.l);
    int iy0 = (int) floorf (pmin_y) - (int) floor (margin.b);
    int ix1 = (int) ceilf  (pmax_x) + (int) ceil  (margin.r);
    int iy1 = (int) ceilf  (pmax_y) + (int) ceil  (margin.t);
    if (ix1 <= ix0 || iy1 <= iy0)
      return false;

    ext->x_origin = ix0;
    ext->y_origin = iy0;
    ext->width = (unsigned) (ix1 - ix0);
    ext->height = (unsigned) (iy1 - iy0);
    ext->stride = 0;
    return true;
  }

  hb_color_t foreground_for_glyph (unsigned glyph_index) const
  {
    if (foreground_use_palette && foreground_palette && foreground_palette->len)
    {
      auto &c = g_array_index (foreground_palette, rgba_color_t,
			       glyph_index % foreground_palette->len);
      return HB_COLOR ((uint8_t) c.b, (uint8_t) c.g, (uint8_t) c.r, (uint8_t) c.a);
    }
    return fg_color;
  }

  static uint8_t mul8 (uint8_t a, uint8_t b)
  {
    unsigned t = (unsigned) a * b + 128;
    return (uint8_t) ((t + (t >> 8)) >> 8);
  }

  static uint32_t premul_pixel (uint8_t r, uint8_t g, uint8_t b, uint8_t a)
  {
    return (uint32_t) mul8 (b, a)
	 | ((uint32_t) mul8 (g, a) << 8)
	 | ((uint32_t) mul8 (r, a) << 16)
	 | ((uint32_t) a << 24);
  }

  static uint32_t src_over_premul (uint32_t src, uint32_t dst)
  {
    uint8_t sa = (uint8_t) (src >> 24);
    if (sa == 255) return src;
    if (!sa) return dst;

    unsigned inv_sa = 255 - sa;
    uint8_t b = (uint8_t) (src & 0xFF) +
		mul8 ((uint8_t) (dst & 0xFF), inv_sa);
    uint8_t g = (uint8_t) ((src >> 8) & 0xFF) +
		mul8 ((uint8_t) ((dst >> 8) & 0xFF), inv_sa);
    uint8_t r = (uint8_t) ((src >> 16) & 0xFF) +
		mul8 ((uint8_t) ((dst >> 16) & 0xFF), inv_sa);
    uint8_t a = sa + mul8 ((uint8_t) (dst >> 24), inv_sa);
    return (uint32_t) b | ((uint32_t) g << 8) | ((uint32_t) r << 16) | ((uint32_t) a << 24);
  }

  uint32_t background_premul_pixel () const
  {
    return premul_pixel (bg_r, bg_g, bg_b, bg_a);
  }

  hb_raster_image_t *composite_background (hb_raster_image_t *img)
  {
    hb_raster_extents_t ext;
    hb_raster_image_get_extents (img, &ext);

    hb_raster_image_t *out = hb_raster_image_create_or_fail ();
    if (!out) return nullptr;
    if (!hb_raster_image_configure (out, HB_RASTER_FORMAT_BGRA32, &ext))
    {
      hb_raster_image_destroy (out);
      return nullptr;
    }

    const uint8_t *src = hb_raster_image_get_buffer (img);
    uint8_t *dst = const_cast<uint8_t *> (hb_raster_image_get_buffer (out));
    uint32_t bg = background_premul_pixel ();
    for (unsigned y = 0; y < ext.height; y++)
      for (unsigned x = 0; x < ext.width; x++)
      {
	uint32_t s;
	hb_memcpy (&s, src + y * ext.stride + x * 4, 4);
	uint32_t d = src_over_premul (s, bg);
	hb_memcpy (dst + y * ext.stride + x * 4, &d, 4);
      }

    return out;
  }

  const char *get_output_format (helper_image_protocol_t *protocol)
  {
    if (output_format)
    {
      *protocol = helper_image_protocol_t::NONE;
      return output_format;
    }

#ifdef HAVE_PNG
    return helper_image_get_implicit_output_format (out_fp,
						    HB_RASTER_DEFAULT_OUTPUT_FORMAT,
						    protocol);
#else
    return helper_image_get_implicit_output_format (out_fp,
						    HB_RASTER_DEFAULT_OUTPUT_FORMAT,
						    protocol);
#endif
  }

  void write_blob (hb_blob_t *blob)
  {
    if (!blob) return;
    unsigned len = 0;
    const char *data = hb_blob_get_data (blob, &len);
    if (data && len)
      helper_image_stdio_write_func (out_fp, (const unsigned char *) data, len);
    hb_blob_destroy (blob);
  }

  void write_png (hb_raster_image_t *img HB_UNUSED, helper_image_protocol_t protocol HB_UNUSED)
  {
#ifdef HAVE_PNG
    hb_raster_image_t *png_img = img;
    if (bg_a)
    {
      png_img = composite_background (img);
      if (!png_img) return;
    }

    hb_blob_t *blob = hb_raster_image_serialize_to_png_or_fail (png_img);
    if (!blob)
    {
      if (png_img != img)
	hb_raster_image_destroy (png_img);
      return;
    }

    unsigned len = 0;
    const char *data = hb_blob_get_data (blob, &len);
    if (data && len)
      helper_image_write_png_data ((const unsigned char *) data, len,
				   helper_image_stdio_write_func, out_fp, protocol);
    hb_blob_destroy (blob);
    if (png_img != img)
      hb_raster_image_destroy (png_img);
#endif
  }

  void write_ansi (hb_raster_image_t *img)
  {
    hb_raster_extents_t ext;
    hb_raster_image_get_extents (img, &ext);
    const uint8_t *src = hb_raster_image_get_buffer (img);
    if (!src || !ext.width || !ext.height) return;

    std::vector<uint32_t> rgb (ext.width * ext.height);
    for (unsigned y = 0; y < ext.height; y++)
    {
      const uint8_t *row = src + (size_t) (ext.height - 1 - y) * ext.stride;
      for (unsigned x = 0; x < ext.width; x++)
      {
	uint32_t px;
	hb_memcpy (&px, row + x * 4, 4);
	uint8_t b = (uint8_t) (px & 0xFF);
	uint8_t g = (uint8_t) ((px >> 8) & 0xFF);
	uint8_t r = (uint8_t) ((px >> 16) & 0xFF);
	uint8_t a = (uint8_t) (px >> 24);
	unsigned inv_a = 255 - a;
	r = (uint8_t) (r + ((bg_r * inv_a + 128 + ((bg_r * inv_a + 128) >> 8)) >> 8));
	g = (uint8_t) (g + ((bg_g * inv_a + 128 + ((bg_g * inv_a + 128) >> 8)) >> 8));
	b = (uint8_t) (b + ((bg_b * inv_a + 128 + ((bg_b * inv_a + 128) >> 8)) >> 8));
	rgb[y * ext.width + x] = (uint32_t) b | ((uint32_t) g << 8) | ((uint32_t) r << 16);
      }
    }

    helper_image_write_to_ansi_stream_rgb24 (rgb.data (), ext.width, ext.height, ext.width * 4,
					     helper_image_stdio_write_func, out_fp);
  }

  void write_bgra_image (hb_raster_image_t *img)
  {
    hb_raster_extents_t ext;
    hb_raster_image_get_extents (img, &ext);
    const uint8_t *buf = hb_raster_image_get_buffer (img);

    helper_image_protocol_t protocol;
    const char *format = get_output_format (&protocol);

    if (0 == g_ascii_strcasecmp (format, "ansi"))
      write_ansi (img);
    else if (0 == g_ascii_strcasecmp (format, "png"))
      write_png (img, protocol);
    else
      write_ppm (buf, ext.width, ext.height, ext.stride);
  }

  void write_a8_image (hb_raster_image_t *img,
		       float sx, float sy, float step, bool vertical,
		       const hb_raster_extents_t &fallback_ext)
  {
    hb_raster_extents_t ext = fallback_ext;
    hb_raster_image_get_extents (img, &ext);
    if (!ext.width || !ext.height) return;

    uint8_t fg_r = hb_color_get_red (fg_color);
    uint8_t fg_g = hb_color_get_green (fg_color);
    uint8_t fg_b = hb_color_get_blue (fg_color);
    uint8_t fg_a = hb_color_get_alpha (fg_color);
    uint32_t bg_px = background_premul_pixel ();

    const uint8_t *src = hb_raster_image_get_buffer (img);
    hb_raster_image_t *bgra = hb_raster_image_create_or_fail ();
    if (!bgra) return;
    hb_raster_extents_t bgra_ext = ext;
    bgra_ext.stride = 0;
    if (!hb_raster_image_configure (bgra, HB_RASTER_FORMAT_BGRA32, &bgra_ext))
    {
      hb_raster_image_destroy (bgra);
      return;
    }

    hb_raster_extents_t bgra_actual_ext;
    hb_raster_image_get_extents (bgra, &bgra_actual_ext);
    uint8_t *dst = const_cast<uint8_t *> (hb_raster_image_get_buffer (bgra));
    for (unsigned y = 0; y < ext.height; y++)
      for (unsigned x = 0; x < ext.width; x++)
      {
	uint8_t a = src[y * ext.stride + x];
	uint8_t sa = mul8 (a, fg_a);
	uint32_t fg_px = premul_pixel (fg_r, fg_g, fg_b, sa);
	uint32_t px = src_over_premul (fg_px, bg_px);
	hb_memcpy (dst + y * bgra_actual_ext.stride + x * 4, &px, 4);
      }

    write_bgra_image (bgra);
    hb_raster_image_destroy (bgra);
  }

  /* Write a PPM file from BGRA32 buffer; Y-flipped, composited over white. */
  void write_ppm (const uint8_t *buf, unsigned w, unsigned h, unsigned stride)
  {
    if (!w || !h) return;

    fprintf (out_fp, "P6\n%u %u\n255\n", w, h);
    std::vector<uint8_t> row_buf (w * 3);
    for (unsigned row = 0; row < h; row++)
    {
      const uint8_t *src = buf + (h - 1 - row) * stride;
      for (unsigned x = 0; x < w; x++)
      {
	uint32_t px;
	hb_memcpy (&px, src + x * 4, 4);
	uint8_t b = (uint8_t) (px & 0xFF);
	uint8_t g = (uint8_t) ((px >> 8) & 0xFF);
	uint8_t r = (uint8_t) ((px >> 16) & 0xFF);
	uint8_t a = (uint8_t) (px >> 24);
	/* Composite over background color */
	unsigned inv_a = 255 - a;
	row_buf[x * 3 + 0] = (uint8_t) (r + ((bg_r * inv_a + 128 + ((bg_r * inv_a + 128) >> 8)) >> 8));
	row_buf[x * 3 + 1] = (uint8_t) (g + ((bg_g * inv_a + 128 + ((bg_g * inv_a + 128) >> 8)) >> 8));
	row_buf[x * 3 + 2] = (uint8_t) (b + ((bg_b * inv_a + 128 + ((bg_b * inv_a + 128) >> 8)) >> 8));
      }
      fwrite (row_buf.data (), 1, w * 3, out_fp);
    }
  }

  hb_color_t         fg_color  = HB_COLOR (0, 0, 0, 255);
  uint8_t            bg_r = 255, bg_g = 255, bg_b = 255, bg_a = 255;
  hb_raster_draw_t  *rdr       = nullptr;
  hb_raster_paint_t *pnt       = nullptr;
  hb_font_t         *font      = nullptr;
  unsigned           subpixel_bits = 0;
  bool               has_color     = false;
  hb_direction_t     direction     = HB_DIRECTION_INVALID;
  std::vector<line_t> lines;
};

#undef HB_RASTER_DEFAULT_OUTPUT_FORMAT

#endif
