/* Pango
 * pango-utils.c: Utilities for internal functions and modules
 *
 * 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:pango-version
 * @short_description:Tools for checking Pango version at compile- and run-time.
 * @title:Version Checking
 *
 * The capital-letter macros defined here can be used to check the version of Pango
 * at compile-time, and to <firstterm>encode</firstterm> Pango versions into integers.
 *
 * The functions can be used to check the version of the linked Pango library at run-time.
 */
/**
 * SECTION:utils
 * @short_description:Various convenience and utility functions
 * @title: Miscellaneous Utilities
 *
 * The functions and utilities in this section are mostly used from Pango
 * backends and modules, but may be useful for other purposes too.
 */
#include "config.h"
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <locale.h>

#include "pango-font.h"
#include "pango-features.h"
#include "pango-impl-utils.h"

#include <glib/gstdio.h>

#ifndef HAVE_FLOCKFILE
#  define flockfile(f) (void)1
#  define funlockfile(f) (void)1
#  define getc_unlocked(f) getc(f)
#endif /* !HAVE_FLOCKFILE */

#ifdef G_OS_WIN32

#include <sys/types.h>

#define STRICT
#include <windows.h>

#endif

/**
 * pango_version:
 *
 * This is similar to the macro %PANGO_VERSION except that
 * it returns the encoded version of Pango available at run-time,
 * as opposed to the version available at compile-time.
 *
 * A version number can be encoded into an integer using
 * PANGO_VERSION_ENCODE().
 *
 * Returns: The encoded version of Pango library
 *   available at run time.
 *
 * Since: 1.16
 **/
int
pango_version (void)
{
  return PANGO_VERSION;
}

/**
 * pango_version_string:
 *
 * This is similar to the macro %PANGO_VERSION_STRING except that
 * it returns the version of Pango available at run-time, as opposed to
 * the version available at compile-time.
 *
 * Returns: A string containing the version of Pango library
 *   available at run time.
 *   The returned string is owned by Pango and should not be modified
 *   or freed.
 *
 * Since: 1.16
 **/
const char *
pango_version_string (void)
{
  return PANGO_VERSION_STRING;
}

/**
 * pango_version_check:
 * @required_major: the required major version.
 * @required_minor: the required minor version.
 * @required_micro: the required major version.
 *
 * Checks that the Pango library in use is compatible with the
 * given version. Generally you would pass in the constants
 * %PANGO_VERSION_MAJOR, %PANGO_VERSION_MINOR, %PANGO_VERSION_MICRO
 * as the three arguments to this function; that produces
 * a check that the library in use at run-time is compatible with
 * the version of Pango the application or module was compiled against.
 *
 * Compatibility is defined by two things: first the version
 * of the running library is newer than the version
 * @required_major.required_minor.@required_micro. Second
 * the running library must be binary compatible with the
 * version @required_major.required_minor.@required_micro
 * (same major version.)
 *
 * For compile-time version checking use PANGO_VERSION_CHECK().
 *
 * Return value: %NULL if the Pango library is compatible with the
 *   given version, or a string describing the version mismatch.
 *   The returned string is owned by Pango and should not be modified
 *   or freed.
 *
 * Since: 1.16
 **/
const gchar*
pango_version_check (int required_major,
		     int required_minor,
		     int required_micro)
{
  gint pango_effective_micro = 100 * PANGO_VERSION_MINOR + PANGO_VERSION_MICRO;
  gint required_effective_micro = 100 * required_minor + required_micro;

  if (required_major < PANGO_VERSION_MAJOR)
    return "Pango version too new (major mismatch)";
  if (required_effective_micro < pango_effective_micro - PANGO_BINARY_AGE)
    return "Pango version too new (micro mismatch)";
  if (required_effective_micro > pango_effective_micro)
    return "Pango version too old (micro mismatch)";
  return NULL;
}

/**
 * pango_trim_string:
 * @str: a string
 *
 * Trims leading and trailing whitespace from a string.
 *
 * Return value: A newly-allocated string that must be freed with g_free()
 **/
char *
pango_trim_string (const char *str)
{
  int len;

  g_return_val_if_fail (str != NULL, NULL);

  while (*str && g_ascii_isspace (*str))
    str++;

  len = strlen (str);
  while (len > 0 && g_ascii_isspace (str[len-1]))
    len--;

  return g_strndup (str, len);
}

/**
 * pango_split_file_list:
 * @str: a %G_SEARCHPATH_SEPARATOR separated list of filenames
 *
 * Splits a %G_SEARCHPATH_SEPARATOR-separated list of files, stripping
 * white space and substituting ~/ with $HOME/.
 *
 * Return value: (transfer full) (array zero-terminated=1): a list of
 * strings to be freed with g_strfreev()
 **/
char **
pango_split_file_list (const char *str)
{
  int i = 0;
  int j;
  char **files;

  files = g_strsplit (str, G_SEARCHPATH_SEPARATOR_S, -1);

  while (files[i])
    {
      char *file = pango_trim_string (files[i]);

      /* If the resulting file is empty, skip it */
      if (file[0] == '\0')
	{
	  g_free(file);
	  g_free (files[i]);

	  for (j = i + 1; files[j]; j++)
	    files[j - 1] = files[j];

	  files[j - 1] = NULL;

	  continue;
	}
#ifndef G_OS_WIN32
      /* '~' is a quite normal and common character in file names on
       * Windows, especially in the 8.3 versions of long file names, which
       * still occur now and then. Also, few Windows user are aware of the
       * Unix shell convention that '~' stands for the home directory,
       * even if they happen to have a home directory.
       */
      if (file[0] == '~' && file[1] == G_DIR_SEPARATOR)
	{
	  char *tmp = g_strconcat (g_get_home_dir(), file + 1, NULL);
	  g_free (file);
	  file = tmp;
	}
      else if (file[0] == '~' && file[1] == '\0')
	{
	  g_free (file);
	  file = g_strdup (g_get_home_dir());
	}
#endif
      g_free (files[i]);
      files[i] = file;

      i++;
    }

  return files;
}

/**
 * pango_read_line:
 * @stream: a stdio stream
 * @str: (out): #GString buffer into which to write the result
 *
 * Reads an entire line from a file into a buffer. Lines may
 * be delimited with '\n', '\r', '\n\r', or '\r\n'. The delimiter
 * is not written into the buffer. Text after a '#' character is treated as
 * a comment and skipped. '\' can be used to escape a # character.
 * '\' proceeding a line delimiter combines adjacent lines. A '\' proceeding
 * any other character is ignored and written into the output buffer
 * unmodified.
 *
 * Return value: 0 if the stream was already at an %EOF character, otherwise
 *               the number of lines read (this is useful for maintaining
 *               a line number counter which doesn't combine lines with '\')
 **/
gint
pango_read_line (FILE *stream, GString *str)
{
  gboolean quoted = FALSE;
  gboolean comment = FALSE;
  int n_read = 0;
  int lines = 1;

  flockfile (stream);

  g_string_truncate (str, 0);

  while (1)
    {
      int c;

      c = getc_unlocked (stream);

      if (c == EOF)
	{
	  if (quoted)
	    g_string_append_c (str, '\\');

	  goto done;
	}
      else
	n_read++;

      if (quoted)
	{
	  quoted = FALSE;

	  switch (c)
	    {
	    case '#':
	      g_string_append_c (str, '#');
	      break;
	    case '\r':
	    case '\n':
	      {
		int next_c = getc_unlocked (stream);

		if (!(next_c == EOF ||
		      (c == '\r' && next_c == '\n') ||
		      (c == '\n' && next_c == '\r')))
		  ungetc (next_c, stream);

		lines++;

		break;
	      }
	    default:
	      g_string_append_c (str, '\\');
	      g_string_append_c (str, c);
	    }
	}
      else
	{
	  switch (c)
	    {
	    case '#':
	      comment = TRUE;
	      break;
	    case '\\':
	      if (!comment)
		quoted = TRUE;
	      break;
	    case '\n':
	      {
		int next_c = getc_unlocked (stream);

		if (!(c == EOF ||
		      (c == '\r' && next_c == '\n') ||
		      (c == '\n' && next_c == '\r')))
		  ungetc (next_c, stream);

		goto done;
	      }
	    default:
	      if (!comment)
		g_string_append_c (str, c);
	    }
	}
    }

 done:

  funlockfile (stream);

  return (n_read > 0) ? lines : 0;
}

/**
 * pango_skip_space:
 * @pos: (inout): in/out string position
 *
 * Skips 0 or more characters of white space.
 *
 * Return value: %FALSE if skipping the white space leaves
 * the position at a '\0' character.
 **/
gboolean
pango_skip_space (const char **pos)
{
  const char *p = *pos;

  while (g_ascii_isspace (*p))
    p++;

  *pos = p;

  return !(*p == '\0');
}

/**
 * pango_scan_word:
 * @pos: (inout): in/out string position
 * @out: (out): a #GString into which to write the result
 *
 * Scans a word into a #GString buffer. A word consists
 * of [A-Za-z_] followed by zero or more [A-Za-z_0-9]
 * Leading white space is skipped.
 *
 * Return value: %FALSE if a parse error occurred.
 **/
gboolean
pango_scan_word (const char **pos, GString *out)
{
  const char *p = *pos;

  while (g_ascii_isspace (*p))
    p++;

  if (!((*p >= 'A' && *p <= 'Z') ||
	(*p >= 'a' && *p <= 'z') ||
	*p == '_'))
    return FALSE;

  g_string_truncate (out, 0);
  g_string_append_c (out, *p);
  p++;

  while ((*p >= 'A' && *p <= 'Z') ||
	 (*p >= 'a' && *p <= 'z') ||
	 (*p >= '0' && *p <= '9') ||
	 *p == '_')
    {
      g_string_append_c (out, *p);
      p++;
    }

  *pos = p;

  return TRUE;
}

/**
 * pango_scan_string:
 * @pos: (inout): in/out string position
 * @out: (out): a #GString into which to write the result
 *
 * Scans a string into a #GString buffer. The string may either
 * be a sequence of non-white-space characters, or a quoted
 * string with '"'. Instead a quoted string, '\"' represents
 * a literal quote. Leading white space outside of quotes is skipped.
 *
 * Return value: %FALSE if a parse error occurred.
 **/
gboolean
pango_scan_string (const char **pos, GString *out)
{
  const char *p = *pos;

  while (g_ascii_isspace (*p))
    p++;

  if (G_UNLIKELY (!*p))
    return FALSE;
  else if (*p == '"')
    {
      gboolean quoted = FALSE;
      g_string_truncate (out, 0);

      p++;

      while (TRUE)
	{
	  if (quoted)
	    {
	      int c = *p;

	      switch (c)
		{
		case '\0':
		  return FALSE;
		case 'n':
		  c = '\n';
		  break;
		case 't':
		  c = '\t';
		  break;
		default:
		  break;
		}

	      quoted = FALSE;
	      g_string_append_c (out, c);
	    }
	  else
	    {
	      switch (*p)
		{
		case '\0':
		  return FALSE;
		case '\\':
		  quoted = TRUE;
		  break;
		case '"':
		  p++;
		  goto done;
		default:
		  g_string_append_c (out, *p);
		  break;
		}
	    }
	  p++;
	}
    done:
      ;
    }
  else
    {
      g_string_truncate (out, 0);

      while (*p && !g_ascii_isspace (*p))
	{
	  g_string_append_c (out, *p);
	  p++;
	}
    }

  *pos = p;

  return TRUE;
}

/**
 * pango_scan_int:
 * @pos: (inout): in/out string position
 * @out: (out): an int into which to write the result
 *
 * Scans an integer.
 * Leading white space is skipped.
 *
 * Return value: %FALSE if a parse error occurred.
 **/
gboolean
pango_scan_int (const char **pos, int *out)
{
  char *end;
  long temp;

  errno = 0;
  temp = strtol (*pos, &end, 10);
  if (errno == ERANGE)
    {
      errno = 0;
      return FALSE;
    }

  *out = (int)temp;
  if ((long)(*out) != temp)
    {
      return FALSE;
    }

  *pos = end;

  return TRUE;
}


static void
read_config_file (const char *filename, gboolean enoent_error, GHashTable *ht)
{
  GKeyFile *key_file = g_key_file_new();
  GError *key_file_error = NULL;
  gchar **groups;
  gsize groups_count = 0;
  guint group_index;

  if (!g_key_file_load_from_file(key_file,filename, 0, &key_file_error))
    {
      if (key_file_error)
	{
	  if (key_file_error->domain != G_FILE_ERROR || key_file_error->code != G_FILE_ERROR_NOENT || enoent_error)
	    {
	      g_warning ("error opening config file '%s': %s\n",
			  filename, key_file_error->message);
	    }
	  g_error_free(key_file_error);
	}
      g_key_file_free(key_file);
      return;
    }

  groups = g_key_file_get_groups (key_file, &groups_count);
  for (group_index = 0; group_index < groups_count; group_index++)
    {
      gsize keys_count = 0;
      const gchar *group = groups[group_index];
      GError *keys_error = NULL;
      gchar **keys;

      keys = g_key_file_get_keys(key_file, group, &keys_count, &keys_error);

      if (keys)
	{
	  guint key_index;

	  for (key_index = 0; key_index < keys_count; key_index++)
	    {
	      const gchar *key = keys[key_index];
	      GError *key_error = NULL;
	      gchar *value =  g_key_file_get_value(key_file, group, key, &key_error);
	      if (value != NULL)
		{
		  g_hash_table_insert (ht,
				       g_strdup_printf ("%s/%s", group, key),
				       value);
		}
	      if (key_error)
		{
		  g_warning ("error getting key '%s/%s' in config file '%s'\n",
			     group, key, filename);
		  g_error_free(key_error);
		}
	    }
	  g_strfreev(keys);
	}

      if (keys_error)
	{
	  g_warning ("error getting keys in group '%s' of config file '%s'\n",
		     filename, group);
	  g_error_free(keys_error);
	}
    }
  g_strfreev(groups);
  g_key_file_free(key_file);
}

static GHashTable *
read_config_system (void)
{
  char *filename;
  GHashTable *config_hash;

  config_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
                                       (GDestroyNotify)g_free,
                                       (GDestroyNotify)g_free);

  filename = g_build_filename (pango_get_sysconf_subdirectory (),
                               "pangorc",
			       NULL);
  read_config_file (filename, FALSE, config_hash);
  g_free (filename);

  return config_hash;
}

static GHashTable *
read_config (void)
{
  static GHashTable *config_hash = NULL;

  if (g_once_init_enter (&config_hash))
    {
      GHashTable *tmp_hash;
      char *filename;
      const char *envvar;

      tmp_hash = read_config_system ();

      filename = g_build_filename (g_get_user_config_dir (),
                                   "pango",
                                   "pangorc",
                                   NULL);
      read_config_file (filename, FALSE, tmp_hash);
      g_free (filename);

      envvar = g_getenv ("PANGO_RC_FILE");
      if (envvar)
        read_config_file (envvar, TRUE, tmp_hash);

      g_once_init_leave (&config_hash, tmp_hash);
    }

  return config_hash;
}

/**
 * pango_config_key_get_system:
 * @key: Key to look up, in the form "SECTION/KEY".
 *
 * Looks up a key, consulting only the Pango system config database
 * in $sysconfdir/pango/pangorc.
 *
 * Return value: the value, if found, otherwise %NULL. The value is a
 * newly-allocated string and must be freed with g_free().
 **/
char *
pango_config_key_get_system (const char *key)
{
  GHashTable *config_hash;
  gchar *ret;

  g_return_val_if_fail (key != NULL, NULL);

  config_hash = read_config_system ();
  ret = g_strdup (g_hash_table_lookup (config_hash, key));
  g_hash_table_unref (config_hash);

  return ret;
}

/**
 * pango_config_key_get:
 * @key: Key to look up, in the form "SECTION/KEY".
 *
 * Looks up a key in the Pango config database
 * (pseudo-win.ini style, read from $sysconfdir/pango/pangorc,
 *  $XDG_CONFIG_HOME/pango/pangorc, and getenv (PANGO_RC_FILE).)
 *
 * Return value: the value, if found, otherwise %NULL. The value is a
 * newly-allocated string and must be freed with g_free().
 **/
char *
pango_config_key_get (const char *key)
{
  GHashTable *config_hash;

  g_return_val_if_fail (key != NULL, NULL);

  config_hash = read_config ();

  return g_strdup (g_hash_table_lookup (config_hash, key));
}

#ifdef G_OS_WIN32

/* DllMain function needed to tuck away the DLL handle */

static HMODULE pango_dll; /* MT-safe */

BOOL WINAPI
DllMain (HINSTANCE hinstDLL,
	 DWORD     fdwReason,
	 LPVOID    lpvReserved)
{
  switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
      pango_dll = (HMODULE) hinstDLL;
      break;
    }

  return TRUE;
}

#endif

/**
 * pango_get_sysconf_subdirectory:
 *
 * On Unix, returns the name of the "pango" subdirectory of SYSCONFDIR
 * (which is set at compile time). On Windows, returns the etc\pango
 * subdirectory of the Pango installation directory (which is deduced
 * at run time from the DLL's location).
 *
 * Return value: the Pango sysconf directory. The returned string should
 * not be freed.
 */
const char *
pango_get_sysconf_subdirectory (void)
{
  static const gchar *result = NULL; /* MT-safe */

  if (g_once_init_enter (&result))
    {
      const char *tmp_result = NULL;
#ifdef G_OS_WIN32
      gchar *root = g_win32_get_package_installation_directory_of_module (pango_dll);
      tmp_result = g_build_filename (root, "etc\\pango", NULL);
      g_free (root);
#else
      const char *sysconfdir = g_getenv ("PANGO_SYSCONFDIR");
      if (sysconfdir != NULL)
	tmp_result = g_build_filename (sysconfdir, "pango", NULL);
      else
	tmp_result = SYSCONFDIR "/pango";
#endif
      g_once_init_leave(&result, tmp_result);
    }
  return result;
}

/**
 * pango_get_lib_subdirectory:
 *
 * On Unix, returns the name of the "pango" subdirectory of LIBDIR
 * (which is set at compile time). On Windows, returns the lib\pango
 * subdirectory of the Pango installation directory (which is deduced
 * at run time from the DLL's location).
 *
 * Return value: the Pango lib directory. The returned string should
 * not be freed.
 */
const char *
pango_get_lib_subdirectory (void)
{
  static const gchar *result = NULL; /* MT-safe */

  if (g_once_init_enter (&result))
    {
      const gchar *tmp_result = NULL;
#ifdef G_OS_WIN32
      gchar *root = g_win32_get_package_installation_directory_of_module (pango_dll);
      /* If we are running against an uninstalled copy of the Pango DLL,
       * use the compile-time installation prefix.
       */
      if (g_str_has_suffix (root, "\\.libs"))
	tmp_result = g_strdup (LIBDIR "/pango");
      else
	tmp_result = g_build_filename (root, "lib\\pango", NULL);
      g_free (root);
#else
      const char *libdir = g_getenv ("PANGO_LIBDIR");
      if (libdir != NULL)
	tmp_result = g_build_filename (libdir, "pango", NULL);
      else
	tmp_result = LIBDIR "/pango";
#endif
      g_once_init_leave(&result, tmp_result);
    }
  return result;
}


static gboolean
parse_int (const char *word,
	   int        *out)
{
  char *end;
  long val = strtol (word, &end, 10);
  int i = val;

  if (end != word && *end == '\0' && val >= 0 && val == i)
    {
      if (out)
        *out = i;

      return TRUE;
    }

  return FALSE;
}

/**
 * pango_parse_enum:
 * @type: enum type to parse, eg. %PANGO_TYPE_ELLIPSIZE_MODE.
 * @str: (allow-none): string to parse.  May be %NULL.
 * @value: (out) (allow-none): integer to store the result in, or %NULL.
 * @warn: if %TRUE, issue a g_warning() on bad input.
 * @possible_values: (out) (allow-none): place to store list of possible values on failure, or %NULL.
 *
 * Parses an enum type and stores the result in @value.
 *
 * If @str does not match the nick name of any of the possible values for the
 * enum and is not an integer, %FALSE is returned, a warning is issued
 * if @warn is %TRUE, and a
 * string representing the list of possible values is stored in
 * @possible_values.  The list is slash-separated, eg.
 * "none/start/middle/end".  If failed and @possible_values is not %NULL,
 * returned string should be freed using g_free().
 *
 * Return value: %TRUE if @str was successfully parsed.
 *
 * Since: 1.16
 **/
gboolean
pango_parse_enum (GType       type,
		  const char *str,
		  int        *value,
		  gboolean    warn,
		  char      **possible_values)
{
  GEnumClass *class = NULL;
  gboolean ret = TRUE;
  GEnumValue *v = NULL;

  class = g_type_class_ref (type);

  if (G_LIKELY (str))
    v = g_enum_get_value_by_nick (class, str);

  if (v)
    {
      if (G_LIKELY (value))
	*value = v->value;
    }
  else if (!parse_int (str, value))
    {
      ret = FALSE;
      if (G_LIKELY (warn || possible_values))
	{
	  int i;
	  GString *s = g_string_new (NULL);

	  for (i = 0, v = g_enum_get_value (class, i); v;
	       i++  , v = g_enum_get_value (class, i))
	    {
	      if (i)
		g_string_append_c (s, '/');
	      g_string_append (s, v->value_nick);
	    }

	  if (warn)
	    g_warning ("%s must be one of %s",
		       G_ENUM_CLASS_TYPE_NAME(class),
		       s->str);

	  if (possible_values)
	    *possible_values = s->str;

	  g_string_free (s, possible_values ? FALSE : TRUE);
	}
    }

  g_type_class_unref (class);

  return ret;
}

/**
 * pango_lookup_aliases:
 * @fontname: an ascii string
 * @families: (out) (array length=n_families): will be set to an array of font family names.
 *    this array is owned by pango and should not be freed.
 * @n_families: (out): will be set to the length of the @families array.
 *
 * Look up all user defined aliases for the alias @fontname.
 * The resulting font family names will be stored in @families,
 * and the number of families in @n_families.
 *
 * Deprecated: 1.32: This function is not thread-safe.
 */
void
pango_lookup_aliases (const char   *fontname,
		      char       ***families,
		      int          *n_families)
{
  *families = NULL;
  *n_families = 0;
}

/**
 * pango_find_base_dir:
 * @text:   the text to process
 * @length: length of @text in bytes (may be -1 if @text is nul-terminated)
 *
 * Searches a string the first character that has a strong
 * direction, according to the Unicode bidirectional algorithm.
 *
 * Return value: The direction corresponding to the first strong character.
 * If no such character is found, then %PANGO_DIRECTION_NEUTRAL is returned.
 *
 * Since: 1.4
 */
PangoDirection
pango_find_base_dir (const gchar *text,
		     gint         length)
{
  PangoDirection dir = PANGO_DIRECTION_NEUTRAL;
  const gchar *p;

  g_return_val_if_fail (text != NULL || length == 0, PANGO_DIRECTION_NEUTRAL);

  p = text;
  while ((length < 0 || p < text + length) && *p)
    {
      gunichar wc = g_utf8_get_char (p);

      dir = pango_unichar_direction (wc);

      if (dir != PANGO_DIRECTION_NEUTRAL)
	break;

      p = g_utf8_next_char (p);
    }

  return dir;
}

/**
 * pango_is_zero_width:
 * @ch: a Unicode character
 *
 * Checks @ch to see if it is a character that should not be
 * normally rendered on the screen.  This includes all Unicode characters
 * with "ZERO WIDTH" in their name, as well as <firstterm>bidi</firstterm> formatting characters, and
 * a few other ones.  This is totally different from g_unichar_iszerowidth()
 * and is at best misnamed.
 *
 * Return value: %TRUE if @ch is a zero-width character, %FALSE otherwise
 *
 * Since: 1.10
 */
gboolean
pango_is_zero_width (gunichar ch)
{
/* Zero Width characters:
 *
 *  00AD  SOFT HYPHEN
 *  034F  COMBINING GRAPHEME JOINER
 *
 *  200B  ZERO WIDTH SPACE
 *  200C  ZERO WIDTH NON-JOINER
 *  200D  ZERO WIDTH JOINER
 *  200E  LEFT-TO-RIGHT MARK
 *  200F  RIGHT-TO-LEFT MARK
 *
 *  2028  LINE SEPARATOR
 *
 *  202A  LEFT-TO-RIGHT EMBEDDING
 *  202B  RIGHT-TO-LEFT EMBEDDING
 *  202C  POP DIRECTIONAL FORMATTING
 *  202D  LEFT-TO-RIGHT OVERRIDE
 *  202E  RIGHT-TO-LEFT OVERRIDE
 *
 *  2060  WORD JOINER
 *  2061  FUNCTION APPLICATION
 *  2062  INVISIBLE TIMES
 *  2063  INVISIBLE SEPARATOR
 *
 *  FEFF  ZERO WIDTH NO-BREAK SPACE
 */
  return ((ch & ~(gunichar)0x007F) == 0x2000 && (
		(ch >= 0x200B && ch <= 0x200F) ||
		(ch >= 0x202A && ch <= 0x202E) ||
		(ch >= 0x2060 && ch <= 0x2063) ||
		(ch == 0x2028)
	 )) || G_UNLIKELY (ch == 0x00AD
			|| ch == 0x034F
			|| ch == 0xFEFF);
}

/**
 * pango_quantize_line_geometry:
 * @thickness: (inout): pointer to the thickness of a line, in Pango units
 * @position: (inout): corresponding position
 *
 * Quantizes the thickness and position of a line, typically an
 * underline or strikethrough, to whole device pixels, that is integer
 * multiples of %PANGO_SCALE. The purpose of this function is to avoid
 * such lines looking blurry.
 *
 * Care is taken to make sure @thickness is at least one pixel when this
 * function returns, but returned @position may become zero as a result
 * of rounding.
 *
 * Since: 1.12
 */
void
pango_quantize_line_geometry (int *thickness,
			      int *position)
{
  int thickness_pixels = (*thickness + PANGO_SCALE / 2) / PANGO_SCALE;
  if (thickness_pixels == 0)
    thickness_pixels = 1;

  if (thickness_pixels & 1)
    {
      int new_center = ((*position - *thickness / 2) & ~(PANGO_SCALE - 1)) + PANGO_SCALE / 2;
      *position = new_center + (PANGO_SCALE * thickness_pixels) / 2;
    }
  else
    {
      int new_center = ((*position - *thickness / 2 + PANGO_SCALE / 2) & ~(PANGO_SCALE - 1));
      *position = new_center + (PANGO_SCALE * thickness_pixels) / 2;
    }

  *thickness = thickness_pixels * PANGO_SCALE;
}

/**
 * pango_units_from_double:
 * @d: double floating-point value
 *
 * Converts a floating-point number to Pango units: multiplies
 * it by %PANGO_SCALE and rounds to nearest integer.
 *
 * Return value: the value in Pango units.
 *
 * Since: 1.16
 */
int
pango_units_from_double (double d)
{
  return (int)floor (d * PANGO_SCALE + 0.5);
}

/**
 * pango_units_to_double:
 * @i: value in Pango units
 *
 * Converts a number in Pango units to floating-point: divides
 * it by %PANGO_SCALE.
 *
 * Return value: the double value.
 *
 * Since: 1.16
 */
double
pango_units_to_double (int i)
{
  return (double)i / PANGO_SCALE;
}

/**
 * pango_extents_to_pixels:
 * @inclusive: (allow-none): rectangle to round to pixels inclusively, or %NULL.
 * @nearest: (allow-none): rectangle to round to nearest pixels, or %NULL.
 *
 * Converts extents from Pango units to device units, dividing by the
 * %PANGO_SCALE factor and performing rounding.
 *
 * The @inclusive rectangle is converted by flooring the x/y coordinates and extending
 * width/height, such that the final rectangle completely includes the original
 * rectangle.
 *
 * The @nearest rectangle is converted by rounding the coordinates
 * of the rectangle to the nearest device unit (pixel).
 *
 * The rule to which argument to use is: if you want the resulting device-space
 * rectangle to completely contain the original rectangle, pass it in as @inclusive.
 * If you want two touching-but-not-overlapping rectangles stay
 * touching-but-not-overlapping after rounding to device units, pass them in
 * as @nearest.
 *
 * Since: 1.16
 **/
void
pango_extents_to_pixels (PangoRectangle *inclusive,
			 PangoRectangle *nearest)
{
  if (inclusive)
    {
      int orig_x = inclusive->x;
      int orig_y = inclusive->y;

      inclusive->x = PANGO_PIXELS_FLOOR (inclusive->x);
      inclusive->y = PANGO_PIXELS_FLOOR (inclusive->y);

      inclusive->width  = PANGO_PIXELS_CEIL (orig_x + inclusive->width ) - inclusive->x;
      inclusive->height = PANGO_PIXELS_CEIL (orig_y + inclusive->height) - inclusive->y;
    }

  if (nearest)
    {
      int orig_x = nearest->x;
      int orig_y = nearest->y;

      nearest->x = PANGO_PIXELS (nearest->x);
      nearest->y = PANGO_PIXELS (nearest->y);

      nearest->width  = PANGO_PIXELS (orig_x + nearest->width ) - nearest->x;
      nearest->height = PANGO_PIXELS (orig_y + nearest->height) - nearest->y;
    }
}





/*********************************************************
 * Some internal functions for handling PANGO_ATTR_SHAPE *
 ********************************************************/

void
_pango_shape_shape (const char       *text,
		    unsigned int      n_chars,
		    PangoRectangle   *shape_ink G_GNUC_UNUSED,
		    PangoRectangle   *shape_logical,
		    PangoGlyphString *glyphs)
{
  int i;
  const char *p;

  pango_glyph_string_set_size (glyphs, n_chars);

  for (i=0, p = text; i < n_chars; i++, p = g_utf8_next_char (p))
    {
      glyphs->glyphs[i].glyph = PANGO_GLYPH_EMPTY;
      glyphs->glyphs[i].geometry.x_offset = 0;
      glyphs->glyphs[i].geometry.y_offset = 0;
      glyphs->glyphs[i].geometry.width = shape_logical->width;
      glyphs->glyphs[i].attr.is_cluster_start = 1;

      glyphs->log_clusters[i] = p - text;
    }
}

void
_pango_shape_get_extents (gint              n_chars,
			  PangoRectangle   *shape_ink,
			  PangoRectangle   *shape_logical,
			  PangoRectangle   *ink_rect,
			  PangoRectangle   *logical_rect)
{
  if (n_chars > 0)
    {
      if (ink_rect)
	{
	  ink_rect->x = MIN (shape_ink->x, shape_ink->x + shape_logical->width * (n_chars - 1));
	  ink_rect->width = MAX (shape_ink->width, shape_ink->width + shape_logical->width * (n_chars - 1));
	  ink_rect->y = shape_ink->y;
	  ink_rect->height = shape_ink->height;
	}
      if (logical_rect)
	{
	  logical_rect->x = MIN (shape_logical->x, shape_logical->x + shape_logical->width * (n_chars - 1));
	  logical_rect->width = MAX (shape_logical->width, shape_logical->width + shape_logical->width * (n_chars - 1));
	  logical_rect->y = shape_logical->y;
	  logical_rect->height = shape_logical->height;
	}
    }
  else
    {
      if (ink_rect)
	{
	  ink_rect->x = 0;
	  ink_rect->y = 0;
	  ink_rect->width = 0;
	  ink_rect->height = 0;
	}

      if (logical_rect)
	{
	  logical_rect->x = 0;
	  logical_rect->y = 0;
	  logical_rect->width = 0;
	  logical_rect->height = 0;
	}
    }
}

