/* Pango
 * shape.c: Convert characters into glyphs.
 *
 * Copyright (C) 1999 Red Hat Software
 *
 * 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 "pango-impl-utils.h"
#include "pango-glyph.h"

#include "pangohb-private.h"

#include <string.h>

/**
 * pango_shape:
 * @text: the text to process
 * @length: the length (in bytes) of @text
 * @analysis: `PangoAnalysis` structure from [func@itemize]
 * @glyphs: glyph string in which to store results
 *
 * Convert the characters in @text into glyphs.
 *
 * Given a segment of text and the corresponding `PangoAnalysis` structure
 * returned from [func@itemize], convert the characters into glyphs. You
 * may also pass in only a substring of the item from [func@itemize].
 *
 * It is recommended that you use [func@shape_full] instead, since
 * that API allows for shaping interaction happening across text item
 * boundaries.
 *
 * Note that the extra attributes in the @analyis that is returned from
 * [func@itemize] have indices that are relative to the entire paragraph,
 * so you need to subtract the item offset from their indices before
 * calling [func@shape].
 */
void
pango_shape (const gchar         *text,
	     gint                 length,
	     const PangoAnalysis *analysis,
	     PangoGlyphString    *glyphs)
{
  pango_shape_full (text, length, text, length, analysis, glyphs);
}

/**
 * pango_shape_full:
 * @item_text: valid UTF-8 text to shape.
 * @item_length: the length (in bytes) of @item_text. -1 means nul-terminated text.
 * @paragraph_text: (nullable): text of the paragraph (see details).  May be %NULL.
 * @paragraph_length: the length (in bytes) of @paragraph_text. -1 means nul-terminated text.
 * @analysis: `PangoAnalysis` structure from [func@itemize].
 * @glyphs: glyph string in which to store results.
 *
 * Convert the characters in @text into glyphs.
 *
 * Given a segment of text and the corresponding `PangoAnalysis` structure
 * returned from [func@itemize], convert the characters into glyphs. You may
 * also pass in only a substring of the item from [func@itemize].
 *
 * This is similar to [func@shape], except it also can optionally take
 * the full paragraph text as input, which will then be used to perform
 * certain cross-item shaping interactions. If you have access to the broader
 * text of which @item_text is part of, provide the broader text as
 * @paragraph_text. If @paragraph_text is %NULL, item text is used instead.
 *
 * Note that the extra attributes in the @analyis that is returned from
 * [func@itemize] have indices that are relative to the entire paragraph,
 * so you do not pass the full paragraph text as @paragraph_text, you need
 * to subtract the item offset from their indices before calling [func@shape_full].
 *
 * Since: 1.32
 */
void
pango_shape_full (const char          *item_text,
                  int                  item_length,
                  const char          *paragraph_text,
                  int                  paragraph_length,
                  const PangoAnalysis *analysis,
                  PangoGlyphString    *glyphs)
{
  pango_shape_with_flags (item_text, item_length,
                          paragraph_text, paragraph_length,
                          analysis,
                          glyphs,
                          PANGO_SHAPE_NONE);
}

static void
fallback_shape (const char          *text,
                unsigned int         length,
                const PangoAnalysis *analysis,
                PangoGlyphString    *glyphs)
{
  int n_chars;
  const char *p;
  int cluster = 0;
  int i;

  n_chars = text ? pango_utf8_strlen (text, length) : 0;

  pango_glyph_string_set_size (glyphs, n_chars);

  p = text;
  for (i = 0; i < n_chars; i++)
    {
      gunichar wc;
      PangoGlyph glyph;
      PangoRectangle logical_rect;

      wc = g_utf8_get_char (p);

      if (g_unichar_type (wc) != G_UNICODE_NON_SPACING_MARK)
        cluster = p - text;

      if (pango_is_zero_width (wc))
        glyph = PANGO_GLYPH_EMPTY;
      else
        glyph = PANGO_GET_UNKNOWN_GLYPH (wc);

      pango_font_get_glyph_extents (analysis->font, glyph, NULL, &logical_rect);

      glyphs->glyphs[i].glyph = glyph;

      glyphs->glyphs[i].geometry.x_offset = 0;
      glyphs->glyphs[i].geometry.y_offset = 0;
      glyphs->glyphs[i].geometry.width = logical_rect.width;

      glyphs->log_clusters[i] = cluster;

      p = g_utf8_next_char (p);
    }

  if (analysis->level & 1)
    pango_glyph_string_reverse_range (glyphs, 0, glyphs->num_glyphs);
}

/* FIXME: This is very ugly. We are out of room for vfuncs, so we can't
 * easily add api to get is_hinted and the matrix from the PangoFcFont.
 *
 * Keep in sync with pangofc-font.h!
 */
struct _PangoFcFont
{
  PangoFont parent_instance;

  gpointer font_pattern;          /* fully resolved pattern */
  PangoFontMap *fontmap;            /* associated map */
  gpointer priv;                    /* used internally */
  PangoMatrix matrix;               /* used internally */
  PangoFontDescription *description;

  GSList *metrics_by_lang;

  guint is_hinted : 1;
  guint is_transformed : 1;
};
typedef struct _PangoFcFont PangoFcFont;

#define PANGO_IS_FC_FONT(obj) \
  ((obj) != NULL && \
   g_type_is_a (((GTypeInstance*)(obj))->g_class->g_type, \
                g_type_from_name ("PangoFcFont")))

/**
 * pango_shape_with_flags:
 * @item_text: valid UTF-8 text to shape
 * @item_length: the length (in bytes) of @item_text.
 *     -1 means nul-terminated text.
 * @paragraph_text: (nullable): text of the paragraph (see details).
 *     May be %NULL.
 * @paragraph_length: the length (in bytes) of @paragraph_text.
 *     -1 means nul-terminated text.
 * @analysis:  `PangoAnalysis` structure from [func@itemize]
 * @glyphs: glyph string in which to store results
 * @flags: flags influencing the shaping process
 *
 * Convert the characters in @text into glyphs.
 *
 * Given a segment of text and the corresponding `PangoAnalysis` structure
 * returned from [func@itemize], convert the characters into glyphs. You may
 * also pass in only a substring of the item from [func@itemize].
 *
 * This is similar to [func@shape_full], except it also takes flags that can
 * influence the shaping process.
 *
 * Note that the extra attributes in the @analyis that is returned from
 * [func@itemize] have indices that are relative to the entire paragraph,
 * so you do not pass the full paragraph text as @paragraph_text, you need
 * to subtract the item offset from their indices before calling
 * [func@shape_with_flags].
 *
 * Since: 1.44
 */
void
pango_shape_with_flags (const gchar         *item_text,
                        gint                 item_length,
                        const gchar         *paragraph_text,
                        gint                 paragraph_length,
                        const PangoAnalysis *analysis,
                        PangoGlyphString    *glyphs,
                        PangoShapeFlags      flags)
{
  int i;
  int last_cluster;

  glyphs->num_glyphs = 0;

  if (item_length == -1)
    item_length = strlen (item_text);

  if (!paragraph_text)
    {
      paragraph_text = item_text;
      paragraph_length = item_length;
    }
  if (paragraph_length == -1)
    paragraph_length = strlen (paragraph_text);

  g_return_if_fail (paragraph_text <= item_text);
  g_return_if_fail (paragraph_text + paragraph_length >= item_text + item_length);

  if (analysis->font)
    {
      pango_hb_shape (analysis->font,
                      item_text, item_length,
                      analysis, glyphs,
                      paragraph_text, paragraph_length);

      if (G_UNLIKELY (glyphs->num_glyphs == 0))
	{
	  /* If a font has been correctly chosen, but no glyphs are output,
	   * there's probably something wrong with the font.
	   *
	   * Trying to be informative, we print out the font description,
	   * and the text, but to not flood the terminal with
	   * zillions of the message, we set a flag to only err once per
	   * font.
	   */
          GQuark warned_quark = g_quark_from_static_string ("pango-shape-fail-warned");

	  if (!g_object_get_qdata (G_OBJECT (analysis->font), warned_quark))
	    {
              PangoFontDescription *desc;
              char *font_name;

              desc = pango_font_describe (analysis->font);
              font_name = pango_font_description_to_string (desc);
              pango_font_description_free (desc);

              g_warning ("shaping failure, expect ugly output. font='%s', text='%.*s'",
                         font_name, item_length, item_text);

              g_free (font_name);

              g_object_set_qdata (G_OBJECT (analysis->font), warned_quark,
                                  GINT_TO_POINTER (1));
            }
	}
    }
  else
    glyphs->num_glyphs = 0;

  if (G_UNLIKELY (!glyphs->num_glyphs))
    {
      fallback_shape (item_text, item_length, analysis, glyphs);
      if (G_UNLIKELY (!glyphs->num_glyphs))
        return;
    }

  /* make sure last_cluster is invalid */
  last_cluster = glyphs->log_clusters[0] - 1;
  for (i = 0; i < glyphs->num_glyphs; i++)
    {
      /* Set glyphs[i].attr.is_cluster_start based on log_clusters[] */
      if (glyphs->log_clusters[i] != last_cluster)
	{
	  glyphs->glyphs[i].attr.is_cluster_start = TRUE;
	  last_cluster = glyphs->log_clusters[i];
	}
      else
	glyphs->glyphs[i].attr.is_cluster_start = FALSE;


      /* Shift glyph if width is negative, and negate width.
       * This is useful for rotated font matrices and shouldn't
       * harm in normal cases.
       */
      if (glyphs->glyphs[i].geometry.width < 0)
	{
	  glyphs->glyphs[i].geometry.width = -glyphs->glyphs[i].geometry.width;
	  glyphs->glyphs[i].geometry.x_offset += glyphs->glyphs[i].geometry.width;
	}
    }

  /* Make sure glyphstring direction conforms to analysis->level */
  if (G_UNLIKELY ((analysis->level & 1) &&
		  glyphs->log_clusters[0] < glyphs->log_clusters[glyphs->num_glyphs - 1]))
    {
      g_warning ("Expected RTL run but got LTR. Fixing.");

      /* *Fix* it so we don't crash later */
      pango_glyph_string_reverse_range (glyphs, 0, glyphs->num_glyphs);
    }

  if (flags & PANGO_SHAPE_ROUND_POSITIONS)
    {
      if (PANGO_IS_FC_FONT (analysis->font))
        {
          PangoFcFont *fc_font = (PangoFcFont *)analysis->font;
          if (fc_font->is_hinted)
            {
              double x_scale_inv, y_scale_inv;
              double x_scale, y_scale;

              x_scale_inv = y_scale_inv = 1.0;
              pango_matrix_get_font_scale_factors (&fc_font->matrix, &x_scale_inv, &y_scale_inv);
              if (PANGO_GRAVITY_IS_IMPROPER (analysis->gravity))
                {
                  x_scale_inv = -x_scale_inv;
                  y_scale_inv = -y_scale_inv;
                }
              x_scale = 1. / x_scale_inv;
              y_scale = 1. / y_scale_inv;

              if (x_scale == 1.0 && y_scale == 1.0)
                {
                  for (i = 0; i < glyphs->num_glyphs; i++)
                    glyphs->glyphs[i].geometry.width = PANGO_UNITS_ROUND (glyphs->glyphs[i].geometry.width);
                }
              else
                {
    #if 0
                  if (PANGO_GRAVITY_IS_VERTICAL (analysis->gravity))
                    {
                      /* XXX */
                      double tmp = x_scale;
                      x_scale = y_scale;
                      y_scale = -tmp;
                    }
    #endif
    #define HINT(value, scale_inv, scale) (PANGO_UNITS_ROUND ((int) ((value) * scale)) * scale_inv)
    #define HINT_X(value) HINT ((value), x_scale, x_scale_inv)
    #define HINT_Y(value) HINT ((value), y_scale, y_scale_inv)
                  for (i = 0; i < glyphs->num_glyphs; i++)
                    {
                      glyphs->glyphs[i].geometry.width    = HINT_X (glyphs->glyphs[i].geometry.width);
                      glyphs->glyphs[i].geometry.x_offset = HINT_X (glyphs->glyphs[i].geometry.x_offset);
                      glyphs->glyphs[i].geometry.y_offset = HINT_Y (glyphs->glyphs[i].geometry.y_offset);
                    }
    #undef HINT_Y
    #undef HINT_X
    #undef HINT
                }
            }
        }
    }
}
