/*
 * - Need to document error code meanings.
 * - Need to do something with \* on destinations.
 *
 * reader.c - RTF file reader.  Release 1.11.
 *
 * ASCII 10 (\n) and 13 (\r) are ignored and silently discarded.
 * Nulls are also discarded.
 * (although the read hook will still get a look at them.)
 *
 * "\:" is not a ":", it's a control symbol.  But some versions of
 * Word seem to write "\:" for ":".  This reader treats "\:" as a
 * plain text ":"
 *
 * 19 Mar 93
 * - Add hack to skip "{\*\keycode ... }" group in stylesheet.
 * This is probably the wrong thing to do, but it's simple.
 * 13 Jul 93
 * - Add THINK C awareness to malloc() declaration.  Necessary so
 * compiler knows the malloc argument is 4 bytes.  Ugh.
 * 07 Sep 93
 * - Text characters are mapped onto standard codes, which are placed
 * in rtfMinor.
 * - Eliminated use of index() function.
 * 05 Mar 94
 * - Added zillions of new symbols (those defined in RTF spec 1.2).
 * 14 Mar 94
 * - Public functions RTFMsg() and RTFPanic() now take variable arguments.
 * This means RTFPanic() now is used in place of what was formerly the
 * internal function Error().
 * - 8-bit characters are now legal, so they're not converted to \'xx
 * hex char representation now.
 * 01 Apr 94
 * - Added public variables rtfLineNum and rtfLinePos.
 * - #include string.h or strings.h, avoiding strncmp() problem where
 * last argument is treated as zero when prototype isn't available.
 * 04 Apr 94
 * - Treat style numbers 222 and 0 properly as "no style" and "normal".
 * 08 Apr 94
 * - Control symbol table is no longer compiled in.  It's read in the
 * first time that RTFInit() is called from the library file rtf-ctrl.
 * This shrinks all the translator binaries.  rtf-ctrl is built by
 * rtfprep.
 * 23 Sep 94
 * - Fixed bug in control symbol table reader - was eating backslashes
 * rather than passing them through.
 * 02 Apr 95
 * - Replaced linear search through control word lookup table with
 * binary search.
 * 06 Apr 95
 * - Allocate one big block and copy all control word token strings
 * into it instead of allocating lots of individual strings.
 * 28 Oct 97
 * - Handle color tables where last entry doesn't have a terminating ";"
 * before the "}" that terminates the color table group.  I believe
 * this is illegal, but it happens.
 */

# include       <stdio.h>
# include       <ctype.h>
# include       <string.h>
# include       <stdarg.h>
# include       <stdlib.h>
# include       <stdint.h>

# include       "tokenscan.h"

# define        rtfInternal
# include       "rtf.h"
# undef         rtfInternal

# include       "rtf2latex2e.h"
# include       "init.h"
extern char     texMapQualifier[];

/*
 * Return pointer to new element of type t, or NULL
 * if no memory available.
 */

# define        New(t)  ((t *) RTFAlloc (sizeof (t)))

static void _RTFGetToken(void);
static void _RTFGetToken2(void);
static short GetChar(void);
static void ReadFontTbl(void);
static void ReadStyleSheet(void);
static void ReadInfoGroup(void);
static void ReadPictGroup(void);
static void ReadObjGroup(void);
static void Lookup(char *s);

void DebugMessage(void);

void ExamineToken(void);

/*
 * Public variables (listed in rtf.h)
 */

short rtfClass;
short rtfMajor;
short rtfMinor;
int32_t rtfParam;
char *rtfTextBuf = NULL;
short rtfTextLen;

int32_t rtfLineNum;
short rtfLinePos;
short rtfTokenIndex;


/*
 * Private stuff
 */

static short pushedChar;        /* pushback char if read too far */

static short pushedClass;       /* pushed token info for RTFUngetToken() */
static short pushedMajor;
static short pushedMinor;
static int32_t pushedParam;
static char *pushedTextBuf = NULL;

static short prevChar;
static short bumpLine;


static RTFFont *fontList = NULL;    /* these lists MUST be */
static RTFColor *colorList = NULL; /* initialized to NULL */
static RTFStyle *styleList = NULL;


static FILE *rtffp;

static char *inputName = NULL;
static char *outputName = NULL;


/*
 * This array is used to map standard character names onto their numeric codes.
 * The position of the name within the array is the code.
 * stdcharnames.h is generated in the ../h directory.
 */

static char *stdCharName[] = {
# include       "stdcharnames.h"
    NULL
};


/*
 * These arrays are used to map RTF input character values onto the standard
 * character names represented by the values.  Input character values are
 * used as indices into the arrays to produce standard character codes.
 */


short *genCharCode = NULL;
short *curCharCode = NULL;

int defaultFontNumber;

/*
 * Initialize the reader.  This may be called multiple times,
 * to read multiple files.  The only thing not reset is the input
 * stream; that must be done with RTFSetStream().
 */
void RTFInit(void)
{
    short i;
    RTFColor *cp;
    RTFFont *fp;
    RTFStyle *sp;
    RTFStyleElt *eltList, *ep;

    rtfClass = -1;
    pushedClass = -1;
    pushedChar = EOF;

    rtfLineNum = 0;
    rtfLinePos = 0;
    prevChar = EOF;
    bumpLine = 0;

    if (!rtfTextBuf) {  /* initialize text buffers */
        rtfTextBuf = RTFAlloc(rtfBufSiz);
        pushedTextBuf = RTFAlloc(rtfBufSiz);
        if (!rtfTextBuf || !pushedTextBuf) {
            RTFPanic("Cannot allocate text buffers.");
            exit(1);
        }
        rtfTextBuf[0] = '\0';
        pushedTextBuf[0] = '\0';
    }

    RTFFree(inputName);
    RTFFree(outputName);
    inputName = outputName = NULL;

    for (i = 0; i < rtfMaxClass; i++)
        RTFSetClassCallback(i, NULL);
    for (i = rtfMinDestination; i <= rtfMaxDestination; i++)
        RTFSetDestinationCallback(i, NULL);

    /* install built-in destination readers */
    RTFSetDestinationCallback(rtfFontTbl, ReadFontTbl);
    RTFSetDestinationCallback(rtfColorTbl, ReadColorTbl);
    RTFSetDestinationCallback(rtfStyleSheet, ReadStyleSheet);
    RTFSetDestinationCallback(rtfInfo, ReadInfoGroup);
    RTFSetDestinationCallback(rtfPict, ReadPictGroup);
    RTFSetDestinationCallback(rtfObject, ReadObjGroup);

    RTFSetReadHook(NULL);

    /* dump old lists if necessary */

    while (fontList != NULL) {
        fp = fontList->rtfNextFont;
        RTFFree(fontList->rtfFName);
        RTFFree((char *) fontList);
        fontList = fp;
    }
    while (colorList != NULL) {
        cp = colorList->rtfNextColor;
        RTFFree((char *) colorList);
        colorList = cp;
    }
    while (styleList != NULL) {
        sp = styleList->rtfNextStyle;
        eltList = styleList->rtfSSEList;
        while (eltList != NULL) {
            ep = eltList->rtfNextSE;
            RTFFree(eltList->rtfSEText);
            RTFFree((char *) eltList);
            eltList = ep;
        }
        RTFFree(styleList->rtfSName);
        RTFFree((char *) styleList);
        styleList = sp;
    }

    genCharCode = cp1252CharCode;
    curCharCode = cp1252CharCode;
    RTFInitStack();
}


/*
 * Set the reader's input stream to the given stream.  Can
 * be used to redirect to other than the default (stdin).
 */
void RTFSetStream(FILE *stream)
{
    rtffp = stream;
}


/*
 * Set or get the input or output file name.  These are never guaranteed
 * to be accurate, only insofar as the calling program makes them so.
 */
void RTFSetInputName(char *name)
{
    if ((inputName = RTFStrSave(name)) == NULL)
        RTFPanic("RTFSetInputName: out of memory");
}


char *RTFGetInputName(void)
{
    return (inputName);
}


void RTFSetOutputName(char *name)
{
    if ((outputName = RTFStrSave(name)) == NULL)
        RTFPanic("RTFSetOutputName: out of memory");
}


char *RTFGetOutputName(void)
{
    return (outputName);
}


/* ----------------------------------------------------------------------
 * Callback table manipulation routines
 *
 * Install or return a writer callback for a token class
 */


static RTFFuncPtr ccb[rtfMaxClass];     /* class callbacks */


void RTFSetClassCallback(short class, RTFFuncPtr callback)
{
    if (class >= 0 && class < rtfMaxClass)
        ccb[class] = callback;
}


RTFFuncPtr RTFGetClassCallback(short class)
{
    if (class >= 0 && class < rtfMaxClass)
        return (ccb[class]);
    return (NULL);
}


/*
 * Install or return a writer callback for a destination type
 */

static RTFFuncPtr dcb[rtfNumDestinations];      /* destination callbacks */


void RTFSetDestinationCallback(short dest, RTFFuncPtr callback)
{
    if (dest >= rtfMinDestination && dest <= rtfMaxDestination)
        dcb[dest - rtfMinDestination] = callback;
}


RTFFuncPtr RTFGetDestinationCallback(short dest)
{
    if (dest >= rtfMinDestination && dest <= rtfMaxDestination)
        return (dcb[dest - rtfMinDestination]);
    return (NULL);
}

/* ---------------------------------------------------------------------- */

/*
 * Routines to handle mapping of RTF character sets
 * onto standard characters.
 *
 * RTFStdCharCode(name) given char name, produce numeric code
 * RTFStdCharName(code) given char code, return name
 * RTFMapChar(c)        map input (RTF) char code to std code
 *
 */



/* ---------------------------------------------------------------------- */

/*
 * Token reading routines
 */


/*
 * Read the input stream, invoking the writer's callbacks
 * where appropriate.
 */

void RTFRead(void)
{
    while (RTFGetToken() != rtfEOF) {
/*      ExamineToken(); */
        RTFRouteToken();
    }
}

/*
 * Route a token.  If it's a destination for which a reader is
 * installed, process the destination internally, otherwise
 * pass the token to the writer's class callback.
 */
void RTFRouteToken(void)
{
    RTFFuncPtr p;

    if (rtfClass < 0 || rtfClass >= rtfMaxClass) {      /* watchdog */
        RTFPanic("Unknown class %d: %s (reader malfunction)",
                 rtfClass, rtfTextBuf);
    }
    if (RTFCheckCM(rtfControl, rtfDestination)) {
        /* invoke destination-specific callback if there is one */

        if ((p = RTFGetDestinationCallback(rtfMinor))
            != NULL) {
            (*p) ();
            return;
        }
    }
    /* invoke class callback if there is one */
    if ((p = RTFGetClassCallback(rtfClass)) != NULL)
        (*p) ();
}


/*
 * Skip to the end of the current group.  When this returns,
 * writers that maintain a state stack may want to call their
 * state unstacker; global vars will still be set to the group's
 * closing brace.
 */

void RTFSkipGroup(void)
{
    short level = 1;

    while (RTFGetToken() != rtfEOF) {
        if (rtfClass == rtfGroup) {
            if (rtfMajor == rtfBeginGroup)
                ++level;
            else if (rtfMajor == rtfEndGroup) {
                if (--level < 1)
                    break;      /* end of initial group */
            }
        }
    }
}


/*
 * Read one token.  Call the read hook if there is one.  The
 * token class is the return value.  Returns rtfEOF when there
 * are no more tokens.
 */

short RTFGetToken(void)
{
    RTFFuncPtr p;

    for (;;) {
        _RTFGetToken();
        DebugMessage();
        if ((p = RTFGetReadHook()) != NULL)
            (*p) ();            /* give read hook a look at token */

        /* Silently discard newlines, carriage returns, nulls.  */
        if (!(rtfClass == rtfText  && (rtfMajor == '\n' || rtfMajor == '\r' || rtfMajor == '\0')))
            break;
    }
    return (rtfClass);
}


/*
 * Install or return a token reader hook.
 */

static RTFFuncPtr readHook;


void RTFSetReadHook(RTFFuncPtr f)
{
    readHook = f;
}


RTFFuncPtr RTFGetReadHook(void)
{
    return (readHook);
}


void RTFUngetToken(void)
{
    if (pushedClass >= 0)       /* there's already an ungotten token */
        RTFPanic("cannot unget two tokens");
    if (rtfClass < 0)
        RTFPanic("no token to unget");
    pushedClass = rtfClass;
    pushedMajor = rtfMajor;
    pushedMinor = rtfMinor;
    pushedParam = rtfParam;
    (void) strcpy(pushedTextBuf, rtfTextBuf);
}


short RTFPeekToken(void)
{
    _RTFGetToken();
    RTFUngetToken();
    return (rtfClass);
}


static void _RTFGetToken(void)
{
    /* first check for pushed token from RTFUngetToken() */

    if (pushedClass >= 0) {
        rtfClass = pushedClass;
        rtfMajor = pushedMajor;
        rtfMinor = pushedMinor;
        rtfParam = pushedParam;
        (void) strcpy(rtfTextBuf, pushedTextBuf);
        rtfTextLen = (short) strlen(rtfTextBuf);
        pushedClass = -1;
        return;
    }

    /*
     * Beyond this point, no token is ever seen twice, which is
     * important, e.g., for making sure no "}" pops the font stack twice.
     */

    _RTFGetToken2();
    if (rtfClass == rtfText)    /* map RTF char to standard code */
        rtfMinor = RTFMapChar(rtfMajor);

    if (RTFCheckCMM(rtfControl, rtfCharAttr, rtfFontNum)) {
        RTFFont *fp = RTFGetFont(rtfFontNum);
        if (fp) curCharCode = fp->rtfFCharCode;
        return;
    }
    
    /* \cchs indicates any characters not belonging to the default document character
     * set and tells which character set they do belong to. Macintosh character sets 
     * are represented by values greater than 255. The values for N correspond to the 
     * values for the \ fcharset control word.
     */
    if (RTFCheckCMM(rtfControl, rtfCharAttr, rtfCharCharSet)) {
    
        if (rtfParam>255) {
            curCharCode = cp1252CharCode;
            return;
        }
        
        switch (rtfParam) {
        case 1:
            curCharCode = genCharCode;
            break;
        case 2:
            curCharCode = symCharCode;
            break;
        default:
            curCharCode = cp1252CharCode;
            break;
        }
        return;
    }

    if (rtfClass == rtfGroup) {
        switch (rtfMajor) {
        case rtfBeginGroup:
            RTFPushStack();
            break;
        case rtfEndGroup:
            RTFPopStack();
            break;
        }
        return;
    }
}


/* this shouldn't be called anywhere but from _RTFGetToken() */

static void _RTFGetToken2(void)
{
    short sign;
    short c;

    /* initialize token vars */

    rtfClass = rtfUnknown;
    rtfParam = rtfNoParam;
    rtfTextBuf[rtfTextLen = 0] = '\0';

    /* get first character, which may be a pushback from previous token */

    if (pushedChar != EOF) {
        c = pushedChar;
        rtfTextBuf[rtfTextLen++] = c;
        rtfTextBuf[rtfTextLen] = '\0';
        pushedChar = EOF;
    } else if ((c = GetChar()) == EOF) {
        rtfClass = rtfEOF;
        return;
    }

    if (c == '{') {
        rtfClass = rtfGroup;
        rtfMajor = rtfBeginGroup;
        return;
    }

    if (c == '}') {
        rtfClass = rtfGroup;
        rtfMajor = rtfEndGroup;
        return;
    }

    if (c != '\\') {
        /*
         * Two possibilities here:
         * 1) ASCII 9, effectively like \tab control symbol
         * 2) literal text char
         */
        if (c == '\t') {        /* ASCII 9 */
            rtfClass = rtfControl;
            rtfMajor = rtfSpecialChar;
            rtfMinor = rtfTab;
        } else {
            rtfClass = rtfText;
            rtfMajor = c;
        }
        return;
    }
    
    /* get the character following the backslash */
    c = GetChar();
    
    /* \<newline>text         ---> \par text */
    /* \<newline><spaces>text ---> \text */
    if (c == '\n' || c == '\r') {
        while (c == '\n' || c == '\r')  
            c = GetChar();
    
        if (c != ' ') {
            pushedChar = c;
            strcpy(rtfTextBuf,"\\par");
            Lookup("\\par"); 
            return;
        }
        
        while (c == ' ') 
            c = GetChar();

        rtfTextBuf[1] = c;
        rtfTextBuf[2] = '\0';
        rtfTextLen = 2;
    }
    

    if (c == EOF) {
        /* early eof, whoops (class is rtfUnknown) */
        return;
    }

    if (!isalpha(c)) {
        /*
         * Three possibilities here:
         * 1) hex encoded text char, e.g., \'d5, \'d3
         * 2) special escaped text char, e.g., \{, \}
         * 3) control symbol, e.g., \_, \-, \|, \<10>
         */
        if (c == '\'') {        /* hex char */
            short c2;

            if ((c = GetChar()) != EOF && (c2 = GetChar()) != EOF) {
                /* should do isxdigit check! */
                rtfClass = rtfText;
                rtfMajor = RTFCharToHex(c) * 16 + RTFCharToHex(c2);
                return;
            }
            /* early eof, whoops (class is rtfUnknown) */
            return;
        }

        /* escaped char */
        /*if (index (":{}\\", c) != NULL)  */
        if (c == ':' || c == '{' || c == '}' || c == '\\') {
            rtfClass = rtfText;
            rtfMajor = c;
            return;
        }

        /* control symbol */
        Lookup(rtfTextBuf);     /* sets class, major, minor */
        return;
    }

    /* control word */
    while (isalpha(c)) {
        if ((c = GetChar()) == EOF)
            break;
    }

    /*
     * At this point, the control word is all collected, so the
     * major/minor numbers are determined before the parameter
     * (if any) is scanned.  There will be one too many characters
     * in the buffer, though, so fix up before and restore after
     * looking up.
     */

/*  fprintf(stderr,"command '%s'\n", rtfTextBuf); */

    if (c != EOF)
        rtfTextBuf[rtfTextLen - 1] = '\0';
    Lookup(rtfTextBuf);         /* sets class, major, minor */
    if (c != EOF)
        rtfTextBuf[rtfTextLen - 1] = c;

    /*
     * Should be looking at first digit of parameter if there
     * is one, unless it's negative.  In that case, next char
     * is '-', so need to gobble next char, and remember sign.
     */

    sign = 1;
    if (c == '-') {
        sign = -1;
        c = GetChar();
    }

    if (c != EOF && isdigit(c)) {
        rtfParam = 0;
        while (isdigit(c)) {    /* gobble parameter */
            rtfParam = rtfParam * 10 + c - '0';
            if ((c = GetChar()) == EOF)
                break;
        }
        rtfParam *= sign;
    }
    /*
     * If control symbol delimiter was a blank, gobble it.
     * Otherwise the character is first char of next token, so
     * push it back for next call.  In either case, delete the
     * delimiter from the token buffer.
     */
    if (c != EOF) {
        if (c != ' ')
            pushedChar = c;
        rtfTextBuf[--rtfTextLen] = '\0';
    }
}


/*
 * Read the next character from the input.  This handles setting the
 * current line and position-within-line variables.  Those variable are
 * set correctly whether lines end with CR, LF, or CRLF (the last being
 * the tricky case).
 *
 * bumpLine indicates whether the line number should be incremented on
 * the *next* input character.
 */

static short GetChar(void)
{
    short c;
    short oldBumpLine;

    if ((c = getc(rtffp)) != EOF) {
        rtfTextBuf[rtfTextLen++] = c;
        rtfTextBuf[rtfTextLen] = '\0';
    }
   /* fputc(c,stderr); */
    if (prevChar == EOF)
        bumpLine = 1;
    oldBumpLine = bumpLine;     /* non-zero if prev char was line ending */
    bumpLine = 0;
    if (c == '\r')
        bumpLine = 1;
    else if (c == '\n') {
        bumpLine = 1;
        if (prevChar == '\r')   /* oops, previous \r wasn't */
            oldBumpLine = 0;    /* really a line ending */
    }
    ++rtfLinePos;
    if (oldBumpLine) {          /* were we supposed to increment the *//* line count on this char? */
        ++rtfLineNum;
        rtfLinePos = 1;
    }
    prevChar = c;
    return (c);
}


/*
 * Synthesize a token by setting the global variables to the
 * values supplied.  Typically this is followed with a call
 * to RTFRouteToken().
 *
 * If a param value other than rtfNoParam is passed, it becomes
 * part of the token text.
 */

void RTFSetToken(short class, short major, short minor, int32_t param, char *text)
{
    rtfClass = class;
    rtfMajor = major;
    rtfMinor = minor;
    rtfParam = param;
    if (param == rtfNoParam)
        (void) strcpy(rtfTextBuf, text);
    else
        snprintf(rtfTextBuf, rtfBufSiz, "%s%d", text, (int) param);
    rtfTextLen = strlen(rtfTextBuf);
}


/*
 * Given a standard character name (a string), find its code (a number).
 * Return -1 if name is unknown.
 */

short RTFStdCharCode(char *name)
{
    short i;

    for (i = 0; i < rtfSC_MaxChar; i++) {
        if (strcmp(name, stdCharName[i]) == 0)
            return (i);
    }
    return (-1);
}


/*
 * Given a standard character code (a number), find its name (a string).
 * Return NULL if code is unknown.
 */

char *RTFStdCharName(short code)
{
    if (code < 0 || code >= rtfSC_MaxChar)
        return (NULL);
    return (stdCharName[code]);
}


/*
 * Given an RTF input character code, find standard character code.
 * The translator should read the appropriate charset maps when it finds a
 * charset control.  However, the file might not contain one.  In this
 * case, no map will be available.  When the first attempt is made to
 * map a character under these circumstances, RTFMapChar() assumes ANSI
 * and reads the map as necessary.
 */

short RTFMapChar(short c)
{
    if (c < 0 || c >= CHAR_SET_SIZE)
        return (rtfSC_nothing);
    return (curCharCode[c]);
}


/* ---------------------------------------------------------------------- */

/*
 * Special destination readers.  They gobble the destination so the
 * writer doesn't have to deal with them.  That's wrong for any
 * translator that wants to process any of these itself.  In that
 * case, these readers should be overridden by installing a different
 * destination callback.
 *
 * NOTE: The last token read by each of these reader will be the
 * destination's terminating '}', which will then be the current token.
 * That '}' token is passed to RTFRouteToken() - the writer has already
 * seen the '{' that began the destination group, and may have pushed a
 * state; it also needs to know at the end of the group that a state
 * should be popped.
 *
 * It's important that rtf.h and the control token lookup table list
 * as many symbols as possible, because these destination readers
 * unfortunately make strict assumptions about the input they expect,
 * and a token of class rtfUnknown will throw them off easily.
 */


/*
 * Read { \fonttbl ... } destination.  Old font tables don't have
 * braces around each table entry; try to adjust for that.
 */

static void ReadFontTbl(void)
{
    RTFFont *fp = NULL;
    char buf[rtfBufSiz], *bp;
    short old = -1;
    char *fn = "ReadFontTbl";

    for (;;) {
        (void) RTFGetToken();

        if (RTFCheckCM(rtfGroup, rtfEndGroup))
            break;
            
        if (rtfClass==rtfText && rtfMinor == rtfSC_space)
            continue;

        if (old < 0) {          /* first entry - determine tbl type */
            if (RTFCheckCMM(rtfControl, rtfCharAttr, rtfFontNum))
                old = 1;        /* no brace */
            else if (RTFCheckCM(rtfGroup, rtfBeginGroup))
                old = 0;        /* brace */
            else                /* can't tell! */
                RTFPanic("%s: Cannot determine format", fn);
        }
        
        if (old == 0) {         /* need to find "{" here */
            if (!RTFCheckCM(rtfGroup, rtfBeginGroup))
                RTFPanic("%s: xmissing \"{\"", fn);
            (void) RTFGetToken();       /* yes, skip to next token */
        }

        fp = New(RTFFont);
        if (!fp) {
            RTFPanic("%s: cannot allocate font entry", fn);
            exit(1);
        }

        fp->rtfNextFont = fontList;
        fontList = fp;

        fp->rtfFName = NULL;
        fp->rtfFAltName = NULL;
        fp->rtfFNum = -1;
        fp->rtfFFamily = 0;
        fp->rtfFCharSet = -1;
        fp->rtfFPitch = 0;
        fp->rtfFType = 0;
        fp->rtfFCharCode = genCharCode;
        fp->rtfFCodePage = 0;

        while (rtfClass != rtfEOF && !RTFCheckCM(rtfText, ';')) {

            if (rtfClass == rtfControl) {
            
                switch (rtfMajor) {
                default:
                    /* ignore token but announce it */
                    RTFMsg("%s: unknown token \"%s\"\n", fn, rtfTextBuf);
                    
                case rtfFontFamily:
                    fp->rtfFFamily = rtfMinor;
                    break;
                    
                case rtfCharAttr:
                    switch (rtfMinor) {
                    default:
                        break;  /* ignore unknown? */
                    case rtfFontNum:
                        fp->rtfFNum = rtfParam;
                        break;
                    }
                    break;
                    
                case rtfFontAttr:
                    switch (rtfMinor) {
                    default:
                        break;  /* ignore unknown? */
                    case rtfFontCharSet:
                        fp->rtfFCharSet = rtfParam;
                        break;
                    case rtfFontPitch:
                        fp->rtfFPitch = rtfParam;
                        break;
                    case rtfFontCodePage:
                        fp->rtfFCodePage = rtfParam;
                        break;
                    case rtfFTypeNil:
                    case rtfFTypeTrueType:
                        fp->rtfFType = rtfParam;
                        break;
                    }
                    break;
                }
                
            } else if (RTFCheckCM(rtfGroup, rtfBeginGroup)) {   /* dest */
                RTFSkipGroup(); /* ignore for now */
                
            } else if (rtfClass == rtfText) {   /* font name */
                bp = buf;
                while (rtfClass != rtfEOF && !RTFCheckCM(rtfText, ';')) {
                    *bp++ = rtfMajor;
                    (void) RTFGetToken();
                }
                *bp = '\0';
                fp->rtfFName = RTFStrSave(buf);
                if (fp->rtfFName == NULL)
                    RTFPanic("%s: cannot allocate font name", fn);
                    
                if (strcasecmp(fp->rtfFName,"Symbol")==0)
                    fp->rtfFCharCode = symCharCode;
                    
                /* already have next token; don't read another at bottom of loop */
                continue;
            } else {
                /* ignore token but and don't announce it */
                RTFMsg("%s: unknown token \"%s\"\n", fn, rtfTextBuf);

            }

            (void) RTFGetToken();
        }

        if (old == 0) {         /* need to see "}" here */
            (void) RTFGetToken();
            if (!RTFCheckCM(rtfGroup, rtfEndGroup))
                RTFPanic("%s: missing \"}\"", fn);
        }
    }
    if (fp == NULL || fp->rtfFNum == -1)
        RTFPanic("%s: missing font number", fn);
/*
 * Could check other pieces of structure here, too, I suppose.
 */
    RTFRouteToken();            /* feed "}" back to router */

    if (defaultFontNumber > -1) {
        RTFFont *fp1 = RTFGetFont(defaultFontNumber);
        if (fp1) curCharCode = fp1->rtfFCharCode;
    }
    
}

/*
 * The color table entries have color values of -1 if
 * the default color should be used for the entry (only
 * a semi-colon is given in the definition, no color values).
 * There will be a problem if a partial entry (1 or 2 but
 * not 3 color values) is given.  The possibility is ignored
 * here.
 */

void ReadColorTbl(void)
{
    RTFColor *cp;
    short cnum = 0;
    char *fn = "ReadColorTbl";

    for (;;) {
        (void) RTFGetToken();
        if (RTFCheckCM(rtfGroup, rtfEndGroup))
            break;

        cp = New(RTFColor);
        if (!cp) {
            RTFPanic("%s: cannot allocate color entry", fn);
            exit(1);
        }

        cp->rtfCNum = cnum++;
        cp->rtfCRed = cp->rtfCGreen = cp->rtfCBlue = -1;
        cp->rtfNextColor = colorList;
        colorList = cp;
        while (RTFCheckCM(rtfControl, rtfColorName)) {
            switch (rtfMinor) {
            case rtfRed:
                cp->rtfCRed = rtfParam;
                break;
            case rtfGreen:
                cp->rtfCGreen = rtfParam;
                break;
            case rtfBlue:
                cp->rtfCBlue = rtfParam;
                break;
            }
            RTFGetToken();
        }
        /*
         * Normally a semicolon should terminate a color table entry,
         * but some writers write the last entry without one, so that
         * the entry is followed by the "}" that terminates the table.
         * Allow for that possibility here.
         */
        if (RTFCheckCM(rtfGroup, rtfEndGroup))
            break;
        if (!RTFCheckCM(rtfText, (short) ';'))
            RTFPanic("%s: malformed entry", fn);
    }
    RTFRouteToken();            /* feed "}" back to router */
}


/* correlates the user defined conversion with a styles "name" */
static short Style2LatexItem(char *name)
{
    int i;

    for (i = 0; i < MAX_STYLE_MAPPINGS; i++) {
        if (!Style2LatexStyle[i]) return -1; 
        
        if (strcasecmp(name, Style2LatexStyle[i]) == 0)
            return i;
    }
    return (-1);
}


/*
 * The "Normal" style definition doesn't contain any style number,
 * all others do.  Normal style is given style rtfNormalStyleNum.
 */

static void ReadStyleSheet(void)
{
    RTFStyle *sp;
    RTFStyleElt *sep, *sepLast;
    char buf[rtfBufSiz], *bp;
    char *fn = "ReadStyleSheet";

    for (;;) {
        (void) RTFGetToken();

        if (RTFCheckCM(rtfGroup, rtfEndGroup))
            break;

        sp = New(RTFStyle);
        if (!sp) {
            RTFPanic("%s: cannot allocate stylesheet entry", fn);
            exit(1);
        }

        sp->rtfSName = NULL;
        sp->rtfSNum = -1;
        sp->rtfSType = rtfParStyle;
        sp->rtfSAdditive = 0;
        sp->rtfSBasedOn = rtfNoStyleNum;
        sp->rtfSNextPar = -1;
        sp->rtfSSEList = sepLast = NULL;
        sp->rtfNextStyle = styleList;
        sp->rtfExpanding = 0;
        styleList = sp;
        if (!RTFCheckCM(rtfGroup, rtfBeginGroup))
            RTFPanic("%s: missing \"{\"", fn);
        for (;;) {
            (void) RTFGetToken();
            if (rtfClass == rtfEOF || RTFCheckCM(rtfText, ';'))
                break;

            if (rtfClass == rtfControl) {
                if (RTFCheckMM(rtfSpecialChar, rtfOptDest))
                    continue;   /* ignore "\*" */
                    
                if (RTFCheckMM(rtfParAttr, rtfStyleNum)) {
                    sp->rtfSNum = rtfParam;
                    sp->rtfSType = rtfParStyle;
                    continue;
                }
                if (RTFCheckMM(rtfCharAttr, rtfCharStyleNum)) {
                    sp->rtfSNum = rtfParam;
                    sp->rtfSType = rtfCharStyle;
                    continue;
                }
                if (RTFCheckMM(rtfSectAttr, rtfSectStyleNum)) {
                    sp->rtfSNum = rtfParam;
                    sp->rtfSType = rtfSectStyle;
                    continue;
                }
                if (RTFCheckMM(rtfStyleAttr, rtfBasedOn)) {
                    sp->rtfSBasedOn = rtfParam;
                    continue;
                }
                if (RTFCheckMM(rtfStyleAttr, rtfAdditive)) {
                    sp->rtfSAdditive = 1;
                    continue;
                }
                if (RTFCheckMM(rtfStyleAttr, rtfNext)) {
                    sp->rtfSNextPar = rtfParam;
                    continue;
                }
                if (RTFCheckMM(rtfStyleAttr, rtfTableStyleNum)) {
                    sp->rtfSNum = rtfParam;
                    sp->rtfSType = rtfTableStyle;
                    continue;
                }

                sep = New(RTFStyleElt);
                if (!sep) {
                    RTFPanic("%s: cannot allocate style element", fn);
                    exit(1);
                }

                sep->rtfSEClass = rtfClass;
                sep->rtfSEMajor = rtfMajor;
                sep->rtfSEMinor = rtfMinor;
                sep->rtfSEParam = rtfParam;
                sep->rtfSEText = RTFStrSave(rtfTextBuf);
                if (! sep->rtfSEText)
                    RTFPanic("%s: cannot allocate style element text", fn);
                    
                if (sepLast == NULL)
                    sp->rtfSSEList = sep;       /* first element */
                else            /* add to end */
                    sepLast->rtfNextSE = sep;
                sep->rtfNextSE = NULL;
                sepLast = sep;
            } else if (RTFCheckCM(rtfGroup, rtfBeginGroup)) {
                /*
                 * This passes over "{\*\keycode ... }, among
                 * other things. A temporary (perhaps) hack.
                 */
                RTFSkipGroup();
                continue;
            } else if (rtfClass == rtfText) {   /* style name */
                bp = buf;
                while (rtfClass == rtfText) {
                    if (rtfMajor == ';') {
                        /* put back for "for" loop */
                        (void) RTFUngetToken();
                        break;
                    }
                    *bp++ = rtfMajor;
                    (void) RTFGetToken();
                }
                *bp = '\0';
                if ((sp->rtfSName = RTFStrSave(buf)) == NULL)
                    RTFPanic("%s: cannot allocate style name", fn);
            } else {            /* unrecognized */

                /* ignore token and do not announce it
                RTFMsg ("%s: unknown token \"%s\"\n", fn, rtfTextBuf);
                */

            }
        }
        (void) RTFGetToken();
   
        while (!RTFCheckCM(rtfGroup, rtfEndGroup)) {
            RTFGetToken();
            if (rtfClass == rtfEOF) {
                RTFPanic("%s: Stylesheet malformed!\n", fn);
                break;
            }
        }

        /*
         * Check over the style structure.  A name is a must.
         * If no style number was specified, check whether it's the
         * Normal style (in which case it's given style number
         * rtfNormalStyleNum).  Note that some "normal" style names
         * just begin with "Normal" and can have other stuff following,
         * e.g., "Normal,Times 10 point".  Ugh.
         *
         * Some German RTF writers use "Standard" instead of "Normal".
         */
        if (sp->rtfSName == NULL)
            RTFPanic("%s: missing style name", fn);
            
        if (sp->rtfSNum < 0) {
            if (strncmp(buf, "Normal", 6) != 0 && strncmp(buf, "Standard", 8) != 0) {
                RTFMsg("%s: style is '%s'\n",fn, buf);
                RTFMsg("%s: number is %d\n",fn, sp->rtfSNum);
                RTFPanic("%s: missing style number", fn);
            }
            sp->rtfSNum = rtfNormalStyleNum;
        }
        
        /* this provides direct access to the right style mapping without
           needing to compare all the style names every time we want to know
           If there is no mapping, then the index is just set to -1 */
        if (sp->rtfSNum < MAX_STYLE_MAPPINGS)
            Style2LatexMapIndex[sp->rtfSNum] = Style2LatexItem(sp->rtfSName);
        
        if (sp->rtfSNextPar == -1)      /* if \snext not given, */
            sp->rtfSNextPar = sp->rtfSNum;      /* next is itself */
    }
    RTFRouteToken();            /* feed "}" back to router */
}


static void ReadInfoGroup(void)
{
    RTFSkipGroup();
    RTFRouteToken();            /* feed "}" back to router */
}


static void ReadPictGroup(void)
{
    RTFSkipGroup();
    RTFRouteToken();            /* feed "}" back to router */
}


static void ReadObjGroup(void)
{
    RTFSkipGroup();
    RTFRouteToken();            /* feed "}" back to router */
}

/* ---------------------------------------------------------------------- */

/*
 * Routines to return pieces of stylesheet, or font or color tables.
 * References to style 0 are mapped onto the Normal style.
 */


RTFStyle *RTFGetStyle(short num)
{
    RTFStyle *s;

    if (num == -1)
        return (styleList);
    for (s = styleList; s != NULL; s = s->rtfNextStyle) {
        if (s->rtfSNum == num)
            break;
    }
    return (s);                 /* NULL if not found */
}


RTFFont *RTFGetFont(short num)
{
    RTFFont *f;

    if (num == -1)
        return (fontList);
    for (f = fontList; f != NULL; f = f->rtfNextFont) {
        if (f->rtfFNum == num) {
            /* check for NULL font name contributed by Keith Refson */
            if (f->rtfFName == NULL)
                f->rtfFName = "noName";
            break;
        }
    }
    return (f);                 /* NULL if not found */
}


RTFColor *RTFGetColor(short num)
{
    RTFColor *c;

    if (num == -1)
        return (colorList);
    for (c = colorList; c != NULL; c = c->rtfNextColor) {
        if (c->rtfCNum == num)
            break;
    }
    return (c);                 /* NULL if not found */
}


/* ---------------------------------------------------------------------- */


/*
 * Expand style n, if there is such a style.
 */

void RTFExpandStyle(short n)
{
    RTFStyle *s;
    RTFStyleElt *se;

    if (n == -1 || (s = RTFGetStyle(n)) == NULL) {
        RTFMsg("¥ n is %d, returning without expanding style\n", n);
        return;
    }
    if (s->rtfExpanding != 0)
        RTFPanic("Style expansion loop, style %d", n);
    s->rtfExpanding = 1;        /* set expansion flag for loop detection */
    /*
     * Expand "based-on" style (unless it's the same as the current
     * style -- Normal style usually gives itself as its own based-on
     * style).  Based-on style expansion is done by synthesizing
     * the token that the writer needs to see in order to trigger
     * another style expansion, and feeding to token back through
     * the router so the writer sees it.
     */
    if (n != s->rtfSBasedOn) {
        RTFSetToken(rtfControl, rtfParAttr, rtfStyleNum,
                    s->rtfSBasedOn, "\\s");
        RTFRouteToken();
    }
    /*
     * Now route the tokens unique to this style.  RTFSetToken()
     * isn't used because it would add the param value to the end
     * of the token text, which already has it in.
     */
    for (se = s->rtfSSEList; se != NULL;
         se = se->rtfNextSE) {
        rtfClass = se->rtfSEClass;
        rtfMajor = se->rtfSEMajor;
        rtfMinor = se->rtfSEMinor;
        rtfParam = se->rtfSEParam;
        (void) strcpy(rtfTextBuf, se->rtfSEText);
        rtfTextLen = strlen(rtfTextBuf);
        RTFRouteToken();
    }
    s->rtfExpanding = 0;        /* done - clear expansion flag */
}


/* ---------------------------------------------------------------------- */

/*
 * Control symbol lookup routines
 */


RTFCtrl **rtfCtrl = NULL;
short nCtrls;



/*
 * Determine major and minor number of control token.  If it's
 * not found, the class turns into rtfUnknown.
 *
 * Algorithm uses a binary search to locate the token.  It assumes
 * the tokens are stored in the table in sorted order.
 */

static void Lookup(char *s)
{
    RTFCtrl *rp;
    char c1, c2;
/* short        i; */
    short index, result;
    short lower, upper;

    ++s;                        /* skip over the leading \ character */
    c1 = *s;                    /* save first char for comparisons */

    lower = 0;
    upper = nCtrls - 1;

    for (;;) {
        index = (lower + upper) / 2;
        rp = rtfCtrl[index];
        /*
         * Do quick check against first character to avoid function
         * call if possible.  If character matches, then call strcmp().
         */
        c2 = rp->str[0];
        result = (c1 - c2);
        if (result == 0)
            result = strcmp(s, rp->str);
        if (result == 0) {
            rtfClass = rtfControl;
            rtfMajor = rp->major;
            rtfMinor = rp->minor;
            rtfTokenIndex = rp->index;
            return;
        }
        if (lower >= upper)     /* can't subdivide range further, */
            break;              /* so token wasn't found */
        if (result < 0)
            upper = index - 1;
        else
            lower = index + 1;
    }
    rtfClass = rtfUnknown;
}

void DebugMessage(void)
{
    if (0 && g_debug_level > 0)
        if (strcmp(rtfCtrl[rtfTokenIndex]->str,"objdata"))
            fprintf(stderr, "%s (%d,%d,%d)\n",rtfCtrl[rtfTokenIndex]->str,rtfClass,rtfMajor,rtfMinor);
}
/* ---------------------------------------------------------------------- */

/*
 * Memory allocation routines
 */


/*
 * Return pointer to block of size bytes, or NULL if there's
 * not enough memory available.
 *
 * This is called through RTFAlloc(), a define which coerces the
 * argument to int32_t.  This avoids the persistent problem of allocation
 * failing and causing mysterious crashes under THINK C when an argument
 * of the wrong size is passed.
 */

char * RTFAlloc(size_t size)
{
    return ((char *) malloc(size));
}


/*
 * Saves a string on the heap and returns a pointer to it.
 */


char *RTFStrSave(char *s)
{
    char *p;

    if ((p = RTFAlloc(strlen(s) + 1)) == NULL)
        return (NULL);
    return (strcpy(p, s));
}


void RTFFree(char *p)
{
    if (p != NULL)
        free(p);
}


/* ---------------------------------------------------------------------- */


/*
 * Token comparison routines
 */

short RTFCheckCM(short class, short major)
{
    return (rtfClass == class && rtfMajor == major);
}


short RTFCheckCMM(short class, short major, short minor)
{
    return (rtfClass == class && rtfMajor == major && rtfMinor == minor);
}


short RTFCheckMM(short major, short minor)
{
    return (rtfMajor == major && rtfMinor == minor);
}


/* ---------------------------------------------------------------------- */


short RTFCharToHex(char c)
{
    if (isupper(c))
        c = tolower(c);
    if (isdigit(c))
        return (c - '0');       /* '0'..'9' */
    return (c - 'a' + 10);      /* 'a'..'f' */
}


short RTFHexToChar(short i)
{
    if (i < 10)
        return (i + '0');
    return (i - 10 + 'a');
}

/* string must start with 0x or 0X */
int RTFHexStrToInt(char * s)
{
    int i,x;
    if (!s)
        return 0;
    if (strlen(s)<3)
        return 0;
    if (s[0] != '0' || (s[1] != 'x' && s[1] != 'X'))
        return 0;
    i=2;
    x=0;
    while (s[i] != '\0') {
        x = x*16 + RTFCharToHex(s[i]);
        i++;
    }
    return x;
}



/* ---------------------------------------------------------------------- */

/*
 * Open a library file.
 */


static FILE *(*libFileOpen) () = NULL;

void RTFSetOpenLibFileProc(FILE * (*proc) (char *file, char *mode))
{
    libFileOpen = proc;
}


FILE *RTFOpenLibFile(char *file, char *mode)
{
    if (libFileOpen == NULL)
        return (NULL);
    return ((*libFileOpen) (file, mode));
}


/* ---------------------------------------------------------------------- */

/*
 * Print message.  Default is to send message to stderr
 * but this may be overridden with RTFSetMsgProc().
 *
 * Message should include linefeeds as necessary.  If the default
 * function is overridden, the overriding function may want to
 * map linefeeds to another line ending character or sequence if
 * the host system doesn't use linefeeds.
 */


static void DefaultMsgProc(s)
char *s;
{
    fprintf(stderr, "%s", s);
}


static RTFFuncPtr msgProc = DefaultMsgProc;


void RTFSetMsgProc(RTFFuncPtr proc)
{
    msgProc = proc;
}


void RTFMsg(char *fmt, ...)
{
    char buf[rtfBufSiz];

    va_list args;
    va_start(args, fmt);
    vsnprintf(buf, rtfBufSiz, fmt, args);
    va_end(args);
    (*msgProc) (buf);
}

/* ---------------------------------------------------------------------- */

/*
 * Process termination.  Print error message and exit.  Also prints
 * current token, and current input line number and position within
 * line if any input has been read from the current file.  (No input
 * has been read if prevChar is EOF).
 */

static void DefaultPanicProc(char *s)
{
    fprintf(stderr, "%s", s);
    exit(1);
}


static RTFFuncPtr panicProc = DefaultPanicProc;


void RTFSetPanicProc(RTFFuncPtr proc)
{
    panicProc = proc;
}


void RTFPanic(char *fmt, ...)
{
    char buf[rtfBufSiz];

    va_list args;
    va_start(args, fmt);
    vsnprintf(buf, rtfBufSiz, fmt, args);
    va_end(args);
    (void) strcat(buf, "\n");
    if (prevChar != EOF && rtfTextBuf != NULL) {
        snprintf(buf + strlen(buf), rtfBufSiz-strlen(buf),
                "Last token read was \"%s\" near line %d, position %hd.\n",
                rtfTextBuf, (int)rtfLineNum, (int)rtfLinePos);
    }
    (*panicProc) (buf);
}

/*
 Useful functions added by Ujwal Sathyam.
 */

/*
 This function does a simple initialization of the RTF reader. This is useful when
 the file cursor is moved around in the input stream. It basically makes the RTF
 reader forget what it has seen before.
 */

void RTFSimpleInit(void)
{
    rtfClass = -1;
    pushedClass = -1;
    pushedChar = EOF;
}


/*
 This function returns the last character the RTF reader removed from the input stream.
 */
short RTFPushedChar(void)
{
    return (pushedChar);
}

void RTFSetPushedChar(short lastChar)
{
    pushedChar = lastChar;
}

void RTFSetDefaultFont(int fontNumber)
{
    defaultFontNumber = fontNumber;
}

/*
 * Stack for keeping track of text style on group begin/end.  This is
 * necessary because group termination reverts the text style to the previous
 * value, which may implicitly change it.
 */

# define        MAX_STACK             100

short *csStack[MAX_STACK];
short *savedCSStack[MAX_STACK];
parStyleStruct  parStack[MAX_STACK];
textStyleStruct textStyleStack[MAX_STACK];

int braceLevel;
int savedbraceLevel;

void RTFInitStack(void)
{
    braceLevel=0;
    paragraphWritten.firstIndent      = UNINITIALIZED;
    paragraphWritten.leftIndent       = UNINITIALIZED;
    paragraphWritten.lineSpacing      = UNINITIALIZED;
    paragraphWritten.alignment        = UNINITIALIZED;

    paragraph.firstIndent = 720;
    paragraph.leftIndent  = 0;
    paragraph.lineSpacing = 240;
    paragraph.alignment   = 0;
    textStyle.foreColor   = 0;
    textStyle.backColor   = 0;
    textStyle.fontSize    = normalSize;

    RTFPushStack();
}

void RTFPushStack(void)
{
    csStack[braceLevel] = curCharCode;
    parStack[braceLevel] = paragraph;
    textStyleStack[braceLevel] = textStyle;

//    fprintf(stderr, "push [%d] alignment now written=%d, set=%d\n",braceLevel, paragraphWritten.alignment, paragraph.alignment);
    braceLevel++;
    if (braceLevel >= MAX_STACK) {
        RTFMsg("Exceeding stack capacity of %d items\n",MAX_STACK);
        braceLevel = MAX_STACK - 1;
    }
}

void RTFPopStack(void)
{
    int i;
    braceLevel--;
    i=braceLevel;

    if (i < 0) {
        RTFMsg("Too many '}'.  Stack Underflow\n");
        i = 0;
    }
    curCharCode = csStack[i];

    paragraph = parStack[i];
    textStyle = textStyleStack[i];
}

void RTFStoreStack(void)
{
    memcpy(savedCSStack, csStack, MAX_STACK * sizeof(short));
    savedbraceLevel = braceLevel;
}

void RTFRestoreStack(void)
{
    memcpy(csStack, savedCSStack, MAX_STACK * sizeof(short));
    braceLevel = savedbraceLevel;
    curCharCode = csStack[braceLevel];
}
