/*
 * 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-vector.h"
#include "hb-blob.hh"
#include "hb-geometry.hh"
#include "hb-machinery.hh"
#include "hb-map.hh"
#include "hb-vector-svg-path.hh"
#include "hb-vector-svg-subset.hh"
#include "hb-vector-svg-utils.hh"

#include <algorithm>
#include <math.h>
#include <string.h>


static bool
hb_svg_append_str (hb_vector_t<char> *buf, const char *s)
{
  return hb_svg_append_len (buf, s, (unsigned) strlen (s));
}

static bool
hb_svg_append_unsigned (hb_vector_t<char> *buf, unsigned v)
{
  char tmp[10];
  unsigned n = 0;
  do {
    tmp[n++] = (char) ('0' + (v % 10));
    v /= 10;
  } while (v);

  unsigned old_len = buf->length;
  if (unlikely (!buf->resize_dirty ((int) (old_len + n))))
    return false;

  for (unsigned i = 0; i < n; i++)
    buf->arrayZ[old_len + i] = tmp[n - 1 - i];
  return true;
}

static bool
hb_svg_append_hex_byte (hb_vector_t<char> *buf, unsigned v)
{
  static const char hex[] = "0123456789ABCDEF";
  char tmp[2] = {hex[(v >> 4) & 15], hex[v & 15]};
  return hb_svg_append_len (buf, tmp, 2);
}

HB_UNUSED static bool
hb_svg_append_base64 (hb_vector_t<char> *buf,
                      const uint8_t *data,
                      unsigned len)
{
  static const char b64[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

  unsigned out_len = ((len + 2) / 3) * 4;
  unsigned old_len = buf->length;
  if (unlikely (!buf->resize_dirty ((int) (old_len + out_len))))
    return false;

  char *dst = buf->arrayZ + old_len;
  unsigned di = 0;
  unsigned i = 0;
  while (i + 2 < len)
  {
    unsigned v = ((unsigned) data[i] << 16) |
                 ((unsigned) data[i + 1] << 8) |
                 ((unsigned) data[i + 2]);
    dst[di++] = b64[(v >> 18) & 63];
    dst[di++] = b64[(v >> 12) & 63];
    dst[di++] = b64[(v >> 6) & 63];
    dst[di++] = b64[v & 63];
    i += 3;
  }

  if (i < len)
  {
    unsigned v = (unsigned) data[i] << 16;
    if (i + 1 < len)
      v |= (unsigned) data[i + 1] << 8;
    dst[di++] = b64[(v >> 18) & 63];
    dst[di++] = b64[(v >> 12) & 63];
    dst[di++] = (i + 1 < len) ? b64[(v >> 6) & 63] : '=';
    dst[di++] = '=';
  }

  return true;
}

HB_UNUSED static inline bool
hb_svg_buffer_contains (const hb_vector_t<char> &buf, const char *needle)
{
  unsigned nlen = (unsigned) strlen (needle);
  if (!nlen || buf.length < nlen)
    return false;

  for (unsigned i = 0; i + nlen <= buf.length; i++)
    if (buf.arrayZ[i] == needle[0] &&
        !memcmp (buf.arrayZ + i, needle, nlen))
      return true;
  return false;
}

struct hb_svg_blob_meta_t
{
  char *data;
  int allocated;
  bool transferred;
  bool in_replace;
};

static hb_user_data_key_t hb_svg_blob_meta_user_data_key;

static void
hb_svg_blob_meta_set_buffer (hb_svg_blob_meta_t *meta,
			     char *data,
			     int allocated)
{
  meta->data = data;
  meta->allocated = allocated;
  meta->transferred = false;
}

static void
hb_svg_blob_meta_release_buffer (hb_svg_blob_meta_t *meta)
{
  if (!meta)
    return;
  if (!meta->transferred && meta->data)
    hb_free (meta->data);
  meta->data = nullptr;
  meta->allocated = 0;
  meta->transferred = true;
}

static void
hb_svg_blob_meta_destroy (void *data)
{
  auto *meta = (hb_svg_blob_meta_t *) data;
  hb_svg_blob_meta_release_buffer (meta);
  if (meta->in_replace)
  {
    meta->in_replace = false;
    return;
  }
  hb_free (meta);
}

static hb_blob_t *
hb_svg_blob_from_buffer (hb_blob_t **recycled_blob,
			 hb_vector_t<char> *buf)
{
  unsigned len = 0;
  int allocated = 0;
  char *data = buf->steal (&len, &allocated);
  if (!data)
    return nullptr;

  hb_blob_t *blob = nullptr;
  if (*recycled_blob)
    blob = *recycled_blob;
  bool reused_blob = blob && blob != hb_blob_get_empty ();
  bool new_meta = false;
  auto *meta = reused_blob
             ? (hb_svg_blob_meta_t *) hb_blob_get_user_data (blob, &hb_svg_blob_meta_user_data_key)
             : nullptr;
  if (!meta)
  {
    meta = (hb_svg_blob_meta_t *) hb_malloc (sizeof (hb_svg_blob_meta_t));
    if (!meta)
    {
      hb_free (data);
      return nullptr;
    }
    meta->data = nullptr;
    meta->allocated = 0;
    meta->transferred = true;
    meta->in_replace = false;
    new_meta = true;
  }

  if (reused_blob)
  {
    /* replace_buffer() first destroys old buffer user_data; keep meta alive. */
    meta->in_replace = true;
    blob->replace_buffer (data, len, HB_MEMORY_MODE_WRITABLE, meta, hb_svg_blob_meta_destroy);
    hb_svg_blob_meta_set_buffer (meta, data, allocated);
  }
  else
  {
    hb_svg_blob_meta_set_buffer (meta, data, allocated);
    blob = hb_blob_create_or_fail (data, len, HB_MEMORY_MODE_WRITABLE, meta, hb_svg_blob_meta_destroy);
    if (unlikely (!blob))
      return nullptr;
  }

  if (unlikely (blob == hb_blob_get_empty ()))
  {
    if (new_meta)
      hb_free (meta);
    hb_free (data);
    return nullptr;
  }

  if (new_meta &&
      !hb_blob_set_user_data (blob,
			      &hb_svg_blob_meta_user_data_key,
			      meta,
			      nullptr,
			      true))
  {
    if (!reused_blob)
      hb_blob_destroy (blob);
    return nullptr;
  }

  if (*recycled_blob)
    *recycled_blob = nullptr;

  return blob;
}

static void
hb_svg_recover_recycled_buffer (hb_blob_t *blob,
				hb_vector_t<char> *buf)
{
  if (!blob)
    return;

  auto *meta = (hb_svg_blob_meta_t *) hb_blob_get_user_data (blob, &hb_svg_blob_meta_user_data_key);
  if (!meta || meta->transferred || !meta->data)
    return;

  buf->recycle_buffer (meta->data, 0, meta->allocated);
  meta->data = nullptr;
  meta->allocated = 0;
  meta->transferred = true;
}

HB_UNUSED static void
hb_svg_append_color (hb_vector_t<char> *buf,
                     hb_color_t color,
                     bool with_alpha)
{
  static const char hex[] = "0123456789ABCDEF";
  unsigned r = hb_color_get_red (color);
  unsigned g = hb_color_get_green (color);
  unsigned b = hb_color_get_blue (color);
  unsigned a = hb_color_get_alpha (color);
  hb_svg_append_c (buf, '#');
  if (((r >> 4) == (r & 0xF)) &&
      ((g >> 4) == (g & 0xF)) &&
      ((b >> 4) == (b & 0xF)))
  {
    hb_svg_append_c (buf, hex[r & 0xF]);
    hb_svg_append_c (buf, hex[g & 0xF]);
    hb_svg_append_c (buf, hex[b & 0xF]);
  }
  else
  {
    hb_svg_append_hex_byte (buf, r);
    hb_svg_append_hex_byte (buf, g);
    hb_svg_append_hex_byte (buf, b);
  }
  if (with_alpha && a != 255)
  {
    hb_svg_append_str (buf, "\" fill-opacity=\"");
    hb_svg_append_num (buf, a / 255.f, 4);
  }
}

static void
hb_svg_transform_point (const hb_transform_t<> &t,
                        float x_scale_factor,
                        float y_scale_factor,
                        float x, float y,
                        float *tx, float *ty)
{
  float xx = x, yy = y;
  t.transform_point (xx, yy);
  *tx = xx / (x_scale_factor > 0 ? x_scale_factor : 1.f);
  *ty = yy / (y_scale_factor > 0 ? y_scale_factor : 1.f);
}

static hb_bool_t
hb_svg_set_glyph_extents_common (const hb_transform_t<> &transform,
                                 float x_scale_factor,
                                 float y_scale_factor,
                                 const hb_glyph_extents_t *glyph_extents,
                                 hb_vector_extents_t *extents,
                                 hb_bool_t *has_extents)
{
  float x0 = (float) glyph_extents->x_bearing;
  float y0 = (float) glyph_extents->y_bearing;
  float x1 = x0 + glyph_extents->width;
  float y1 = y0 + glyph_extents->height;

  float px[4] = {x0, x0, x1, x1};
  float py[4] = {y0, y1, y0, y1};

  float tx, ty;
  hb_svg_transform_point (transform, x_scale_factor, y_scale_factor, px[0], py[0], &tx, &ty);
  float tx_min = tx, tx_max = tx;
  float ty_min = ty, ty_max = ty;

  for (unsigned i = 1; i < 4; i++)
  {
    hb_svg_transform_point (transform, x_scale_factor, y_scale_factor, px[i], py[i], &tx, &ty);
    tx_min = hb_min (tx_min, tx);
    tx_max = hb_max (tx_max, tx);
    ty_min = hb_min (ty_min, ty);
    ty_max = hb_max (ty_max, ty);
  }

  if (tx_max <= tx_min || ty_max <= ty_min)
  {
    return false;
  }

  if (*has_extents)
  {
    float x0 = hb_min (extents->x, tx_min);
    float y0 = hb_min (extents->y, ty_min);
    float x1 = hb_max (extents->x + extents->width, tx_max);
    float y1 = hb_max (extents->y + extents->height, ty_max);
    *extents = {x0, y0, x1 - x0, y1 - y0};
  }
  else
  {
    *extents = {tx_min, ty_min, tx_max - tx_min, ty_max - ty_min};
    *has_extents = true;
  }
  return true;
}


struct hb_vector_draw_t
{
  hb_object_header_t header;

  hb_vector_format_t format = HB_VECTOR_FORMAT_SVG;
  hb_transform_t<> transform = {1, 0, 0, 1, 0, 0};
  float x_scale_factor = 1.f;
  float y_scale_factor = 1.f;
  hb_vector_extents_t extents = {0, 0, 0, 0};
  bool has_extents = false;
  unsigned precision = 2;
  bool flat = false;

  hb_vector_t<char> defs;
  hb_vector_t<char> body;
  hb_vector_t<char> path;
  hb_set_t *defined_glyphs = nullptr;
  hb_blob_t *recycled_blob = nullptr;

  void append_xy (float x, float y)
  {
    float tx, ty;
    hb_svg_transform_point (transform, x_scale_factor, y_scale_factor, x, y, &tx, &ty);
    hb_svg_append_num (&path, tx, precision);
    hb_svg_append_c (&path, ',');
    hb_svg_append_num (&path, ty, precision);
  }
};

static inline void
hb_svg_append_instance_transform (hb_vector_t<char> *out,
                                  unsigned precision,
                                  float x_scale_factor,
                                  float y_scale_factor,
                                  float xx, float yx,
                                  float xy, float yy,
                                  float tx, float ty)
{
  unsigned sprec = hb_svg_scale_precision (precision);
  if (xx == 1.f && yx == 0.f && xy == 0.f && yy == 1.f)
  {
    float sx = 1.f / x_scale_factor;
    float sy = 1.f / y_scale_factor;
    hb_svg_append_str (out, "translate(");
    hb_svg_append_num (out, tx / x_scale_factor, precision);
    hb_svg_append_c (out, ',');
    hb_svg_append_num (out, -ty / y_scale_factor, precision);
    hb_svg_append_str (out, ") scale(");
    hb_svg_append_num (out, sx, sprec, true);
    hb_svg_append_c (out, ',');
    hb_svg_append_num (out, -sy, sprec, true);
    hb_svg_append_c (out, ')');
  }
  else
  {
    hb_svg_append_str (out, "matrix(");
    hb_svg_append_num (out, xx / x_scale_factor, sprec, true);
    hb_svg_append_c (out, ',');
    hb_svg_append_num (out, yx / y_scale_factor, sprec, true);
    hb_svg_append_c (out, ',');
    hb_svg_append_num (out, -xy / x_scale_factor, sprec, true);
    hb_svg_append_c (out, ',');
    hb_svg_append_num (out, -yy / y_scale_factor, sprec, true);
    hb_svg_append_c (out, ',');
    hb_svg_append_num (out, tx / x_scale_factor, precision);
    hb_svg_append_c (out, ',');
    hb_svg_append_num (out, -ty / y_scale_factor, precision);
    hb_svg_append_c (out, ')');
  }
}

HB_UNUSED static inline void
hb_svg_append_image_instance_translate (hb_vector_t<char> *out,
                                        unsigned precision,
                                        float x_scale_factor,
                                        float y_scale_factor,
                                        float tx, float ty)
{
  hb_svg_append_str (out, "translate(");
  hb_svg_append_num (out, tx / x_scale_factor, precision);
  hb_svg_append_c (out, ',');
  hb_svg_append_num (out, -ty / y_scale_factor, precision);
  hb_svg_append_c (out, ')');
}


static void
hb_vector_draw_move_to (hb_draw_funcs_t *,
                        void *draw_data,
                        hb_draw_state_t *,
                        float to_x, float to_y,
                        void *)
{
  auto *d = (hb_vector_draw_t *) draw_data;
  hb_svg_append_c (&d->path, 'M');
  d->append_xy (to_x, to_y);
}

static void
hb_vector_draw_line_to (hb_draw_funcs_t *,
                        void *draw_data,
                        hb_draw_state_t *,
                        float to_x, float to_y,
                        void *)
{
  auto *d = (hb_vector_draw_t *) draw_data;
  hb_svg_append_c (&d->path, 'L');
  d->append_xy (to_x, to_y);
}

static void
hb_vector_draw_quadratic_to (hb_draw_funcs_t *,
                             void *draw_data,
                             hb_draw_state_t *,
                             float cx, float cy,
                             float to_x, float to_y,
                             void *)
{
  auto *d = (hb_vector_draw_t *) draw_data;
  hb_svg_append_c (&d->path, 'Q');
  d->append_xy (cx, cy);
  hb_svg_append_c (&d->path, ' ');
  d->append_xy (to_x, to_y);
}

static void
hb_vector_draw_cubic_to (hb_draw_funcs_t *,
                         void *draw_data,
                         hb_draw_state_t *,
                         float c1x, float c1y,
                         float c2x, float c2y,
                         float to_x, float to_y,
                         void *)
{
  auto *d = (hb_vector_draw_t *) draw_data;
  hb_svg_append_c (&d->path, 'C');
  d->append_xy (c1x, c1y);
  hb_svg_append_c (&d->path, ' ');
  d->append_xy (c2x, c2y);
  hb_svg_append_c (&d->path, ' ');
  d->append_xy (to_x, to_y);
}

static void
hb_vector_draw_close_path (hb_draw_funcs_t *,
                           void *draw_data,
                           hb_draw_state_t *,
                           void *)
{
  auto *d = (hb_vector_draw_t *) draw_data;
  hb_svg_append_c (&d->path, 'Z');
}

static inline void free_static_vector_draw_funcs ();

static struct hb_vector_draw_funcs_lazy_loader_t
  : hb_draw_funcs_lazy_loader_t<hb_vector_draw_funcs_lazy_loader_t>
{
  static hb_draw_funcs_t *create ()
  {
    hb_draw_funcs_t *funcs = hb_draw_funcs_create ();
    hb_draw_funcs_set_move_to_func (funcs, (hb_draw_move_to_func_t) hb_vector_draw_move_to, nullptr, nullptr);
    hb_draw_funcs_set_line_to_func (funcs, (hb_draw_line_to_func_t) hb_vector_draw_line_to, nullptr, nullptr);
    hb_draw_funcs_set_quadratic_to_func (funcs, (hb_draw_quadratic_to_func_t) hb_vector_draw_quadratic_to, nullptr, nullptr);
    hb_draw_funcs_set_cubic_to_func (funcs, (hb_draw_cubic_to_func_t) hb_vector_draw_cubic_to, nullptr, nullptr);
    hb_draw_funcs_set_close_path_func (funcs, (hb_draw_close_path_func_t) hb_vector_draw_close_path, nullptr, nullptr);
    hb_draw_funcs_make_immutable (funcs);
    hb_atexit (free_static_vector_draw_funcs);
    return funcs;
  }
} static_vector_draw_funcs;

static inline void
free_static_vector_draw_funcs ()
{
  static_vector_draw_funcs.free_instance ();
}

static hb_draw_funcs_t *
hb_vector_draw_funcs_get ()
{
  return static_vector_draw_funcs.get_unconst ();
}

/**
 * hb_vector_draw_create_or_fail:
 * @format: output format.
 *
 * Creates a new draw context for vector output.
 *
 * Return value: (nullable): a newly allocated #hb_vector_draw_t, or `NULL` on failure.
 *
 * Since: 13.0.0
 */
hb_vector_draw_t *
hb_vector_draw_create_or_fail (hb_vector_format_t format)
{
  if (format != HB_VECTOR_FORMAT_SVG)
    return nullptr;

  hb_vector_draw_t *draw = hb_object_create<hb_vector_draw_t> ();
  if (unlikely (!draw))
    return nullptr;
  draw->format = format;
  draw->defined_glyphs = hb_set_create ();
  draw->defs.alloc (2048);
  draw->body.alloc (8192);
  draw->path.alloc (2048);
  return draw;
}

/**
 * hb_vector_draw_reference:
 * @draw: a draw context.
 *
 * Increases the reference count of @draw.
 *
 * Return value: (transfer full): referenced @draw.
 *
 * Since: 13.0.0
 */
hb_vector_draw_t *
hb_vector_draw_reference (hb_vector_draw_t *draw)
{
  return hb_object_reference (draw);
}

/**
 * hb_vector_draw_destroy:
 * @draw: a draw context.
 *
 * Decreases the reference count of @draw and destroys it when it reaches zero.
 *
 * Since: 13.0.0
 */
void
hb_vector_draw_destroy (hb_vector_draw_t *draw)
{
  if (!hb_object_should_destroy (draw))
    return;

  hb_blob_destroy (draw->recycled_blob);
  hb_set_destroy (draw->defined_glyphs);
  hb_object_actually_destroy (draw);
  hb_free (draw);
}

/**
 * hb_vector_draw_set_user_data:
 * @draw: a draw context.
 * @key: user-data key.
 * @data: user-data value.
 * @destroy: (nullable): destroy callback for @data.
 * @replace: whether to replace an existing value for @key.
 *
 * Attaches user data to @draw.
 *
 * Return value: `true` on success, `false` otherwise.
 *
 * Since: 13.0.0
 */
hb_bool_t
hb_vector_draw_set_user_data (hb_vector_draw_t   *draw,
                              hb_user_data_key_t *key,
                              void               *data,
                              hb_destroy_func_t   destroy,
                              hb_bool_t           replace)
{
  return hb_object_set_user_data (draw, key, data, destroy, replace);
}

/**
 * hb_vector_draw_get_user_data:
 * @draw: a draw context.
 * @key: user-data key.
 *
 * Gets previously attached user data from @draw.
 *
 * Return value: (nullable): user-data value associated with @key.
 *
 * Since: 13.0.0
 */
void *
hb_vector_draw_get_user_data (hb_vector_draw_t   *draw,
                              hb_user_data_key_t *key)
{
  return hb_object_get_user_data (draw, key);
}

/**
 * hb_vector_draw_set_transform:
 * @draw: a draw context.
 * @xx: transform xx component.
 * @yx: transform yx component.
 * @xy: transform xy component.
 * @yy: transform yy component.
 * @dx: transform x translation.
 * @dy: transform y translation.
 *
 * Sets the affine transform used when drawing glyphs.
 *
 * Since: 13.0.0
 */
void
hb_vector_draw_set_transform (hb_vector_draw_t *draw,
                              float xx, float yx,
                              float xy, float yy,
                              float dx, float dy)
{
  draw->transform = {xx, yx, xy, yy, dx, dy};
}

/**
 * hb_vector_draw_get_transform:
 * @draw: a draw context.
 * @xx: (out) (nullable): transform xx component.
 * @yx: (out) (nullable): transform yx component.
 * @xy: (out) (nullable): transform xy component.
 * @yy: (out) (nullable): transform yy component.
 * @dx: (out) (nullable): transform x translation.
 * @dy: (out) (nullable): transform y translation.
 *
 * Gets the affine transform used when drawing glyphs.
 *
 * Since: 13.0.0
 */
void
hb_vector_draw_get_transform (hb_vector_draw_t *draw,
                              float *xx, float *yx,
                              float *xy, float *yy,
                              float *dx, float *dy)
{
  if (xx) *xx = draw->transform.xx;
  if (yx) *yx = draw->transform.yx;
  if (xy) *xy = draw->transform.xy;
  if (yy) *yy = draw->transform.yy;
  if (dx) *dx = draw->transform.x0;
  if (dy) *dy = draw->transform.y0;
}

/**
 * hb_vector_draw_set_scale_factor:
 * @draw: a draw context.
 * @x_scale_factor: x scale factor.
 * @y_scale_factor: y scale factor.
 *
 * Sets additional output scaling factors.
 *
 * Since: 13.0.0
 */
void
hb_vector_draw_set_scale_factor (hb_vector_draw_t *draw,
                                 float x_scale_factor,
                                 float y_scale_factor)
{
  draw->x_scale_factor = x_scale_factor > 0.f ? x_scale_factor : 1.f;
  draw->y_scale_factor = y_scale_factor > 0.f ? y_scale_factor : 1.f;
}

/**
 * hb_vector_draw_get_scale_factor:
 * @draw: a draw context.
 * @x_scale_factor: (out) (nullable): x scale factor.
 * @y_scale_factor: (out) (nullable): y scale factor.
 *
 * Gets additional output scaling factors.
 *
 * Since: 13.0.0
 */
void
hb_vector_draw_get_scale_factor (hb_vector_draw_t *draw,
                                 float *x_scale_factor,
                                 float *y_scale_factor)
{
  if (x_scale_factor) *x_scale_factor = draw->x_scale_factor;
  if (y_scale_factor) *y_scale_factor = draw->y_scale_factor;
}

/**
 * hb_vector_draw_set_extents:
 * @draw: a draw context.
 * @extents: (nullable): output extents to set or expand.
 *
 * Sets or expands output extents on @draw. Passing `NULL` clears extents.
 *
 * Since: 13.0.0
 */
void
hb_vector_draw_set_extents (hb_vector_draw_t *draw,
                            const hb_vector_extents_t *extents)
{
  if (!extents)
  {
    draw->extents = {0, 0, 0, 0};
    draw->has_extents = false;
    return;
  }

  if (!(extents->width > 0.f && extents->height > 0.f))
    return;

  if (draw->has_extents)
  {
    float x0 = hb_min (draw->extents.x, extents->x);
    float y0 = hb_min (draw->extents.y, extents->y);
    float x1 = hb_max (draw->extents.x + draw->extents.width,
                       extents->x + extents->width);
    float y1 = hb_max (draw->extents.y + draw->extents.height,
                       extents->y + extents->height);
    draw->extents = {x0, y0, x1 - x0, y1 - y0};
  }
  else
  {
    draw->extents = *extents;
    draw->has_extents = true;
  }
}

/**
 * hb_vector_draw_get_extents:
 * @draw: a draw context.
 * @extents: (out) (nullable): where to store current output extents.
 *
 * Gets current output extents from @draw.
 *
 * Return value: `true` if extents are set, `false` otherwise.
 *
 * Since: 13.0.0
 */
hb_bool_t
hb_vector_draw_get_extents (hb_vector_draw_t *draw,
                            hb_vector_extents_t *extents)
{
  if (!draw->has_extents)
    return false;

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

/**
 * hb_vector_draw_set_glyph_extents:
 * @draw: a draw context.
 * @glyph_extents: glyph extents in font units.
 *
 * Expands @draw extents using @glyph_extents under the current transform.
 *
 * Return value: `true` on success, `false` otherwise.
 *
 * Since: 13.0.0
 */
hb_bool_t
hb_vector_draw_set_glyph_extents (hb_vector_draw_t *draw,
                                  const hb_glyph_extents_t *glyph_extents)
{
  hb_bool_t has_extents = draw->has_extents;
  hb_bool_t ret = hb_svg_set_glyph_extents_common (draw->transform,
						   draw->x_scale_factor,
						   draw->y_scale_factor,
						   glyph_extents,
						   &draw->extents,
						   &has_extents);
  draw->has_extents = has_extents;
  return ret;
}

/**
 * hb_vector_draw_get_funcs:
 *
 * Gets draw callbacks implemented by the vector draw backend.
 *
 * Return value: (transfer none): immutable #hb_draw_funcs_t singleton.
 *
 * Since: 13.0.0
 */
hb_draw_funcs_t *
hb_vector_draw_get_funcs (void)
{
  return hb_vector_draw_funcs_get ();
}

/**
 * hb_vector_draw_glyph:
 * @draw: a draw context.
 * @font: font object.
 * @glyph: glyph ID.
 * @pen_x: glyph x origin before context transform.
 * @pen_y: glyph y origin before context transform.
 * @extents_mode: extents update mode.
 *
 * Draws one glyph into @draw.
 *
 * Return value: `true` if glyph data was emitted, `false` otherwise.
 *
 * Since: 13.0.0
 */
hb_bool_t
hb_vector_draw_glyph (hb_vector_draw_t *draw,
                      hb_font_t *font,
                      hb_codepoint_t glyph,
                      float pen_x,
                      float pen_y,
                      hb_vector_extents_mode_t extents_mode)
{
  if (draw->format != HB_VECTOR_FORMAT_SVG)
    return false;

  if (extents_mode == HB_VECTOR_EXTENTS_MODE_EXPAND)
  {
    hb_glyph_extents_t ge;
    if (hb_font_get_glyph_extents (font, glyph, &ge))
    {
      float xx = draw->transform.xx;
      float yx = draw->transform.yx;
      float xy = draw->transform.xy;
      float yy = draw->transform.yy;
      float tx = draw->transform.x0 + xx * pen_x + xy * pen_y;
      float ty = draw->transform.y0 + yx * pen_x + yy * pen_y;
      hb_transform_t<> extents_transform = {xx, yx, -xy, -yy, tx, ty};

      hb_bool_t has_extents = draw->has_extents;
      hb_svg_set_glyph_extents_common (extents_transform,
                                       draw->x_scale_factor,
                                       draw->y_scale_factor,
                                       &ge,
                                       &draw->extents,
                                       &has_extents);
      draw->has_extents = has_extents;
    }
  }

  bool needs_def = !draw->flat && !hb_set_has (draw->defined_glyphs, glyph);
  if (needs_def)
  {
    draw->path.clear ();
    hb_svg_path_sink_t sink = {&draw->path, draw->precision};
    hb_font_draw_glyph (font, glyph, hb_svg_path_draw_funcs_get (), &sink);
    if (!draw->path.length)
      return false;
    hb_svg_append_str (&draw->defs, "<path id=\"p");
    hb_svg_append_unsigned (&draw->defs, glyph);
    hb_svg_append_str (&draw->defs, "\" d=\"");
    hb_svg_append_len (&draw->defs, draw->path.arrayZ, draw->path.length);
    hb_svg_append_str (&draw->defs, "\"/>\n");
    hb_set_add (draw->defined_glyphs, glyph);
  }

  if (draw->flat)
  {
    draw->path.clear ();
    hb_svg_path_sink_t sink = {&draw->path, draw->precision};
    hb_font_draw_glyph (font, glyph, hb_svg_path_draw_funcs_get (), &sink);

    if (!draw->path.length)
      return false;

    float xx = draw->transform.xx;
    float yx = draw->transform.yx;
    float xy = draw->transform.xy;
    float yy = draw->transform.yy;
    float tx = draw->transform.x0 + xx * pen_x + xy * pen_y;
    float ty = draw->transform.y0 + yx * pen_x + yy * pen_y;

    hb_svg_append_str (&draw->body, "<path d=\"");
    hb_svg_append_len (&draw->body, draw->path.arrayZ, draw->path.length);
    hb_svg_append_str (&draw->body, "\" transform=\"");
    hb_svg_append_instance_transform (&draw->body,
                                      draw->precision,
                                      draw->x_scale_factor,
                                      draw->y_scale_factor,
                                      xx, yx, xy, yy, tx, ty);
    hb_svg_append_str (&draw->body, "\"/>\n");
    return true;
  }

  float xx = draw->transform.xx;
  float yx = draw->transform.yx;
  float xy = draw->transform.xy;
  float yy = draw->transform.yy;
  float tx = draw->transform.x0 + xx * pen_x + xy * pen_y;
  float ty = draw->transform.y0 + yx * pen_x + yy * pen_y;

  hb_svg_append_str (&draw->body, "<use href=\"#p");
  hb_svg_append_unsigned (&draw->body, glyph);
  hb_svg_append_str (&draw->body, "\" transform=\"");
  hb_svg_append_instance_transform (&draw->body,
                                    draw->precision,
                                    draw->x_scale_factor,
                                    draw->y_scale_factor,
                                    xx, yx, xy, yy, tx, ty);
  hb_svg_append_str (&draw->body, "\"/>\n");
  return true;
}

/**
 * hb_vector_svg_set_flat:
 * @draw: a draw context.
 * @flat: whether to flatten geometry and disable reuse.
 *
 * Enables or disables SVG draw flattening.
 *
 * Since: 13.0.0
 */
void
hb_vector_svg_set_flat (hb_vector_draw_t *draw,
                        hb_bool_t flat)
{
  draw->flat = !!flat;
}

/**
 * hb_vector_svg_set_precision:
 * @draw: a draw context.
 * @precision: decimal precision.
 *
 * Sets numeric output precision for SVG draw output.
 *
 * Since: 13.0.0
 */
void
hb_vector_svg_set_precision (hb_vector_draw_t *draw,
                             unsigned precision)
{
  draw->precision = hb_min (precision, 12u);
}

/**
 * hb_vector_draw_render:
 * @draw: a draw context.
 *
 * Renders accumulated draw content to an SVG blob.
 *
 * Return value: (transfer full) (nullable): output blob, or `NULL` if rendering cannot proceed.
 *
 * Since: 13.0.0
 */
hb_blob_t *
hb_vector_draw_render (hb_vector_draw_t *draw)
{
  if (draw->format != HB_VECTOR_FORMAT_SVG)
    return nullptr;
  if (!draw->has_extents)
    return nullptr;

  hb_vector_t<char> out;
  hb_svg_recover_recycled_buffer (draw->recycled_blob, &out);
  unsigned estimated = draw->defs.length +
                       (draw->body.length ? draw->body.length : draw->path.length) +
                       256;
  out.alloc (estimated);
  hb_svg_append_str (&out, "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"");
  hb_svg_append_num (&out, draw->extents.x, draw->precision);
  hb_svg_append_c (&out, ' ');
  hb_svg_append_num (&out, draw->extents.y, draw->precision);
  hb_svg_append_c (&out, ' ');
  hb_svg_append_num (&out, draw->extents.width, draw->precision);
  hb_svg_append_c (&out, ' ');
  hb_svg_append_num (&out, draw->extents.height, draw->precision);
  hb_svg_append_str (&out, "\" width=\"");
  hb_svg_append_num (&out, draw->extents.width, draw->precision);
  hb_svg_append_str (&out, "\" height=\"");
  hb_svg_append_num (&out, draw->extents.height, draw->precision);
  hb_svg_append_str (&out, "\">\n");

  if (draw->defs.length)
  {
    hb_svg_append_str (&out, "<defs>\n");
    hb_svg_append_len (&out, draw->defs.arrayZ, draw->defs.length);
    hb_svg_append_str (&out, "</defs>\n");
  }

  if (draw->body.length)
  {
    hb_svg_append_len (&out, draw->body.arrayZ, draw->body.length);
  }
  else if (draw->path.length)
  {
    hb_svg_append_str (&out, "<path d=\"");
    hb_svg_append_len (&out, draw->path.arrayZ, draw->path.length);
    hb_svg_append_str (&out, "\"/>\n");
  }

  hb_svg_append_str (&out, "</svg>\n");

  hb_blob_t *blob = hb_svg_blob_from_buffer (&draw->recycled_blob, &out);

  draw->path.clear ();
  draw->defs.clear ();
  draw->body.clear ();
  hb_set_clear (draw->defined_glyphs);
  draw->has_extents = false;
  draw->extents = {0, 0, 0, 0};

  return blob;
}

/**
 * hb_vector_draw_reset:
 * @draw: a draw context.
 *
 * Resets @draw state and clears accumulated content.
 *
 * Since: 13.0.0
 */
void
hb_vector_draw_reset (hb_vector_draw_t *draw)
{
  draw->transform = {1, 0, 0, 1, 0, 0};
  draw->x_scale_factor = 1.f;
  draw->y_scale_factor = 1.f;
  draw->extents = {0, 0, 0, 0};
  draw->has_extents = false;
  draw->precision = 2;
  draw->flat = false;
  draw->defs.clear ();
  draw->body.clear ();
  draw->path.clear ();
  hb_set_clear (draw->defined_glyphs);
}

/**
 * hb_vector_draw_recycle_blob:
 * @draw: a draw context.
 * @blob: (nullable): previously rendered blob to recycle.
 *
 * Provides a blob for internal buffer reuse by later render calls.
 *
 * Since: 13.0.0
 */
void
hb_vector_draw_recycle_blob (hb_vector_draw_t *draw,
                             hb_blob_t *blob)
{
  hb_blob_destroy (draw->recycled_blob);
  draw->recycled_blob = nullptr;
  if (!blob || blob == hb_blob_get_empty ())
    return;
  draw->recycled_blob = blob;
}
