/* Pango
 * pango-ot-buffer.c: Buffer of glyphs for shaping/positioning
 *
 * Copyright (C) 2004 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 "pango-ot-private.h"

#define PANGO_SCALE_26_6 (PANGO_SCALE / (1<<6))
#define PANGO_UNITS_26_6(d) (PANGO_SCALE_26_6 * (d))

/**
 * pango_ot_buffer_new
 * @font: a #PangoFcFont
 *
 * Since: 1.4
 **/ 
PangoOTBuffer *
pango_ot_buffer_new (PangoFcFont *font)
{
  /* We lock the font here immediately for the silly reason
   * of getting the FT_Memory; otherwise we'd have to
   * add a new operation to PangoFcFontmap; callers will
   * probably already have the font locked, however,
   * so there is little performance penalty.
   */
  PangoOTBuffer *buffer = g_new (PangoOTBuffer, 1);
  FT_Face face = pango_fc_font_lock_face (font);

  if (otl_buffer_new (face->memory, &buffer->buffer) != FT_Err_Ok)
    g_error ("Allocation of OTLBuffer failed");

  buffer->font = g_object_ref (font);
  buffer->applied_gpos = FALSE;
  buffer->rtl = FALSE;
  buffer->zero_width_marks = FALSE;

  pango_fc_font_unlock_face (font);

  return buffer;
}

/**
 * pango_ot_buffer_destroy
 * @buffer: a #PangoOTBuffer
 *
 * Since: 1.4
 **/ 
void
pango_ot_buffer_destroy (PangoOTBuffer *buffer)
{
  otl_buffer_free (buffer->buffer);
  g_object_unref (buffer->font);
  g_free (buffer);
}

/**
 * pango_ot_buffer_clear
 * @buffer: a #PangoOTBuffer
 *
 * Since: 1.4
 **/ 
void
pango_ot_buffer_clear (PangoOTBuffer *buffer)
{
  otl_buffer_clear (buffer->buffer);
  buffer->applied_gpos = FALSE;
}

/**
 * pango_ot_buffer_add_glyph
 * @buffer: a #PangoOTBuffer
 * @glyph_index:
 * @properties:
 * @cluster: 
 *
 * Since: 1.4
 **/ 
void
pango_ot_buffer_add_glyph (PangoOTBuffer *buffer,
			   guint          glyph_index,
			   guint          properties,
			   guint          cluster)
{
  otl_buffer_add_glyph (buffer->buffer,
			glyph_index, properties, cluster);

}

/**
 * pango_ot_buffer_set_rtl
 * @buffer: a #PangoOTBuffer
 * @rtl: %TRUE for right-to-left 
 *
 * Since: 1.4
 **/ 
void
pango_ot_buffer_set_rtl (PangoOTBuffer *buffer,
			 gboolean       rtl)
{
  rtl = rtl != FALSE;
  buffer->rtl = rtl;
}

/**
 * pango_ot_buffer_set_zero_width_marks:
 * @buffer: a #PangoOTBuffer
 * @zero_width_marks: %TRUE if characters with a mark class should
 *  be forced to zero width.
 * 
 * Sets whether characters with a mark class should be forced to zero width.
 * This setting is needed for proper positioning of Arabic accents,
 * but will produce incorrect results with standard OpenType indic
 * fonts.
 *
 * Since: 1.6
 **/
void
pango_ot_buffer_set_zero_width_marks (PangoOTBuffer     *buffer,
				      gboolean           zero_width_marks)
{
  buffer->zero_width_marks = zero_width_marks != FALSE;
}

/**
 * pango_ot_buffer_get_glyphs
 * @buffer: a #PangoOTBuffer
 * @glyph: 
 * @n_glyphs:
 *
 * Since: 1.4
 **/ 
void
pango_ot_buffer_get_glyphs (PangoOTBuffer  *buffer,
			    PangoOTGlyph  **glyphs,
			    int            *n_glyphs)
{
  if (glyphs)
    *glyphs = (PangoOTGlyph *)buffer->buffer->in_string;

  if (n_glyphs)
    *n_glyphs = buffer->buffer->in_length;
}

static void
swap_range (PangoGlyphString *glyphs, int start, int end)
{
  int i, j;
  
  for (i = start, j = end - 1; i < j; i++, j--)
    {
      PangoGlyphInfo glyph_info;
      gint log_cluster;
      
      glyph_info = glyphs->glyphs[i];
      glyphs->glyphs[i] = glyphs->glyphs[j];
      glyphs->glyphs[j] = glyph_info;
      
      log_cluster = glyphs->log_clusters[i];
      glyphs->log_clusters[i] = glyphs->log_clusters[j];
      glyphs->log_clusters[j] = log_cluster;
    }
}

static void
apply_gpos_ltr (PangoGlyphString *glyphs,
		OTL_Position      positions)
{
  int i;
	    
  for (i = 0; i < glyphs->num_glyphs; i++)
    {
      FT_Pos x_pos = positions[i].x_pos;
      FT_Pos y_pos = positions[i].y_pos;
      int back = i;
      int j;
      
      while (positions[back].back != 0)
	{
	  back  -= positions[back].back;
	  x_pos += positions[back].x_pos;
	  y_pos += positions[back].y_pos;
	}
      
      for (j = back; j < i; j++)
	glyphs->glyphs[i].geometry.x_offset -= glyphs->glyphs[j].geometry.width;
      
      glyphs->glyphs[i].geometry.x_offset += PANGO_UNITS_26_6(x_pos);
      glyphs->glyphs[i].geometry.y_offset -= PANGO_UNITS_26_6(y_pos);
      
      if (positions[i].new_advance)
	glyphs->glyphs[i].geometry.width  = PANGO_UNITS_26_6(positions[i].x_advance);
      else
	glyphs->glyphs[i].geometry.width += PANGO_UNITS_26_6(positions[i].x_advance);
    }
}

static void
apply_gpos_rtl (PangoGlyphString *glyphs,
		OTL_Position      positions)
{
  int i;
  
  for (i = 0; i < glyphs->num_glyphs; i++)
    {
      int i_rev = glyphs->num_glyphs - i - 1;
      int back_rev = i_rev;
      int back;
      FT_Pos x_pos = positions[i_rev].x_pos;
      FT_Pos y_pos = positions[i_rev].y_pos;
      int j;

      while (positions[back_rev].back != 0)
	{
	  back_rev -= positions[back_rev].back;
	  x_pos += positions[back_rev].x_pos;
	  y_pos += positions[back_rev].y_pos;
	}

      back = glyphs->num_glyphs - back_rev - 1;
      
      for (j = i; j < back; j++)
	glyphs->glyphs[i].geometry.x_offset += glyphs->glyphs[j].geometry.width;
      
      glyphs->glyphs[i].geometry.x_offset += PANGO_UNITS_26_6(x_pos);
      glyphs->glyphs[i].geometry.y_offset -= PANGO_UNITS_26_6(y_pos);
      
      if (positions[i_rev].new_advance)
	glyphs->glyphs[i].geometry.width  = PANGO_UNITS_26_6(positions[i_rev].x_advance);
      else
	glyphs->glyphs[i].geometry.width += PANGO_UNITS_26_6(positions[i_rev].x_advance);
    }
}

/**
 * pango_ot_buffer_output
 * @buffer: a #PangoOTBuffer
 * @glyphs: a #PangoGlyphString
 *
 * Since: 1.4
 **/ 
void
pango_ot_buffer_output (PangoOTBuffer    *buffer,
			PangoGlyphString *glyphs)
{
  FT_Face face;
  PangoOTInfo *info;
  TTO_GDEF gdef = NULL;
  int i;
  int last_cluster;

  face = pango_fc_font_lock_face (buffer->font);
  g_assert (face);
  
  /* Copy glyphs into output glyph string */
  pango_glyph_string_set_size (glyphs, buffer->buffer->in_length);

  last_cluster = -1;
  for (i = 0; i < buffer->buffer->in_length; i++)
    {
      OTL_GlyphItem item = &buffer->buffer->in_string[i];
      
      glyphs->glyphs[i].glyph = item->gindex;

      glyphs->log_clusters[i] = item->cluster;
      if (glyphs->log_clusters[i] != last_cluster)
	glyphs->glyphs[i].attr.is_cluster_start = 1;
      else
	glyphs->glyphs[i].attr.is_cluster_start = 0;

      last_cluster = glyphs->log_clusters[i];
    }

  info = pango_ot_info_get (face);
  gdef = pango_ot_info_get_gdef (info);
  
  /* Apply default positioning */
  for (i = 0; i < glyphs->num_glyphs; i++)
    {
      if (glyphs->glyphs[i].glyph)
	{
	  PangoRectangle logical_rect;
	  
	  FT_UShort property;

	  if (buffer->zero_width_marks &&
	      gdef &&
	      TT_GDEF_Get_Glyph_Property (gdef, glyphs->glyphs[i].glyph, &property) == FT_Err_Ok &&
	      (property == TTO_MARK || (property & IGNORE_SPECIAL_MARKS) != 0))
	    {
	      glyphs->glyphs[i].geometry.width = 0;
	    }
	  else
	    {
	      pango_font_get_glyph_extents ((PangoFont *)buffer->font, glyphs->glyphs[i].glyph, NULL, &logical_rect);
	      glyphs->glyphs[i].geometry.width = logical_rect.width;
	    }
	}
      else
	glyphs->glyphs[i].geometry.width = 0;
  
      glyphs->glyphs[i].geometry.x_offset = 0;
      glyphs->glyphs[i].geometry.y_offset = 0;
    }

  if (buffer->rtl)
    {
      /* Swap all glyphs */
      swap_range (glyphs, 0, glyphs->num_glyphs);
    }

  if (buffer->applied_gpos)
    {
      if (buffer->rtl)
	apply_gpos_rtl (glyphs, buffer->buffer->positions);
      else
	apply_gpos_ltr (glyphs, buffer->buffer->positions);
    }

  pango_fc_font_unlock_face (buffer->font);
}
