/* Pango
 * pango-coverage.c: Coverage maps for fonts
 *
 * Copyright (C) 2000 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.
 */

/**
 * SECTION:coverage-maps
 * @short_description:Unicode character range coverage storage
 * @title:Coverage Maps
 *
 * It is often necessary in Pango to determine if a particular font can
 * represent a particular character, and also how well it can represent
 * that character. The #PangoCoverage is a data structure that is used
 * to represent that information.
 */
#include "config.h"
#include <string.h>

#include "pango-coverage.h"

typedef struct _PangoBlockInfo PangoBlockInfo;

#define N_BLOCKS_INCREMENT 256

/* The structure of a PangoCoverage object is a two-level table, with blocks of size 256.
 * each block is stored as a packed array of 2 bit values for each index, in LSB order.
 */

struct _PangoBlockInfo
{
  guchar *data;
  PangoCoverageLevel level;	/* Used if data == NULL */
};

struct _PangoCoverage
{
  guint ref_count;
  int n_blocks;

  PangoBlockInfo *blocks;
};

/**
 * pango_coverage_new:
 *
 * Create a new #PangoCoverage
 *
 * Return value: the newly allocated #PangoCoverage,
 *               initialized to %PANGO_COVERAGE_NONE
 *               with a reference count of one, which
 *               should be freed with pango_coverage_unref().
 **/
PangoCoverage *
pango_coverage_new (void)
{
  PangoCoverage *coverage = g_slice_new (PangoCoverage);

  coverage->n_blocks = N_BLOCKS_INCREMENT;
  coverage->blocks = g_new0 (PangoBlockInfo, coverage->n_blocks);
  coverage->ref_count = 1;

  return coverage;
}

/**
 * pango_coverage_copy:
 * @coverage: a #PangoCoverage
 *
 * Copy an existing #PangoCoverage. (This function may now be unnecessary
 * since we refcount the structure. File a bug if you use it.)
 *
 * Return value: (transfer full): the newly allocated #PangoCoverage,
 *               with a reference count of one, which should be freed
 *               with pango_coverage_unref().
 **/
PangoCoverage *
pango_coverage_copy (PangoCoverage *coverage)
{
  int i;
  PangoCoverage *result;

  g_return_val_if_fail (coverage != NULL, NULL);

  result = g_slice_new (PangoCoverage);
  result->n_blocks = coverage->n_blocks;
  result->blocks = g_new (PangoBlockInfo, coverage->n_blocks);
  result->ref_count = 1;

  for (i=0; i<coverage->n_blocks; i++)
    {
      if (coverage->blocks[i].data)
	{
	  result->blocks[i].data = g_new (guchar, 64);
	  memcpy (result->blocks[i].data, coverage->blocks[i].data, 64);
	}
      else
	result->blocks[i].data = NULL;

      result->blocks[i].level = coverage->blocks[i].level;
    }

  return result;
}

/**
 * pango_coverage_ref:
 * @coverage: a #PangoCoverage
 *
 * Increase the reference count on the #PangoCoverage by one
 *
 * Return value: @coverage
 **/
PangoCoverage *
pango_coverage_ref (PangoCoverage *coverage)
{
  g_return_val_if_fail (coverage != NULL, NULL);

  g_atomic_int_inc ((int *) &coverage->ref_count);

  return coverage;
}

/**
 * pango_coverage_unref:
 * @coverage: a #PangoCoverage
 *
 * Decrease the reference count on the #PangoCoverage by one.
 * If the result is zero, free the coverage and all associated memory.
 **/
void
pango_coverage_unref (PangoCoverage *coverage)
{
  int i;

  g_return_if_fail (coverage != NULL);
  g_return_if_fail (coverage->ref_count > 0);

  if (g_atomic_int_dec_and_test ((int *) &coverage->ref_count))
    {
      for (i=0; i<coverage->n_blocks; i++)
	g_slice_free1 (64, coverage->blocks[i].data);

      g_free (coverage->blocks);
      g_slice_free (PangoCoverage, coverage);
    }
}

/**
 * pango_coverage_get:
 * @coverage: a #PangoCoverage
 * @index_: the index to check
 *
 * Determine whether a particular index is covered by @coverage
 *
 * Return value: the coverage level of @coverage for character @index_.
 **/
PangoCoverageLevel
pango_coverage_get (PangoCoverage *coverage,
		    int            index)
{
  int block_index;

  g_return_val_if_fail (coverage != NULL, PANGO_COVERAGE_NONE);

  /* index should really have been defined unsigned.  Work around
   * it by just returning NONE.
   */
  if (G_UNLIKELY (index < 0))
    return PANGO_COVERAGE_NONE;

  block_index = index / 256;

  if (block_index >= coverage->n_blocks)
    return PANGO_COVERAGE_NONE;
  else
    {
      guchar *data = coverage->blocks[block_index].data;
      if (data)
	{
	  int i = index % 256;
	  int shift = (i % 4) * 2;

	  return (data[i/4] >> shift) & 0x3;
	}
      else
	return coverage->blocks[block_index].level;
    }
}

/**
 * pango_coverage_set:
 * @coverage: a #PangoCoverage
 * @index_: the index to modify
 * @level: the new level for @index_
 *
 * Modify a particular index within @coverage
 **/
void
pango_coverage_set (PangoCoverage     *coverage,
		    int                index,
		    PangoCoverageLevel level)
{
  int block_index, i;
  guchar *data;

  g_return_if_fail (coverage != NULL);
  g_return_if_fail (index >= 0);
  g_return_if_fail ((guint) level <= 3);

  block_index = index / 256;

  if (block_index >= coverage->n_blocks)
    {
      int old_n_blocks = coverage->n_blocks;

      coverage->n_blocks =
	N_BLOCKS_INCREMENT * ((block_index + N_BLOCKS_INCREMENT) / N_BLOCKS_INCREMENT);

      coverage->blocks = g_renew (PangoBlockInfo, coverage->blocks, coverage->n_blocks);
      memset (coverage->blocks + old_n_blocks, 0,
	      sizeof (PangoBlockInfo) * (coverage->n_blocks - old_n_blocks));
    }

  data = coverage->blocks[block_index].data;
  if (!data)
    {
      guchar byte;

      if (level == coverage->blocks[block_index].level)
	return;

      data = g_slice_alloc (64);
      coverage->blocks[block_index].data = data;

      byte = coverage->blocks[block_index].level |
	(coverage->blocks[block_index].level << 2) |
	(coverage->blocks[block_index].level << 4) |
	(coverage->blocks[block_index].level << 6);

      memset (data, byte, 64);
    }

  i = index % 256;
  data[i/4] |= level << ((i % 4) * 2);
}

/**
 * pango_coverage_max:
 * @coverage: a #PangoCoverage
 * @other: another #PangoCoverage
 *
 * Set the coverage for each index in @coverage to be the max (better)
 * value of the current coverage for the index and the coverage for
 * the corresponding index in @other.
 **/
void
pango_coverage_max (PangoCoverage *coverage,
		    PangoCoverage *other)
{
  int block_index, i;
  int old_blocks;

  g_return_if_fail (coverage != NULL);

  old_blocks = MIN (coverage->n_blocks, other->n_blocks);

  if (other->n_blocks > coverage->n_blocks)
    {
      coverage->n_blocks = other->n_blocks;
      coverage->blocks = g_renew (PangoBlockInfo, coverage->blocks, coverage->n_blocks);

      for (block_index = old_blocks; block_index < coverage->n_blocks; block_index++)
	{
	  if (other->blocks[block_index].data)
	    {
	      coverage->blocks[block_index].data = g_new (guchar, 64);
	      memcpy (coverage->blocks[block_index].data, other->blocks[block_index].data, 64);
	    }
	  else
	    coverage->blocks[block_index].data = NULL;

	  coverage->blocks[block_index].level = other->blocks[block_index].level;
	}
    }

  for (block_index = 0; block_index < old_blocks; block_index++)
    {
      if (!coverage->blocks[block_index].data && !other->blocks[block_index].data)
	{
	  coverage->blocks[block_index].level = MAX (coverage->blocks[block_index].level, other->blocks[block_index].level);
	}
      else if (coverage->blocks[block_index].data && other->blocks[block_index].data)
	{
	  guchar *data = coverage->blocks[block_index].data;

	  for (i=0; i<64; i++)
	    {
	      int byte1 = data[i];
	      int byte2 = other->blocks[block_index].data[i];

	      /* There are almost certainly some clever logical ops to do this */
	      data[i] =
		MAX (byte1 & 0x3, byte2 & 0x3) |
		MAX (byte1 & 0xc, byte2 & 0xc) |
		MAX (byte1 & 0x30, byte2 & 0x30) |
		MAX (byte1 & 0xc0, byte2 & 0xc0);
	    }
	}
      else
	{
	  guchar *src, *dest;
	  int level, byte2;

	  if (coverage->blocks[block_index].data)
	    {
	      src = dest = coverage->blocks[block_index].data;
	      level = other->blocks[block_index].level;
	    }
	  else
	    {
	      src = other->blocks[block_index].data;
	      dest = g_new (guchar, 64);
	      coverage->blocks[block_index].data = dest;
	      level = coverage->blocks[block_index].level;
	    }

	  byte2 = level | (level << 2) | (level << 4) | (level << 6);

	  for (i=0; i<64; i++)
	    {
	      int byte1 = src[i];

	      /* There are almost certainly some clever logical ops to do this */
	      dest[i] =
		MAX (byte1 & 0x3, byte2 & 0x3) |
		MAX (byte1 & 0xc, byte2 & 0xc) |
		MAX (byte1 & 0x30, byte2 & 0x30) |
		MAX (byte1 & 0xc0, byte2 & 0xc0);
	    }
	}
    }
}

#define PANGO_COVERAGE_MAGIC 0xc89dbd5e

/**
 * pango_coverage_to_bytes:
 * @coverage: a #PangoCoverage
 * @bytes: (out) (array length=n_bytes) (element-type guint8):
 *   location to store result (must be freed with g_free())
 * @n_bytes: (out): location to store size of result
 *
 * Convert a #PangoCoverage structure into a flat binary format
 **/
void
pango_coverage_to_bytes   (PangoCoverage  *coverage,
			   guchar        **bytes,
			   int            *n_bytes)
{
  int i, j;
  int size = 8 + 4 * coverage->n_blocks;
  guchar *data;
  int offset;

  for (i=0; i<coverage->n_blocks; i++)
    {
      if (coverage->blocks[i].data)
	size += 64;
    }

  data = g_malloc (size);

  *(guint32 *)&data[0] = g_htonl (PANGO_COVERAGE_MAGIC); /* Magic */
  *(guint32 *)&data[4] = g_htonl (coverage->n_blocks);
  offset = 8;

  for (i=0; i<coverage->n_blocks; i++)
    {
      guint32 header_val;

      /* Check for solid blocks. This is a sort of random place
       * to do the optimization, but we care most about getting
       * it right when storing it somewhere persistant.
       */
      if (coverage->blocks[i].data != NULL)
	{
	  guchar *data = coverage->blocks[i].data;
	  guchar first_val = data[0];

	  if (first_val == 0 || first_val == 0xff)
	    {
	      for (j = 1 ; j < 64; j++)
		if (data[j] != first_val)
		  break;

	      if (j == 64)
		{
		  g_slice_free1 (64, data);
		  coverage->blocks[i].data = NULL;
		  coverage->blocks[i].level = first_val & 0x3;
		}
	    }
	}

      if (coverage->blocks[i].data != NULL)
	header_val = (guint32)-1;
      else
	header_val = coverage->blocks[i].level;

      *(guint32 *)&data[offset] = g_htonl (header_val);
      offset += 4;

      if (coverage->blocks[i].data)
	{
	  memcpy (data + offset, coverage->blocks[i].data, 64);
	  offset += 64;
	}
    }

  *bytes = data;
  *n_bytes = size;
}

static guint32
pango_coverage_get_uint32 (guchar **ptr)
{
  guint32 val;

  memcpy (&val, *ptr, 4);
  *ptr += 4;

  return g_ntohl (val);
}

/**
 * pango_coverage_from_bytes:
 * @bytes: (array length=n_bytes) (element-type guint8): binary data
 *   representing a #PangoCoverage
 * @n_bytes: the size of @bytes in bytes
 *
 * Convert data generated from pango_coverage_to_bytes() back
 * to a #PangoCoverage
 *
 * Return value: (transfer full) (nullable): a newly allocated
 *               #PangoCoverage, or %NULL if the data was invalid.
 **/
PangoCoverage *
pango_coverage_from_bytes (guchar *bytes,
			   int     n_bytes)
{
  PangoCoverage *coverage = g_slice_new0 (PangoCoverage);
  guchar *ptr = bytes;
  int i;

  coverage->ref_count = 1;

  if (n_bytes < 8)
    goto error;

  if (pango_coverage_get_uint32 (&ptr) != PANGO_COVERAGE_MAGIC)
    goto error;

  coverage->n_blocks = pango_coverage_get_uint32 (&ptr);
  coverage->blocks = g_new0 (PangoBlockInfo, coverage->n_blocks);

  for (i = 0; i < coverage->n_blocks; i++)
    {
      guint val;

      if (ptr + 4 > bytes + n_bytes)
	goto error;

      val = pango_coverage_get_uint32 (&ptr);
      if (val == (guint32)-1)
	{
	  if (ptr + 64 > bytes + n_bytes)
	    goto error;

	  coverage->blocks[i].data = g_new (guchar, 64);
	  memcpy (coverage->blocks[i].data, ptr, 64);
	  ptr += 64;
	}
      else
	coverage->blocks[i].level = val;
    }

  return coverage;

 error:

  pango_coverage_unref (coverage);
  return NULL;
}


