/* Pango
 * pangofc-shape.c: Basic shaper for FreeType-based backends
 *
 * Copyright (C) 2000, 2007, 2009 Red Hat Software
 * Authors:
 *   Owen Taylor <otaylor@redhat.com>
 *   Behdad Esfahbod <behdad@behdad.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"
#include <string.h>
#include <math.h>

#include "pangohb-private.h"
#include "pango-impl-utils.h"

#include <glib.h>

/* cache a single hb_buffer_t */
static hb_buffer_t *cached_buffer = NULL; /* MT-safe */
G_LOCK_DEFINE_STATIC (cached_buffer);

static hb_buffer_t *
acquire_buffer (gboolean *free_buffer)
{
  hb_buffer_t *buffer;

  if (G_LIKELY (G_TRYLOCK (cached_buffer)))
    {
      if (G_UNLIKELY (!cached_buffer))
	cached_buffer = hb_buffer_create ();

      buffer = cached_buffer;
      *free_buffer = FALSE;
    }
  else
    {
      buffer = hb_buffer_create ();
      *free_buffer = TRUE;
    }

  return buffer;
}

static void
release_buffer (hb_buffer_t *buffer, gboolean free_buffer)
{
  if (G_LIKELY (!free_buffer))
    {
      hb_buffer_reset (buffer);
      G_UNLOCK (cached_buffer);
    }
  else
    hb_buffer_destroy (buffer);
}

static void
apply_extra_attributes (GSList       *attrs,
                        hb_feature_t *features,
                        guint         length,
                        guint        *num_features)
{
  GSList *l;

  for (l = attrs; l && *num_features < length; l = l->next)
    {
      PangoAttribute *attr = l->data;
      if (attr->klass->type == PANGO_ATTR_FONT_FEATURES)
        {
          PangoAttrFontFeatures *fattr = (PangoAttrFontFeatures *) attr;
          const gchar *feat;
          const gchar *end;
          int len;

          feat = fattr->features;

          while (feat != NULL && *num_features < length)
            {
              end = strchr (feat, ',');
              if (end)
                len = end - feat;
              else
                len = -1;
              if (hb_feature_from_string (feat, len, &features[*num_features]))
                {
                  features[*num_features].start = attr->start_index;
                  features[*num_features].end = attr->end_index;
                  (*num_features)++;
                }

              if (end == NULL)
                break;

              feat = end + 1;
            }
        }
    }

  /* Turn off ligatures when letterspacing */
  for (l = attrs; l && *num_features < length; l = l->next)
    {
      PangoAttribute *attr = l->data;
      if (attr->klass->type == PANGO_ATTR_LETTER_SPACING)
        {
          hb_tag_t tags[] = {
            HB_TAG('l','i','g','a'),
            HB_TAG('c','l','i','g'),
            HB_TAG('d','l','i','g'),
          };
          int i;
          for (i = 0; i < G_N_ELEMENTS (tags); i++)
            {
              features[*num_features].tag = tags[i];
              features[*num_features].value = 0;
              features[*num_features].start = attr->start_index;
              features[*num_features].end = attr->end_index;
              (*num_features)++;
            }
        }
    }
}

typedef struct
{
  PangoFont *font;
  hb_font_t *parent;
  PangoShowFlags show_flags;
} PangoHbShapeContext;

static hb_bool_t
pango_hb_font_get_nominal_glyph (hb_font_t      *font,
                                 void           *font_data,
                                 hb_codepoint_t  unicode,
                                 hb_codepoint_t *glyph,
                                 void           *user_data G_GNUC_UNUSED)
{
  PangoHbShapeContext *context = (PangoHbShapeContext *) font_data;

  if (context->show_flags != 0)
    {
      if ((context->show_flags & PANGO_SHOW_SPACES) != 0 &&
          g_unichar_type (unicode) == G_UNICODE_SPACE_SEPARATOR)
        {
          /* Replace 0x20 by visible space, since we
           * don't draw a hex box for 0x20
           */
          if (unicode == 0x20)
            *glyph = PANGO_GET_UNKNOWN_GLYPH (0x2423);
          else
            *glyph = PANGO_GET_UNKNOWN_GLYPH (unicode);
          return TRUE;
        }

      if ((context->show_flags & PANGO_SHOW_IGNORABLES) != 0 &&
          pango_get_ignorable (unicode))
        {
          *glyph = PANGO_GET_UNKNOWN_GLYPH (unicode);
          return TRUE;
        }

      if ((context->show_flags & PANGO_SHOW_LINE_BREAKS) != 0 &&
          unicode == 0x2028)
        {
          /* Always mark LS as unknown. If it ends up
           * at the line end, PangoLayout takes care of
           * hiding them, and if they end up in the middle
           * of a line, we are in single paragraph mode
           * and want to show the LS
           */
          *glyph = PANGO_GET_UNKNOWN_GLYPH (unicode);
          return TRUE;
        }
    }

  if (hb_font_get_nominal_glyph (context->parent, unicode, glyph))
    return TRUE;

  *glyph = PANGO_GET_UNKNOWN_GLYPH (unicode);

  /* We draw our own invalid-Unicode shape, so prevent HarfBuzz
   * from using REPLACEMENT CHARACTER. */
  if (unicode > 0x10FFFF)
    return TRUE;

  return FALSE;
}

static hb_position_t
pango_hb_font_get_glyph_h_advance (hb_font_t      *font,
                                   void           *font_data,
                                   hb_codepoint_t  glyph,
                                   void           *user_data G_GNUC_UNUSED)
{
  PangoHbShapeContext *context = (PangoHbShapeContext *) font_data;

  if (glyph & PANGO_GLYPH_UNKNOWN_FLAG)
    {
      PangoRectangle logical;

      pango_font_get_glyph_extents (context->font, glyph, NULL, &logical);
      return logical.width;
    }

  return hb_font_get_glyph_h_advance (context->parent, glyph);
}

static hb_position_t
pango_hb_font_get_glyph_v_advance (hb_font_t      *font,
                                   void           *font_data,
                                   hb_codepoint_t  glyph,
                                   void           *user_data G_GNUC_UNUSED)
{
  PangoHbShapeContext *context = (PangoHbShapeContext *) font_data;

  if (glyph & PANGO_GLYPH_UNKNOWN_FLAG)
    {
      PangoRectangle logical;

      pango_font_get_glyph_extents (context->font, glyph, NULL, &logical);
      return logical.height;
    }

  return hb_font_get_glyph_v_advance (context->parent, glyph);
}

static hb_bool_t
pango_hb_font_get_glyph_extents (hb_font_t          *font,
                                 void               *font_data,
                                 hb_codepoint_t      glyph,
                                 hb_glyph_extents_t *extents,
                                 void               *user_data G_GNUC_UNUSED)
{
  PangoHbShapeContext *context = (PangoHbShapeContext *) font_data;

  if (glyph & PANGO_GLYPH_UNKNOWN_FLAG)
    {
      PangoRectangle ink;

      pango_font_get_glyph_extents (context->font, glyph, &ink, NULL);

      extents->x_bearing = ink.x;
      extents->y_bearing = ink.y;
      extents->width     = ink.width;
      extents->height    = ink.height;

      return TRUE;
    }

  return hb_font_get_glyph_extents (context->parent, glyph, extents);
}

static hb_font_t *
pango_font_get_hb_font_for_context (PangoFont           *font,
                                    PangoHbShapeContext *context)
{
  hb_font_t *hb_font;
  static hb_font_funcs_t *funcs;

  hb_font = pango_font_get_hb_font (font);

  if (G_UNLIKELY (!funcs))
    {
      funcs = hb_font_funcs_create ();

      hb_font_funcs_set_nominal_glyph_func (funcs, pango_hb_font_get_nominal_glyph, NULL, NULL);
      hb_font_funcs_set_glyph_h_advance_func (funcs, pango_hb_font_get_glyph_h_advance, NULL, NULL);
      hb_font_funcs_set_glyph_v_advance_func (funcs, pango_hb_font_get_glyph_v_advance, NULL, NULL);
      hb_font_funcs_set_glyph_extents_func (funcs, pango_hb_font_get_glyph_extents, NULL, NULL);

      hb_font_funcs_make_immutable (funcs);
    }

  context->font = font;
  context->parent = hb_font;

  hb_font = hb_font_create_sub_font (hb_font);
  hb_font_set_funcs (hb_font, funcs, context, NULL);

  return hb_font;
}

static PangoShowFlags
find_show_flags (const PangoAnalysis *analysis)
{
  GSList *l;
  PangoShowFlags flags = 0;

  for (l = analysis->extra_attrs; l; l = l->next)
    {
      PangoAttribute *attr = l->data;

      if (attr->klass->type == PANGO_ATTR_SHOW)
        flags |= ((PangoAttrInt*)attr)->value;
    }

  return flags;
}

void
pango_hb_shape (PangoFont           *font,
                const char          *item_text,
                unsigned int         item_length,
                const PangoAnalysis *analysis,
                PangoGlyphString    *glyphs,
                const char          *paragraph_text,
                unsigned int         paragraph_length)
{
  PangoHbShapeContext context = { 0, };
  hb_buffer_flags_t hb_buffer_flags;
  hb_font_t *hb_font;
  hb_buffer_t *hb_buffer;
  hb_direction_t hb_direction;
  gboolean free_buffer;
  hb_glyph_info_t *hb_glyph;
  hb_glyph_position_t *hb_position;
  int last_cluster;
  guint i, num_glyphs;
  unsigned int item_offset = item_text - paragraph_text;
  hb_feature_t features[32];
  unsigned int num_features = 0;
  PangoGlyphInfo *infos;

  g_return_if_fail (font != NULL);
  g_return_if_fail (analysis != NULL);

  context.show_flags = find_show_flags (analysis);
  hb_font = pango_font_get_hb_font_for_context (font, &context);
  hb_buffer = acquire_buffer (&free_buffer);

  hb_direction = PANGO_GRAVITY_IS_VERTICAL (analysis->gravity) ? HB_DIRECTION_TTB : HB_DIRECTION_LTR;
  if (analysis->level % 2)
    hb_direction = HB_DIRECTION_REVERSE (hb_direction);
  if (PANGO_GRAVITY_IS_IMPROPER (analysis->gravity))
    hb_direction = HB_DIRECTION_REVERSE (hb_direction);

  hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT;

  if (context.show_flags & PANGO_SHOW_IGNORABLES)
    hb_buffer_flags |= HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES;

  /* setup buffer */

  hb_buffer_set_direction (hb_buffer, hb_direction);
  hb_buffer_set_script (hb_buffer, (hb_script_t) g_unicode_script_to_iso15924 (analysis->script));
  hb_buffer_set_language (hb_buffer, hb_language_from_string (pango_language_to_string (analysis->language), -1));
  hb_buffer_set_cluster_level (hb_buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
  hb_buffer_set_flags (hb_buffer, hb_buffer_flags);
  hb_buffer_set_invisible_glyph (hb_buffer, PANGO_GLYPH_EMPTY);

  hb_buffer_add_utf8 (hb_buffer, paragraph_text, paragraph_length, item_offset, item_length);
  if (analysis->flags & PANGO_ANALYSIS_FLAG_NEED_HYPHEN)
    {
      /* Insert either a Unicode or ASCII hyphen. We may
       * want to look for script-specific hyphens here.
       */
      const char *p = paragraph_text + item_offset + item_length;
      int last_char_len = p - g_utf8_prev_char (p);
      hb_codepoint_t glyph;

      if (hb_font_get_nominal_glyph (hb_font, 0x2010, &glyph))
        hb_buffer_add (hb_buffer, 0x2010, item_offset + item_length - last_char_len);
      else if (hb_font_get_nominal_glyph (hb_font, '-', &glyph))
        hb_buffer_add (hb_buffer, '-', item_offset + item_length - last_char_len);
    }

  pango_font_get_features (font, features, G_N_ELEMENTS (features), &num_features);
  apply_extra_attributes (analysis->extra_attrs, features, G_N_ELEMENTS (features), &num_features);

  hb_shape (hb_font, hb_buffer, features, num_features);

  if (PANGO_GRAVITY_IS_IMPROPER (analysis->gravity))
    hb_buffer_reverse (hb_buffer);

  /* buffer output */
  num_glyphs = hb_buffer_get_length (hb_buffer);
  hb_glyph = hb_buffer_get_glyph_infos (hb_buffer, NULL);
  pango_glyph_string_set_size (glyphs, num_glyphs);
  infos = glyphs->glyphs;
  last_cluster = -1;
  for (i = 0; i < num_glyphs; i++)
    {
      infos[i].glyph = hb_glyph->codepoint;
      glyphs->log_clusters[i] = hb_glyph->cluster - item_offset;
      infos[i].attr.is_cluster_start = glyphs->log_clusters[i] != last_cluster;
      hb_glyph++;
      last_cluster = glyphs->log_clusters[i];
    }

  hb_position = hb_buffer_get_glyph_positions (hb_buffer, NULL);
  if (PANGO_GRAVITY_IS_VERTICAL (analysis->gravity))
    for (i = 0; i < num_glyphs; i++)
      {
        /* 90 degrees rotation counter-clockwise. */
	infos[i].geometry.width    = - hb_position->y_advance;
	infos[i].geometry.x_offset = - hb_position->y_offset;
	infos[i].geometry.y_offset = - hb_position->x_offset;
	hb_position++;
      }
  else /* horizontal */
    for (i = 0; i < num_glyphs; i++)
      {
	infos[i].geometry.width    =   hb_position->x_advance;
	infos[i].geometry.x_offset =   hb_position->x_offset;
	infos[i].geometry.y_offset = - hb_position->y_offset;
	hb_position++;
      }

  release_buffer (hb_buffer, free_buffer);
  hb_font_destroy (hb_font);
}
