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

/*
 * Changes made for OpenOffice.org
 *
 *  10/24/2000 pl       - changed code to compile with c++-compilers
 *                      - added namespace to avoid symbol clashes
 *                      - replaced BOOL by bool
 *                      - added function to free space allocated by parseFile
 *  10/26/2000 pl       - added additional keys
 *                      - added ability to parse slightly broken files
 *                      - added charwidth member to GlobalFontInfo
 *  04/26/2001 pl       - added OpenOffice header
 *  10/19/2005 pl       - performance increase:
 *                         - fread file in one pass
 *                         - replace file io by buffer access
 *  10/20/2005 pl       - performance increase:
 *                         - use one table lookup in token() routine
 *                           instead of many conditions
 *                         - return token length in toke() routine
 *                         - use hash lookup instead of binary search
 *                           in recognize() routine
 */

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"

/* 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <math.h>

#include "parseAFM.hxx"
#include "vcl/strhelper.hxx"

#include "rtl/alloc.h"

#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)

namespace psp {

class FileInputStream
{
    char*         m_pMemory;
    unsigned int        m_nPos;
    unsigned int        m_nLen;
    public:
    FileInputStream( const char* pFilename );
    ~FileInputStream();
    
    int getChar() { return (m_nPos < m_nLen) ? int(m_pMemory[m_nPos++]) : -1; }
    void ungetChar()
    {
        if( m_nPos > 0 )
            m_nPos--;
    }
    unsigned int tell() const { return m_nPos; }
    void seek( unsigned int nPos )
    // NOTE: do not check input data since only results of tell()
    // get seek()ed in this file
    { m_nPos = nPos; }
};

FileInputStream::FileInputStream( const char* pFilename ) :
    m_pMemory( NULL ),
    m_nPos( 0 ),
    m_nLen( 0 )
{
    struct stat aStat;
    if( ! stat( pFilename, &aStat ) &&
        S_ISREG( aStat.st_mode )    &&
        aStat.st_size > 0
      )
    {
        FILE* fp = fopen( pFilename, "r" );
        if( fp )
        {
            m_pMemory = (char*)rtl_allocateMemory( aStat.st_size );
            m_nLen = (unsigned int)fread( m_pMemory, 1, aStat.st_size, fp );
            fclose( fp );
        }
    }
}

FileInputStream::~FileInputStream()
{
    rtl_freeMemory( m_pMemory );
}

/*************************** GLOBALS ***********************/
/* "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.
 */

// some metrics have Ascent, Descent instead Ascender, Descender or Em
// which is not allowed per afm spcification, but let us handle
// this gently
enum parseKey {
    ASCENDER, ASCENT, CHARBBOX, CODE, COMPCHAR, CODEHEX, CAPHEIGHT, CHARWIDTH, CHARACTERSET, CHARACTERS, COMMENT,
    DESCENDER, DESCENT, EM, ENCODINGSCHEME, ENDCHARMETRICS, ENDCOMPOSITES, ENDDIRECTION,
    ENDFONTMETRICS, ENDKERNDATA, ENDKERNPAIRS, ENDTRACKKERN,
    FAMILYNAME, FONTBBOX, FONTNAME, FULLNAME, ISBASEFONT, ISFIXEDPITCH,
    ITALICANGLE, KERNPAIR, KERNPAIRXAMT, LIGATURE, MAPPINGSCHEME, METRICSSETS, CHARNAME,
    NOTICE, COMPCHARPIECE, STARTCHARMETRICS, STARTCOMPOSITES, STARTDIRECTION,
    STARTFONTMETRICS, STARTKERNDATA, STARTKERNPAIRS,
    STARTTRACKKERN, STDHW, STDVW, TRACKKERN, UNDERLINEPOSITION,
    UNDERLINETHICKNESS, VVECTOR, VERSION, XYWIDTH, X0WIDTH, XWIDTH, WEIGHT, XHEIGHT,
    NOPE
};

/*************************** 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.
 */
 
// token white space is ' ', '\n', '\r', ',', '\t', ';'
static const bool is_white_Array[ 256 ] =
{   false, false, false, false, false, false, false, false, // 0-7
    false,  true,  true, false, false,  true, false, false, // 8-15
    false, false, false, false, false, false, false, false, // 16-23
    false, false, false, false, false, false, false, false, // 24-31
     true, false, false, false, false, false, false, false, // 32-39
    false, false, false, false,  true, false, false, false, // 40-47
    false, false, false, false, false, false, false, false, // 48-55
    false, false, false,  true, false, false, false, false, // 56-63
    
    false, false, false, false, false, false, false, false, // 64 -
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false, // 127
    
    false, false, false, false, false, false, false, false, // 128 -
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false, // 191

    false, false, false, false, false, false, false, false, // 192 -
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false, // 255
};
// token delimiters are ' ', '\n', '\r', '\t', ':', ';'
static const bool is_delimiter_Array[ 256 ] =
{   false, false, false, false, false, false, false, false, // 0-7
    false,  true,  true, false, false,  true, false, false, // 8-15
    false, false, false, false, false, false, false, false, // 16-23
    false, false, false, false, false, false, false, false, // 24-31
     true, false, false, false, false, false, false, false, // 32-39
    false, false, false, false, false, false, false, false, // 40-47
    false, false, false, false, false, false, false, false, // 48-55
    false, false,  true,  true, false, false, false, false, // 56-63
    
    false, false, false, false, false, false, false, false, // 64 -
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false, // 127
    
    false, false, false, false, false, false, false, false, // 128 -
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false, // 191

    false, false, false, false, false, false, false, false, // 192 -
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false,
    false, false, false, false, false, false, false, false, // 255
};
static char *token( FileInputStream* stream, int& rLen )
{
    static char ident[MAX_NAME]; /* storage buffer for keywords */

    int ch, idx;

    /* skip over white space */
    // relies on EOF = -1
    while( is_white_Array[ (ch = stream->getChar()) & 255 ] )
        ;
    
    idx = 0;
    while( ch != -1 && ! is_delimiter_Array[ ch & 255 ] && idx < MAX_NAME-1 )
    {
        ident[idx++] = ch;
        ch = stream->getChar();
    }

    if (ch == -1 && idx < 1) return ((char *)NULL);
    if (idx >= 1 && ch != ':' && ch != -1) stream->ungetChar();
    if (idx < 1 ) ident[idx++] = ch;    /* single-character token */
    ident[idx] = 0;
    rLen = idx;
    
    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( FileInputStream* stream )
{
    static char ident[MAX_NAME]; /* storage buffer for keywords */
    int ch, idx;

    while ((ch = stream->getChar()) == ' ' || ch == '\t' ) ; 
    
    idx = 0;
    while (ch != -1 && ch != lineterm && ch != '\r' && idx < MAX_NAME-1 ) 
    {
        ident[idx++] = ch;
        ch = stream->getChar();
    } /* while */
    
    stream->ungetChar();
    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.
 */
#include "afm_hash.cpp"

static inline enum parseKey recognize( register char* ident, int len)
{
    const hash_entry* pEntry = AfmKeywordHash::in_word_set( ident, len );
    return pEntry ? pEntry->eKey : 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 int parseGlobals( FileInputStream* fp, register GlobalFontInfo* gfi )
{  
    bool cont = true, save = (gfi != NULL);
    int error = ok;
    register char *keyword;
    int direction = -1;
    int tokenlen;
   
    while (cont)
    {
        keyword = token(fp, tokenlen);
        
        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, tokenlen))  
            {               
                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, tokenlen))
            {
                case STARTFONTMETRICS:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->afmVersion = strdup( keyword );
                    break;
                case COMMENT:
                    keyword = linetoken(fp);
                    break;
                case FONTNAME:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->fontName = strdup( keyword );
                    break;
                case ENCODINGSCHEME:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->encodingScheme = strdup( keyword );
                    break; 
                case FULLNAME:
                    if ((keyword = linetoken(fp)) != NULL)
                        gfi->fullName = strdup( keyword );
                    break; 
                case FAMILYNAME:           
                    if ((keyword = linetoken(fp)) != NULL)
                        gfi->familyName = strdup( keyword );
                    break; 
                case WEIGHT:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->weight = strdup( keyword );
                    break;
                case ITALICANGLE:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->italicAngle = StringToDouble( keyword );
                    break;
                case ISFIXEDPITCH:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                    {
                        if (MATCH(keyword, False))
                            gfi->isFixedPitch = 0;
                        else 
                            gfi->isFixedPitch = 1;
                    }
                    break; 
                case UNDERLINEPOSITION:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->underlinePosition = atoi(keyword);
                    break; 
                case UNDERLINETHICKNESS:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->underlineThickness = atoi(keyword);
                    break;
                case VERSION:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->version = strdup( keyword );
                    break; 
                case NOTICE:
                    if ((keyword = linetoken(fp)) != NULL)
                        gfi->notice = strdup( keyword );
                    break; 
                case FONTBBOX:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->fontBBox.llx = atoi(keyword);
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->fontBBox.lly = atoi(keyword);
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->fontBBox.urx = atoi(keyword);
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->fontBBox.ury = atoi(keyword);
                    break;
                case CAPHEIGHT:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->capHeight = atoi(keyword);
                    break;
                case XHEIGHT:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->xHeight = atoi(keyword);
                    break;
                case DESCENT:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->descender = -atoi(keyword);
                    break;
                case DESCENDER:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->descender = atoi(keyword);
                    break;
                case ASCENT:
                case ASCENDER:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        gfi->ascender = atoi(keyword);
                    break;
                case STARTCHARMETRICS:
                    cont = false;
                    break;
                case ENDFONTMETRICS:
                    cont = false;
                    error = normalEOF;
                    break;
                case EM:
                    // skip one token
                    keyword = token(fp,tokenlen);
                    break;
                case STARTDIRECTION:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        direction = atoi(keyword);
                    break; /* ignore this for now */
                case ENDDIRECTION:
                    break; /* ignore this for now */
                case MAPPINGSCHEME:
                    keyword = token(fp,tokenlen);
                    break; /* ignore     this for now */
                case CHARACTERS:
                    keyword = token(fp,tokenlen);
                    break; /* ignore this for now */
                case ISBASEFONT:
                    keyword = token(fp,tokenlen);
                    break; /* ignore this for now */
                case CHARACTERSET:
                    keyword=token(fp,tokenlen); //ignore
                    break;
                case STDHW:
                    keyword=token(fp,tokenlen); //ignore
                    break;
                case STDVW:
                    keyword=token(fp,tokenlen); //ignore
                    break;
                case CHARWIDTH:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                    {
                        if (direction == 0)
                            gfi->charwidth = atoi(keyword);
                    }
                    keyword = token(fp,tokenlen);
                    /* ignore y-width for now */
                    break; 
                case METRICSSETS:
                    keyword = token(fp,tokenlen);
                    break; /* ignore this for now */
                case NOPE:
                default:
                    error = parseError;
                    break;
            } /* switch */
    } /* while */
    
    return(error);
    
} /* parseGlobals */    


#if 0
/************************* 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
 *  FileInputStream is reset to be where it was upon entering this function.
 */
 
static int initializeArray( FileInputStream* fp, register int* cwi)
{  
    bool cont = true, found = false;
    unsigned int opos = fp->tell();
    int code = 0, width = 0, i = 0, error = 0, tokenlen;
    register char *keyword;
  
    while (cont)
    {
        keyword = token(fp,tokenlen);
        if (keyword == NULL)
        {
            error = earlyEOF;
            break; /* get out of loop */
        }
        switch(recognize(keyword,tokenlen))
        {
            case COMMENT:
                keyword = linetoken(fp);
                break;
            case CODE:
                if ((keyword = token(fp,tokenlen)) != NULL)
                    code = atoi(keyword);
                break;
            case CODEHEX:
                if ((keyword = token(fp,tokenlen)) != NULL)
                    sscanf(keyword,"<%x>", &code);
                break;
            case XWIDTH:
                if ((keyword = token(fp,tokenlen)) != NULL)
                    width = atoi(keyword);
                break;
            case X0WIDTH:
                (void) token(fp,tokenlen);
                break;
            case CHARNAME: 
                if ((keyword = token(fp,tokenlen)) != NULL)
                    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;
    
    fp->seek(opos);
    
    return(error);
        
} /* initializeArray */    
#endif

/************************* 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( FileInputStream* fp, register int* cwi)
{  
    bool cont = true, save = (cwi != NULL);
    int pos = 0, error = ok, tokenlen;
    register char *keyword;
    
    while (cont)
    {
        keyword = token(fp,tokenlen);
        /* 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,tokenlen))  
            {               
                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,tokenlen))
            {
                case COMMENT:
                    keyword = linetoken(fp);
                    break;
                case CODE:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        pos = atoi(keyword);
                    break;
                case XYWIDTH:
                    /* PROBLEM: Should be no Y-WIDTH when doing "quick & dirty" */
                    keyword = token(fp,tokenlen); keyword = token(fp,tokenlen); /* eat values */
                    error = parseError;
                    break;
                case CODEHEX:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        sscanf(keyword, "<%x>", &pos);
                    break;
                case X0WIDTH:
                    (void) token(fp,tokenlen);
                    break;
                case XWIDTH:
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        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,tokenlen); 
                    break;
                case CHARBBOX: 
                    keyword = token(fp,tokenlen); keyword = token(fp,tokenlen);
                    keyword = token(fp,tokenlen); keyword = token(fp,tokenlen);
                    break;
                case LIGATURE:
                    keyword = token(fp,tokenlen); keyword = token(fp,tokenlen);
                    break;
                case VVECTOR:
                    keyword = token(fp,tokenlen);
                    keyword = token(fp,tokenlen);
                    break;
                case NOPE:
                default: 
                    error = parseError;
                    break;
            } /* switch */
    } /* while */
    
    return(error);
    
} /* parseCharWidths */    


/*
 * number of char metrics is almost allways inaccurate, so be gentle and try to
 * adapt our internal storage by adjusting the allocated list
 */ 

static int
reallocFontMetrics( void **pp_fontmetrics, int *p_oldcount, int n_newcount, unsigned int n_size )
{
    char *p_tmpmetrics = NULL;

    if ((pp_fontmetrics == NULL) || (*pp_fontmetrics == NULL))
        return storageProblem;

    if (*p_oldcount == n_newcount)
        return ok;

    p_tmpmetrics = (char*)realloc(*pp_fontmetrics, n_newcount * n_size);
    if (p_tmpmetrics == NULL)
        return storageProblem;

    if ( n_newcount > *p_oldcount )
    {
        char *p_inimetrics = p_tmpmetrics + n_size * *p_oldcount;  
        int   n_inimetrics = n_size * (n_newcount - *p_oldcount);
        memset( p_inimetrics, 0, n_inimetrics );
    }

    *pp_fontmetrics = p_tmpmetrics;
    *p_oldcount    = n_newcount;

    return ok;
}

static unsigned int
enlargeCount( unsigned int n_oldcount )
{
    unsigned int n_newcount = n_oldcount + n_oldcount / 5;
    if (n_oldcount == n_newcount )
        n_newcount = n_oldcount + 5;

    return n_newcount; 
}

/************************* 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( FileInputStream* fp, register FontInfo* fi)
{  
    bool cont = true, firstTime = true;
    int error = ok, count = 0, tokenlen;
    register CharMetricInfo *temp = fi->cmi;
    register char *keyword;
  
    while (cont)
    {
        keyword = token(fp,tokenlen);
        if (keyword == NULL)
        {
            error = earlyEOF;
            break; /* get out of loop */
        }
        switch(recognize(keyword,tokenlen))
        {
            case COMMENT:
                keyword = linetoken(fp);
                break; 
            case CODE:
                if (!(count < fi->numOfChars))
                {
                    reallocFontMetrics( (void**)&(fi->cmi), 
                                        &(fi->numOfChars), enlargeCount(fi->numOfChars), 
                                        sizeof(CharMetricInfo) );
                    temp = &(fi->cmi[ count - 1 ]);
                }
                if (count < fi->numOfChars)
                { 
                    if (firstTime) firstTime = false;
                    else temp++;
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        temp->code = atoi(keyword);
                    if (fi->gfi && fi->gfi->charwidth)
                        temp->wx = fi->gfi->charwidth;
                    count++;
                }
                else
                {
                    error = parseError;
                    cont = false;
                }
                break;
            case CODEHEX:
                if (!(count < fi->numOfChars ))
                {
                    reallocFontMetrics( (void**)&(fi->cmi), 
                                        &(fi->numOfChars), enlargeCount(fi->numOfChars), 
                                        sizeof(CharMetricInfo) );
                    temp = &(fi->cmi[ count - 1 ]);
                }
                if (count < fi->numOfChars) {
                    if (firstTime)
                        firstTime = false;
                    else
                        temp++;
                    if ((keyword = token(fp,tokenlen)) != NULL)
                        sscanf(keyword,"<%x>", &temp->code);
                    if (fi->gfi && fi->gfi->charwidth)
                        temp->wx = fi->gfi->charwidth;
                    count++;
                }
                else {
                    error = parseError;
                    cont = false;
                }
                break;
            case XYWIDTH:
                if ((keyword = token(fp,tokenlen)) != NULL)
                    temp->wx = atoi(keyword);
                if ((keyword = token(fp,tokenlen)) != NULL)
                    temp->wy = atoi(keyword);
                break;                 
            case X0WIDTH:
                if ((keyword = token(fp,tokenlen)) != NULL)
                    temp->wx = atoi(keyword);
                break;
            case XWIDTH: 
                if ((keyword = token(fp,tokenlen)) != NULL)
                    temp->wx = atoi(keyword);
                break;
            case CHARNAME: 
                if ((keyword = token(fp,tokenlen)) != NULL)
                    temp->name = (char *)strdup(keyword);
                break;            
            case CHARBBOX: 
                if ((keyword = token(fp,tokenlen)) != NULL)
                    temp->charBBox.llx = atoi(keyword);
                if ((keyword = token(fp,tokenlen)) != NULL)
                    temp->charBBox.lly = atoi(keyword);
                if ((keyword = token(fp,tokenlen)) != NULL)
                    temp->charBBox.urx = atoi(keyword);
                if ((keyword = token(fp,tokenlen)) != NULL)
                    temp->charBBox.ury = atoi(keyword);
                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));
                if ((keyword = token(fp,tokenlen)) != NULL)
                    (*tail)->succ = (char *)strdup(keyword);
                if ((keyword = token(fp,tokenlen)) != NULL)
                    (*tail)->lig = (char *)strdup(keyword);
                break; }
            case ENDCHARMETRICS:
                cont = false;;
                break; 
            case ENDFONTMETRICS: 
                cont = false;
                error = normalEOF;
                break; 
            case VVECTOR:
                keyword = token(fp,tokenlen);
                keyword = token(fp,tokenlen);
                break;
            case NOPE:
            default:
                error = parseError; 
                break; 
        } /* switch */
    } /* while */
    
    if ((error == ok) && (count != fi->numOfChars)) 
        error = reallocFontMetrics( (void**)&(fi->cmi), &(fi->numOfChars), 
                                    count, sizeof(CharMetricInfo) );

    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( FileInputStream* fp, register FontInfo* fi)
{  
    bool cont = true, save = (fi->tkd != NULL);
    int pos = 0, error = ok, tcount = 0, tokenlen;
    register char *keyword;
 
    while (cont)
    {
        keyword = token(fp,tokenlen);
        
        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,tokenlen))
            {
                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,tokenlen))
            {
                case COMMENT:
                    keyword = linetoken(fp);
                    break;
                case TRACKKERN:
                    if (!(tcount < fi->numOfTracks))
                    {
                        reallocFontMetrics( (void**)&(fi->tkd), &(fi->numOfTracks), 
                                            enlargeCount(fi->numOfTracks), sizeof(TrackKernData) );
                    }

                    if (tcount < fi->numOfTracks)
                    {
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->tkd[pos].degree = atoi(keyword);
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->tkd[pos].minPtSize = StringToDouble(keyword);
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->tkd[pos].minKernAmt = StringToDouble(keyword);
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->tkd[pos].maxPtSize = StringToDouble(keyword);
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->tkd[pos++].maxKernAmt = StringToDouble(keyword);
                        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 = reallocFontMetrics( (void**)&(fi->tkd), &(fi->numOfTracks), 
                                    tcount, sizeof(TrackKernData) );

    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( FileInputStream* fp, register FontInfo* fi)
{  
    bool cont = true, save = (fi->pkd != NULL);
    int pos = 0, error = ok, pcount = 0, tokenlen;
    register char *keyword;
  
    while (cont)
    {
        keyword = token(fp,tokenlen);
        
        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,tokenlen))
            {
                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,tokenlen))
            {
                case COMMENT:
                    keyword = linetoken(fp);
                    break;
                case KERNPAIR:
                    if (!(pcount < fi->numOfPairs))
                    {
                        reallocFontMetrics( (void**)&(fi->pkd), &(fi->numOfPairs), 
                                            enlargeCount(fi->numOfPairs), sizeof(PairKernData) );
                    }
                    if (pcount < fi->numOfPairs)
                    {
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->pkd[pos].name1 = strdup( keyword );
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->pkd[pos].name2 = strdup( keyword );
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->pkd[pos].xamt = atoi(keyword);
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->pkd[pos++].yamt = atoi(keyword);
                        pcount++;
                    }
                    else
                    {
                        error = parseError;
                        cont = false;
                    }
                    break;
                case KERNPAIRXAMT:
                    if (!(pcount < fi->numOfPairs))
                    {
                        reallocFontMetrics( (void**)&(fi->pkd), &(fi->numOfPairs), 
                                            enlargeCount(fi->numOfPairs), sizeof(PairKernData) );
                    }
                    if (pcount < fi->numOfPairs)
                    {
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->pkd[pos].name1 = strdup( keyword );
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->pkd[pos].name2 = strdup( keyword );
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            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 = reallocFontMetrics( (void**)&(fi->pkd), &(fi->numOfPairs), 
                                    pcount, sizeof(PairKernData) );

    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( FileInputStream* fp, register FontInfo* fi)
{  
    bool cont = true, firstTime = true, save = (fi->ccd != NULL);
    int pos = 0, j = 0, error = ok, ccount = 0, pcount = 0, tokenlen;
    register char *keyword;
  
    while (cont)
    {
        keyword = token(fp,tokenlen);
        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)
        {
            reallocFontMetrics( (void**)&(fi->ccd), &(fi->numOfComps), 
                                enlargeCount(fi->numOfComps), sizeof(CompCharData) );
        }
        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,tokenlen))
            {
                case ENDCOMPOSITES:
                    cont = false;
                    break;
                case ENDFONTMETRICS:
                    cont = false;
                    error = normalEOF;
                    break;
                case COMMENT:
                case COMPCHAR:
                    keyword = linetoken(fp);
                    break;
                default:
                    break;
            } /* switch */
        else
            /* otherwise parse entire Composite Character info section, */
            /* saving the data */
            switch(recognize(keyword,tokenlen))
            {
                case COMMENT:
                    keyword = linetoken(fp);
                    break;
                case COMPCHAR:
                    if (!(ccount < fi->numOfComps))
                    {
                        reallocFontMetrics( (void**)&(fi->ccd), &(fi->numOfComps), 
                                            enlargeCount(fi->numOfComps), sizeof(CompCharData) );
                    }
                    if (ccount < fi->numOfComps)
                    {
                        keyword = token(fp,tokenlen);
                        if (pcount != fi->ccd[pos].numOfPieces)
                            error = parseError;
                        pcount = 0;
                        if (firstTime) firstTime = false;
                        else pos++;
                        fi->ccd[pos].ccName = strdup( keyword );
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            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)
                    {
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->ccd[pos].pieces[j].pccName = strdup( keyword );
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            fi->ccd[pos].pieces[j].deltax = atoi(keyword);
                        if ((keyword = token(fp,tokenlen)) != NULL)
                            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)
        reallocFontMetrics( (void**)&(fi->ccd), &(fi->numOfComps), 
                            ccount, sizeof(CompCharData) );

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

int parseFile( const char* pFilename, FontInfo** fi, FLAGS flags)
{
    FileInputStream aFile( pFilename );
    
    int code = ok;  /* return code from each of the parsing routines */
    int error = ok; /* used as the return code from this function */
    int tokenlen;
    
    register char *keyword; /* used to store a token */  
    

    (*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(&aFile, (*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))
    {
        if ((keyword = token(&aFile,tokenlen)) != NULL)
            (*fi)->numOfChars = atoi(keyword);
        if (flags & (P_M ^ P_W))
        {
            (*fi)->cmi = (CharMetricInfo *) 
                calloc((*fi)->numOfChars, sizeof(CharMetricInfo));
            if ((*fi)->cmi == NULL) {error = storageProblem; return(error);}
            code = parseCharMetrics(&aFile, *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(&aFile, (*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(&aFile,tokenlen);
        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,tokenlen))
        {
            case STARTKERNDATA:
                break;
            case ENDKERNDATA:
                break;
            case STARTTRACKKERN:
                keyword = token(&aFile,tokenlen);
                if ((flags & P_T) && keyword)
                {
                    (*fi)->numOfTracks = atoi(keyword);
                    (*fi)->tkd = (TrackKernData *) 
                        calloc((*fi)->numOfTracks, sizeof(TrackKernData));
                    if ((*fi)->tkd == NULL) 
                    {
                        error = storageProblem; 
                        return(error);
                    }
                } /* if */
                code = parseTrackKernData(&aFile, *fi);
                break;
            case STARTKERNPAIRS:
                keyword = token(&aFile,tokenlen);
                if ((flags & P_P) && keyword)
                {
                    (*fi)->numOfPairs = atoi(keyword);
                    (*fi)->pkd = (PairKernData *) 
                        calloc((*fi)->numOfPairs, sizeof(PairKernData));
                    if ((*fi)->pkd == NULL) 
                    {
                        error = storageProblem; 
                        return(error);
                    }
                } /* if */
                code = parsePairKernData(&aFile, *fi);
                break;
            case STARTCOMPOSITES:
                keyword = token(&aFile,tokenlen);
                if ((flags & P_C) && keyword)
                { 
                    (*fi)->numOfComps = atoi(keyword);
                    (*fi)->ccd = (CompCharData *) 
                        calloc((*fi)->numOfComps, sizeof(CompCharData));
                    if ((*fi)->ccd == NULL) 
                    {
                        error = storageProblem; 
                        return(error);
                    }
                } /* if */
                code = parseCompCharData(&aFile, *fi); 
                break;    
            case ENDFONTMETRICS:
                code = normalEOF;
                break;
            case COMMENT:
                linetoken(&aFile);
                break;
            case NOPE:
            default:
                code = parseError;
                break;
        } /* switch */
        
        if ((error != earlyEOF) && (code < 0)) error = code;
        
    } /* while */
  
    if ((error != earlyEOF) && (code < 0)) error = code;
    
    return(error);
  
} /* parseFile */

void
freeFontInfo (FontInfo *fi)
{
    int i, j;

    if (fi->gfi)
    {
        free (fi->gfi->afmVersion);
        free (fi->gfi->fontName);
        free (fi->gfi->fullName);
        free (fi->gfi->familyName);
        free (fi->gfi->weight);
        free (fi->gfi->version);
        free (fi->gfi->notice);
        free (fi->gfi->encodingScheme);
        free (fi->gfi);
    }

    free (fi->cwi);

    if (fi->cmi)
    {
        for (i = 0; i < fi->numOfChars; i++)
        {
            Ligature *ligs;
            free (fi->cmi[i].name);
            ligs = fi->cmi[i].ligs;
            while (ligs)
            {
                Ligature *tmp;
                tmp = ligs;
                ligs = ligs->next;
                free (tmp->succ);
                free (tmp->lig);
                free (tmp);
            }
        }
        free (fi->cmi);
    }

    free (fi->tkd);

    if (fi->pkd)
    {
        for ( i = 0; i < fi->numOfPairs; i++)
        {
            free (fi->pkd[i].name1);
            free (fi->pkd[i].name2);
        }
        free (fi->pkd);
    }

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

            free (fi->ccd[i].pieces);
        }
        free (fi->ccd);
    }

    free (fi);
}

} // namspace