/* Pango2
 * 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.
 */

#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

/**
 * pango2_version:
 *
 * Returns the encoded version of Pango available at run-time.
 *
 * This is similar to the macro %PANGO2_VERSION except that the macro
 * returns the encoded version available at compile-time. A version
 * number can be encoded into an integer using PANGO2_VERSION_ENCODE().
 *
 * Returns: The encoded version of Pango library available at run time.
 */
int
pango2_version (void)
{
  return PANGO2_VERSION;
}

/**
 * pango2_version_string:
 *
 * Returns the version of Pango available at run-time.
 *
 * This is similar to the macro %PANGO2_VERSION_STRING except that the
 * macro returns 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.
 */
const char *
pango2_version_string (void)
{
  return PANGO2_VERSION_STRING;
}

/**
 * pango2_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 %PANGO2_VERSION_MAJOR,
 * %PANGO2_VERSION_MINOR, %PANGO2_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 PANGO2_VERSION_CHECK().
 *
 * Return value: (nullable): %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.
 */
const char *
pango2_version_check (int required_major,
                      int required_minor,
                      int required_micro)
{
  int pango2_effective_micro = 100 * PANGO2_VERSION_MINOR + PANGO2_VERSION_MICRO;
  int required_effective_micro = 100 * required_minor + required_micro;

  if (required_major > PANGO2_VERSION_MAJOR)
    return "Pango2 version too old (major mismatch)";
  if (required_major < PANGO2_VERSION_MAJOR)
    return "Pango2 version too new (major mismatch)";
  if (required_effective_micro < pango2_effective_micro - PANGO2_BINARY_AGE)
    return "Pango2 version too new (micro mismatch)";
  if (required_effective_micro > pango2_effective_micro)
    return "Pango2 version too old (micro mismatch)";
  return NULL;
}

/**
 * pango2_is_zero_width:
 * @ch: a Unicode character
 *
 * Checks if a character that should not be normally rendered.
 *
 * This includes all Unicode characters with "ZERO WIDTH" in their name,
 * as well as *bidi* formatting characters, and a few other ones.
 *
 * This is totally different from [func@GLib.unichar_iszerowidth] and is at best misnamed.
 *
 * Return value: %TRUE if @ch is a zero-width character, %FALSE otherwise
 */
gboolean
pango2_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
 *
 *  2060  WORD JOINER
 *  2061  FUNCTION APPLICATION
 *  2062  INVISIBLE TIMES
 *  2063  INVISIBLE SEPARATOR
 *
 *  2066  LEFT-TO-RIGHT ISOLATE
 *  2067  RIGHT-TO-LEFT ISOLATE
 *  2068  FIRST STRONG ISOLATE
 *  2069  POP DIRECTIONAL ISOLATE
 *
 *  202A  LEFT-TO-RIGHT EMBEDDING
 *  202B  RIGHT-TO-LEFT EMBEDDING
 *  202C  POP DIRECTIONAL FORMATTING
 *  202D  LEFT-TO-RIGHT OVERRIDE
 *  202E  RIGHT-TO-LEFT OVERRIDE
 *
 *  FEFF  ZERO WIDTH NO-BREAK SPACE
 */
  return ((ch & ~(gunichar)0x007F) == 0x2000 && (
                (ch >= 0x200B && ch <= 0x200F) ||
                (ch >= 0x202A && ch <= 0x202E) ||
                (ch >= 0x2060 && ch <= 0x2063) ||
                (ch >= 0x2066 && ch <= 0x2069) ||
                (ch == 0x2028)
         )) || G_UNLIKELY (ch == 0x00AD
                        || ch == 0x034F
                        || ch == 0xFEFF);
}

/**
 * pango2_units_from_double:
 * @d: double floating-point value
 *
 * Converts a floating-point number to Pango units.
 *
 * The conversion is done by multiplying @d by %PANGO2_SCALE and
 * rounding the result to nearest integer.
 *
 * Return value: the value in Pango units.
 */
int
pango2_units_from_double (double d)
{
  return (int)floor (d * PANGO2_SCALE + 0.5);
}

/**
 * pango2_units_to_double:
 * @i: value in Pango units
 *
 * Converts a number in Pango units to floating-point.
 *
 * The conversion is done by dividing @i by %PANGO2_SCALE.
 *
 * Return value: the double value.
 */
double
pango2_units_to_double (int i)
{
  return (double)i / PANGO2_SCALE;
}

/**
 * pango2_extents_to_pixels:
 * @inclusive: (nullable): rectangle to round to pixels inclusively
 * @nearest: (nullable): rectangle to round to nearest pixels
 *
 * Converts extents from Pango units to device units.
 *
 * The conversion is done by dividing by the %PANGO2_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.
 */
void
pango2_extents_to_pixels (Pango2Rectangle *inclusive,
                          Pango2Rectangle *nearest)
{
  if (inclusive)
    {
      int orig_x = inclusive->x;
      int orig_y = inclusive->y;

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

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

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

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

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

/**
 * pango2_find_paragraph_boundary:
 * @text: UTF-8 text
 * @length: length of @text in bytes, or -1 if nul-terminated
 * @paragraph_delimiter_index: (out): return location for index of
 *   delimiter
 * @next_paragraph_start: (out): return location for start of next
 *   paragraph
 *
 * Locates a paragraph boundary in @text.
 *
 * A boundary is caused by delimiter characters, such as
 * a newline, carriage return, carriage return-newline pair,
 * or Unicode paragraph separator character.
 *
 * The index of the run of delimiters is returned in
 * @paragraph_delimiter_index. The index of the start of the
 * next paragraph (index after all delimiters) is stored n
 * @next_paragraph_start.
 *
 * If no delimiters are found, both @paragraph_delimiter_index
 * and @next_paragraph_start are filled with the length of @text
 * (an index one off the end).
 */
void
pango2_find_paragraph_boundary (const char *text,
                                int         length,
                                int        *paragraph_delimiter_index,
                                int        *next_paragraph_start)
{
  const char *p = text;
  const char *end;
  const char *start = NULL;
  const char *delimiter = NULL;

  /* Only one character has type G_UNICODE_PARAGRAPH_SEPARATOR in
   * Unicode 5.0; update the following code if that changes.
   */

  /* prev_sep is the first byte of the previous separator.  Since
   * the valid separators are \r, \n, and PARAGRAPH_SEPARATOR, the
   * first byte is enough to identify it.
   */
  char prev_sep;

#define PARAGRAPH_SEPARATOR_STRING "\xE2\x80\xA9"

  if (length < 0)
    length = strlen (text);

  end = text + length;

  if (paragraph_delimiter_index)
    *paragraph_delimiter_index = length;

  if (next_paragraph_start)
    *next_paragraph_start = length;

  if (length == 0)
    return;

  prev_sep = 0;
  while (p < end)
    {
      if (prev_sep == '\n' ||
          prev_sep == PARAGRAPH_SEPARATOR_STRING[0])
        {
          g_assert (delimiter);
          start = p;
          break;
        }
      else if (prev_sep == '\r')
        {
          /* don't break between \r and \n */
          if (*p != '\n')
            {
              g_assert (delimiter);
              start = p;
              break;
            }
        }

      if (*p == '\n' ||
           *p == '\r' ||
           !strncmp(p, PARAGRAPH_SEPARATOR_STRING, strlen (PARAGRAPH_SEPARATOR_STRING)))
        {
          if (delimiter == NULL)
            delimiter = p;
          prev_sep = *p;
        }
      else
        prev_sep = 0;

      p = g_utf8_next_char (p);
    }

  if (delimiter && paragraph_delimiter_index)
    *paragraph_delimiter_index = delimiter - text;

  if (start && next_paragraph_start)
    *next_paragraph_start = start - text;
}
