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

/* hb-ft-raster: like hb-raster but uses FreeType's rasterizer for
   pixel-level comparison.  Same pipeline (shaping, glyph positioning),
   but calls FT_Outline_Render instead of hb_raster_draw_render. */

#include "batch.hh"
#include "font-options.hh"
#include "main-font-text.hh"
#include "output-options.hh"
#include "shape-consumer.hh"
#include "text-options.hh"

#include <hb.h>
#include <hb-ft.h>

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_OUTLINE_H
#include FT_BITMAP_H
#include FT_BBOX_H
#include FT_MULTIPLE_MASTERS_H

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


const unsigned DEFAULT_FONT_SIZE = FONT_SIZE_UPEM;
const unsigned SUBPIXEL_BITS = 6;

struct ft_raster_output_t : output_options_t<true>
{
  static const bool repeat_shape = false;

  ~ft_raster_output_t ()
  {
    hb_font_destroy (font);
    if (ft_face) FT_Done_Face (ft_face);
    if (ft_lib)  FT_Done_FreeType (ft_lib);
  }

  void add_options (option_parser_t *parser)
  {
    parser->set_summary ("Rasterize text with FreeType rasterizer.");
    parser->set_description ("Renders shaped text as a PPM raster image using FreeType.");
    output_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;

    /* Set up FreeType */
    FT_Init_FreeType (&ft_lib);

    /* Get the font file blob from hb_face */
    hb_face_t *face = hb_font_get_face (font);
    hb_blob_t *blob = hb_face_reference_blob (face);
    unsigned blob_len;
    const char *blob_data = hb_blob_get_data (blob, &blob_len);

    FT_New_Memory_Face (ft_lib, (const FT_Byte *) blob_data, blob_len,
			hb_face_get_index (face), &ft_face);
    hb_blob_destroy (blob);

    int x_scale = 0, y_scale = 0;
    hb_font_get_scale (font, &x_scale, &y_scale);
    unsigned ft_x_scale = x_scale ? (unsigned) fabsf ((float) x_scale) : 1;
    unsigned ft_y_scale = y_scale ? (unsigned) fabsf ((float) y_scale) : 1;

    /* Match FT outlines to the shaping font scale. */
    FT_Set_Char_Size (ft_face,
		      (FT_F26Dot6) ft_x_scale * 64,
		      (FT_F26Dot6) ft_y_scale * 64,
		      72, 72);

#ifndef HB_NO_VAR
    unsigned coords_len;
    const float *coords = hb_font_get_var_coords_design (font, &coords_len);
    if (coords_len)
    {
      FT_Fixed *ft_coords = (FT_Fixed *) malloc (coords_len * sizeof (FT_Fixed));
      for (unsigned i = 0; i < coords_len; i++)
	ft_coords[i] = (FT_Fixed) (coords[i] * 65536.f);
      FT_Set_Var_Design_Coordinates (ft_face, coords_len, ft_coords);
      free (ft_coords);
    }
#endif
  }

  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);

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

    hb_font_extents_t extents = {};
    hb_font_get_extents_for_direction (font, dir, &extents);
    float step = fabsf ((float) (extents.ascender - extents.descender + extents.line_gap));
    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;
    }

    /* First pass: compute bounding box of all glyphs in pixel space */
    float img_xmin = 1e30f, img_ymin = 1e30f;
    float img_xmax = -1e30f, img_ymax = -1e30f;

    struct glyph_render_t {
      hb_codepoint_t gid;
      float pen_x, pen_y;
    };
    std::vector<glyph_render_t> renders;

    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) * sx;
	float pen_y = (g.y + off_y) * sy;

	FT_Load_Glyph (ft_face, g.gid, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP);
	FT_Outline *outline = &ft_face->glyph->outline;

	FT_BBox bbox;
	FT_Outline_Get_BBox (outline, &bbox);

	/* bbox is in 26.6 font-space; transform to pixel space */
	float gx0 = pen_x + (float) bbox.xMin * sx / 64.f;
	float gy0 = pen_y + (float) bbox.yMin * sy / 64.f;
	float gx1 = pen_x + (float) bbox.xMax * sx / 64.f;
	float gy1 = pen_y + (float) bbox.yMax * sy / 64.f;

	if (gx0 < img_xmin) img_xmin = gx0;
	if (gy0 < img_ymin) img_ymin = gy0;
	if (gx1 > img_xmax) img_xmax = gx1;
	if (gy1 > img_ymax) img_ymax = gy1;

	renders.push_back ({g.gid, pen_x, pen_y});
      }
    }

    if (renders.empty () || img_xmin >= img_xmax || img_ymin >= img_ymax)
    {
      cleanup ();
      return;
    }

    int x0 = (int) floorf (img_xmin);
    int y0 = (int) floorf (img_ymin);
    int x1 = (int) ceilf (img_xmax);
    int y1 = (int) ceilf (img_ymax);
    unsigned width  = (unsigned) (x1 - x0);
    unsigned height = (unsigned) (y1 - y0);
    unsigned stride = (width + 3u) & ~3u;

    std::vector<uint8_t> pixels (stride * height, 0);
    for (unsigned int iter = 0; iter < num_iterations; iter++)
    {
      std::fill (pixels.begin (), pixels.end (), 0);

      /* Second pass: rasterize each glyph with FreeType */
      for (const auto &r : renders)
      {
	FT_Load_Glyph (ft_face, r.gid, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP);
	FT_Outline *outline = &ft_face->glyph->outline;

	/* We need to transform the outline from font-space 26.6 to pixel space,
	   then translate so that pixel (x0, y0) maps to bitmap (0, 0). */
	FT_Matrix matrix;
	matrix.xx = (FT_Fixed) (sx * 65536.f);
	matrix.xy = 0;
	matrix.yx = 0;
	matrix.yy = (FT_Fixed) (sy * 65536.f);

	/* pen position in 26.6 pixel space */
	FT_Vector delta;
	delta.x = (FT_Pos) roundf ((r.pen_x - x0) * 64.f);
	delta.y = (FT_Pos) roundf ((r.pen_y - y0) * 64.f);

	FT_Outline_Transform (outline, &matrix);
	FT_Outline_Translate (outline, delta.x, delta.y);

	FT_Bitmap bitmap;
	FT_Bitmap_Init (&bitmap);
	bitmap.rows       = height;
	bitmap.width      = width;
	bitmap.pitch      = (int) stride;
	bitmap.buffer     = pixels.data ();
	bitmap.pixel_mode = FT_PIXEL_MODE_GRAY;
	bitmap.num_grays  = 256;

	FT_Raster_Params params = {};
	params.target = &bitmap;
	params.flags  = FT_RASTER_FLAG_AA;
	FT_Outline_Render (ft_lib, outline, &params);
      }

      if (iter + 1 == num_iterations)
      {
	/* Write PPM (black on white), y-flipped so text reads correctly. */
	fprintf (out_fp, "P6\n%u %u\n255\n", width, height);
	std::vector<uint8_t> rgb_row (width * 3);
	for (unsigned row = 0; row < height; row++)
	{
	  const uint8_t *src = pixels.data () + row * stride;
	  for (unsigned x = 0; x < width; x++)
	    rgb_row[x * 3 + 0] = rgb_row[x * 3 + 1] = rgb_row[x * 3 + 2] = (uint8_t) (255 - src[x]);
	  fwrite (rgb_row.data (), 1, width * 3, out_fp);
	}
      }
    }

    cleanup ();
  }

  private:

  void cleanup ()
  {
    if (ft_face) { FT_Done_Face (ft_face); ft_face = nullptr; }
    if (ft_lib)  { FT_Done_FreeType (ft_lib); ft_lib = nullptr; }
    hb_font_destroy (font);        font      = nullptr;
    lines.clear ();
    direction = HB_DIRECTION_INVALID;
  }

  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;
  };

  hb_font_t        *font      = nullptr;
  unsigned          subpixel_bits = 0;
  hb_direction_t    direction     = HB_DIRECTION_INVALID;
  std::vector<line_t> lines;
  FT_Library ft_lib  = nullptr;
  FT_Face    ft_face = nullptr;
};


int
main (int argc, char **argv)
{
  using main_t = main_font_text_t<shape_consumer_t<ft_raster_output_t>, font_options_t, shape_text_options_t>;
  argv_t args (argc, argv);
  return batch_main<main_t, true> (args.argc, args.argv);
}
