//
// Implementation file for TTF library
//
//     https://github.com/michaelrsweet/ttf
//
// Copyright © 2018-2024 by Michael R Sweet.
//
// Licensed under Apache License v2.0.  See the file "LICENSE" for more
// information.
//

#ifdef _WIN32
#  define _CRT_SECURE_NO_WARNINGS
#endif // _WIN32

#include "ttf.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <fcntl.h>

#ifdef _WIN32
#  include <io.h>
#  include <direct.h>

//
// Microsoft renames the POSIX functions to _name, and introduces
// a broken compatibility layer using the original names.  As a result,
// random crashes can occur when, for example, strdup() allocates memory
// from a different heap than used by malloc() and free().
//
// To avoid moronic problems like this, we #define the POSIX function
// names to the corresponding non-standard Microsoft names.
//

#  define access	_access
#  define close		_close
#  define fileno	_fileno
#  define lseek		_lseek
#  define mkdir(d,p)	_mkdir(d)
#  define open		_open
#  define read		_read
#  define rmdir		_rmdir
#  define snprintf	_snprintf
#  define strdup	_strdup
#  define unlink	_unlink
#  define vsnprintf	_vsnprintf
#  define write		_write

//
// Map various parameters for POSIX...
//

#  define F_OK		00
#  define W_OK		02
#  define R_OK		04
#  define O_BINARY	_O_BINARY
#  define O_RDONLY	_O_RDONLY
#  define O_WRONLY	_O_WRONLY
#  define O_CREAT	_O_CREAT
#  define O_TRUNC	_O_TRUNC

typedef __int64 ssize_t;		// POSIX type not present on Windows... @private@

#else
#  include <unistd.h>
#  define O_BINARY	0
#endif // _WIN32


//
// DEBUG is typically defined for debug builds.  TTF_DEBUG maps to fprintf when
// DEBUG is defined and is a no-op otherwise...
//

#ifdef DEBUG
#  define TTF_DEBUG(...) fprintf(stderr, __VA_ARGS__)
#else
#  define TTF_DEBUG(...)
#endif // DEBUG


//
// TTF_FORMAT_ARGS tells the compiler to validate printf-style format
// arguments, producing warnings when there are issues...
//

#if defined(__has_extension) || defined(__GNUC__)
#  define TTF_FORMAT_ARGS(a,b) __attribute__ ((__format__(__printf__, a, b)))
#else
#  define TTF_FORMAT_ARGS(a,b)
#endif // __has_extension || __GNUC__


//
// Constants...
//

#define TTF_FONT_MAX_CHAR	262144	// Maximum number of character values
#define TTF_FONT_MAX_GROUPS	65536	// Maximum number of sub-groups
#define TTF_FONT_MAX_NAMES	16777216// Maximum size of names table we support


//
// TTF/OFF tag constants...
//

#define TTF_OFF_cmap	0x636d6170	// Character to glyph mapping
#define TTF_OFF_head	0x68656164	// Font header
#define TTF_OFF_hhea	0x68686561	// Horizontal header
#define TTF_OFF_hmtx	0x686d7478	// Horizontal metrics
#define TTF_OFF_maxp	0x6d617870	// Maximum profile
#define TTF_OFF_name	0x6e616d65	// Naming table
#define TTF_OFF_OS_2	0x4f532f32	// OS/2 and Windows specific metrics
#define TTF_OFF_post	0x706f7374	// PostScript information

#define TTF_OFF_Unicode	0	// Unicode platform ID

#define TTF_OFF_Mac		1	// Macintosh platform ID
#define TTF_OFF_Mac_Roman	0	// Macintosh Roman encoding ID
#define TTF_OFF_Mac_USEnglish	0	// Macintosh US English language ID

#define TTF_OFF_Windows	3	// Windows platform ID
#define TTF_OFF_Windows_English 9	// Windows English language ID base
#define TTF_OFF_Windows_UCS2	1	// Windows UCS-2 encoding
#define TTF_OFF_Windows_UCS4	10	// Windows UCS-4 encoding

#define TTF_OFF_Copyright	0	// Copyright naming string
#define TTF_OFF_FontFamily	1	// Font family naming string ("Arial")
#define TTF_OFF_FontSubfamily	2	// Font sub-family naming string ("Bold")
#define TTF_OFF_FontFullName	4	// Font full name ("Arial Bold")
#define TTF_OFF_FontVersion	5	// Font version number
#define TTF_OFF_PostScriptName	6	// Font PostScript name


//
// Local types...
//

typedef struct _ttf_metric_s		//*** Font metric information ****/
{
  short		width,			// Advance width
		left_bearing;		// Left side bearing
} _ttf_metric_t;

typedef struct _ttf_off_dir_s		// OFF/TTF directory entry
{
  unsigned	tag;			// Table identifier
  unsigned	checksum;		// Checksum of table
  unsigned	offset;			// Offset from the beginning of the file
  unsigned	length;			// Length
} _ttf_off_dir_t;

typedef struct _ttf_off_table_s		// OFF/TTF offset table
{
  int		num_entries;		// Number of table entries
  _ttf_off_dir_t *entries;		// Table entries
} _ttf_off_table_t;

typedef struct _ttf_off_name_s		// OFF/TTF name string
{
  unsigned short platform_id,		// Platform identifier
		encoding_id,		// Encoding identifier
		language_id,		// Language identifier
		name_id,		// Name identifier
		length,			// Length of string
		offset;			// Offset from start of storage area
} _ttf_off_name_t;

typedef struct _ttf_off_names_s		// OFF/TTF naming table
{
  int		num_names;		// Number of names
  _ttf_off_name_t *names;		// Names
  unsigned char	*storage;		// Storage area
  unsigned	storage_size;		// Size of storage area
} _ttf_off_names_t;

struct _ttf_s
{
  int		fd;			// File descriptor
  size_t	idx;			// Font number in file
  ttf_err_cb_t	err_cb;			// Error callback, if any
  void		*err_data;		// Error callback data
  _ttf_off_table_t table;		// Offset table
  _ttf_off_names_t names;		// Names
  size_t	num_fonts;		// Number of fonts in this file
  char		*copyright;		// Copyright string
  char		*family;		// Font family string
  char		*postscript_name;	// PostScript name string
  char		*version;		// Font version string
  bool		is_fixed;		// Is this a fixed-width font?
  int		max_char,		// Last character in font
		min_char;		// First character in font
  size_t	num_cmap;		// Number of entries in glyph map
  int		*cmap;			// Unicode character to glyph map
  _ttf_metric_t	*widths[TTF_FONT_MAX_CHAR / 256];
					// Character metrics (sparse array)
  float		units;			// Width units
  short		ascent,			// Maximum ascent above baseline
		descent,		// Maximum descent below baseline
		cap_height,		// "A" height
		x_height,		// "x" height
		x_max,			// Bounding box
		x_min,
		y_max,
		y_min,
		weight;			// Font weight
  float		italic_angle;		// Angle of italic text
  ttf_stretch_t	stretch;		// Font stretch value
  ttf_style_t	style;			// Font style
};

typedef struct _ttf_off_cmap4_s		// Format 4 cmap table
{
  unsigned short startCode,		// First character
		endCode,		// Last character
		idRangeOffset;		// Offset for range (modulo 65536)
  short		idDelta;		// Delta for range (modulo 65536)
} _ttf_off_cmap4_t;

typedef struct _ttf_off_cmap12_s	// Format 12 cmap table
{
  unsigned	startCharCode,		// First character
		endCharCode,		// Last character
		startGlyphID;		// Glyph index for the first character
} _ttf_off_cmap12_t;

typedef struct _ttf_off_cmap13_s	// Format 13 cmap table
{
  unsigned	startCharCode,		// First character
		endCharCode,		// Last character
		glyphID;		// Glyph index for all characters
} _ttf_off_cmap13_t;

typedef struct _ttf_off_head_s		// Font header
{
  unsigned short unitsPerEm;		// Units for widths/coordinates
  short		xMin,			// Bounding box of all glyphs
		yMin,
		xMax,
		yMax;
  unsigned short macStyle;		// Mac style bits
} _ttf_off_head_t;

#define TTF_OFF_macStyle_Bold		0x01
#define TTF_OFF_macStyle_Italic	0x02
#define TTF_OFF_macStyle_Underline	0x04
#define TTF_OFF_macStyle_Outline	0x08
#define TTF_OFF_macStyle_Shadow	0x10
#define TTF_OFF_macStyle_Condensed	0x20
#define TTF_OFF_macStyle_Extended	0x40

typedef struct _ttf_off_hhea_s		// Horizontal header
{
  short		ascender,		// Ascender
		descender;		// Descender
  unsigned short numberOfHMetrics;	// Number of horizontal metrics
} _ttf_off_hhea_t;

typedef struct _ttf_off_os_2_s		// OS/2 information
{
  unsigned short usWeightClass,		// Font weight
		usWidthClass,		// Font weight
		fsType;			// Type bits
  short		sTypoAscender,		// Ascender
		sTypoDescender,		// Descender
		sxHeight,		// xHeight
		sCapHeight;		// CapHeight
} _ttf_off_os_2_t;

typedef struct _ttf_off_post_s		// PostScript information
{
  float		italicAngle;		// Italic angle
  unsigned	isFixedPitch;		// Fixed-width font?
} _ttf_off_post_t;


//
// Local functions...
//

static char	*copy_name(ttf_t *font, unsigned name_id);
static void	errorf(ttf_t *font, const char *message, ...) TTF_FORMAT_ARGS(2,3);
static bool	read_cmap(ttf_t *font);
static bool	read_head(ttf_t *font, _ttf_off_head_t *head);
static bool	read_hhea(ttf_t *font, _ttf_off_hhea_t *hhea);
static _ttf_metric_t *read_hmtx(ttf_t *font, _ttf_off_hhea_t *hhea);
static int	read_maxp(ttf_t *font);
static bool	read_names(ttf_t *font);
static bool	read_os_2(ttf_t *font, _ttf_off_os_2_t *os_2);
static bool	read_post(ttf_t *font, _ttf_off_post_t *post);
static int	read_short(ttf_t *font);
static bool	read_table(ttf_t *font);
static unsigned	read_ulong(ttf_t *font);
static int	read_ushort(ttf_t *font);
static unsigned	seek_table(ttf_t *font, unsigned tag, unsigned offset, bool required);


//
// 'ttfCreate()' - Create a new font object for the named font file.
//
// This function creates a new font object for the named TrueType or OpenType
// font file or collection.  The "filename" argument specifies the name of the
// file to read.
//
// The "idx" argument specifies the font to load from a collection - the first
// font is number `0`.  Once created, you can call the @link ttfGetNumFonts@
// function to determine whether the loaded font file is a collection with more
// than one font.
//
// The "err_cb" and "err_data" arguments specify a callback function and data
// pointer for receiving error messages.  If `NULL`, errors are sent to the
// `stderr` file.  The callback function receives the data pointer and a text
// message string, for example:
//
// ```
// void my_err_cb(void *err_data, const char *message)
// {
//   fprintf(stderr, "ERROR: %s\n", message);
// }
// ```
//

ttf_t *					// O - New font object
ttfCreate(const char   *filename,	// I - Filename
          size_t       idx,		// I - Font number to create in collection (0-based)
          ttf_err_cb_t err_cb,		// I - Error callback or `NULL` to log to stderr
          void         *err_data)	// I - Error callback data
{
  ttf_t			*font = NULL;	// New font object
  size_t		i;		// Looping var
  _ttf_metric_t		*widths = NULL;	// Glyph metrics
  _ttf_off_head_t	head;		// head table
  _ttf_off_hhea_t	hhea;		// hhea table
  _ttf_off_os_2_t	os_2;		// OS/2 table
  _ttf_off_post_t	post;		// PostScript table


  TTF_DEBUG("ttfCreate(filename=\"%s\", idx=%u, err_cb=%p, err_data=%p)\n", filename, (unsigned)idx, err_cb, err_data);

  // Range check input..
  if (!filename)
  {
    errno = EINVAL;
    return (NULL);
  }

  // Allocate memory...
  if ((font = (ttf_t *)calloc(1, sizeof(ttf_t))) == NULL)
    return (NULL);

  font->idx      = idx;
  font->err_cb   = err_cb;
  font->err_data = err_data;

  // Open the font file...
  if ((font->fd = open(filename, O_RDONLY | O_BINARY)) < 0)
  {
    errorf(font, "Unable to open '%s': %s", filename, strerror(errno));
    goto error;
  }

  TTF_DEBUG("ttfCreate: fd=%d\n", font->fd);

  // Read the table of contents and the identifying names...
  if (!read_table(font))
    goto error;

  TTF_DEBUG("ttfCreate: num_entries=%d\n", font->table.num_entries);

  if (!read_names(font))
    goto error;

  TTF_DEBUG("ttfCreate: num_names=%d\n", font->names.num_names);

  // Copy key font meta data strings...
  font->copyright       = copy_name(font, TTF_OFF_Copyright);
  font->family          = copy_name(font, TTF_OFF_FontFamily);
  font->postscript_name = copy_name(font, TTF_OFF_PostScriptName);
  font->version         = copy_name(font, TTF_OFF_FontVersion);

  if (read_post(font, &post))
  {
    font->italic_angle = post.italicAngle;
    font->is_fixed     = post.isFixedPitch != 0;
  }

  TTF_DEBUG("ttfCreate: copyright=\"%s\"\n", font->copyright);
  TTF_DEBUG("ttfCreate: family=\"%s\"\n", font->family);
  TTF_DEBUG("ttfCreate: postscript_name=\"%s\"\n", font->postscript_name);
  TTF_DEBUG("ttfCreate: version=\"%s\"\n", font->version);
  TTF_DEBUG("ttfCreate: italic_angle=%g\n", font->italic_angle);
  TTF_DEBUG("ttfCreate: is_fixed=%s\n", font->is_fixed ? "true" : "false");

  if (!read_cmap(font))
    goto error;

  if (!read_head(font, &head))
    goto error;

  font->units = (float)head.unitsPerEm;
  font->x_max = head.xMax;
  font->x_min = head.xMin;
  font->y_max = head.yMax;
  font->y_min = head.yMin;

  if (head.macStyle & TTF_OFF_macStyle_Italic)
  {
    if (font->postscript_name && strstr(font->postscript_name, "Oblique"))
      font->style = TTF_STYLE_OBLIQUE;
    else
      font->style = TTF_STYLE_ITALIC;
  }
  else
    font->style = TTF_STYLE_NORMAL;

  if (!read_hhea(font, &hhea))
    goto error;

  font->ascent  = hhea.ascender;
  font->descent = hhea.descender;

  if (read_maxp(font) < 0)
    goto error;

  if (hhea.numberOfHMetrics > 0)
  {
    if ((widths = read_hmtx(font, &hhea)) == NULL)
      goto error;
  }
  else
  {
    errorf(font, "Number of horizontal metrics is 0.");
    goto error;
  }

  if (read_os_2(font, &os_2))
  {
    // Copy key values from OS/2 table...
    static const ttf_stretch_t stretches[] =
    {
      TTF_STRETCH_ULTRA_CONDENSED,	// ultra-condensed
      TTF_STRETCH_EXTRA_CONDENSED,	// extra-condensed
      TTF_STRETCH_CONDENSED,		// condensed
      TTF_STRETCH_SEMI_CONDENSED,	// semi-condensed
      TTF_STRETCH_NORMAL,		// normal
      TTF_STRETCH_SEMI_EXPANDED,	// semi-expanded
      TTF_STRETCH_EXPANDED,		// expanded
      TTF_STRETCH_EXTRA_EXPANDED,	// extra-expanded
      TTF_STRETCH_ULTRA_EXPANDED	// ultra-expanded
    };

    if (os_2.usWidthClass >= 1 && os_2.usWidthClass <= (int)(sizeof(stretches) / sizeof(stretches[0])))
      font->stretch = stretches[os_2.usWidthClass - 1];

    font->weight     = (short)os_2.usWeightClass;
    font->cap_height = os_2.sCapHeight;
    font->x_height   = os_2.sxHeight;
  }
  else
  {
    // Default key values since there isn't an OS/2 table...
    TTF_DEBUG("ttfCreate: Unable to read OS/2 table.\n");

    font->weight = 400;
  }

  if (font->cap_height == 0)
    font->cap_height = font->ascent;

  if (font->x_height == 0)
    font->x_height = 3 * font->ascent / 5;

  // Build a sparse glyph widths table...
  font->min_char = -1;

  for (i = 0; i < font->num_cmap; i ++)
  {
    if (font->cmap[i] >= 0)
    {
      int	bin = (int)i / 256,	// Sub-array bin
		glyph = font->cmap[i];	// Glyph index

      // Update min/max...
      if (font->min_char < 0)
        font->min_char = (int)i;

      font->max_char = (int)i;

      // Allocate a sub-array as needed...
      if (!font->widths[bin])
        font->widths[bin] = (_ttf_metric_t *)calloc(256, sizeof(_ttf_metric_t));

      // Copy the width of the specified glyph or the last one if we are past
      // the end of the table...
      if (glyph >= hhea.numberOfHMetrics)
	font->widths[bin][i & 255] = widths[hhea.numberOfHMetrics - 1];
      else
	font->widths[bin][i & 255] = widths[glyph];
    }

#ifdef DEBUG
    if (i >= ' ' && i < 127 && font->widths[0])
      TTF_DEBUG("ttfCreate: width['%c']=%d(%d)\n", (char)i, font->widths[0][i].width, font->widths[0][i].left_bearing);
#endif // DEBUG
  }

  // Cleanup and return the font...
  free(widths);

  return (font);

  // If we get here something bad happened...
  error:

  free(widths);
  ttfDelete(font);

  return (NULL);
}


//
// 'ttfDelete()' - Free all memory used for a font family object.
//

void
ttfDelete(ttf_t *font)			// I - Font
{
  int	i;				// Looping var


  // Range check input...
  if (!font)
    return;

  // Close the font file...
  if (font->fd >= 0)
    close(font->fd);

  // Free all memory used...
  free(font->copyright);
  free(font->family);
  free(font->postscript_name);
  free(font->version);

  free(font->table.entries);
  free(font->names.names);
  free(font->names.storage);

  free(font->cmap);

  for (i = 0; i < 256; i ++)
    free(font->widths[i]);

  free(font);
}


//
// 'ttfGetAscent()' - Get the maximum height of non-accented characters.
//

int					// O - Ascent in 1000ths
ttfGetAscent(ttf_t *font)		// I - Font
{
  return (font ? (int)(1000 * font->ascent / font->units) : 0);
}


//
// 'ttfGetBounds()' - Get the bounds of all characters in a font.
//
// This function gets the bounds of all characters in a font.  The "bounds"
// argument is a pointer to a `ttf_rect_t` structure that will be filled with
// the limits for characters in the font scaled to a 1000x1000 unit square.
//

ttf_rect_t *				// O - Bounds or `NULL` on error
ttfGetBounds(ttf_t      *font,		// I - Font
             ttf_rect_t *bounds)	// I - Bounds buffer
{
  // Range check input...
  if (!font || !bounds)
  {
    if (bounds)
      memset(bounds, 0, sizeof(ttf_rect_t));

    return (NULL);
  }

  bounds->left   = 1000.0f * font->x_min / font->units;
  bounds->right  = 1000.0f * font->x_max / font->units;
  bounds->bottom = 1000.0f * font->y_min / font->units;
  bounds->top    = 1000.0f * font->y_max / font->units;

  return (bounds);
}


//
// 'ttfGetCapHeight()' - Get the height of capital letters.
//

int					// O - Capital letter height in 1000ths
ttfGetCapHeight(ttf_t *font)		// I - Font
{
  return (font ? (int)(1000 * font->cap_height / font->units) : 0);
}


//
// 'ttfGetCMap()' - Get the Unicode to glyph mapping table.
//

const int *				// O - CMap table
ttfGetCMap(ttf_t  *font,		// I - Font
           size_t *num_cmap)		// O - Number of entries in table
{
  // Range check input...
  if (!font || !num_cmap)
  {
    if (num_cmap)
      *num_cmap = 0;

    return (NULL);
  }

  *num_cmap = font->num_cmap;
  return (font->cmap);
}


//
// 'ttfGetCopyright()' - Get the copyright text for a font.
//

const char *				// O - Copyright text
ttfGetCopyright(ttf_t *font)		// I - Font
{
  return (font ? font->copyright : NULL);
}


//
// 'ttfGetDescent()' - Get the maximum depth of non-accented characters.
//

int					// O - Descent in 1000ths
ttfGetDescent(ttf_t *font)		// I - Font
{
  return (font ? (int)(1000 * font->descent / font->units) : 0);
}


//
// 'ttfGetExtents()' - Get the extents of a UTF-8 string.
//
// This function computes the extents of the UTF-8 string "s" when rendered
// using the specified font "font" and size "size".  The "extents" argument is
// a pointer to a `ttf_rect_t` structure that is filled with the extents of a
// simple rendering of the string with no kerning or rewriting applied.  The
// values are scaled using the specified font size.
//

ttf_rect_t *				// O - Pointer to extents or `NULL` on error
ttfGetExtents(
    ttf_t      *font,			// I - Font
    float      size,			// I - Font size
    const char *s,			// I - String
    ttf_rect_t *extents)		// O - Extents of the string
{
  bool		first = true;		// First character?
  int		ch,			// Current character
		width = 0;		// Width
  _ttf_metric_t	*widths;		// Widths


  TTF_DEBUG("ttfGetExtents(font=%p, size=%.2f, s=\"%s\", extents=%p)\n", (void *)font, size, s, (void *)extents);

  // Make sure extents is zeroed out...
  if (extents)
    memset(extents, 0, sizeof(ttf_rect_t));

  // Range check input...
  if (!font || size <= 0.0f || !s || !extents)
    return (NULL);

  // Loop through the string...
  while (*s)
  {
    // Get the next Unicode character...
    if ((*s & 0xe0) == 0xc0 && (s[1] & 0xc0) == 0x80)
    {
      // Two byte UTF-8
      ch = ((*s & 0x1f) << 6) | (s[1] & 0x3f);
      s += 2;
    }
    else if ((*s & 0xf0) == 0xe0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80)
    {
      // Three byte UTF-8
      ch = ((*s & 0x0f) << 12) | ((s[1] & 0x3f) << 6) | (s[2] & 0x3f);
      s += 3;
    }
    else if ((*s & 0xf8) == 0xf0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80 && (s[3] & 0xc0) == 0x80)
    {
      // Four byte UTF-8
      ch = ((*s & 0x07) << 18) | ((s[1] & 0x3f) << 12) | ((s[2] & 0x3f) << 6) | (s[3] & 0x3f);
      s += 4;
    }
    else if (*s & 0x80)
    {
      // Invalid UTF-8
      errorf(font, "Invalid UTF-8 sequence starting with 0x%02X.", *s & 255);
      return (NULL);
    }
    else
    {
      // ASCII...
      ch = *s++;
    }

    // Find its width...
    if ((widths = font->widths[ch / 256]) != NULL)
    {
      if (first)
      {
        extents->left = -widths[ch & 255].left_bearing / font->units;
        first         = false;
      }

      width += widths[ch & 255].width;
    }
    else if ((widths = font->widths[0]) != NULL)
    {
      // Use the ".notdef" (0) glyph width...
      if (first)
      {
        extents->left = -widths[0].left_bearing / font->units;
        first         = false;
      }

      width += widths[0].width;
    }
  }

  // Calculate the bounding box for the text and return...
  TTF_DEBUG("ttfGetExtents: width=%d\n", width);

  extents->bottom = size * font->y_min / font->units;
  extents->right  = size * width / font->units + extents->left;
  extents->top    = size * font->y_max / font->units;

  return (extents);
}


//
// 'ttfGetFamily()' - Get the family name of a font.
//

const char *				// O - Family name
ttfGetFamily(ttf_t *font)		// I - Font
{
  return (font ? font->family : NULL);
}


//
// 'ttfGetItalicAngle()' - Get the italic angle.
//

float					// O - Angle in degrees
ttfGetItalicAngle(ttf_t *font)		// I - Font
{
  return (font ? font->italic_angle : 0.0f);
}


//
// 'ttfGetMaxChar()' - Get the last character in the font.
//

int					// O - Last character in font
ttfGetMaxChar(ttf_t *font)		// I - Font
{
  return (font ? font->max_char : 0);
}


//
// 'ttfGetMinChar()' - Get the first character in the font.
//

int					// O - First character in font
ttfGetMinChar(ttf_t *font)		// I - Font
{
  return (font ? font->min_char : 0);
}


//
// 'ttfGetNumFonts()' - Get the number of fonts in this collection.
//

size_t					// O - Number of fonts
ttfGetNumFonts(ttf_t *font)		// I - Font
{
  return (font ? font->num_fonts : 0);
}


//
// 'ttfGetPostScriptName()' - Get the PostScript name of a font.
//

const char *				// O - PostScript name
ttfGetPostScriptName(
    ttf_t *font)			// I - Font
{
  return (font ? font->postscript_name : NULL);
}


//
// 'ttfGetStretch()' - Get the font "stretch" value...
//

ttf_stretch_t				// O - Stretch value
ttfGetStretch(ttf_t *font)		// I - Font
{
  return (font ? font->stretch : TTF_STRETCH_NORMAL);
}


//
// 'ttfGetStyle()' - Get the font style.
//

ttf_style_t				// O - Style
ttfGetStyle(ttf_t *font)		// I - Font
{
  return (font ? font->style : TTF_STYLE_NORMAL);
}


//
// 'ttfGetVersion()' - Get the version number of a font.
//

const char *				// O - Version number
ttfGetVersion(ttf_t *font)		// I - Font
{
  return (font ? font->version : NULL);
}


//
// 'ttfGetWeight()' - Get the weight of a font.
//

ttf_weight_t				// O - Weight
ttfGetWeight(ttf_t *font)		// I - Font
{
  return (font ? (ttf_weight_t)font->weight : TTF_WEIGHT_400);
}


//
// 'ttfGetWidth()' - Get the width of a single character.
//

int					// O - Width in 1000ths
ttfGetWidth(ttf_t *font,		// I - Font
            int   ch)			// I - Unicode character
{
  int	bin =  ch >> 8;			// Bin in widths array


  // Range check input...
  if (!font || ch < ' ' || ch == 0x7f)
    return (0);

  if (font->widths[bin])
    return ((int)(1000.0f * font->widths[bin][ch & 255].width / font->units));
  else if (font->widths[0])		// .notdef
    return ((int)(1000.0f * font->widths[0][0].width / font->units));
  else
    return (0);
}


//
// 'ttfGetXHeight()' - Get the height of lowercase letters.
//

int					// O - Lowercase letter height in 1000ths
ttfGetXHeight(ttf_t *font)		// I - Font
{
  return (font ? (int)(1000 * font->x_height / font->units) : 0);
}


//
// 'ttfIsFixedPitch()' - Determine whether a font is fixedpitch.
//

bool					// O - `true` if fixed pitch, `false` otherwise
ttfIsFixedPitch(ttf_t *font)		// I - Font
{
  return (font ? font->is_fixed : false);
}


//
// 'copy_name()' - Copy a name string from a font.
//

static char *				// O - Name string or `NULL`
copy_name(ttf_t    *font,		// I - Font
          unsigned name_id)		// I - Name identifier
{
  int			i;		// Looping var
  _ttf_off_name_t	*name;		// Current name


  for (i = font->names.num_names, name = font->names.names; i > 0; i --, name ++)
  {
    if (name->name_id == name_id &&
        ((name->platform_id == TTF_OFF_Mac && name->language_id == TTF_OFF_Mac_USEnglish) ||
         (name->platform_id == TTF_OFF_Windows && (name->language_id & 0xff) == TTF_OFF_Windows_English)))
    {
      char	temp[1024],	// Temporary string buffer
		*tempptr,	// Pointer into temporary string
		*storptr;	// Pointer into storage
      int	chars,		// Length of string to copy in characters
		bpc;		// Bytes per character

      if ((unsigned)(name->offset + name->length) > font->names.storage_size)
      {
        TTF_DEBUG("copy_name: offset(%d)+length(%d) > storage_size(%d)\n", name->offset, name->length, font->names.storage_size);
        continue;
      }

      if (name->platform_id == TTF_OFF_Windows && name->encoding_id == TTF_OFF_Windows_UCS2)
      {
        storptr = (char *)font->names.storage + name->offset;
        chars   = name->length / 2;
        bpc     = 2;
      }
      else if (name->platform_id == TTF_OFF_Windows && name->encoding_id == TTF_OFF_Windows_UCS4)
      {
        storptr = (char *)font->names.storage + name->offset;
        chars   = name->length / 4;
        bpc     = 4;
      }
      else
      {
        storptr = (char *)font->names.storage + name->offset;
        chars   = name->length;
        bpc     = 1;
      }

      for (tempptr = temp; chars > 0; storptr += bpc, chars --)
      {
        int ch;				// Current character

        // Convert to Unicode...
        if (bpc == 1)
          ch = *storptr;
	else if (bpc == 2)
	  ch = ((storptr[0] & 255) << 8) | (storptr[1] & 255);
	else
	  ch = ((storptr[0] & 255) << 24) | ((storptr[1] & 255) << 16) | ((storptr[2] & 255) << 8) | (storptr[3] & 255);

        // Convert to UTF-8...
        if (ch < 0x80)
        {
          // ASCII...
	  if (tempptr < (temp + sizeof(temp) - 1))
	    *tempptr++ = (char)ch;
	  else
	    break;
	}
	else if (ch < 0x400)
	{
	  // Two byte UTF-8
	  if (tempptr < (temp + sizeof(temp) - 2))
	  {
	    *tempptr++ = (char)(0xc0 | (ch >> 6));
	    *tempptr++ = (char)(0x80 | (ch & 0x3f));
	  }
	  else
	    break;
	}
	else if (ch < 0x10000)
	{
	  // Three byte UTF-8
	  if (tempptr < (temp + sizeof(temp) - 3))
	  {
	    *tempptr++ = (char)(0xe0 | (ch >> 12));
	    *tempptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
	    *tempptr++ = (char)(0x80 | (ch & 0x3f));
	  }
	  else
	    break;
	}
	else
	{
	  // Four byte UTF-8
	  if (tempptr < (temp + sizeof(temp) - 4))
	  {
	    *tempptr++ = (char)(0xf0 | (ch >> 18));
	    *tempptr++ = (char)(0x80 | ((ch >> 12) & 0x3f));
	    *tempptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
	    *tempptr++ = (char)(0x80 | (ch & 0x3f));
	  }
	  else
	    break;
	}
      }

      *tempptr = '\0';

      TTF_DEBUG("copy_name: name_id(%d) = \"%s\"\n", name_id, temp);

      return (strdup(temp));
    }
  }

  TTF_DEBUG("copy_name: No English name string for %d.\n", name_id);
#ifdef DEBUG
  for (i = font->names.num_names, name = font->names.names; i > 0; i --, name ++)
  {
    if (name->name_id == name_id)
      TTF_DEBUG("copy_name: Found name_id=%d, platform_id=%d, language_id=%d(0x%04x)\n", name_id, name->platform_id, name->language_id, name->language_id);
  }
#endif // DEBUG

  return (NULL);
}


//
// 'errorf()' - Show an error message.
//

static void
errorf(ttf_t      *font,		// I - Font
       const char *message,		// I - `printf`-style message string
       ...)				// I - Addition arguments as needed
{
  va_list	ap;			// Argument pointer
  char		buffer[2048];		// Message buffer


  // Format the message...
  va_start(ap, message);
  vsnprintf(buffer, sizeof(buffer), message, ap);
  va_end(ap);

  // If an error callback is set, sent the message there.  Otherwise write it
  // to stderr...
  if (font && font->err_cb)
    (font->err_cb)(font->err_data, buffer);
  else
    fprintf(stderr, "%s\n", message);
}


/*
 * 'read_cmap()' - Read the cmap table, getting the Unicode mapping table.
 */

static bool				// O - `true` on success, `false` on error
read_cmap(ttf_t *font)			// I - Font
{
  unsigned	length;			// Length of cmap table
  int		i,			// Looping var
		temp,			// Temporary value
		num_tables,		// Number of cmap tables
		platform_id,		// Platform identifier (Windows or Mac)
		encoding_id,		// Encoding identifier (varies)
		cformat;		// Formap of cmap data
  unsigned	clength,		// Length of cmap data
		coffset = 0,		// Offset to cmap data
		roman_offset = 0;	// MacRoman offset
  int		*cmapptr;		// Pointer into cmap
#if 0
  const int	*unimap = NULL;		// Unicode character map, if any
  static const int romanmap[256] =	// MacRoman to Unicode map
  {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
        0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
        0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
        0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
        0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
        0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
        0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
        0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
    0xC4, 0xC5, 0xC7, 0xC9, 0xD1, 0xD6, 0xDC, 0xE1,
        0xE0, 0xE2, 0xE4, 0xE3, 0xE5, 0xE7, 0xE9, 0xE8,
    0xEA, 0xEB, 0xED, 0xEC, 0xEE, 0xEF, 0xF1, 0xF3,
        0xF2, 0xF4, 0xF6, 0xF5, 0xFA, 0xF9, 0xFB, 0xFC,
    0x2020, 0xB0, 0xA2, 0xA3, 0xA7, 0x2022, 0xB6, 0xDF,
        0xAE, 0xA9, 0x2122, 0xB4, 0xA8, 0x2260, 0xC6, 0xD8,
    0x221E, 0xB1, 0x2264, 0x2265, 0xA5, 0xB5, 0x2202, 0x2211,
        0x220F, 0x3C0, 0x222B, 0xAA, 0xBA, 0x3A9, 0xE6, 0xF8,
    0xBF, 0xA1, 0xAC, 0x221A, 0x192, 0x2248, 0x2206, 0xAB,
        0xBB, 0x2026, 0xA0, 0xC0, 0xC3, 0xD5, 0x152, 0x153,
    0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0xF7, 0x25CA,
        0xFF, 0x178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02,
    0x2021, 0xB7, 0x201A, 0x201E, 0x2030, 0xC2, 0xCA, 0xC1,
        0xCB, 0xC8, 0xCD, 0xCE, 0xCF, 0xCC, 0xD3, 0xD4,
    0xF8FF, 0xD2, 0xDA, 0xDB, 0xD9, 0x131, 0x2C6, 0x2DC,
        0xAF, 0x2D8, 0x2D9, 0x2DA, 0xB8, 0x2DD, 0x2DB, 0x2C7
  };
#endif /* 0 */


  // Find the cmap table...
  if (seek_table(font, TTF_OFF_cmap, 0, true) == 0)
    return (false);

  if ((temp = read_ushort(font)) != 0)
  {
    errorf(font, "Unknown cmap version %d.", temp);
    return (false);
  }

  if ((num_tables = read_ushort(font)) < 1)
  {
    errorf(font, "No cmap tables to read.");
    return (false);
  }

  TTF_DEBUG("read_cmap: num_tables=%d\n", num_tables);

  // Find a Unicode table we can use...
  for (i = 0; i < num_tables; i ++)
  {
    platform_id = read_ushort(font);
    encoding_id = read_ushort(font);
    coffset     = read_ulong(font);

    TTF_DEBUG("read_cmap: table[%d].platform_id=%d, encoding_id=%d, coffset=%u\n", i, platform_id, encoding_id, coffset);

    if (platform_id == TTF_OFF_Unicode || (platform_id == TTF_OFF_Windows && encoding_id == TTF_OFF_Windows_UCS2))
      break;

    if (platform_id == TTF_OFF_Mac && encoding_id == TTF_OFF_Mac_Roman)
      roman_offset = coffset;
  }

  if (i >= num_tables)
  {
    if (roman_offset)
    {
      TTF_DEBUG("read_cmap: Using MacRoman cmap table.\n");
      coffset = roman_offset;
    }
    else
    {
      errorf(font, "No usable cmap table.");
      return (false);
    }
  }

  if ((length = seek_table(font, TTF_OFF_cmap, coffset, true)) == 0)
    return (false);

  if ((cformat = read_ushort(font)) < 0)
  {
    errorf(font, "Unable to read cmap table format at offset %u.", coffset);
    return (false);
  }

  TTF_DEBUG("read_cmap: cformat=%d\n", cformat);

  switch (cformat)
  {
    case 0 :
        {
          // Format 0: Byte encoding table.
          //
          // This is a simple 8-bit mapping.
          size_t	j;		// Looping var
          unsigned char bmap[256];	// Byte map buffer

	  if ((unsigned)read_ushort(font) == (unsigned)-1)
	  {
	    errorf(font, "Unable to read cmap table length at offset %u.", coffset);
	    return (false);
	  }

          /* language = */ read_ushort(font);

          if (length > (256 + 6))
          {
	    errorf(font, "Bad cmap table length at offset %u.", coffset);
	    return (false);
          }

	  font->num_cmap = length - 6;

	  if ((font->cmap = (int *)malloc(font->num_cmap * sizeof(int))) == NULL)
	  {
	    errorf(font, "Unable to allocate cmap table.");
	    return (false);
	  }

          if (read(font->fd, bmap, font->num_cmap) != (ssize_t)font->num_cmap)
          {
	    errorf(font, "Unable to read cmap table length at offset %u.", coffset);
	    return (false);
          }

	  // Copy into the actual cmap table...
	  for (j = 0; j < font->num_cmap; j ++)
	    font->cmap[j] = bmap[j];
        }
        break;

    case 4 :
        {
          // Format 4: Segment mapping to delta values.
          //
          // This is an overly complicated linear way of encoding a sparse
          // mapping table.  And it uses 1-based indexing with modulo
          // arithmetic...
          int		ch,		// Current character
			seg,		// Current segment
			glyph,		// Current glyph
			segCount,	// Number of segments
			numGlyphIdArray,// Number of glyph IDs
			*glyphIdArray;	// Glyph IDs
          _ttf_off_cmap4_t *segments,	// Segment data
			*segment;	// This segment


          // Read the table...
	  if ((clength = (unsigned)read_ushort(font)) == (unsigned)-1)
	  {
	    errorf(font, "Unable to read cmap table length at offset %u.", coffset);
	    return (false);
	  }

	  TTF_DEBUG("read_cmap: clength=%u\n", clength);

          /* language = */       read_ushort(font);
          segCount             = read_ushort(font) / 2;
	  /* searchRange = */    read_ushort(font);
	  /* entrySelectoed = */ read_ushort(font);
	  /* rangeShift = */     read_ushort(font);

          TTF_DEBUG("read_cmap: segCount=%d\n", segCount);

          if (segCount < 2)
          {
	    errorf(font, "Bad cmap table.");
	    return (false);
          }

          numGlyphIdArray = ((int)clength - 8 * segCount - 16) / 2;
          segments        = (_ttf_off_cmap4_t *)calloc((size_t)segCount, sizeof(_ttf_off_cmap4_t));
          glyphIdArray    = (int *)calloc((size_t)numGlyphIdArray, sizeof(int));

          if (!segments || !glyphIdArray)
          {
            errorf(font, "Unable to allocate memory for cmap.");
            free(segments);
            free(glyphIdArray);
            return (false);
	  }

          TTF_DEBUG("read_cmap: numGlyphIdArray=%d\n", numGlyphIdArray);

          for (i = 0; i < segCount; i ++)
            segments[i].endCode = (unsigned short)read_ushort(font);

	  /* reservedPad = */ read_ushort(font);

          for (i = 0; i < segCount; i ++)
            segments[i].startCode = (unsigned short)read_ushort(font);

          for (i = 0; i < segCount; i ++)
            segments[i].idDelta = (short)read_short(font);

          for (i = 0; i < segCount; i ++)
            segments[i].idRangeOffset = (unsigned short)read_ushort(font);

          for (i = 0; i < numGlyphIdArray; i ++)
            glyphIdArray[i] = read_ushort(font);

          for (i = 0, segment = segments; i < segCount; i ++, segment ++)
          {
            TTF_DEBUG("read_cmap: segment[%d].startCode=%d, endCode=%d, idDelta=%d, idRangeOffset=%d\n", i, segment->startCode, segment->endCode, segment->idDelta, segment->idRangeOffset);

            if (segment->startCode > segment->endCode)
            {
	      errorf(font, "Bad cmap table segment %u to %u.", segments->startCode, segment->endCode);
	      free(segments);
	      free(glyphIdArray);
	      return (false);
            }

            // Based on the end code of the segment table, allocate space for the
            // uncompressed cmap table...
            if (segment->endCode >= font->num_cmap)
	      font->num_cmap = segment->endCode + 1;
          }

#ifdef DEBUG
          for (i = 0; i < numGlyphIdArray; i ++)
            TTF_DEBUG("read_cmap: glyphIdArray[%d]=%d\n", i, glyphIdArray[i]);
#endif /* DEBUG */

	  if (font->num_cmap == 0 || font->num_cmap > TTF_FONT_MAX_CHAR)
	  {
	    errorf(font, "Invalid cmap table with %u characters.", (unsigned)font->num_cmap);
	    free(segments);
	    free(glyphIdArray);
	    return (false);
	  }

	  font->cmap = cmapptr = (int *)malloc(font->num_cmap * sizeof(int));

	  if (!font->cmap)
          {
            errorf(font, "Unable to allocate memory for cmap.");
            free(segments);
            free(glyphIdArray);
            return (false);
	  }

          memset(cmapptr, -1, font->num_cmap * sizeof(int));

          // Now loop through the segments and assign glyph indices from the
          // array...
          for (seg = segCount, segment = segments; seg > 0; seg --, segment ++)
          {
            for (ch = segment->startCode; ch <= segment->endCode; ch ++)
            {
              if (segment->idRangeOffset)
              {
                // Use an "obscure indexing trick" (words from the spec, not
                // mine) to look up the glyph index...
                temp = (int)(segment->idRangeOffset / 2 - segCount + (ch - segment->startCode) + (segment - segments));

                TTF_DEBUG("read_cmap: ch=%d, temp=%d\n", ch, temp);
                if (temp < 0 || temp >= numGlyphIdArray)
                  glyph = -1;
		else
		  glyph = (glyphIdArray[temp] + segment->idDelta) & 65535;
              }
              else
              {
                // Just use idDelta to compute a glyph index...
                glyph = (ch + segment->idDelta) & 65535;
	      }

	      cmapptr[ch] = glyph;
            }
	  }

          // Free the segment data...
	  free(segments);
	  free(glyphIdArray);
        }
        break;

    case 12 :
	{
	  // Format 12: Segmented coverage
	  //
	  // A simple sparse linear segment mapping format.
	  unsigned	ch,		// Current character
			gidx,		// Current group
			nGroups;	// Number of groups
	  _ttf_off_cmap12_t *groups,	// Groups
			*group;		// This group

	  // Read the table...
          /* reserved */ read_ushort(font);

	  if (read_ulong(font) == 0)
	  {
	    errorf(font, "Unable to read cmap table length at offset %u.", coffset);
	    return (false);
	  }

	  /* language = */ read_ulong(font);
	  nGroups        = read_ulong(font);

	  TTF_DEBUG("read_cmap: nGroups=%u\n", nGroups);

	  if (nGroups > TTF_FONT_MAX_GROUPS)
	  {
	    errorf(font, "Invalid cmap table with %u groups.", nGroups);
	    return (false);
	  }

	  if ((groups = (_ttf_off_cmap12_t *)calloc(nGroups, sizeof(_ttf_off_cmap12_t))) == NULL)
          {
            errorf(font, "Unable to allocate memory for cmap.");
            return (false);
	  }

	  for (gidx = 0, group = groups, font->num_cmap = 0; gidx < nGroups; gidx ++, group ++)
	  {
	    group->startCharCode = read_ulong(font);
	    group->endCharCode   = read_ulong(font);
	    group->startGlyphID  = read_ulong(font);
	    TTF_DEBUG("read_cmap: [%u] startCharCode=%u, endCharCode=%u, startGlyphID=%u\n", gidx, group->startCharCode, group->endCharCode, group->startGlyphID);

            if (group->startCharCode > group->endCharCode)
            {
	      errorf(font, "Bad cmap table segment %u to %u.", group->startCharCode, group->endCharCode);
	      free(groups);
	      return (false);
            }

            if (group->endCharCode >= font->num_cmap)
              font->num_cmap = group->endCharCode + 1;
	  }

	  // Based on the end code of the segent table, allocate space for the
	  // uncompressed cmap table...
          TTF_DEBUG("read_cmap: num_cmap=%u\n", (unsigned)font->num_cmap);

	  if (font->num_cmap == 0 || font->num_cmap > TTF_FONT_MAX_CHAR)
	  {
	    errorf(font, "Invalid cmap table with %u characters.", (unsigned)font->num_cmap);
	    free(groups);
	    return (false);
	  }

	  font->cmap = cmapptr = (int *)malloc(font->num_cmap * sizeof(int));

	  if (!font->cmap)
          {
            errorf(font, "Unable to allocate memory for cmap.");
            free(groups);
            return (false);
	  }

	  memset(cmapptr, -1, font->num_cmap * sizeof(int));

	  // Now loop through the groups and assign glyph indices from the
	  // array...
	  for (gidx = 0, group = groups; gidx < nGroups; gidx ++, group ++)
	  {
            for (ch = group->startCharCode; ch <= group->endCharCode && ch < TTF_FONT_MAX_CHAR; ch ++)
              cmapptr[ch] = (int)(group->startGlyphID + ch - group->startCharCode);
          }

	  // Free the group data...
	  free(groups);
	}
        break;

    case 13 :
	{
	  // Format 13: Many-to-one range mappings
	  //
	  // Typically used for fonts of last resort where multiple characters
	  // map to the same glyph.
	  unsigned	ch,		// Current character
			gidx,		// Current group
			nGroups;	// Number of groups
	  _ttf_off_cmap13_t *groups,	// Groups
			*group;		// This group

	  // Read the table...
          /* reserved */ read_ushort(font);

	  if (read_ulong(font) == 0)
	  {
	    errorf(font, "Unable to read cmap table length at offset %u.", coffset);
	    return (false);
	  }

	  /* language = */ read_ulong(font);
	  nGroups        = read_ulong(font);

	  TTF_DEBUG("read_cmap: nGroups=%u\n", nGroups);

	  if (nGroups > TTF_FONT_MAX_GROUPS)
	  {
	    errorf(font, "Invalid cmap table with %u groups.", nGroups);
	    return (false);
	  }

	  if ((groups = (_ttf_off_cmap13_t *)calloc(nGroups, sizeof(_ttf_off_cmap13_t))) == NULL)
	  {
	    errorf(font, "Unable to allocate memory for cmap.");
	    return (false);
	  }

	  for (gidx = 0, group = groups, font->num_cmap = 0; gidx < nGroups; gidx ++, group ++)
	  {
	    group->startCharCode = read_ulong(font);
	    group->endCharCode   = read_ulong(font);
	    group->glyphID       = read_ulong(font);
	    TTF_DEBUG("read_cmap: [%u] startCharCode=%u, endCharCode=%u, glyphID=%u\n", gidx, group->startCharCode, group->endCharCode, group->glyphID);

            if (group->startCharCode > group->endCharCode)
            {
	      errorf(font, "Bad cmap table segment %u to %u.", group->startCharCode, group->endCharCode);
	      free(groups);
	      return (false);
            }

            if (group->endCharCode >= font->num_cmap)
              font->num_cmap = group->endCharCode + 1;
	  }

	  // Based on the end code of the segent table, allocate space for the
	  // uncompressed cmap table...
          TTF_DEBUG("read_cmap: num_cmap=%u\n", (unsigned)font->num_cmap);

	  if (font->num_cmap == 0 || font->num_cmap > TTF_FONT_MAX_CHAR)
	  {
	    errorf(font, "Invalid cmap table with %u characters.", (unsigned)font->num_cmap);
	    free(groups);
	    return (false);
	  }

	  font->cmap = cmapptr = (int *)malloc(font->num_cmap * sizeof(int));

	  if (!font->cmap)
	  {
	    errorf(font, "Unable to allocate cmap.");
	    free(groups);
	    return (false);
	  }

	  memset(cmapptr, -1, font->num_cmap * sizeof(int));

	  // Now loop through the groups and assign glyph indices from the
	  // array...
	  for (gidx = 0, group = groups; gidx < nGroups; gidx ++, group ++)
	  {
            for (ch = group->startCharCode; ch <= group->endCharCode && ch < TTF_FONT_MAX_CHAR; ch ++)
              cmapptr[ch] = (int)group->glyphID;
          }

	  // Free the group data...
	  free(groups);
	}
        break;

    default :
        errorf(font, "Format %d cmap tables are not yet supported.", cformat);
        return (false);
  }

#ifdef DEBUG
  cmapptr = font->cmap;
  for (i = 0; i < (int)font->num_cmap && i < 127; i ++)
  {
    if (cmapptr[i] >= 0)
      TTF_DEBUG("read_cmap; cmap[%d]=%d\n", i, cmapptr[i]);
  }
#endif // DEBUG

  return (true);
}


//
// 'read_head()' - Read the head table.
//

static bool				// O - `true` on success, `false` on error
read_head(ttf_t           *font,	// I - Font
	  _ttf_off_head_t *head)	// O - head table data
{
  memset(head, 0, sizeof(_ttf_off_head_t));

  if (seek_table(font, TTF_OFF_head, 0, true) == 0)
    return (false);

  /* majorVersion */       read_ushort(font);
  /* minorVersion */       read_ushort(font);
  /* fontRevision */       read_ulong(font);
  /* checkSumAdjustment */ read_ulong(font);
  /* magicNumber */        read_ulong(font);
  /* flags */              read_ushort(font);
  head->unitsPerEm       = (unsigned short)read_ushort(font);
  /* created */            read_ulong(font); read_ulong(font);
  /* modified */           read_ulong(font); read_ulong(font);
  head->xMin             = (short)read_short(font);
  head->yMin             = (short)read_short(font);
  head->xMax             = (short)read_short(font);
  head->yMax             = (short)read_short(font);
  head->macStyle         = (unsigned short)read_ushort(font);

  return (true);
}


//
// 'read_hhea()' - Read the hhea table.
//

static bool				// O - `true` on success, `false` on error
read_hhea(ttf_t           *font,	// I - Font
          _ttf_off_hhea_t *hhea)	// O - hhea table data
{
  memset(hhea, 0, sizeof(_ttf_off_hhea_t));

  if (seek_table(font, TTF_OFF_hhea, 0, true) == 0)
    return (false);

  /* majorVersion */        read_ushort(font);
  /* minorVersion */        read_ushort(font);
  hhea->ascender          = (short)read_short(font);
  hhea->descender         = (short)read_short(font);
  /* lineGap */             read_short(font);
  /* advanceWidthMax */     read_ushort(font);
  /* minLeftSideBearing */  read_short(font);
  /* minRightSideBearing */ read_short(font);
  /* mMaxExtent */          read_short(font);
  /* caretSlopeRise */      read_short(font);
  /* caretSlopeRun */       read_short(font);
  /* caretOffset */         read_short(font);
  /* (reserved) */          read_short(font);
  /* (reserved) */          read_short(font);
  /* (reserved) */          read_short(font);
  /* (reserved) */          read_short(font);
  /* metricDataFormat */    read_short(font);
  hhea->numberOfHMetrics  = (unsigned short)read_ushort(font);

  return (true);
}


/*
 * 'read_hmtx()' - Read the horizontal metrics from the font.
 */

static _ttf_metric_t *			// O - Array of glyph metrics
read_hmtx(ttf_t           *font,	// I - Font
          _ttf_off_hhea_t *hhea)	// O - hhea table data
{
  unsigned	length;			// Length of hmtx table
  unsigned	i;			// Looping var
  _ttf_metric_t	*widths;		// Glyph metrics array


  if ((length = seek_table(font, TTF_OFF_hmtx, 0, true)) == 0)
    return (NULL);

  if (length < (unsigned)(4 * hhea->numberOfHMetrics))
  {
    errorf(font, "Length of hhea table is only %u, expected at least %d.", length, 4 * hhea->numberOfHMetrics);
    return (NULL);
  }

  if ((widths = (_ttf_metric_t *)calloc((size_t)hhea->numberOfHMetrics, sizeof(_ttf_metric_t))) == NULL)
    return (NULL);

  for (i = 0; i < hhea->numberOfHMetrics; i ++)
  {
    widths[i].width        = (short)read_ushort(font);
    widths[i].left_bearing = (short)read_short(font);

    TTF_DEBUG("read_hmtx: widths[%d].width=%d, .left_bearing=%d\n", i, widths[i].width, widths[i].left_bearing);
  }

  return (widths);
}


//
// 'read_maxp()' - Read the number of glyphs in the font.
//

static int				// O - Number of glyphs or -1 on error
read_maxp(ttf_t *font)			// I - Font
{
  // All we care about is the number of glyphs, so just grab that...
  if (seek_table(font, TTF_OFF_maxp, 4, true) == 0)
    return (-1);
  else
    return (read_ushort(font));
}


//
// 'read_names()' - Read the name strings from a font.
//

static bool				// O - `true` on success, `false` on error
read_names(ttf_t *font)			// I - Font
{
  unsigned	length;			// Length of names table
  int		i,			// Looping var
		format,			// Name table format
		offset;			// Offset to storage
  _ttf_off_name_t *name;		// Current name


  // Find the name table...
  if ((length = seek_table(font, TTF_OFF_name, 0, true)) == 0)
    return (false);

  if ((format = read_ushort(font)) < 0 || format > 1)
  {
    errorf(font, "Unsupported name table format %d.", format);
    return (false);
  }

  TTF_DEBUG("read_names: format=%d\n", format);

  if ((font->names.num_names = read_ushort(font)) < 1)
    return (false);

  if ((font->names.names = (_ttf_off_name_t *)calloc((size_t)font->names.num_names, sizeof(_ttf_off_name_t))) == NULL)
    return (false);

  if ((offset = read_ushort(font)) < 0 || (unsigned)offset >= length)
    return (false);

  font->names.storage_size = length - (unsigned)offset;
  if (font->names.storage_size > TTF_FONT_MAX_NAMES)
  {
    errorf(font, "Name table too large - %u bytes.", (unsigned)font->names.storage_size);
    return (false);
  }

  if ((font->names.storage = malloc(font->names.storage_size)) == NULL)
    return (false);

  memset(font->names.storage, 'A', font->names.storage_size);

  for (i = font->names.num_names, name = font->names.names; i > 0; i --, name ++)
  {
    name->platform_id = (unsigned short)read_ushort(font);
    name->encoding_id = (unsigned short)read_ushort(font);
    name->language_id = (unsigned short)read_ushort(font);
    name->name_id     = (unsigned short)read_ushort(font);
    name->length      = (unsigned short)read_ushort(font);
    name->offset      = (unsigned short)read_ushort(font);

    TTF_DEBUG("name->platform_id=%d, encoding_id=%d, language_id=%d(0x%04x), name_id=%d, length=%d, offset=%d\n", name->platform_id, name->encoding_id, name->language_id, name->language_id, name->name_id, name->length, name->offset);
  }

  if (format == 1)
  {
    // Skip language_id table...
    int count = read_ushort(font);	// Number of language IDs

    TTF_DEBUG("read_names: Skipping language_id table...\n");

    while (count > 0)
    {
      /* length = */read_ushort(font);
      /* offset = */read_ushort(font);
      count --;
    }
  }

  length -= (unsigned)offset;

  if (read(font->fd, font->names.storage, length) < 0)
  {
    errorf(font, "Unable to read name table: %s", strerror(errno));
    return (false);
  }

  return (true);
}


//
// 'read_os_2()' - Read the OS/2 table.
//

static bool				// O - `true` on success, `false` on error
read_os_2(ttf_t           *font,	// I - Font
          _ttf_off_os_2_t *os_2)	// O - OS/2 table
{
  int		version;		// OS/2 table version
  unsigned char	panose[10];		// panose value


  memset(os_2, 0, sizeof(_ttf_off_os_2_t));

  // Find the OS/2 table...
  if (seek_table(font, TTF_OFF_OS_2, 0, false) == 0)
    return (false);

  if ((version = read_ushort(font)) < 0)
    return (false);

  TTF_DEBUG("read_names: version=%d\n", version);

  /* xAvgCharWidth */       read_short(font);
  os_2->usWeightClass     = (unsigned short)read_ushort(font);
  os_2->usWidthClass      = (unsigned short)read_ushort(font);
  os_2->fsType            = (unsigned short)read_ushort(font);
  /* ySubscriptXSize */     read_short(font);
  /* ySubscriptYSize */     read_short(font);
  /* ySubscriptXOffset */   read_short(font);
  /* ySubscriptYOffset */   read_short(font);
  /* ySuperscriptXSize */   read_short(font);
  /* ySuperscriptYSize */   read_short(font);
  /* ySuperscriptXOffset */ read_short(font);
  /* ySuperscriptYOffset */ read_short(font);
  /* yStrikeoutSize */      read_short(font);
  /* yStrikeoutOffset */    read_short(font);
  /* sFamilyClass */        read_short(font);
  /* panose[10] */
  if (read(font->fd, panose, sizeof(panose)) != (ssize_t)sizeof(panose))
    return (false);
  /* ulUnicodeRange1 */     read_ulong(font);
  /* ulUnicodeRange2 */     read_ulong(font);
  /* ulUnicodeRange3 */     read_ulong(font);
  /* ulUnicodeRange4 */     read_ulong(font);
  /* achVendID */           read_ulong(font); read_ulong(font);
                            read_ulong(font); read_ulong(font);
  /* fsSelection */         read_ushort(font);
  /* usFirstCharIndex */    read_ushort(font);
  /* usLastCharIndex */     read_ushort(font);
  os_2->sTypoAscender     = (short)read_short(font);
  os_2->sTypoDescender    = (short)read_short(font);
  /* sTypoLineGap */        read_short(font);
  /* usWinAscent */         read_ushort(font);
  /* usWinDescent */        read_ushort(font);

  if (version >= 4)
  {
    /* ulCodePageRange1 */  read_ulong(font);
    /* ulCodePageRange2 */  read_ulong(font);
    os_2->sxHeight        = (short)read_short(font);
    os_2->sCapHeight      = (short)read_short(font);
  }

  return (true);
}


//
// 'read_post()' - Read the PostScript table.
//

static bool				// O - `true` on success, `false` otherwise
read_post(ttf_t           *font,	// I - Font
          _ttf_off_post_t *post)	// I - PostScript table
{
  memset(post, 0, sizeof(_ttf_off_post_t));

  if (seek_table(font, TTF_OFF_post, 0, false) == 0)
    return (false);

  /* version            = */read_ulong(font);
  post->italicAngle     = (int)read_ulong(font) / 65536.0f;
  /* underlinePosition  = */read_ushort(font);
  /* underlineThickness = */read_ushort(font);
  post->isFixedPitch    = read_ulong(font);

  return (true);
}


//
// 'read_short()' - Read a 16-bit signed integer.
//

static int				// O - 16-bit signed integer value or EOF
read_short(ttf_t *font)			// I - Font
{
  unsigned char	buffer[2];		// Read buffer


  if (read(font->fd, buffer, sizeof(buffer)) != sizeof(buffer))
    return (EOF);
  else if (buffer[0] & 0x80)
    return (((buffer[0] << 8) | buffer[1]) - 65536);
  else
    return ((buffer[0] << 8) | buffer[1]);
}


//
// 'read_table()' - Read an OFF/TTF offset table.
//

static bool				// O - `true` on success, `false` on error
read_table(ttf_t  *font)		// I - Font
{
  int		i;			// Looping var
  unsigned	temp;			// Temporary value
  _ttf_off_dir_t *current;		// Current table entry


  // Read the table header:
  //
  //     Fixed  sfnt version (should be 0x10000 for version 1.0)
  //     USHORT numTables
  //     USHORT searchRange
  //     USHORT entrySelector
  //     USHORT rangeShift
  /* sfnt version */
  if ((temp = read_ulong(font)) != 0x10000 && temp != 0x4f54544f && temp != 0x74746366)
  {
    errorf(font, "Invalid font file.");
    return (false);
  }

  if (temp == 0x74746366)
  {
    // Font collection, get the number of fonts and then seek to the start of
    // the offset table for the desired font...
    size_t	idx;			// Current index

    TTF_DEBUG("read_table: Font collection\n");

    /* Version */
    if ((temp = read_ulong(font)) != 0x10000 && temp != 0x20000)
    {
      errorf(font, "Unsupported font collection version %f.", temp / 65536.0);
      return (false);
    }

    TTF_DEBUG("read_table: Collection version=%f\n", temp / 65536.0);

    /* numFonts */
    if ((temp = read_ulong(font)) == 0)
    {
      errorf(font, "No fonts in collection.");
      return (false);
    }

    font->num_fonts = (size_t)temp;

    TTF_DEBUG("read_table: numFonts=%u\n", temp);

    if (font->idx >= font->num_fonts)
      return (false);

    /* OffsetTable */
    temp = read_ulong(font);
    for (idx = font->idx; idx > 0; idx --)
      temp = read_ulong(font);

    TTF_DEBUG("read_table: Offset for font %u is %u.\n", (unsigned)font->idx, temp);

    if (lseek(font->fd, temp + 4, SEEK_SET) < 0)
    {
      errorf(font, "Unable to seek to font %u: %s", (unsigned)font->idx, strerror(errno));
      return (false);
    }
  }
  else
  {
    // Not a collection so just one font...
    font->num_fonts = 1;
  }

  // numTables
  if ((font->table.num_entries = read_ushort(font)) <= 0)
  {
    errorf(font, "Unable to read font tables.");
    return (false);
  }

  TTF_DEBUG("read_table: num_entries=%u\n", (unsigned)font->table.num_entries);

  // searchRange
  if (read_ushort(font) < 0)
  {
    errorf(font, "Unable to read font tables.");
    return (false);
  }

  // entrySelector
  if (read_ushort(font) < 0)
  {
    errorf(font, "Unable to read font tables.");
    return (false);
  }

  // rangeShift
  if (read_ushort(font) < 0)
  {
    errorf(font, "Unable to read font tables.");
    return (false);
  }

 /*
  * Read the table entries...
  */

  if ((font->table.entries = calloc((size_t)font->table.num_entries, sizeof(_ttf_off_dir_t))) == NULL)
  {
    errorf(font, "Unable to allocate memory for font tables.");
    return (false);
  }

  for (i = font->table.num_entries, current = font->table.entries; i > 0; i --, current ++)
  {
    current->tag      = read_ulong(font);
    current->checksum = read_ulong(font);
    current->offset   = read_ulong(font);
    current->length   = read_ulong(font);

    TTF_DEBUG("read_table: [%d] tag='%c%c%c%c' checksum=%u offset=%u length=%u\n", i, (current->tag >> 24) & 255, (current->tag >> 16) & 255, (current->tag >> 8) & 255, current->tag & 255, current->checksum, current->offset, current->length);
  }

  return (true);
}


//
// 'read_ulong()' - Read a 32-bit unsigned integer.
//

static unsigned				// O - 32-bit unsigned integer value or EOF
read_ulong(ttf_t *font)			// I - Font
{
  unsigned char	buffer[4];		// Read buffer


  if (read(font->fd, buffer, sizeof(buffer)) != sizeof(buffer))
    return ((unsigned)EOF);
  else
    return ((unsigned)((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]));
}


//
// 'read_ushort()' - Read a 16-bit unsigned integer.
//

static int				// O - 16-bit unsigned integer value or EOF
read_ushort(ttf_t *font)		// I - Font
{
  unsigned char	buffer[2];		// Read buffer


  if (read(font->fd, buffer, sizeof(buffer)) != sizeof(buffer))
    return (EOF);
  else
    return ((buffer[0] << 8) | buffer[1]);
}


//
// 'seek_table()' - Seek to a specific table in a font.
//

static unsigned				// O - Length of table or 0 if not found
seek_table(ttf_t    *font,		// I - Font
           unsigned tag,		// I - Tag to find
           unsigned offset,		// I - Additional offset
           bool     required)		// I - Required table?
{
  int		i;			// Looping var
  _ttf_off_dir_t *current;		// Current entry


  // Look up the tag in the table...
  for (i = font->table.num_entries, current = font->table.entries; i  > 0; i --, current ++)
  {
    if (current->tag == tag)
    {
      // Found it, seek and return...
      if (lseek(font->fd, current->offset + offset, SEEK_SET) == (current->offset + offset))
      {
        // Successful seek...
        return (current->length - offset);
      }
      else
      {
        // Seek failed...
        errorf(font, "Unable to seek to %c%c%c%c table: %s", (tag >> 24) & 255, (tag >> 16) & 255, (tag >> 8) & 255, tag & 255, strerror(errno));
        return (0);
      }
    }
  }

  // Not found, return 0...
  if (required)
    errorf(font, "%c%c%c%c table not found.", (tag >> 24) & 255, (tag >> 16) & 255, (tag >> 8) & 255, tag & 255);

  return (0);
}
