/*
 * (C) 1988, 1989, 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * This file may be freely copied and redistributed as long as:
 *   1) This entire notice continues to be included in the file,
 *   2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * PostScript, Display PostScript, and Adobe are registered trademarks of
 * Adobe Systems Incorporated.
 *
 * ************************************************************************
 * THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO CHANGE WITHOUT
 * NOTICE, AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY ADOBE SYSTEMS
 * INCORPORATED. ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY OR
 * LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO WARRANTY OF ANY
 * KIND (EXPRESS, IMPLIED OR STATUTORY) WITH RESPECT TO THIS INFORMATION,
 * AND EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * ************************************************************************
 */

/*
 * modified for MDVI:
 *   - some names changed to avoid conflicts with T1lib
 *   - changed to ANSI C prototypes, as used by MDVI
 * mal - 3/01
 */

/* parseAFM.c
 *
 * This file is used in conjunction with the parseAFM.h header file.
 * This file contains several procedures that are used to parse AFM
 * files. It is intended to work with an application program that needs
 * font metric information. The program can be used as is by making a
 * procedure call to "parseFile" (passing in the expected parameters)
 * and having it fill in a data structure with the data from the
 * AFM file, or an application developer may wish to customize this
 * code.
 *
 * There is also a file, parseAFMclient.c, that is a sample application
 * showing how to call the "parseFile" procedure and how to use the data
 * after "parseFile" has returned.
 *
 * Please read the comments in parseAFM.h and parseAFMclient.c.
 *
 * History:
 *	original: DSM  Thu Oct 20 17:39:59 PDT 1988
 *  modified: DSM  Mon Jul  3 14:17:50 PDT 1989
 *    - added 'storageProblem' return code
 *	  - fixed bug of not allocating extra byte for string duplication
 *    - fixed typos
 *  modified: DSM  Tue Apr  3 11:18:34 PDT 1990
 *    - added free(ident) at end of parseFile routine
 *  modified: DSM  Tue Jun 19 10:16:29 PDT 1990
 *    - changed (width == 250) to (width = 250) in initializeArray
 */

#include <config.h>
#include "sysdeps.h"

#ifdef WITH_AFM_FILES

#include <stdlib.h> /* added for MDVI */
#include <string.h> /* added for MDVI */

#include <stdio.h>
#include <errno.h>
#include <sys/file.h>
#include <math.h>
#include "afmparse.h"
#undef VERSION

#define lineterm EOL	/* line terminating character */
#define normalEOF 1	/* return code from parsing routines used only */
			/* in this module */
#define Space "space"   /* used in string comparison to look for the width */
			/* of the space character to init the widths array */
#define False "false"   /* used in string comparison to check the value of */
			/* boolean keys (e.g. IsFixedPitch)  */

#define MATCH(A,B)		(strncmp((A),(B), MAX_NAME) == 0)



/*************************** GLOBALS ***********************/

static char *ident = NULL; /* storage buffer for keywords */


/* "shorts" for fast case statement
 * The values of each of these enumerated items correspond to an entry in the
 * table of strings defined below. Therefore, if you add a new string as
 * new keyword into the keyStrings table, you must also add a corresponding
 * parseKey AND it MUST be in the same position!
 *
 * IMPORTANT: since the sorting algorithm is a binary search, the strings of
 * keywords must be placed in lexicographical order, below. [Therefore, the
 * enumerated items are not necessarily in lexicographical order, depending
 * on the name chosen. BUT, they must be placed in the same position as the
 * corresponding key string.] The NOPE shall remain in the last position,
 * since it does not correspond to any key string, and it is used in the
 * "recognize" procedure to calculate how many possible keys there are.
 */

enum parseKey {
  ASCENDER, CHARBBOX, CODE, COMPCHAR, CAPHEIGHT, COMMENT,
  DESCENDER, ENCODINGSCHEME, ENDCHARMETRICS, ENDCOMPOSITES,
  ENDFONTMETRICS, ENDKERNDATA, ENDKERNPAIRS, ENDTRACKKERN,
  FAMILYNAME, FONTBBOX, FONTNAME, FULLNAME, ISFIXEDPITCH,
  ITALICANGLE, KERNPAIR, KERNPAIRXAMT, LIGATURE, CHARNAME,
  NOTICE, COMPCHARPIECE, STARTCHARMETRICS, STARTCOMPOSITES,
  STARTFONTMETRICS, STARTKERNDATA, STARTKERNPAIRS,
  STARTTRACKKERN, TRACKKERN, UNDERLINEPOSITION,
  UNDERLINETHICKNESS, VERSION, XYWIDTH, XWIDTH, WEIGHT, XHEIGHT,
  NOPE } ;

/* keywords for the system:
 * This a table of all of the current strings that are vaild AFM keys.
 * Each entry can be referenced by the appropriate parseKey value (an
 * enumerated data type defined above). If you add a new keyword here,
 * a corresponding parseKey MUST be added to the enumerated data type
 * defined above, AND it MUST be added in the same position as the
 * string is in this table.
 *
 * IMPORTANT: since the sorting algorithm is a binary search, the keywords
 * must be placed in lexicographical order. And, NULL should remain at the
 * end.
 */

static char *keyStrings[] = {
  "Ascender", "B", "C", "CC", "CapHeight", "Comment",
  "Descender", "EncodingScheme", "EndCharMetrics", "EndComposites",
  "EndFontMetrics", "EndKernData", "EndKernPairs", "EndTrackKern",
  "FamilyName", "FontBBox", "FontName", "FullName", "IsFixedPitch",
  "ItalicAngle", "KP", "KPX", "L", "N",
  "Notice", "PCC", "StartCharMetrics", "StartComposites",
  "StartFontMetrics", "StartKernData", "StartKernPairs",
  "StartTrackKern", "TrackKern", "UnderlinePosition",
  "UnderlineThickness", "Version", "W", "WX", "Weight", "XHeight",
  NULL };

/*************************** PARSING ROUTINES **************/

/*************************** token *************************/

/*  A "AFM File Conventions" tokenizer. That means that it will
 *  return the next token delimited by white space.  See also
 *  the `linetoken' routine, which does a similar thing but
 *  reads all tokens until the next end-of-line.
 */

static char *token(FILE *stream)
{
    int ch, idx;

    /* skip over white space */
    while ((ch = fgetc(stream)) == ' ' || ch == lineterm ||
            ch == ',' || ch == '\t' || ch == ';');

    idx = 0;
    while (ch != EOF && ch != ' ' && ch != lineterm
           && ch != '\t' && ch != ':' && ch != ';' && idx < (MAX_NAME - 1))
    {
        ident[idx++] = ch;
        ch = fgetc(stream);
    } /* while */

    if (ch == EOF && idx < 1) return ((char *)NULL);
    if (idx >= 1 && ch != ':' ) ungetc(ch, stream);
    if (idx < 1 ) ident[idx++] = ch;	/* single-character token */
    ident[idx] = 0;

    return(ident);	/* returns pointer to the token */

} /* token */


/*************************** linetoken *************************/

/*  "linetoken" will get read all tokens until the EOL character from
 *  the given stream.  This is used to get any arguments that can be
 *  more than one word (like Comment lines and FullName).
 */

static char *linetoken(FILE *stream)
{
    int ch, idx;

    while ((ch = fgetc(stream)) == ' ' || ch == '\t' );

    idx = 0;
    while (ch != EOF && ch != lineterm && idx < (MAX_NAME - 1))
    {
        ident[idx++] = ch;
        ch = fgetc(stream);
    } /* while */

    ungetc(ch, stream);
    ident[idx] = 0;

    return(ident);	/* returns pointer to the token */

} /* linetoken */


/*************************** recognize *************************/

/*  This function tries to match a string to a known list of
 *  valid AFM entries (check the keyStrings array above).
 *  "ident" contains everything from white space through the
 *  next space, tab, or ":" character.
 *
 *  The algorithm is a standard Knuth binary search.
 */

static enum parseKey recognize(char *ident)
{
    int lower = 0, upper = (int) NOPE, midpoint, cmpvalue;
    BOOL found = FALSE;

    while ((upper >= lower) && !found)
    {
        midpoint = (lower + upper)/2;
        if (keyStrings[midpoint] == NULL) break;
        cmpvalue = strncmp(ident, keyStrings[midpoint], MAX_NAME);
        if (cmpvalue == 0) found = TRUE;
        else if (cmpvalue < 0) upper = midpoint - 1;
        else lower = midpoint + 1;
    } /* while */

    if (found) return (enum parseKey) midpoint;
    else return NOPE;

} /* recognize */


/************************* parseGlobals *****************************/

/*  This function is called by "parseFile". It will parse the AFM File
 *  up to the "StartCharMetrics" keyword, which essentially marks the
 *  end of the Global Font Information and the beginning of the character
 *  metrics information.
 *
 *  If the caller of "parseFile" specified that it wanted the Global
 *  Font Information (as defined by the "AFM File Specification"
 *  document), then that information will be stored in the returned
 *  data structure.
 *
 *  Any Global Font Information entries that are not found in a
 *  given file, will have the usual default initialization value
 *  for its type (i.e. entries of type int will be 0, etc).
 *
 *  This function returns an error code specifying whether there was
 *  a premature EOF or a parsing error. This return value is used by
 *  parseFile to determine if there is more file to parse.
 */

static BOOL parseGlobals(FILE *fp, GlobalFontInfo *gfi)
{
    BOOL cont = TRUE, save = (gfi != NULL);
    int error = ok;
    register char *keyword;

    while (cont)
    {
        keyword = token(fp);

        if (keyword == NULL)
          /* Have reached an early and unexpected EOF. */
          /* Set flag and stop parsing */
        {
            error = earlyEOF;
            break;   /* get out of loop */
        }
        if (!save)
          /* get tokens until the end of the Global Font info section */
          /* without saving any of the data */
            switch (recognize(keyword))
            {
                case STARTCHARMETRICS:
                    cont = FALSE;
                    break;
                case ENDFONTMETRICS:
                    cont = FALSE;
                    error = normalEOF;
                    break;
                default:
                    break;
            } /* switch */
        else
          /* otherwise parse entire global font info section, */
          /* saving the data */
            switch(recognize(keyword))
            {
                case STARTFONTMETRICS:
                    keyword = token(fp);
                    gfi->afmVersion = (char *) malloc(strlen(keyword) + 1);
                    strcpy(gfi->afmVersion, keyword);
                    break;
                case COMMENT:
                    keyword = linetoken(fp);
                    break;
                case FONTNAME:
                    keyword = token(fp);
                    gfi->fontName = (char *) malloc(strlen(keyword) + 1);
                    strcpy(gfi->fontName, keyword);
                    break;
                case ENCODINGSCHEME:
                    keyword = token(fp);
                    gfi->encodingScheme = (char *)
                    	malloc(strlen(keyword) + 1);
                    strcpy(gfi->encodingScheme, keyword);
                    break;
                case FULLNAME:
                    keyword = linetoken(fp);
                    gfi->fullName = (char *) malloc(strlen(keyword) + 1);
                    strcpy(gfi->fullName, keyword);
                    break;
                case FAMILYNAME:
                   keyword = linetoken(fp);
                    gfi->familyName = (char *) malloc(strlen(keyword) + 1);
                    strcpy(gfi->familyName, keyword);
                    break;
                case WEIGHT:
                    keyword = token(fp);
                    gfi->weight = (char *) malloc(strlen(keyword) + 1);
                    strcpy(gfi->weight, keyword);
                    break;
                case ITALICANGLE:
                    keyword = token(fp);
                    gfi->italicAngle = atof(keyword);
                    if (errno == ERANGE) error = parseError;
                    break;
                case ISFIXEDPITCH:
                    keyword = token(fp);
                    if (MATCH(keyword, False))
                        gfi->isFixedPitch = 0;
                    else
                        gfi->isFixedPitch = 1;
                    break;
	            case UNDERLINEPOSITION:
                    keyword = token(fp);
	                gfi->underlinePosition = atoi(keyword);
                    break;
                case UNDERLINETHICKNESS:
                    keyword = token(fp);
                    gfi->underlineThickness = atoi(keyword);
                    break;
                case VERSION:
                    keyword = token(fp);
                    gfi->version = (char *) malloc(strlen(keyword) + 1);
                    strcpy(gfi->version, keyword);
                    break;
                case NOTICE:
                    keyword = linetoken(fp);
                    gfi->notice = (char *) malloc(strlen(keyword) + 1);
                    strcpy(gfi->notice, keyword);
                    break;
                case FONTBBOX:
                    keyword = token(fp);
                    gfi->fontBBox.llx = atoi(keyword);
                    keyword = token(fp);
                    gfi->fontBBox.lly = atoi(keyword);
                    keyword = token(fp);
                    gfi->fontBBox.urx = atoi(keyword);
                    keyword = token(fp);
                    gfi->fontBBox.ury = atoi(keyword);
                    break;
                case CAPHEIGHT:
                    keyword = token(fp);
                    gfi->capHeight = atoi(keyword);
                    break;
                case XHEIGHT:
                    keyword = token(fp);
                    gfi->xHeight = atoi(keyword);
                    break;
                case DESCENDER:
                    keyword = token(fp);
                    gfi->descender = atoi(keyword);
                    break;
                case ASCENDER:
                    keyword = token(fp);
                    gfi->ascender = atoi(keyword);
                    break;
                case STARTCHARMETRICS:
                    cont = FALSE;
                    break;
                case ENDFONTMETRICS:
                    cont = FALSE;
                    error = normalEOF;
                    break;
                case NOPE:
                default:
                    error = parseError;
                    break;
            } /* switch */
    } /* while */

    return(error);

} /* parseGlobals */



#if 0 /* this function does not seem to be used anywhere */
/************************* initializeArray ************************/

/*  Unmapped character codes are (at Adobe Systems) assigned the
 *  width of the space character (if one exists) else they get the
 *  value of 250 ems. This function initializes all entries in the
 *  char widths array to have this value. Then any mapped character
 *  codes will be replaced with the width of the appropriate character
 *  when parsing the character metric section.

 *  This function parses the Character Metrics Section looking
 *  for a space character (by comparing character names). If found,
 *  the width of the space character will be used to initialize the
 *  values in the array of character widths.
 *
 *  Before returning, the position of the read/write pointer of the
 *  file is reset to be where it was upon entering this function.
 */

static int initializeArray(FILE *fp, int *cwi)
{
    BOOL cont = TRUE, found = FALSE;
    long opos = ftell(fp);
    int code = 0, width = 0, i = 0, error = 0;
    register char *keyword;

    while (cont)
    {
        keyword = token(fp);
        if (keyword == NULL)
        {
            error = earlyEOF;
            break; /* get out of loop */
        }
        switch(recognize(keyword))
        {
            case COMMENT:
                keyword = linetoken(fp);
                break;
            case CODE:
                code = atoi(token(fp));
                break;
            case XWIDTH:
                width = atoi(token(fp));
                break;
            case CHARNAME:
                keyword = token(fp);
                if (MATCH(keyword, Space))
                {
                    cont = FALSE;
                    found = TRUE;
                }
                break;
            case ENDCHARMETRICS:
                cont = FALSE;
                break;
            case ENDFONTMETRICS:
                cont = FALSE;
                error = normalEOF;
                break;
            case NOPE:
            default:
                error = parseError;
                break;
        } /* switch */
    } /* while */

    if (!found)
        width = 250;

    for (i = 0; i < 256; ++i)
        cwi[i] = width;

    fseek(fp, opos, 0);

    return(error);

} /* initializeArray */
#endif /* unused */

/************************* parseCharWidths **************************/

/*  This function is called by "parseFile". It will parse the AFM File
 *  up to the "EndCharMetrics" keyword. It will save the character
 *  width info (as opposed to all of the character metric information)
 *  if requested by the caller of parseFile. Otherwise, it will just
 *  parse through the section without saving any information.
 *
 *  If data is to be saved, parseCharWidths is passed in a pointer
 *  to an array of widths that has already been initialized by the
 *  standard value for unmapped character codes. This function parses
 *  the Character Metrics section only storing the width information
 *  for the encoded characters into the array using the character code
 *  as the index into that array.
 *
 *  This function returns an error code specifying whether there was
 *  a premature EOF or a parsing error. This return value is used by
 *  parseFile to determine if there is more file to parse.
 */

static int parseCharWidths(FILE *fp, int *cwi)
{
    BOOL cont = TRUE, save = (cwi != NULL);
    int pos = 0, error = ok;
    register char *keyword;

    while (cont)
    {
        keyword = token(fp);
          /* Have reached an early and unexpected EOF. */
          /* Set flag and stop parsing */
        if (keyword == NULL)
        {
            error = earlyEOF;
            break; /* get out of loop */
        }
        if (!save)
          /* get tokens until the end of the Char Metrics section without */
          /* saving any of the data*/
            switch (recognize(keyword))
            {
                case ENDCHARMETRICS:
                    cont = FALSE;
                    break;
                case ENDFONTMETRICS:
                    cont = FALSE;
                    error = normalEOF;
                    break;
                default:
                    break;
            } /* switch */
        else
          /* otherwise parse entire char metrics section, saving */
          /* only the char x-width info */
            switch(recognize(keyword))
            {
                case COMMENT:
                    keyword = linetoken(fp);
                    break;
                case CODE:
                    keyword = token(fp);
                    pos = atoi(keyword);
                    break;
                case XYWIDTH:
                /* PROBLEM: Should be no Y-WIDTH when doing "quick & dirty" */
                    keyword = token(fp); keyword = token(fp); /* eat values */
                    error = parseError;
                    break;
                case XWIDTH:
                    keyword = token(fp);
                    if (pos >= 0) /* ignore unmapped chars */
                        cwi[pos] = atoi(keyword);
                    break;
                case ENDCHARMETRICS:
                    cont = FALSE;
                    break;
                case ENDFONTMETRICS:
                    cont = FALSE;
                    error = normalEOF;
                    break;
                case CHARNAME:	/* eat values (so doesn't cause parseError) */
                    keyword = token(fp);
                    break;
            	case CHARBBOX:
                    keyword = token(fp); keyword = token(fp);
                    keyword = token(fp); keyword = token(fp);
		    break;
		case LIGATURE:
                    keyword = token(fp); keyword = token(fp);
		    break;
                case NOPE:
                default:
                    error = parseError;
                    break;
            } /* switch */
    } /* while */

    return(error);

} /* parseCharWidths */


/************************* parseCharMetrics ************************/

/*  This function is called by parseFile if the caller of parseFile
 *  requested that all character metric information be saved
 *  (as opposed to only the character width information).
 *
 *  parseCharMetrics is passed in a pointer to an array of records
 *  to hold information on a per character basis. This function
 *  parses the Character Metrics section storing all character
 *  metric information for the ALL characters (mapped and unmapped)
 *  into the array.
 *
 *  This function returns an error code specifying whether there was
 *  a premature EOF or a parsing error. This return value is used by
 *  parseFile to determine if there is more file to parse.
 */

static int parseCharMetrics(FILE *fp, FontInfo *fi)
{
    BOOL cont = TRUE, firstTime = TRUE;
    int error = ok, count = 0;
    register CharMetricInfo *temp = fi->cmi;
    register char *keyword;

    while (cont)
    {
        keyword = token(fp);
        if (keyword == NULL)
        {
            error = earlyEOF;
            break; /* get out of loop */
        }
        switch(recognize(keyword))
        {
            case COMMENT:
                keyword = linetoken(fp);
                break;
            case CODE:
                if (count < fi->numOfChars)
                {
                    if (firstTime) firstTime = FALSE;
                    else temp++;
                    temp->code = atoi(token(fp));
                    count++;
                }
                else
                {
                    error = parseError;
                    cont = FALSE;
                }
                break;
            case XYWIDTH:
                temp->wx = atoi(token(fp));
                temp->wy = atoi(token(fp));
                break;
            case XWIDTH:
                temp->wx = atoi(token(fp));
                break;
            case CHARNAME:
                keyword = token(fp);
                temp->name = (char *) malloc(strlen(keyword) + 1);
                strcpy(temp->name, keyword);
                break;
            case CHARBBOX:
                temp->charBBox.llx = atoi(token(fp));
                temp->charBBox.lly = atoi(token(fp));
                temp->charBBox.urx = atoi(token(fp));
                temp->charBBox.ury = atoi(token(fp));
                break;
            case LIGATURE: {
                Ligature **tail = &(temp->ligs);
                Ligature *node = *tail;

                if (*tail != NULL)
                {
                    while (node->next != NULL)
                        node = node->next;
                    tail = &(node->next);
                }

                *tail = (Ligature *) calloc(1, sizeof(Ligature));
                keyword = token(fp);
                (*tail)->succ = (char *) malloc(strlen(keyword) + 1);
                strcpy((*tail)->succ, keyword);
                keyword = token(fp);
                (*tail)->lig = (char *) malloc(strlen(keyword) + 1);
                strcpy((*tail)->lig, keyword);
                break; }
            case ENDCHARMETRICS:
                cont = FALSE;;
                break;
            case ENDFONTMETRICS:
                cont = FALSE;
                error = normalEOF;
                break;
            case NOPE:
            default:
                error = parseError;
                break;
        } /* switch */
    } /* while */

    if ((error == ok) && (count != fi->numOfChars))
        error = parseError;

    return(error);

} /* parseCharMetrics */



/************************* parseTrackKernData ***********************/

/*  This function is called by "parseFile". It will parse the AFM File
 *  up to the "EndTrackKern" or "EndKernData" keywords. It will save the
 *  track kerning data if requested by the caller of parseFile.
 *
 *  parseTrackKernData is passed in a pointer to the FontInfo record.
 *  If data is to be saved, the FontInfo record will already contain
 *  a valid pointer to storage for the track kerning data.
 *
 *  This function returns an error code specifying whether there was
 *  a premature EOF or a parsing error. This return value is used by
 *  parseFile to determine if there is more file to parse.
 */

static int parseTrackKernData(FILE *fp, FontInfo *fi)
{
    BOOL cont = TRUE, save = (fi->tkd != NULL);
    int pos = 0, error = ok, tcount = 0;
    register char *keyword;

    while (cont)
    {
        keyword = token(fp);

        if (keyword == NULL)
        {
            error = earlyEOF;
            break; /* get out of loop */
        }
        if (!save)
          /* get tokens until the end of the Track Kerning Data */
          /* section without saving any of the data */
            switch(recognize(keyword))
            {
                case ENDTRACKKERN:
                case ENDKERNDATA:
                    cont = FALSE;
                    break;
                case ENDFONTMETRICS:
                    cont = FALSE;
                    error = normalEOF;
                    break;
                default:
                    break;
            } /* switch */
	else
          /* otherwise parse entire Track Kerning Data section, */
          /* saving the data */
            switch(recognize(keyword))
            {
                case COMMENT:
                    keyword = linetoken(fp);
                    break;
                case TRACKKERN:
                    if (tcount < fi->numOfTracks)
                    {
                        keyword = token(fp);
                        fi->tkd[pos].degree = atoi(keyword);
                        keyword = token(fp);
                        fi->tkd[pos].minPtSize = atof(keyword);
                        if (errno == ERANGE) error = parseError;
                        keyword = token(fp);
                        fi->tkd[pos].minKernAmt = atof(keyword);
                        if (errno == ERANGE) error = parseError;
                        keyword = token(fp);
                        fi->tkd[pos].maxPtSize = atof(keyword);
                        if (errno == ERANGE) error = parseError;
                        keyword = token(fp);
                        fi->tkd[pos++].maxKernAmt = atof(keyword);
                        if (errno == ERANGE) error = parseError;
                        tcount++;
                    }
                    else
                    {
                        error = parseError;
                        cont = FALSE;
                    }
                    break;
                case ENDTRACKKERN:
                case ENDKERNDATA:
                    cont = FALSE;
                    break;
                case ENDFONTMETRICS:
                    cont = FALSE;
                    error = normalEOF;
                    break;
                case NOPE:
                default:
                    error = parseError;
                    break;
            } /* switch */
    } /* while */

    if (error == ok && tcount != fi->numOfTracks)
        error = parseError;

    return(error);

} /* parseTrackKernData */


/************************* parsePairKernData ************************/

/*  This function is called by "parseFile". It will parse the AFM File
 *  up to the "EndKernPairs" or "EndKernData" keywords. It will save
 *  the pair kerning data if requested by the caller of parseFile.
 *
 *  parsePairKernData is passed in a pointer to the FontInfo record.
 *  If data is to be saved, the FontInfo record will already contain
 *  a valid pointer to storage for the pair kerning data.
 *
 *  This function returns an error code specifying whether there was
 *  a premature EOF or a parsing error. This return value is used by
 *  parseFile to determine if there is more file to parse.
 */

static int parsePairKernData(FILE *fp, FontInfo *fi)
{
    BOOL cont = TRUE, save = (fi->pkd != NULL);
    int pos = 0, error = ok, pcount = 0;
    register char *keyword;

    while (cont)
    {
        keyword = token(fp);

        if (keyword == NULL)
        {
            error = earlyEOF;
            break; /* get out of loop */
        }
        if (!save)
          /* get tokens until the end of the Pair Kerning Data */
          /* section without saving any of the data */
            switch(recognize(keyword))
            {
                case ENDKERNPAIRS:
                case ENDKERNDATA:
                    cont = FALSE;
                    break;
                case ENDFONTMETRICS:
                    cont = FALSE;
                    error = normalEOF;
                    break;
                default:
                    break;
            } /* switch */
	else
          /* otherwise parse entire Pair Kerning Data section, */
          /* saving the data */
            switch(recognize(keyword))
            {
                case COMMENT:
                    keyword = linetoken(fp);
                    break;
                case KERNPAIR:
                    if (pcount < fi->numOfPairs)
                    {
                        keyword = token(fp);
                        fi->pkd[pos].name1 = (char *)
                            malloc(strlen(keyword) + 1);
                        strcpy(fi->pkd[pos].name1, keyword);
                        keyword = token(fp);
                        fi->pkd[pos].name2 = (char *)
                            malloc(strlen(keyword) + 1);
                        strcpy(fi->pkd[pos].name2, keyword);
                        keyword = token(fp);
                        fi->pkd[pos].xamt = atoi(keyword);
                        keyword = token(fp);
                        fi->pkd[pos++].yamt = atoi(keyword);
                        pcount++;
                    }
                    else
                    {
                        error = parseError;
                        cont = FALSE;
                    }
                    break;
                case KERNPAIRXAMT:
                    if (pcount < fi->numOfPairs)
                    {
                        keyword = token(fp);
                        fi->pkd[pos].name1 = (char *)
                            malloc(strlen(keyword) + 1);
                        strcpy(fi->pkd[pos].name1, keyword);
                        keyword = token(fp);
                        fi->pkd[pos].name2 = (char *)
                            malloc(strlen(keyword) + 1);
                        strcpy(fi->pkd[pos].name2, keyword);
                        keyword = token(fp);
                        fi->pkd[pos++].xamt = atoi(keyword);
                        pcount++;
                    }
                    else
                    {
                        error = parseError;
                        cont = FALSE;
                    }
                    break;
                case ENDKERNPAIRS:
                case ENDKERNDATA:
                    cont = FALSE;
                    break;
                case ENDFONTMETRICS:
                    cont = FALSE;
                    error = normalEOF;
                    break;
                case NOPE:
                default:
                    error = parseError;
                    break;
            } /* switch */
    } /* while */

    if (error == ok && pcount != fi->numOfPairs)
        error = parseError;

    return(error);

} /* parsePairKernData */


/************************* parseCompCharData **************************/

/*  This function is called by "parseFile". It will parse the AFM File
 *  up to the "EndComposites" keyword. It will save the composite
 *  character data if requested by the caller of parseFile.
 *
 *  parseCompCharData is passed in a pointer to the FontInfo record, and
 *  a boolean representing if the data should be saved.
 *
 *  This function will create the appropriate amount of storage for
 *  the composite character data and store a pointer to the storage
 *  in the FontInfo record.
 *
 *  This function returns an error code specifying whether there was
 *  a premature EOF or a parsing error. This return value is used by
 *  parseFile to determine if there is more file to parse.
 */

static int parseCompCharData(FILE *fp, FontInfo *fi)
{
    BOOL cont = TRUE, firstTime = TRUE, save = (fi->ccd != NULL);
    int pos = 0, j = 0, error = ok, ccount = 0, pcount = 0;
    register char *keyword;

    while (cont)
    {
        keyword = token(fp);
        if (keyword == NULL)
          /* Have reached an early and unexpected EOF. */
          /* Set flag and stop parsing */
        {
            error = earlyEOF;
            break; /* get out of loop */
        }
        if (ccount > fi->numOfComps)
        {
            error = parseError;
            break; /* get out of loop */
        }
        if (!save)
          /* get tokens until the end of the Composite Character info */
          /* section without saving any of the data */
            switch(recognize(keyword))
            {
                case ENDCOMPOSITES:
                    cont = FALSE;
                    break;
                case ENDFONTMETRICS:
                    cont = FALSE;
                    error = normalEOF;
                    break;
                default:
                    break;
            } /* switch */
	else
          /* otherwise parse entire Composite Character info section, */
          /* saving the data */
            switch(recognize(keyword))
            {
                case COMMENT:
                    keyword = linetoken(fp);
                    break;
                case COMPCHAR:
                    if (ccount < fi->numOfComps)
                    {
                        keyword = token(fp);
                        if (pcount != fi->ccd[pos].numOfPieces)
                            error = parseError;
                        pcount = 0;
                        if (firstTime) firstTime = FALSE;
                        else pos++;
                        fi->ccd[pos].ccName = (char *)
                            malloc(strlen(keyword) + 1);
                        strcpy(fi->ccd[pos].ccName, keyword);
                        keyword = token(fp);
                        fi->ccd[pos].numOfPieces = atoi(keyword);
                        fi->ccd[pos].pieces = (Pcc *)
                            calloc(fi->ccd[pos].numOfPieces, sizeof(Pcc));
                        j = 0;
                        ccount++;
                    }
                    else
                    {
                        error = parseError;
                        cont = FALSE;
                    }
                    break;
                case COMPCHARPIECE:
                    if (pcount < fi->ccd[pos].numOfPieces)
                    {
                        keyword = token(fp);
                        fi->ccd[pos].pieces[j].pccName = (char *)
                                malloc(strlen(keyword) + 1);
                        strcpy(fi->ccd[pos].pieces[j].pccName, keyword);
                        keyword = token(fp);
                        fi->ccd[pos].pieces[j].deltax = atoi(keyword);
                        keyword = token(fp);
                        fi->ccd[pos].pieces[j++].deltay = atoi(keyword);
                        pcount++;
                    }
                    else
                        error = parseError;
                    break;
                case ENDCOMPOSITES:
                    cont = FALSE;
                    break;
                case ENDFONTMETRICS:
                    cont = FALSE;
                    error = normalEOF;
                    break;
                case NOPE:
                default:
                    error = parseError;
                    break;
            } /* switch */
    } /* while */

    if (error == ok && ccount != fi->numOfComps)
        error = parseError;

    return(error);

} /* parseCompCharData */




/*************************** 'PUBLIC' FUNCTION ********************/


/*************************** parseFile *****************************/

/*  parseFile is the only 'public' procedure available. It is called
 *  from an application wishing to get information from an AFM file.
 *  The caller of this function is responsible for locating and opening
 *  an AFM file and handling all errors associated with that task.
 *
 *  parseFile expects 3 parameters: a vaild file pointer, a pointer
 *  to a (FontInfo *) variable (for which storage will be allocated and
 *  the data requested filled in), and a mask specifying which
 *  data from the AFM File should be saved in the FontInfo structure.
 *
 *  The file will be parsed and the requested data will be stored in
 *  a record of type FontInfo (refer to ParseAFM.h).
 *
 *  parseFile returns an error code as defined in parseAFM.h.
 *
 *  The position of the read/write pointer associated with the file
 *  pointer upon return of this function is undefined.
 */

extern int afm_parse_file(FILE *fp, FontInfo **fi, FLAGS flags)
{

    int code = ok; 	/* return code from each of the parsing routines */
    int error = ok;	/* used as the return code from this function */

    register char *keyword; /* used to store a token */


    /* storage data for the global variable ident */
    ident = (char *) calloc(MAX_NAME, sizeof(char));
    if (ident == NULL) {error = storageProblem; return(error);}

    (*fi) = (FontInfo *) calloc(1, sizeof(FontInfo));
    if ((*fi) == NULL) {error = storageProblem; return(error);}

    if (flags & P_G)
    {
        (*fi)->gfi = (GlobalFontInfo *) calloc(1, sizeof(GlobalFontInfo));
        if ((*fi)->gfi == NULL) {error = storageProblem; return(error);}
    }

    /* The AFM File begins with Global Font Information. This section */
    /* will be parsed whether or not information should be saved. */
    code = parseGlobals(fp, (*fi)->gfi);

    if (code < 0) error = code;

    /* The Global Font Information is followed by the Character Metrics */
    /* section. Which procedure is used to parse this section depends on */
    /* how much information should be saved. If all of the metrics info */
    /* is wanted, parseCharMetrics is called. If only the character widths */
    /* is wanted, parseCharWidths is called. parseCharWidths will also */
    /* be called in the case that no character data is to be saved, just */
    /* to parse through the section. */

    if ((code != normalEOF) && (code != earlyEOF))
    {
        (*fi)->numOfChars = atoi(token(fp));
	    if (flags & (P_M ^ P_W))
        {
            (*fi)->cmi = (CharMetricInfo *)
                      calloc((*fi)->numOfChars, sizeof(CharMetricInfo));
           if ((*fi)->cmi == NULL) {error = storageProblem; return(error);}
            code = parseCharMetrics(fp, *fi);
        }
        else
        {
            if (flags & P_W)
            {
                (*fi)->cwi = (int *) calloc(256, sizeof(int));
                if ((*fi)->cwi == NULL)
                {
                	error = storageProblem;
                	return(error);
                }
            }
            /* parse section regardless */
            code = parseCharWidths(fp, (*fi)->cwi);
        } /* else */
    } /* if */

    if ((error != earlyEOF) && (code < 0)) error = code;

    /* The remaining sections of the AFM are optional. This code will */
    /* look at the next keyword in the file to determine what section */
    /* is next, and then allocate the appropriate amount of storage */
    /* for the data (if the data is to be saved) and call the */
    /* appropriate parsing routine to parse the section. */

    while ((code != normalEOF) && (code != earlyEOF))
    {
        keyword = token(fp);
        if (keyword == NULL)
          /* Have reached an early and unexpected EOF. */
          /* Set flag and stop parsing */
        {
            code = earlyEOF;
            break; /* get out of loop */
        }
        switch(recognize(keyword))
        {
            case STARTKERNDATA:
                break;
            case ENDKERNDATA:
                break;
            case STARTTRACKKERN:
                keyword = token(fp);
                if (flags & P_T)
                {
                    (*fi)->numOfTracks = atoi(keyword);
                    (*fi)->tkd = (TrackKernData *)
                        calloc((*fi)->numOfTracks, sizeof(TrackKernData));
                    if ((*fi)->tkd == NULL)
                    {
                    	error = storageProblem;
                    	return(error);
                    }
                } /* if */
                code = parseTrackKernData(fp, *fi);
                break;
            case STARTKERNPAIRS:
                keyword = token(fp);
                if (flags & P_P)
                {
                    (*fi)->numOfPairs = atoi(keyword);
                    (*fi)->pkd = (PairKernData *)
                        calloc((*fi)->numOfPairs, sizeof(PairKernData));
                    if ((*fi)->pkd == NULL)
                    {
                    	error = storageProblem;
                    	return(error);
                    }
                } /* if */
                code = parsePairKernData(fp, *fi);
                break;
            case STARTCOMPOSITES:
                keyword = token(fp);
                if (flags & P_C)
                {
                    (*fi)->numOfComps = atoi(keyword);
                    (*fi)->ccd = (CompCharData *)
                        calloc((*fi)->numOfComps, sizeof(CompCharData));
                    if ((*fi)->ccd == NULL)
                    {
                    	error = storageProblem;
                    	return(error);
                    }
                } /* if */
                code = parseCompCharData(fp, *fi);
                break;
            case ENDFONTMETRICS:
                code = normalEOF;
                break;
            case NOPE:
            default:
                code = parseError;
                break;
        } /* switch */

        if ((error != earlyEOF) && (code < 0)) error = code;

    } /* while */

    if ((error != earlyEOF) && (code < 0)) error = code;

    if (ident != NULL) { free(ident); ident = NULL; }

    return(error);

} /* parseFile */

/* added for MDVI: this function was copied from `parseAFMclient.c' */

void afm_free_fontinfo(FontInfo *fi)
{
    if (fi != NULL)
    {
        if (fi->gfi != NULL)
        {
            free(fi->gfi->afmVersion); fi->gfi->afmVersion = NULL;
            free(fi->gfi->fontName); fi->gfi->fontName = NULL;
            free(fi->gfi->fullName); fi->gfi->fullName = NULL;
            free(fi->gfi->familyName); fi->gfi->familyName = NULL;
            free(fi->gfi->weight); fi->gfi->weight = NULL;
            free(fi->gfi->version); fi->gfi->version = NULL;
            free(fi->gfi->notice); fi->gfi->notice = NULL;
            free(fi->gfi->encodingScheme); fi->gfi->encodingScheme = NULL;
            free(fi->gfi); fi->gfi = NULL;
        }

        if (fi->cwi != NULL)
        { free(fi->cwi); fi->cwi = NULL; }

        if (fi->cmi != NULL)
        {
            int i = 0;
            CharMetricInfo *temp = fi->cmi;
            Ligature *node = temp->ligs;

            for (i = 0; i < fi->numOfChars; ++i)
            {
                for (node = temp->ligs; node != NULL; node = node->next)
                {
                    free(node->succ); node->succ = NULL;
                    free(node->lig); node->lig = NULL;
                }

                free(temp->name); temp->name = NULL;
                temp++;
            }

            free(fi->cmi); fi->cmi = NULL;
        }

        if (fi->tkd != NULL)
        { free(fi->tkd); fi->tkd = NULL; }

        if (fi->pkd != NULL)
        {
            free(fi->pkd->name1); fi->pkd->name1 = NULL;
            free(fi->pkd->name2); fi->pkd->name2 = NULL;
            free(fi->pkd); fi->pkd = NULL;
        }

        if (fi->ccd != NULL)
        {
            int i = 0, j = 0;
    	    CompCharData *ccd = fi->ccd;

    	    for (i = 0; i < fi->numOfComps; ++i)
    	    {
    	        for (j = 0; j < ccd[i].numOfPieces; ++j)
    	        {
    	            free(ccd[i].pieces[j].pccName);
    	            ccd[i].pieces[j].pccName = NULL;
    	        }

    	        free(ccd[i].ccName); ccd[i].ccName = NULL;
    	    }

            free(fi->ccd); fi->ccd = NULL;
        }

        free(fi);

    } /* if */

} /* afm_free_fontinfo */

#endif /* WITH_AFM_FILES */
