/*******************************************************************************
 *  Adobe Font Metric (AFM) file parsing functions for Wine PostScript driver.
 *  See http://partners.adobe.com/asn/developer/pdfs/tn/5004.AFM_Spec.pdf.
 *
 *  Copyright 2001  Ian Pilcher
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 *  NOTE:  Many of the functions in this file can return either fatal errors
 *  	(memory allocation failure) or non-fatal errors (unusable AFM file).
 *  	Fatal errors are indicated by returning FALSE; see individual function
 *  	descriptions for how they indicate non-fatal errors.
 *
 */

#include "config.h"
#include "wine/port.h"

#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <ctype.h>
#include <limits.h> 	    /* INT_MIN */

#ifdef HAVE_FLOAT_H
#include <float.h>  	    /* FLT_MAX */
#endif

#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "winreg.h"
#include "psdrv.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(psdrv);

/*******************************************************************************
 *  ReadLine
 *
 *  Reads a line from a text file into the buffer and trims trailing whitespace.
 *  Can handle DOS and Unix text files, including weird DOS EOF.  Returns FALSE
 *  for unexpected I/O errors; otherwise returns TRUE and sets *p_result to
 *  either the number of characters in the returned string or one of the
 *  following:
 *
 *  	0:  	    Blank (or all whitespace) line.  This is just a special case
 *  	    	    of the normal behavior.
 *
 *  	EOF:	    End of file has been reached.
 *
 *  	INT_MIN:    Buffer overflow.  Returned string is truncated (duh!) and
 *  	    	    trailing whitespace is *not* trimmed.  Remaining text in
 *  	    	    line is discarded.  (I.e. the file pointer is positioned at
 *  	    	    the beginning of the next line.)
 *
 */
static BOOL ReadLine(FILE *file, CHAR buffer[], INT bufsize, INT *p_result)
{
     CHAR   *cp;
     INT    i;

     if (fgets(buffer, bufsize, file) == NULL)
     {
     	if (feof(file) == 0)	    	    	    	/* EOF or error? */
	{
	    ERR("%s\n", strerror(errno));
    	    return FALSE;
	}

	*p_result = EOF;
	return TRUE;
    }

    cp = strchr(buffer, '\n');
    if (cp == NULL)
    {
    	i = strlen(buffer);

	if (i == bufsize - 1)	    /* max possible; was line truncated? */
	{
	    do
	    	i = fgetc(file);    	    	/* find the newline or EOF */
	    while (i != '\n' && i != EOF);

	    if (i == EOF)
	    {
	    	if (feof(file) == 0)	    	    	/* EOF or error? */
		{
		    ERR("%s\n", strerror(errno));
		    return FALSE;
		}

		WARN("No newline at EOF\n");
	    }

	    *p_result = INT_MIN;
	    return TRUE;
	}
	else	    	    	    	/* no newline and not truncated */
	{
	    if (strcmp(buffer, "\x1a") == 0)	    /* test for DOS EOF */
	    {
	    	*p_result = EOF;
		return TRUE;
	    }

	    WARN("No newline at EOF\n");
	    cp = buffer + i;	/* points to \0 where \n should have been */
	}
    }

    do
    {
    	*cp = '\0'; 	    	    	    	/* trim trailing whitespace */
	if (cp == buffer)
	    break;  	    	    	    	/* don't underflow buffer */
	--cp;
    }
    while (isspace(*cp));

    *p_result = strlen(buffer);
    return TRUE;
}

/*******************************************************************************
 *  FindLine
 *
 *  Finds a line in the file that begins with the given string.  Returns FALSE
 *  for unexpected I/O errors; returns an empty (zero character) string if the
 *  requested line is not found.
 *
 *  NOTE:  The file pointer *MUST* be positioned at the beginning of a line when
 *  	this function is called.  Otherwise, an infinite loop can result.
 *
 */
static BOOL FindLine(FILE *file, CHAR buffer[], INT bufsize, LPCSTR key)
{
    INT     len = strlen(key);
    LONG    start = ftell(file);

    do
    {
	INT 	result;
	BOOL	ok;

	ok = ReadLine(file, buffer, bufsize, &result);
	if (ok == FALSE)
	    return FALSE;

	if (result > 0 && strncmp(buffer, key, len) == 0)
	    return TRUE;

	if (result == EOF)
	{
	    rewind(file);
	}
	else if (result == INT_MIN)
	{
	    WARN("Line beginning '%32s...' is too long; ignoring\n", buffer);
	}
    }
    while (ftell(file) != start);

    WARN("Couldn't find line '%s...' in AFM file\n", key);
    buffer[0] = '\0';
    return TRUE;
}

/*******************************************************************************
 *  DoubleToFloat
 *
 *  Utility function to convert double to float while checking for overflow.
 *  Will also catch strtod overflows, since HUGE_VAL > FLT_MAX (at least on
 *  Linux x86/gcc).
 *
 */
static inline BOOL DoubleToFloat(float *p_f, double d)
{
    if (d > (double)FLT_MAX || d < -(double)FLT_MAX)
    	return FALSE;

    *p_f = (float)d;
    return TRUE;
}

/*******************************************************************************
 *  Round
 *
 *  Utility function to add or subtract 0.5 before converting to integer type.
 *
 */
static inline float Round(float f)
{
    return (f >= 0.0) ? (f + 0.5) : (f - 0.5);
}

/*******************************************************************************
 *  ReadFloat
 *
 *  Finds and parses a line of the form '<key> <value>', where value is a
 *  number.  Sets *p_found to FALSE if a corresponding line cannot be found, or
 *  it cannot be parsed; also sets *p_ret to 0.0, so calling functions can just
 *  skip the check of *p_found if the item is not required.
 *
 */
static BOOL ReadFloat(FILE *file, CHAR buffer[], INT bufsize, LPCSTR key,
    	FLOAT *p_ret, BOOL *p_found)
{
    CHAR    *cp, *end_ptr;
    double  d;

    if (FindLine(file, buffer, bufsize, key) == FALSE)
    	return FALSE;

    if (buffer[0] == '\0')  	    /* line not found */
    {
    	*p_found = FALSE;
	*p_ret = 0.0;
	return TRUE;
    }

    cp = buffer + strlen(key);	    	    	    /* first char after key */
    errno = 0;
    d = strtod(cp, &end_ptr);

    if (end_ptr == cp || errno != 0 || DoubleToFloat(p_ret, d) == FALSE)
    {
    	WARN("Error parsing line '%s'\n", buffer);
    	*p_found = FALSE;
	*p_ret = 0.0;
	return TRUE;
    }

    *p_found = TRUE;
    return TRUE;
}

/*******************************************************************************
 *  ReadInt
 *
 *  See description of ReadFloat.
 *
 */
static BOOL ReadInt(FILE *file, CHAR buffer[], INT bufsize, LPCSTR key,
    	INT *p_ret, BOOL *p_found)
{
    BOOL    retval;
    FLOAT   f;

    retval = ReadFloat(file, buffer, bufsize, key, &f, p_found);
    if (retval == FALSE || *p_found == FALSE)
    {
    	*p_ret = 0;
    	return retval;
    }

    f = Round(f);

    if (f > (FLOAT)INT_MAX || f < (FLOAT)INT_MIN)
    {
    	WARN("Error parsing line '%s'\n", buffer);
    	*p_ret = 0;
	*p_found = FALSE;
	return TRUE;
    }

    *p_ret = (INT)f;
    return TRUE;
}

/*******************************************************************************
 *  ReadString
 *
 *  Returns FALSE on I/O error or memory allocation failure; sets *p_str to NULL
 *  if line cannot be found or can't be parsed.
 *
 */
static BOOL ReadString(FILE *file, CHAR buffer[], INT bufsize, LPCSTR key,
    	LPSTR *p_str)
{
    CHAR    *cp;

    if (FindLine(file, buffer, bufsize, key) == FALSE)
    	return FALSE;

    if (buffer[0] == '\0')
    {
    	*p_str = NULL;
	return TRUE;
    }

    cp = buffer + strlen(key);	    	    	    /* first char after key */
    if (*cp == '\0')
    {
    	*p_str = NULL;
	return TRUE;
    }

    while (isspace(*cp))    	    	/* find first non-whitespace char */
    	++cp;

    *p_str = HeapAlloc(PSDRV_Heap, 0, strlen(cp) + 1);
    if (*p_str == NULL)
    	return FALSE;

    strcpy(*p_str, cp);
    return TRUE;
}

/*******************************************************************************
 *  ReadBBox
 *
 *  Similar to ReadFloat above.
 *
 */
static BOOL ReadBBox(FILE *file, CHAR buffer[], INT bufsize, AFM *afm,
    	BOOL *p_found)
{
    CHAR    *cp, *end_ptr;
    double  d;

    if (FindLine(file, buffer, bufsize, "FontBBox") == FALSE)
    	return FALSE;

    if (buffer[0] == '\0')
    {
    	*p_found = FALSE;
	return TRUE;
    }

    errno = 0;

    cp = buffer + sizeof("FontBBox");
    d = strtod(cp, &end_ptr);
    if (end_ptr == cp || errno != 0 ||
    	    DoubleToFloat(&(afm->FontBBox.llx), d) == FALSE)
    	goto parse_error;

    cp = end_ptr;
    d = strtod(cp, &end_ptr);
    if (end_ptr == cp || errno != 0 ||
    	    DoubleToFloat(&(afm->FontBBox.lly), d) == FALSE)
    	goto parse_error;

    cp = end_ptr;
    d = strtod(cp, &end_ptr);
    if (end_ptr == cp || errno != 0
    	    || DoubleToFloat(&(afm->FontBBox.urx), d) == FALSE)
    	goto parse_error;

    cp = end_ptr;
    d = strtod(cp, &end_ptr);
    if (end_ptr == cp || errno != 0
    	    || DoubleToFloat(&(afm->FontBBox.ury), d) == FALSE)
    	goto parse_error;

    *p_found = TRUE;
    return TRUE;

    parse_error:
    	WARN("Error parsing line '%s'\n", buffer);
	*p_found = FALSE;
	return TRUE;
}

/*******************************************************************************
 *  ReadWeight
 *
 *  Finds and parses the 'Weight' line of an AFM file.  Only tries to determine
 *  if a font is bold (FW_BOLD) or not (FW_NORMAL) -- ignoring all those cute
 *  little FW_* typedefs in the Win32 doc.  AFAICT, this is what the Windows
 *  PostScript driver does.
 *
 */
static const struct { LPCSTR keyword; INT weight; } afm_weights[] =
{
    { "REGULAR",	FW_NORMAL },
    { "NORMAL",         FW_NORMAL },
    { "ROMAN",	    	FW_NORMAL },
    { "BOLD",	    	FW_BOLD },
    { "BOOK",	    	FW_NORMAL },
    { "MEDIUM",     	FW_NORMAL },
    { "LIGHT",	    	FW_NORMAL },
    { "BLACK",	    	FW_BOLD },
    { "HEAVY",	    	FW_BOLD },
    { "DEMI",	    	FW_BOLD },
    { "ULTRA",	    	FW_BOLD },
    { "SUPER" ,     	FW_BOLD },
    { NULL, 	    	0 }
};

static BOOL ReadWeight(FILE *file, CHAR buffer[], INT bufsize, AFM *afm,
    	BOOL *p_found)
{
    LPSTR   sz;
    CHAR    *cp;
    INT     i;

    if (ReadString(file, buffer, bufsize, "Weight", &sz) == FALSE)
    	return FALSE;

    if (sz == NULL)
    {
    	*p_found = FALSE;
	return TRUE;
    }

    for (cp = sz; *cp != '\0'; ++cp)
    	*cp = toupper(*cp);

    for (i = 0; afm_weights[i].keyword != NULL; ++i)
    {
    	if (strstr(sz, afm_weights[i].keyword) != NULL)
	{
	    afm->Weight = afm_weights[i].weight;
	    *p_found = TRUE;
	    HeapFree(PSDRV_Heap, 0, sz);
	    return TRUE;
	}
    }

    WARN("Unknown weight '%s'; treating as Roman\n", sz);

    afm->Weight = FW_NORMAL;
    *p_found = TRUE;
    HeapFree(PSDRV_Heap, 0, sz);
    return TRUE;
}

/*******************************************************************************
 *  ReadFixedPitch
 *
 */
static BOOL ReadFixedPitch(FILE *file, CHAR buffer[], INT bufsize, AFM *afm,
    	BOOL *p_found)
{
    LPSTR   sz;

    if (ReadString(file, buffer, bufsize, "IsFixedPitch", &sz) == FALSE)
    	return FALSE;

    if (sz == NULL)
    {
    	*p_found = FALSE;
	return TRUE;
    }

    if (strcasecmp(sz, "false") == 0)
    {
    	afm->IsFixedPitch = FALSE;
	*p_found = TRUE;
	HeapFree(PSDRV_Heap, 0, sz);
	return TRUE;
    }

    if (strcasecmp(sz, "true") == 0)
    {
    	afm->IsFixedPitch = TRUE;
	*p_found = TRUE;
	HeapFree(PSDRV_Heap, 0, sz);
	return TRUE;
    }

    WARN("Can't parse line '%s'\n", buffer);

    *p_found = FALSE;
    HeapFree(PSDRV_Heap, 0, sz);
    return TRUE;
}

/*******************************************************************************
 *  ReadFontMetrics
 *
 *  Allocates space for the AFM on the driver heap and reads basic font metrics.
 *  Returns FALSE for memory allocation failure; sets *p_afm to NULL if AFM file
 *  is unusable.
 *
 */
static BOOL ReadFontMetrics(FILE *file, CHAR buffer[], INT bufsize, AFM **p_afm)
{
    AFM     *afm;
    BOOL    retval, found;

    *p_afm = afm = HeapAlloc(PSDRV_Heap, 0, sizeof(*afm));
    if (afm == NULL)
    	return FALSE;

    retval = ReadWeight(file, buffer, bufsize, afm, &found);
    if (retval == FALSE || found == FALSE)
    	goto cleanup_afm;

    retval = ReadFloat(file, buffer, bufsize, "ItalicAngle",
    	    &(afm->ItalicAngle), &found);
    if (retval == FALSE || found == FALSE)
    	goto cleanup_afm;

    retval = ReadFixedPitch(file, buffer, bufsize, afm, &found);
    if (retval == FALSE || found == FALSE)
    	goto cleanup_afm;

    retval = ReadBBox(file, buffer, bufsize, afm, &found);
    if (retval == FALSE || found == FALSE)
    	goto cleanup_afm;

    retval = ReadFloat(file, buffer, bufsize, "UnderlinePosition",
    	    &(afm->UnderlinePosition), &found);
    if (retval == FALSE || found == FALSE)
    	goto cleanup_afm;

    retval = ReadFloat(file, buffer, bufsize, "UnderlineThickness",
    	    &(afm->UnderlineThickness), &found);
    if (retval == FALSE || found == FALSE)
    	goto cleanup_afm;

    retval = ReadFloat(file, buffer, bufsize, "Ascender",    	/* optional */
    	    &(afm->Ascender), &found);
    if (retval == FALSE)
    	goto cleanup_afm;

    retval = ReadFloat(file, buffer, bufsize, "Descender",   	/* optional */
    	    &(afm->Descender), &found);
    if (retval == FALSE)
    	goto cleanup_afm;

    afm->WinMetrics.usUnitsPerEm = 1000;
    afm->WinMetrics.sTypoAscender = (SHORT)Round(afm->Ascender);
    afm->WinMetrics.sTypoDescender = (SHORT)Round(afm->Descender);

    if (afm->WinMetrics.sTypoAscender == 0)
    	afm->WinMetrics.sTypoAscender = (SHORT)Round(afm->FontBBox.ury);

    if (afm->WinMetrics.sTypoDescender == 0)
    	afm->WinMetrics.sTypoDescender = (SHORT)Round(afm->FontBBox.lly);

    afm->WinMetrics.sTypoLineGap = 1200 -
    	    (afm->WinMetrics.sTypoAscender - afm->WinMetrics.sTypoDescender);
    if (afm->WinMetrics.sTypoLineGap < 0)
    	afm->WinMetrics.sTypoLineGap = 0;

    return TRUE;

    cleanup_afm:    	    	    	/* handle fatal or non-fatal errors */
    	HeapFree(PSDRV_Heap, 0, afm);
	*p_afm = NULL;
	return retval;
}

/*******************************************************************************
 *  ParseC
 *
 *  Fatal error:    	return FALSE (none defined)
 *
 *  Non-fatal error:	leave metrics->C set to INT_MAX
 *
 */
static BOOL ParseC(LPSTR sz, OLD_AFMMETRICS *metrics)
{
    int     base = 10;
    long    l;
    CHAR    *cp, *end_ptr;

    cp = sz + 1;

    if (*cp == 'H')
    {
    	base = 16;
	++cp;
    }

    errno = 0;
    l = strtol(cp, &end_ptr, base);
    if (end_ptr == cp || errno != 0 || l > INT_MAX || l < INT_MIN)
    {
    	WARN("Error parsing character code '%s'\n", sz);
	return TRUE;
    }

    metrics->C = (INT)l;
    return TRUE;
}

/*******************************************************************************
 *  ParseW
 *
 *  Fatal error:    	return FALSE (none defined)
 *
 *  Non-fatal error:	leave metrics->WX set to FLT_MAX
 *
 */
static BOOL ParseW(LPSTR sz, OLD_AFMMETRICS *metrics)
{
    CHAR    *cp, *end_ptr;
    BOOL    vector = TRUE;
    double  d;

    cp = sz + 1;

    if (*cp == '0')
    	++cp;

    if (*cp == 'X')
    {
    	vector = FALSE;
	++cp;
    }

    if (!isspace(*cp))
    	goto parse_error;

    errno = 0;
    d = strtod(cp, &end_ptr);
    if (end_ptr == cp || errno != 0 ||
    	    DoubleToFloat(&(metrics->WX), d) == FALSE)
    	goto parse_error;

    if (vector == FALSE)
    	return TRUE;

    /*	Make sure that Y component of vector is zero */

    d = strtod(cp, &end_ptr);	    	    	    	    /* errno == 0 */
    if (end_ptr == cp || errno != 0 || d != 0.0)
    {
    	metrics->WX = FLT_MAX;
    	goto parse_error;
    }

    return TRUE;

    parse_error:
    	WARN("Error parsing character width '%s'\n", sz);
	return TRUE;
}

/*******************************************************************************
 *
 *  ParseB
 *
 *  Fatal error:    	return FALSE (none defined)
 *
 *  Non-fatal error:	leave metrics->B.ury set to FLT_MAX
 *
 */
static BOOL ParseB(LPSTR sz, OLD_AFMMETRICS *metrics)
{
    CHAR    *cp, *end_ptr;
    double  d;

    errno = 0;

    cp = sz + 1;
    d = strtod(cp, &end_ptr);
    if (end_ptr == cp || errno != 0 ||
    	    DoubleToFloat(&(metrics->B.llx), d) == FALSE)
	goto parse_error;

    cp = end_ptr;
    d = strtod(cp, &end_ptr);
    if (end_ptr == cp || errno != 0 ||
    	    DoubleToFloat(&(metrics->B.lly), d) == FALSE)
	goto parse_error;

    cp = end_ptr;
    d = strtod(cp, &end_ptr);
    if (end_ptr == cp || errno != 0 ||
    	    DoubleToFloat(&(metrics->B.urx), d) == FALSE)
	goto parse_error;

    cp = end_ptr;
    d = strtod(cp, &end_ptr);
    if (end_ptr == cp || errno != 0 ||
    	    DoubleToFloat(&(metrics->B.ury), d) == FALSE)
	goto parse_error;

    return TRUE;

    parse_error:
    	WARN("Error parsing glyph bounding box '%s'\n", sz);
	return TRUE;
}

/*******************************************************************************
 *  ParseN
 *
 *  Fatal error:    	return FALSE (PSDRV_GlyphName failure)
 *
 *  Non-fatal error:	leave metrics-> set to NULL
 *
 */
static BOOL ParseN(LPSTR sz, OLD_AFMMETRICS *metrics)
{
    CHAR    save, *cp, *end_ptr;

    cp = sz + 1;

    while (isspace(*cp))
    	++cp;

    end_ptr = cp;

    while (*end_ptr != '\0' && !isspace(*end_ptr))
    	++end_ptr;

    if (end_ptr == cp)
    {
    	WARN("Error parsing glyph name '%s'\n", sz);
	return TRUE;
    }

    save = *end_ptr;
    *end_ptr = '\0';

    metrics->N = PSDRV_GlyphName(cp);
    if (metrics->N == NULL)
    	return FALSE;

    *end_ptr = save;
    return TRUE;
}

/*******************************************************************************
 *  ParseCharMetrics
 *
 *  Parses the metrics line for a single glyph in an AFM file.  Returns FALSE on
 *  fatal error; sets *metrics to 'badmetrics' on non-fatal error.
 *
 */
static const OLD_AFMMETRICS badmetrics =
{
    INT_MAX,	    	    	    	    	    /* C */
    LONG_MAX,	    	    	    	    	    /* UV */
    FLT_MAX,	    	    	    	    	    /* WX */
    NULL,   	    	    	    	    	    /* N */
    { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }, 	    /* B */
    NULL    	    	    	    	    	    /* L */
};

static BOOL ParseCharMetrics(LPSTR buffer, INT len, OLD_AFMMETRICS *metrics)
{
    CHAR    *cp = buffer;

    *metrics = badmetrics;

    while (*cp != '\0')
    {
    	while (isspace(*cp))
	    ++cp;

	switch(*cp)
	{
	    case 'C':	if (ParseC(cp, metrics) == FALSE)
	    	    	    return FALSE;
	    	    	break;

	    case 'W':	if (ParseW(cp, metrics) == FALSE)
	    	    	    return FALSE;
	    	    	break;

	    case 'N':	if (ParseN(cp, metrics) == FALSE)
	    	    	    return FALSE;
	    	    	break;

	    case 'B':	if (ParseB(cp, metrics) == FALSE)
	    	    	    return FALSE;
	    	    	break;
	}

	cp = strchr(cp, ';');
	if (cp == NULL)
	{
	    WARN("No terminating semicolon\n");
	    break;
	}

	++cp;
    }

    if (metrics->C == INT_MAX || metrics->WX == FLT_MAX || metrics->N == NULL ||
    	    metrics->B.ury == FLT_MAX)
    {
    	*metrics = badmetrics;
	return TRUE;
    }

    return TRUE;
}

/*******************************************************************************
 *  IsWinANSI
 *
 *  Checks whether Unicode value is part of Microsoft code page 1252
 *
 */
static const LONG ansiChars[21] =
{
    0x0152, 0x0153, 0x0160, 0x0161, 0x0178, 0x017d, 0x017e, 0x0192, 0x02c6,
    0x02c9, 0x02dc, 0x03bc, 0x2013, 0x2014, 0x2026, 0x2030, 0x2039, 0x203a,
    0x20ac, 0x2122, 0x2219
};

static int cmpUV(const void *a, const void *b)
{
    return (int)(*((const LONG *)a) - *((const LONG *)b));
}

static inline BOOL IsWinANSI(LONG uv)
{
    if ((0x0020 <= uv && uv <= 0x007e) || (0x00a0 <= uv && uv <= 0x00ff) ||
    	    (0x2018 <= uv && uv <= 0x201a) || (0x201c <= uv && uv <= 0x201e) ||
	    (0x2020 <= uv && uv <= 0x2022))
    	return TRUE;

    if (bsearch(&uv, ansiChars, 21, sizeof(INT), cmpUV) != NULL)
    	return TRUE;

    return FALSE;
}

/*******************************************************************************
 *  Unicodify
 *
 *  Determines Unicode value (UV) for each glyph, based on font encoding.
 *
 *  	FontSpecific:	Usable encodings (0x20 - 0xff) are mapped into the
 *  	    	    	Unicode private use range U+F020 - U+F0FF.
 *
 *  	other:	    	UV determined by glyph name, based on Adobe Glyph List.
 *
 *  Also does some font metric calculations that require UVs to be known.
 *
 */
static int UnicodeGlyphByNameIndex(const void *a, const void *b)
{
    return ((const UNICODEGLYPH *)a)->name->index -
    	    ((const UNICODEGLYPH *)b)->name->index;
}

static VOID Unicodify(AFM *afm, OLD_AFMMETRICS *metrics)
{
    INT     i;

    if (strcmp(afm->EncodingScheme, "FontSpecific") == 0)
    {
    	for (i = 0; i < afm->NumofMetrics; ++i)
	{
	    if (metrics[i].C >= 0x20 && metrics[i].C <= 0xff)
	    {
		metrics[i].UV = metrics[i].C | 0xf000L;
	    }
	    else
	    {
	    	TRACE("Unencoded glyph '%s'\n", metrics[i].N->sz);
		metrics[i].UV = -1L;
	    }
	}

	afm->WinMetrics.sAscender = (SHORT)Round(afm->FontBBox.ury);
	afm->WinMetrics.sDescender = (SHORT)Round(afm->FontBBox.lly);
    }
    else    	    	    	    	    	/* non-FontSpecific encoding */
    {
    	UNICODEGLYPH	ug, *p_ug;

	PSDRV_IndexGlyphList();     	/* for fast searching of glyph names */

	afm->WinMetrics.sAscender = afm->WinMetrics.sDescender = 0;

	for (i = 0; i < afm->NumofMetrics; ++i)
	{
	    ug.name = metrics[i].N;
	    p_ug = bsearch(&ug, PSDRV_AGLbyName, PSDRV_AGLbyNameSize,
	    	    sizeof(ug), UnicodeGlyphByNameIndex);
	    if (p_ug == NULL)
	    {
	    	TRACE("Glyph '%s' not in Adobe Glyph List\n", ug.name->sz);
		metrics[i].UV = -1L;
	    }
	    else
	    {
	    	metrics[i].UV = p_ug->UV;

		if (IsWinANSI(p_ug->UV))
		{
		    SHORT   ury = (SHORT)Round(metrics[i].B.ury);
		    SHORT   lly = (SHORT)Round(metrics[i].B.lly);

		    if (ury > afm->WinMetrics.sAscender)
		    	afm->WinMetrics.sAscender = ury;
		    if (lly < afm->WinMetrics.sDescender)
		    	afm->WinMetrics.sDescender = lly;
		}
	    }
	}

	if (afm->WinMetrics.sAscender == 0)
	    afm->WinMetrics.sAscender = (SHORT)Round(afm->FontBBox.ury);
	if (afm->WinMetrics.sDescender == 0)
	    afm->WinMetrics.sDescender = (SHORT)Round(afm->FontBBox.lly);
    }

    afm->WinMetrics.sLineGap =
    	    1150 - (afm->WinMetrics.sAscender - afm->WinMetrics.sDescender);
    if (afm->WinMetrics.sLineGap < 0)
    	afm->WinMetrics.sLineGap = 0;

    afm->WinMetrics.usWinAscent = (afm->WinMetrics.sAscender > 0) ?
    	    afm->WinMetrics.sAscender : 0;
    afm->WinMetrics.usWinDescent = (afm->WinMetrics.sDescender < 0) ?
    	    -(afm->WinMetrics.sDescender) : 0;
}

/*******************************************************************************
 *  ReadCharMetrics
 *
 *  Reads metrics for all glyphs.  *p_metrics will be NULL on non-fatal error.
 *
 */
static int OldAFMMetricsByUV(const void *a, const void *b)
{
    return ((const OLD_AFMMETRICS *)a)->UV - ((const OLD_AFMMETRICS *)b)->UV;
}

static BOOL ReadCharMetrics(FILE *file, CHAR buffer[], INT bufsize, AFM *afm,
    	AFMMETRICS **p_metrics)
{
    BOOL    	    retval, found;
    OLD_AFMMETRICS  *old_metrics, *encoded_metrics;
    AFMMETRICS	    *metrics;
    INT     	    i, len;

    retval = ReadInt(file, buffer, bufsize, "StartCharMetrics",
    	    &(afm->NumofMetrics), &found);
    if (retval == FALSE || found == FALSE)
    {
    	*p_metrics = NULL;
	return retval;
    }

    old_metrics = HeapAlloc(PSDRV_Heap, 0,
    	    afm->NumofMetrics * sizeof(*old_metrics));
    if (old_metrics == NULL)
    	return FALSE;

    for (i = 0; i < afm->NumofMetrics; ++i)
    {
    	retval = ReadLine(file, buffer, bufsize, &len);
    	if (retval == FALSE)
	    goto cleanup_old_metrics;

	if(len > 0)
	{
	    retval = ParseCharMetrics(buffer, len, old_metrics + i);
	    if (retval == FALSE || old_metrics[i].C == INT_MAX)
	    	goto cleanup_old_metrics;

	    continue;
	}

	switch (len)
	{
	    case 0: 	    --i;
		    	    continue;

	    case INT_MIN:   WARN("Ignoring long line '%32s...'\n", buffer);
			    goto cleanup_old_metrics;	    /* retval == TRUE */

	    case EOF:	    WARN("Unexpected EOF\n");
	    	    	    goto cleanup_old_metrics;  	    /* retval == TRUE */
	}
    }

    Unicodify(afm, old_metrics);    /* wait until glyph names have been read */

    qsort(old_metrics, afm->NumofMetrics, sizeof(*old_metrics),
    	    OldAFMMetricsByUV);

    for (i = 0; old_metrics[i].UV == -1; ++i);      /* count unencoded glyphs */

    afm->NumofMetrics -= i;
    encoded_metrics = old_metrics + i;

    afm->Metrics = *p_metrics = metrics = HeapAlloc(PSDRV_Heap, 0,
    	    afm->NumofMetrics * sizeof(*metrics));
    if (afm->Metrics == NULL)
    	goto cleanup_old_metrics;   	    	    	    /* retval == TRUE */

    for (i = 0; i < afm->NumofMetrics; ++i, ++metrics, ++encoded_metrics)
    {
    	metrics->C = encoded_metrics->C;
	metrics->UV = encoded_metrics->UV;
	metrics->WX = encoded_metrics->WX;
	metrics->N = encoded_metrics->N;
    }

    HeapFree(PSDRV_Heap, 0, old_metrics);

    afm->WinMetrics.sAvgCharWidth = PSDRV_CalcAvgCharWidth(afm);

    return TRUE;

    cleanup_old_metrics:    	    	/* handle fatal or non-fatal errors */
    	HeapFree(PSDRV_Heap, 0, old_metrics);
	*p_metrics = NULL;
	return retval;
}

/*******************************************************************************
 *  BuildAFM
 *
 *  Builds the AFM for a PostScript font and adds it to the driver font list.
 *  Returns FALSE only on an unexpected error (memory allocation or I/O error).
 *
 */
static BOOL BuildAFM(FILE *file)
{
    CHAR    	buffer[258];    	/* allow for <cr>, <lf>, and <nul> */
    AFM     	*afm;
    AFMMETRICS	*metrics;
    LPSTR   	font_name, full_name, family_name, encoding_scheme;
    BOOL    	retval, added;

    retval = ReadFontMetrics(file, buffer, sizeof(buffer), &afm);
    if (retval == FALSE || afm == NULL)
    	return retval;

    retval = ReadString(file, buffer, sizeof(buffer), "FontName", &font_name);
    if (retval == FALSE || font_name == NULL)
    	goto cleanup_afm;

    retval = ReadString(file, buffer, sizeof(buffer), "FullName", &full_name);
    if (retval == FALSE || full_name == NULL)
    	goto cleanup_font_name;

    retval = ReadString(file, buffer, sizeof(buffer), "FamilyName",
    	    &family_name);
    if (retval == FALSE || family_name == NULL)
    	goto cleanup_full_name;

    retval = ReadString(file, buffer, sizeof(buffer), "EncodingScheme",
    	    &encoding_scheme);
    if (retval == FALSE || encoding_scheme == NULL)
    	goto cleanup_family_name;

    afm->FontName = font_name;
    afm->FullName = full_name;
    afm->FamilyName = family_name;
    afm->EncodingScheme = encoding_scheme;

    retval = ReadCharMetrics(file, buffer, sizeof(buffer), afm, &metrics);
    if (retval == FALSE || metrics == FALSE)
    	goto cleanup_encoding_scheme;

    retval = PSDRV_AddAFMtoList(&PSDRV_AFMFontList, afm, &added);
    if (retval == FALSE || added == FALSE)
    	goto cleanup_encoding_scheme;

    return TRUE;

    /* clean up after fatal or non-fatal errors */

    cleanup_encoding_scheme:
    	HeapFree(PSDRV_Heap, 0, encoding_scheme);
    cleanup_family_name:
    	HeapFree(PSDRV_Heap, 0, family_name);
    cleanup_full_name:
    	HeapFree(PSDRV_Heap, 0, full_name);
    cleanup_font_name:
    	HeapFree(PSDRV_Heap, 0, font_name);
    cleanup_afm:
    	HeapFree(PSDRV_Heap, 0, afm);

    return retval;
}

/*******************************************************************************
 *  ReadAFMFile
 *
 *  Reads font metrics from Type 1 AFM file.  Only returns FALSE for
 *  unexpected errors (memory allocation or I/O).
 *
 */
static BOOL ReadAFMFile(LPCSTR filename)
{
    FILE    *f;
    BOOL    retval;

    TRACE("%s\n", filename);

    f = fopen(filename, "r");
    if (f == NULL)
    {
    	WARN("%s: %s\n", filename, strerror(errno));
	return TRUE;
    }

    retval = BuildAFM(f);

    fclose(f);
    return retval;
}

/*******************************************************************************
 *  ReadAFMDir
 *
 *  Reads all Type 1 AFM files in a directory.
 *
 */
static BOOL ReadAFMDir(LPCSTR dirname)
{
    struct dirent   *dent;
    DIR     	    *dir;
    CHAR    	    filename[256];

    dir = opendir(dirname);
    if (dir == NULL)
    {
    	WARN("%s: %s\n", dirname, strerror(errno));
	return TRUE;
    }

    while ((dent = readdir(dir)) != NULL)
    {
    	CHAR	*file_extension = strchr(dent->d_name, '.');
	int 	fn_len;

	if (file_extension == NULL || strcasecmp(file_extension, ".afm") != 0)
	    continue;

	fn_len = snprintf(filename, 256, "%s/%s", dirname, dent->d_name);
	if (fn_len < 0 || fn_len > sizeof(filename) - 1)
	{
	    WARN("Path '%s/%s' is too long\n", dirname, dent->d_name);
	    continue;
	}

	if (ReadAFMFile(filename) == FALSE)
	{
	    closedir(dir);
	    return FALSE;
	}
    }

    closedir(dir);
    return TRUE;
}

/*******************************************************************************
 *  PSDRV_GetType1Metrics
 *
 *  Reads font metrics from Type 1 AFM font files in directories listed in the
 *  [afmdirs] section of the Wine configuration file.
 *
 *  If this function fails (returns FALSE), the driver will fail to initialize
 *  and the driver heap will be destroyed, so it's not necessary to HeapFree
 *  everything in that event.
 *
 */
BOOL PSDRV_GetType1Metrics(void)
{
    static const WCHAR pathW[] = {'A','F','M','P','a','t','h',0};
    HKEY hkey;
    DWORD len;
    LPWSTR valueW;
    LPSTR valueA, ptr;

    /* @@ Wine registry key: HKCU\Software\Wine\Fonts */
    if (RegOpenKeyA(HKEY_CURRENT_USER, "Software\\Wine\\Fonts", &hkey) != ERROR_SUCCESS)
        return TRUE;

    if (RegQueryValueExW( hkey, pathW, NULL, NULL, NULL, &len ) == ERROR_SUCCESS)
    {
        len += sizeof(WCHAR);
        valueW = HeapAlloc( PSDRV_Heap, 0, len );
        if (RegQueryValueExW( hkey, pathW, NULL, NULL, (LPBYTE)valueW, &len ) == ERROR_SUCCESS)
        {
            len = WideCharToMultiByte( CP_UNIXCP, 0, valueW, -1, NULL, 0, NULL, NULL );
            valueA = HeapAlloc( PSDRV_Heap, 0, len );
            WideCharToMultiByte( CP_UNIXCP, 0, valueW, -1, valueA, len, NULL, NULL );
            TRACE( "got AFM font path %s\n", debugstr_a(valueA) );
            ptr = valueA;
            while (ptr)
            {
                LPSTR next = strchr( ptr, ':' );
                if (next) *next++ = 0;
                if (!ReadAFMDir( ptr ))
                {
                    RegCloseKey(hkey);
                    return FALSE;
                }
                ptr = next;
            }
            HeapFree( PSDRV_Heap, 0, valueA );
        }
        HeapFree( PSDRV_Heap, 0, valueW );
    }

    RegCloseKey(hkey);
    return TRUE;
}
