/* Released under the Modified BSD license attached to Axiom sources.
 * TeX Display Math Mode Line Breaking Program
 *
 * Author: Robert S. Sutor
 *
 * Date:   1991
 *
 * Change History:
 *
 * 01/19/92   RSS   Change to use \[ \] instead of $$ $$
 *
 * 09/01/92   RSS   Format and fix =-.
 *
 * Operation:
 *
 * This program reads standard input and writes to standard output. Display math
 * mode starts with \[ at the beginning of a line and ends with \]. All lines
 * not in display math mode are simply printed on standard output.  The
 * expressions in display math mode are broken so that they fit on a page
 * better (line breaking).
 *
 * The array stuff is being converted to use the array node type.
 *
 * Restrictions:
 *
 * 1.  Assume \[ and \] start in column 1 and are the only things on the lines.
 *
 * 2.  Comments in display math mode are not preserved.
 *
 * 3. This is meant to deal with output from the AXIOM computer algebra system.
 * Unpredictable results may occur if used with hand-generated TeX code or
 * TeX code generated by other programs.
 */

/*
 * Include files and #defines.
 */
#include "useproto.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MATHBUFLEN     8*8192
#define MAXMATHTOKEN   80
#define MAXCHARSINLINE 60
#define FATDELIMMULT   2

#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif

#define TRUE 1
#define FALSE 0

#define STRCHREQ(str,char) (str[0] == char)

/*
 * Type declarations.
 */

enum nodeTypes {
    N_NODE,
    N_TEXT,
    N_ARRAY
};

typedef struct listNodeStruct {
    struct listNodeStruct *nextListNode;
    struct listNodeStruct *prevListNode;
    enum nodeTypes nodeType;
    long width;
    long realWidth;
    union {
        char *text;
        struct listNodeStruct *node;
        struct arrayNodeStruct *array;
    }   data;
}   listNode;

typedef struct arrayNodeStruct {
    int cols;
    listNode *argsNode;
    listNode *entries;          /* linked list of nodes, each pointing to a
                                 * row */
}   arrayNode;


/*
 * Global Variables.
 */

char line[1024];
char blanks[] = "                                                   ";
char lastPrinted = '\0';
int indent = 0;
char mathBuffer[MATHBUFLEN], mathToken[MAXMATHTOKEN];
char bufout[2*MATHBUFLEN];
int lineLen, mathBufferLen, mathBufferPtr, mathTokenLen;
listNode *mathList;
int charsOut, fatDelimiter;
int maxLineWidth = 4500;        /* 4.5  inches * 1000    */
int maxLineSlop = 0;            /* 0.0  inches * 1000    */
int charTable[256];
int avgCharWidth;
int spaceWidths[5], extraOverWidth;
int arrayDepth = 0, arrayMaxDepth = 3;
int charMultNum, charMultDenom;
int sinWidth, cosWidth, tanWidth, erfWidth;
long outLineNum = 0L;

/*
 * Function Prototypes.
 */
#ifndef _NO_PROTO
void        arrayTooDeep();
int         breakFracProd(listNode *, long, char *, char *, int);
int         breakFunction(listNode *, long);
int         breakList(listNode *, long);
int         breakMathList(listNode *, long);
int         breakNumber(listNode *, long);
int         breakPMEB(listNode *, long);
int         breakParen(listNode *, long);
int         bufferMathLines();
void        buildMathList();
int         charWidth(char);
void        computeNodeWidth(listNode *);
long        computeWidth(listNode *);
void        displaySplitMsg(char *, int);
void        error(char *, char *);
void        freeMathList(listNode *);
void        getOptions(int, char **);
void        initCharTable();
listNode   *insertStringAfter(char *, listNode *);
listNode   *insertStringAtBack(char *, listNode *);
listNode   *insertStringAtFront(char *, listNode *);
int         newLineIfNecessary(int);
listNode   *newListNode(enum nodeTypes);
int         nextMathToken();
int         printChar(char);
int         printMathList(listNode *, int);
int         printString(char *);
void        putcBuf(char, char []);
void        putsBuf(char [], char []);
void        resetCharMults();
listNode   *string2NodeList(char *, listNode *);
void        ttCharMults();
#else
void        arrayTooDeep();
int         breakFracProd();
int         breakFunction();
int         breakList();
int         breakMathList();
int         breakNumber();
int         breakPMEB();
int         breakParen();
int         bufferMathLines();
void        buildMathList();
int         charWidth();
void        computeNodeWidth();
long        computeWidth();
void        displaySplitMsg();
void        error();
void        freeMathList();
void        getOptions();
void        initCharTable();
listNode   *insertStringAfter();
listNode   *insertStringAtBack();
listNode   *insertStringAtFront();
int         newLineIfNecessary();
listNode   *newListNode();
int         nextMathToken();
int         printChar();
int         printMathList();
int         printString();
void        putsBuf();
void        putcBuf();
void        resetCharMults();
listNode   *string2NodeList();
void        ttCharMults();
#endif


/*
 * Function Definitions.
 */

int
#ifndef _NO_PROTO
texbreak(char bufinp[])
#else
texbreak(bufinp)
char bufinp[];
#endif
{
    initCharTable();
    strcpy(bufout,"");
    mathBufferLen=strlen(bufinp);
    if (mathBufferLen > MATHBUFLEN) {
       fprintf(stderr, "mathBuffer too small. Need: %d\n", mathBufferLen);
       return 0;
    };
    mathBufferPtr = 0;
    strcpy(mathBuffer,bufinp);
/*  fprintf(stderr, "bufinp: %s\n", mathBuffer); */
    fatDelimiter = 1;
    arrayDepth = 0;
    mathList = newListNode(N_NODE);
    buildMathList(mathList);
    resetCharMults();
    computeWidth(mathList);
/*  if (mathList->width > maxLineWidth)
       fprintf(stderr, "Width = %ld units, Output line = %ld.\n", mathList->width, outLineNum); */
    breakMathList(mathList, maxLineWidth);
    charsOut = 0;
    printMathList(mathList->data.node, TRUE);
    freeMathList(mathList);
    return 1;
}

/*
 * breakFracProd:
 *
 * Arguments:
 *
 * n : the starting node at which we are to try to break
 *
 * lineWidth : the maximum width of a line
 *
 * match :  either "\\over" or "\\ "
 *
 * label :  either "quotient" or "product"
 *
 * paren :  add parentheses (TRUE or FALSE)
 *
 *
 * Returns: TRUE or FALSE, depending on whether the expression was broken
 *
 *
 * Function: Tries to break up a quotient t1 \over t2 or a product t1 \ t2 by
 * splitting and parenthesizing the numerator and/or the denominator.
 */

int
#ifndef _NO_PROTO
breakFracProd(listNode * n, long lineWidth, char *match, char *label, int paren)
#else
breakFracProd(n,lineWidth,match,label,paren)
listNode * n; 
long lineWidth; 
char *match; 
char *label; 
int paren;
#endif
{

    listNode *rootNode, *lastNode, *t1, *t2;
    int ok;
    long workWidth1, workWidth2;

    if (n->nodeType != N_NODE)
        return FALSE;

    rootNode = n;

    ok = FALSE;
    t1 = n = rootNode->data.node;
    n = n->nextListNode;
    if (n) {
        if ((n->nodeType == N_TEXT) &&
            (0 == strcmp(n->data.text, match))) {
            t2 = n->nextListNode;
            if (t2 && (NULL == t2->nextListNode))
                ok = TRUE;
        }
    }

    displaySplitMsg(label, ok);

    if (ok) {

        /* for products, determine rough widths for the two factors */

        if (0 == strcmp(label, "product")) {
            computeNodeWidth(t1);
            computeNodeWidth(t2);
            workWidth1 = lineWidth - charWidth(' ');

            if (workWidth1 / 2 > t1->realWidth) {
                workWidth2 = workWidth1 - t1->realWidth;
                workWidth1 = t1->realWidth;
            }
            else if (workWidth1 / 2 > t2->realWidth) {
                workWidth1 = workWidth1 - t2->realWidth;
                workWidth2 = t2->realWidth;
            }
            else
                workWidth1 = workWidth2 = workWidth1 / 2;

            if (paren) {
                if (t1->realWidth > workWidth1)
                    workWidth1 = workWidth1 - 4 * FATDELIMMULT * charWidth('(');
                if (t2->realWidth > workWidth2)
                    workWidth2 = workWidth2 - 4 * FATDELIMMULT * charWidth('(');
            }
        }
        else                    /* "quotient" */
            workWidth1 = workWidth2 =
                lineWidth - paren * 4 * FATDELIMMULT * charWidth('(');

        if ((t1->nodeType == N_NODE) && (t1->realWidth > workWidth1)) {
            t1->width = t1->realWidth;

            if (breakMathList(t1, workWidth1) && paren) {
                /* insert the \left( */
                lastNode = insertStringAtFront("\\left(", t1);

                while (lastNode->nextListNode)
                    lastNode = lastNode->nextListNode;

                /* insert the \right) */
                insertStringAtBack("\\right)", lastNode);
            }
        }

        if ((t2->nodeType == N_NODE) && (t2->realWidth > workWidth2)) {
            t2->width = t2->realWidth;

            if (breakMathList(t2, workWidth2) && paren) {
                /* insert the \left( */
                lastNode = insertStringAtFront("\\left(", t2);

                while (lastNode->nextListNode)
                    lastNode = lastNode->nextListNode;

                /* insert the \right) */
                insertStringAtBack("\\right)", lastNode);
            }
        }

        return TRUE;
    }
    return FALSE;
}


int
#ifndef _NO_PROTO
breakFunction(listNode * n, long lineWidth)
#else
breakFunction(n,lineWidth)
listNode * n; 
long lineWidth;
#endif
{
    listNode *rootNode, *tmpNode, *lastNode, *t1, *t2, *t3;
    int ok = FALSE;
    long workWidth, maxWidth = 0;

    if (n->nodeType != N_NODE)
        return FALSE;

    n = n->data.node;

    if (n->nodeType == N_NODE)
        return FALSE;

    if ((0 == strcmp(n->data.text, "\\sin")) ||
        (0 == strcmp(n->data.text, "\\cos")) ||
        (0 == strcmp(n->data.text, "\\tan")) ||
        (0 == strcmp(n->data.text, "\\log")) ||
        (0 == strcmp(n->data.text, "\\arctan")) ||
        (0 == strcmp(n->data.text, "\\erf"))) {
        computeNodeWidth(n);
        ok = TRUE;
    }

    displaySplitMsg("function", ok);

    if (ok) {
        t2 = newListNode(N_NODE);
        t2->data.node = n->nextListNode;
        t2->prevListNode = n;
        n->nextListNode = t2;
        ok = breakMathList(t2, lineWidth - n->realWidth);
    }

    return ok;
}

/*
 * breakList:
 *
 * Arguments:
 *
 * n : the starting node at which we are to try to break
 *
 * lineWidth : the maximum width of a line
 *
 *
 * Returns: TRUE or FALSE, depending on whether the expression was broken
 *
 *
 * Function: Tries to split an expression that is bracketed by \left[ and
 * \right] (or \left\{ and \right\} and contains at least one comma.
 */

int
#ifndef _NO_PROTO
breakList(listNode * n, long lineWidth)
#else
breakList(n,lineWidth)
listNode * n; 
long lineWidth;
#endif
{
    listNode *rootNode, *tmpNode, *lastNode, *t1, *t2, *t3;
    int ok, comma;
    long workWidth, maxWidth = 0;

    if (n->nodeType != N_NODE)
        return FALSE;

    rootNode = n;

    t1 = n = rootNode->data.node;
    comma = ok = FALSE;

    if ((t1->nodeType == N_TEXT) &&
        (0 == strcmp(t1->data.text, "\\left")) &&
        (t1->nextListNode) &&
        (t1->nextListNode->nodeType == N_TEXT) &&
        ((0 == strcmp(t1->nextListNode->data.text, "[")) ||
         (0 == strcmp(t1->nextListNode->data.text, "\\{")))) {

        t1 = t1->nextListNode->nextListNode;

        /*
         * Check for a special case: sometimes the whole body of the list is
         * a node. Flatten this, if possible.
         */

        if ((t1->nodeType == N_NODE) &&
            (t1->nextListNode->nodeType == N_TEXT) &&
            (0 == strcmp(t1->nextListNode->data.text, "\\right"))) {
            tmpNode = t1->prevListNode;
            t2 = t1->nextListNode;
            t3 = t1->data.node;
            tmpNode->nextListNode = t3;
            t3->prevListNode = tmpNode;
            while (t3->nextListNode)
                t3 = t3->nextListNode;
            t3->nextListNode = t2;
            t2->prevListNode = t3;
            free(t1);
            t1 = tmpNode->nextListNode;
        }

        while (t1->nextListNode && !ok) {
            if ((t1->nodeType == N_TEXT) &&
                (0 == strcmp(t1->data.text, ",")))
                comma = TRUE;
            else if ((t1->nodeType == N_TEXT) &&
                     (0 == strcmp(t1->data.text, "\\right")) &&
                     (t1->nextListNode->nodeType == N_TEXT) &&
                     ((0 == strcmp(t1->nextListNode->data.text, "]")) ||
                      (0 == strcmp(t1->nextListNode->data.text, "\\}"))) &&
                     (NULL == t1->nextListNode->nextListNode)) {
                ok = comma;
                tmpNode = t1->nextListNode;
            }
            t1 = t1->nextListNode;
        }
    }

    displaySplitMsg("list", ok);

    if (ok) {
        if (arrayDepth >= arrayMaxDepth) {
            arrayTooDeep();
            return FALSE;
        }

        /*
         * Create array environment
         */

        lastNode = insertStringAtFront("\\begin{array}{@{}l}\\displaystyle", rootNode);
        arrayDepth++;
        insertStringAtBack("\\end{array}", tmpNode);

        /*
         * Now break at best place short of width.        Start after the
         * environment begins and after the \left(
         */

        n = lastNode->nextListNode->nextListNode->nextListNode;

        /*
         * try to split the first expression if too big
         */

        tmpNode = n->nextListNode;
        if (breakMathList(n, lineWidth)) {
            workWidth = n->width;
            n = tmpNode;
        }
        else
            workWidth = n->width;
        maxWidth = workWidth;

        while (n->nextListNode) {
            if ((n->nodeType == N_TEXT) &&
                ((0 == strcmp(n->data.text, ",")) ||
                 (0 == strcmp(n->data.text, "\\:"))) &&
                (workWidth + n->nextListNode->width > lineWidth)) {
                maxWidth = max(maxWidth, workWidth);
                n = insertStringAfter("\\right. \\\\ \\\\ \\displaystyle \\left.", n);

                /*
                 * try to split the next expression if too big
                 */

                tmpNode = n->nextListNode;
                if (breakMathList(n, lineWidth)) {
                    workWidth = n->width;
                    n = tmpNode;
                }
                else
                    workWidth = n->width;
            }
            else {
                workWidth += n->nextListNode->width;
                n = n->nextListNode;
            }
        }

        rootNode->width = rootNode->realWidth =
            rootNode->data.node->width = rootNode->data.node->realWidth =
            maxWidth;
        arrayDepth--;

        return TRUE;
    }

    return FALSE;
}

/*
 * breakNumber:
 *
 * Arguments:
 *
 * rootNode : the starting node at which we are to try to break
 *
 * lineWidth : the maximum width of a line
 *
 *
 * Returns: TRUE or FALSE, depending on whether the expression was broken
 *
 *
 * Function: Tries to break an expression that contains only digits and possibly
 * a decimal point.
 */

int
#ifndef _NO_PROTO
breakNumber(listNode * rootNode, long lineWidth)
#else
breakNumber(rootNode,lineWidth)
listNode * rootNode; 
long lineWidth;
#endif
{
    int ok = TRUE;
    listNode *n, *arrNode, *rowNode, *colNode;
    long workWidth, maxWidth = 0;

    if (rootNode->nodeType != N_NODE)
        return FALSE;

    n = rootNode->data.node;
    while (n && ok) {
        if ((n->nodeType == N_TEXT) &&
            (n->data.text[1] == '\0') &&
            (isdigit(n->data.text[0]) || ('.' == n->data.text[0]))) {
            n = n->nextListNode;
        }
        else
            ok = FALSE;
    }

    displaySplitMsg("number", ok);

    if (ok) {
        if (arrayDepth >= arrayMaxDepth) {
            arrayTooDeep();
            return FALSE;
        }

        arrayDepth++;
        arrNode = newListNode(N_ARRAY);
        arrNode->data.array->entries = rowNode = newListNode(N_NODE);
        arrNode->data.array->cols = 1;
        arrNode->data.array->argsNode = newListNode(N_NODE);
        string2NodeList("{@{}l}", arrNode->data.array->argsNode);

        n = rootNode->data.node;
        computeWidth(n);
        maxWidth = workWidth = n->width;
        rowNode->data.node = colNode = newListNode(N_NODE);
        colNode->data.node = n;
        n = n->nextListNode;

        while (n) {
            computeWidth(n);

            if (workWidth + n->width > lineWidth) {
                maxWidth = max(maxWidth, workWidth);

                /*
                 * time to start a new row
                 */

                n->prevListNode->nextListNode = NULL;
                n->prevListNode = NULL;
                workWidth = n->width;
                rowNode->nextListNode = newListNode(N_NODE);
                rowNode = rowNode->nextListNode;
                rowNode->data.node = colNode = newListNode(N_NODE);
                colNode->data.node = n;
            }
            else
                workWidth += (n->nextListNode) ? n->nextListNode->width :0 ;

            n = n->nextListNode;
        }

        rootNode->data.node = arrNode;
        rootNode->width = rootNode->realWidth =
            arrNode->width = arrNode->realWidth = maxWidth;
        arrayDepth--;

        return TRUE;
    }

    return FALSE;
}

void
#ifndef _NO_PROTO
resetWidths(listNode * n)
#else
resetWidths(n)
listNode * n;
#endif
{
    if (n) {
        n->width = -1;
        n->realWidth = 0;
        if (n->nodeType == N_NODE)
            resetWidths(n->data.node);
        resetWidths(n->nextListNode);
    }
}

/*
 * breakParen:
 *
 * Arguments:
 *
 * n : the starting node at which we are to try to break
 *
 * lineWidth : the maximum width of a line
 *
 *
 * Returns: TRUE or FALSE, depending on whether the expression was broken
 *
 *
 * Function: Tries to split an expression that is bracketed by left( and \right)
 * (e.g., a factor).
 */

int
#ifndef _NO_PROTO
breakParen(listNode * n, long lineWidth)
#else
breakParen(n,lineWidth)
listNode * n;
long lineWidth;
#endif
{
    listNode *tmpNode, *workNode;
    int ok = FALSE;

    if (n->nodeType != N_NODE)
        goto say_msg;

    tmpNode = n->data.node;

    /*
     * check for \left
     */

    if ((tmpNode == NULL) ||
        (tmpNode->nodeType == N_NODE) ||
        (0 != strcmp(tmpNode->data.text, "\\left")))
        goto say_msg;

    /*
     * check for '('
     */

    tmpNode = tmpNode->nextListNode;

    if ((tmpNode == NULL) ||
        (tmpNode->nodeType == N_NODE) ||
        ('(' != tmpNode->data.text[0]))
        goto say_msg;

    /*
     * now move to the end
     */

    tmpNode = tmpNode->nextListNode;

    if (tmpNode != NULL) {
        while (tmpNode->nextListNode)
            tmpNode = tmpNode->nextListNode;
        tmpNode = tmpNode->prevListNode;
    }

    /*
     * check for \right
     */

    if ((tmpNode == NULL) ||
        (tmpNode->nodeType == N_NODE) ||
        (0 != strcmp(tmpNode->data.text, "\\right")))
        goto say_msg;

    /*
     * check for ')'
     */

    tmpNode = tmpNode->nextListNode;

    if ((tmpNode == NULL) ||
        (tmpNode->nodeType == N_NODE) ||
        (')' != tmpNode->data.text[0]))
        goto say_msg;

    ok = TRUE;

say_msg:
    displaySplitMsg("parenthesized expression", ok);

    if (ok) {

        /*
         * nest the whole inside if necessary, i.e., there is more than one
         * term between the ( and the \right
         */

        if (tmpNode->prevListNode->prevListNode !=
            n->data.node->nextListNode->nextListNode) {
            workNode = newListNode(N_NODE);
            workNode->data.node = n->data.node->nextListNode->nextListNode;
            n->data.node->nextListNode->nextListNode = workNode;
            tmpNode->prevListNode->prevListNode->nextListNode = NULL;
            tmpNode->prevListNode->prevListNode = workNode;
            workNode->prevListNode = n->data.node->nextListNode;
            workNode->nextListNode = tmpNode->prevListNode;
            resetWidths(workNode);
            computeWidth(workNode);
        }

        return breakMathList(n->data.node->nextListNode->nextListNode,
                             lineWidth - 4 * FATDELIMMULT * charWidth('('));
    }

    return FALSE;
}

/*
 * breakPMEB:
 *
 * Arguments:
 *
 * n : the starting node at which we are to try to break
 *
 * lineWidth : the maximum width of a line
 *
 *
 * Returns: TRUE or FALSE, depending on whether the expression was broken
 *
 *
 * Function: Tries to split an expression that contains only +, -, = or \  as
 * operators.  The split occurs after the operator.
 */

int
#ifndef _NO_PROTO
breakPMEB(listNode * n, long lineWidth)
#else
breakPMEB(n,lineWidth)
listNode * n; 
long lineWidth;
#endif
{
    char *s;
    listNode *rootNode, *tmpNode, *lastNode;
    int ok, op;
    long workWidth, maxWidth = 0;

    if (n->nodeType != N_NODE)
        return FALSE;

    if (n->width <= lineWidth + maxLineSlop)    /* allow a little slop here */
        return FALSE;

    rootNode = n;
    tmpNode = n = n->data.node;
    ok = TRUE;
    op = FALSE;

    while (n && ok) {
        if (n->nodeType == N_TEXT) {
            s = n->data.text;
            if (STRCHREQ(s, '+') || STRCHREQ(s, '-') || STRCHREQ(s, '=') ||
                (0 == strcmp(s, "\\ ")))
                op = TRUE;
            else if ((0 == strcmp(s, "\\left")) ||
                     (0 == strcmp(s, "\\right")) ||
                     (0 == strcmp(s, "\\over")) ||
                     STRCHREQ(s, ',')) {
                ok = FALSE;
                break;
            }
        }
        tmpNode = n;
        n = n->nextListNode;
    }
    ok = ok & op;

    displaySplitMsg("(+,-,=, )-expression", ok);


    if (ok) {
        if (arrayDepth >= arrayMaxDepth) {
            arrayTooDeep();
            return FALSE;
        }

        /*
         * Create array environment
         */

        lastNode = insertStringAtFront("\\begin{array}{@{}l}\\displaystyle", rootNode);
        arrayDepth++;
        insertStringAtBack("\\end{array}", tmpNode);

        /*
         * Now break at best place short of width. Start after the
         * environment begins.
         */

        n = lastNode->nextListNode;

        /*
         * try to split the first expression if too big
         */

        tmpNode = n->nextListNode;
        if (breakMathList(n, lineWidth)) {
            workWidth = n->width;
            n = tmpNode;
        }
        else
            workWidth = n->width;
        maxWidth = workWidth;

        while (n->nextListNode) {
    loop_top:
            if ((n->nodeType == N_TEXT) &&
              (STRCHREQ(n->data.text, '+') || STRCHREQ(n->data.text, '-') ||
               STRCHREQ(n->data.text, '=') ||
               (0 == strcmp(n->data.text, "\\ "))) &&
                (workWidth > 24) &&     /* avoid - or + on their own line */
                (workWidth + n->nextListNode->width > lineWidth)) {

                if ((workWidth < lineWidth / 3) &&
                  (breakMathList(n->nextListNode, lineWidth - workWidth))) {
                    n->nextListNode->width = -1;
                    n->nextListNode->realWidth = 0;
                    computeNodeWidth(n->nextListNode);
                    goto loop_top;
                }

                /*
                 * \  means multiplication. Use a \cdot to make this clearer
                 */

                if (0 == strcmp(n->data.text, "\\ "))
                    n = insertStringAfter("\\cdot \\\\ \\\\ \\displaystyle", n);
                else
                    n = insertStringAfter("\\\\ \\\\ \\displaystyle", n);
                maxWidth = max(maxWidth, workWidth);

                /*
                 * try to split the next expression if too big
                 */

                tmpNode = n->nextListNode;
                if (breakMathList(n, lineWidth)) {
                    workWidth = n->width;
                    n = tmpNode;
                }
                else
                    workWidth = n->width;
            }
            else {
                workWidth += n->nextListNode->width;
                n = n->nextListNode;
            }
        }

        rootNode->width = rootNode->realWidth =
            rootNode->data.node->width = rootNode->data.node->realWidth =
            maxWidth;
        arrayDepth--;

        return TRUE;
    }

    return FALSE;
}

/*
 * breakMathList:
 *
 * Arguments:
 *
 * n : the starting node at which we are to try to break
 *
 * lineWidth : the maximum width of a line
 *
 *
 * Returns: TRUE or FALSE, depending on whether the expression was broken
 *
 *
 * Function: Tries various methods to break the expression up into multiple
 * lines if the expression is too big.
 */

int
#ifndef _NO_PROTO
breakMathList(listNode * n, long lineWidth)
#else
breakMathList(n,lineWidth)
listNode * n; 
long lineWidth;
#endif
{
    int split = FALSE;

    /*
     * Don't do anything if already short enough.
     */

    if (n->width <= lineWidth)
        return FALSE;

    /*
     * Can't split strings, so just return.
     */

    if (n->nodeType == N_TEXT)
        return FALSE;

    blanks[indent] = ' ';
    indent += 2;
    blanks[indent] = '\0';

    /*
     * We know we have a node, so see what we can do.
     */

    /*
     * Case 1: a product: t1 \  t2
     */

    if (split = breakFracProd(n, lineWidth, "\\ ", "product", FALSE))
        goto done;

    /*
     * Case 2: a sequence of tokens separated by +, - or =
     */

    if (split = breakPMEB(n, lineWidth))
        goto done;

    /*
     * Case 3: a fraction of terms: t1 \over t2
     */

    if (split = breakFracProd(n, lineWidth, "\\over", "quotient", TRUE))
        goto done;

    /*
     * Case 4: a list of terms bracketed by \left[ and \right] with a comma
     */

    if (split = breakList(n, lineWidth))
        goto done;

    /*
     * Case 5: a list of digits, possibly with one "."
     */

    if (split = breakNumber(n, lineWidth))
        goto done;

    /*
     * Case 6: a parenthesized expression (e.g., a factor)
     */

    if (split = breakParen(n, lineWidth))
        goto done;

    /*
     * Case 7: a function application
     */

    if (split = breakFunction(n, lineWidth))
        goto done;

done:
    blanks[indent] = ' ';
    indent -= 2;
    blanks[indent] = '\0';

    return split;
}

void
#ifndef _NO_PROTO
buildMathList(listNode * oldNode)
#else
buildMathList(oldNode)
listNode * oldNode;
#endif
{
    listNode *curNode, *tmpNode;

    curNode = NULL;
    while (nextMathToken()) {
        if (mathToken[0] == '}')
            break;
        if (mathToken[0] == '{') {
            tmpNode = newListNode(N_NODE);
            buildMathList(tmpNode);
        }
        else {
            tmpNode = newListNode(N_TEXT);
            tmpNode->data.text = strdup(mathToken);
        }
        if (curNode == NULL) {
            oldNode->data.node = tmpNode;
        }
        else {
            tmpNode->prevListNode = curNode;
            curNode->nextListNode = tmpNode;
        }
        curNode = tmpNode;
    }

    /*
     * leave with one level of nesting, e.g., {{{x}}} --> {x}
     */

    tmpNode = oldNode->data.node;
    while ( tmpNode && (tmpNode->nodeType == N_NODE) &&
           (tmpNode->nextListNode == NULL) ) {
        oldNode->data.node = tmpNode->data.node;
        free(tmpNode);
        tmpNode = oldNode->data.node;
    }
}

void
#ifndef _NO_PROTO
computeNodeWidth(listNode * n)
#else
computeNodeWidth(n)
listNode * n;
#endif
{
    char *s;
    int i;
    listNode *tmp;

    if (n->width != -1)         /* only = -1 if unprocessed */
        return;

    n->realWidth = 0;

    if (n->nodeType == N_TEXT) {
        s = n->data.text;
        if (s[0] == '\\') {
            if (s[2] == '\0') {
                switch (s[1]) {
                  case ' ':
                    n->width = spaceWidths[0];
                    break;
                  case ',':
                    n->width = spaceWidths[1];
                    break;
                  case '!':
                    n->width = spaceWidths[2];
                    break;
                  case ':':
                    n->width = spaceWidths[3];
                    break;
                  case ';':
                    n->width = spaceWidths[4];
                    break;
                  default:
                    n->width = avgCharWidth;
                }
                n->realWidth = n->width;
            }
            else if ((0 == strcmp(s, "\\displaystyle")) ||
                     (0 == strcmp(s, "\\bf")) ||
                     (0 == strcmp(s, "\\sf")) ||
                     (0 == strcmp(s, "\\tt")) ||
                     (0 == strcmp(s, "\\rm")) ||
                     (0 == strcmp(s, "\\hbox")) ||
                     (0 == strcmp(s, "\\mbox")) ||
                     (0 == strcmp(s, "\\overline")) ||
                     (0 == strcmp(s, "\\textstyle")) ||
                     (0 == strcmp(s, "\\scriptstyle")) ||
                     (0 == strcmp(s, "\\scriptscriptstyle"))) {
                n->width = 0;
            }
            else if (0 == strcmp(s, "\\ldots"))
                n->width = 3 * charWidth('.');
            else if (0 == strcmp(s, "\\left")) {
                tmp = n->nextListNode;
                if (tmp->nodeType != N_TEXT)
                    error("unusual token following \\left", "");
                n->realWidth = n->width = (tmp->data.text[0] == '.')
                    ? 0
                    : charWidth(tmp->data.text[0]);
                tmp->width = 0;
                fatDelimiter = 1;
            }
            else if (0 == strcmp(s, "\\over")) {

                /*
                 * have already added in width of numerator
                 */
                computeNodeWidth(n->nextListNode);
                n->realWidth = extraOverWidth + max(n->prevListNode->width, n->nextListNode->width);
                n->width = n->realWidth - n->prevListNode->width;
                n->nextListNode->width = 0;
                fatDelimiter = FATDELIMMULT;
            }
            else if (0 == strcmp(s, "\\right")) {
                tmp = n->nextListNode;
                if (tmp->nodeType != N_TEXT)
                    error("unusual token following \\right", "");
                n->realWidth = n->width = fatDelimiter *
                    ((tmp->data.text[0] == '.') ? 0 : charWidth(tmp->data.text[0]));
                tmp->width = 0;
                fatDelimiter = 1;
            }
            else if (0 == strcmp(s, "\\root")) {
                computeNodeWidth(n->nextListNode);      /* which root */
                n->nextListNode->nextListNode->width = 0;       /* \of */
                tmp = n->nextListNode->nextListNode->nextListNode;
                computeNodeWidth(tmp);  /* root of    */
                n->realWidth = n->width = tmp->width + (avgCharWidth / 2) +
                    max(avgCharWidth, n->nextListNode->width);
                n->nextListNode->width = 0;
                tmp->width = 0;
            }
            else if (0 == strcmp(s, "\\sqrt")) {
                computeNodeWidth(n->nextListNode);
                n->realWidth = n->width =
                    avgCharWidth + (avgCharWidth / 2) + n->nextListNode->width;
                n->nextListNode->width = 0;
            }
            else if (0 == strcmp(s, "\\zag")) {
                computeNodeWidth(n->nextListNode);
                computeNodeWidth(n->nextListNode->nextListNode);
                n->realWidth = n->width = avgCharWidth + max(n->nextListNode->width,
                                      n->nextListNode->nextListNode->width);
                n->nextListNode->width = 0;
                n->nextListNode->nextListNode->width = 0;
                fatDelimiter = FATDELIMMULT;
            }
            else if ((0 == strcmp(s, "\\alpha")) ||
                     (0 == strcmp(s, "\\beta")) ||
                     (0 == strcmp(s, "\\pi"))) {
                n->realWidth = n->width = avgCharWidth;
            }
            else if (0 == strcmp(s, "\\sin"))
                /* should use table lookup here */
                n->realWidth = n->width = sinWidth;
            else if (0 == strcmp(s, "\\cos"))
                n->realWidth = n->width = cosWidth;
            else if (0 == strcmp(s, "\\tan"))
                n->realWidth = n->width = tanWidth;
            else if (0 == strcmp(s, "\\erf"))
                n->realWidth = n->width = erfWidth;

            /*
             * otherwise just compute length of token after \
             */
            else {
                n->width = 0;
                for (i = 1; i < strlen(s); i++)
                    n->width += charWidth(s[i]);
                n->realWidth = n->width;
            }
        }
        else if (s[1] == '\0')
            switch (s[0]) {
              case '^':
              case '_':
                tmp = n->nextListNode;
                computeNodeWidth(tmp);
                n->width = n->width = tmp->width;
                tmp->width = 0;
                break;
              default:
                n->realWidth = n->width = charWidth(s[0]);
            }
        else {
            n->width = 0;
            for (i = 0; i < strlen(s); i++)
                n->width += charWidth(s[i]);
            n->realWidth = n->width;
        }
    }
    else {
        n->realWidth = n->width = computeWidth(n->data.node);
    }
}

long
#ifndef _NO_PROTO
computeWidth(listNode * n)
#else
computeWidth(n)
listNode * n;
#endif
{
    long w = 0;

    while (n != NULL) {
        if (n->width == -1) {
            computeNodeWidth(n);
            w += n->width;
        }
        n = n->nextListNode;
    }
    return w;
}

/*
 * displaySplitMsg:
 *
 * Arguments:
 *
 * s : a string describing the kind of expression we are trying to split.
 *
 * ok : whether we can split it (TRUE or FALSE)
 *
 *
 * Returns: nothing
 *
 *
 * Function: Displays a message on stderr about whether a particular method of
 * line breaking will be successful.
 */

void
#ifndef _NO_PROTO
displaySplitMsg(char *s, int ok)
#else
displaySplitMsg(s,ok)
char *s; 
int ok;
#endif
{
/*  fprintf(stderr, "%sCan split %s: %s\n", blanks, s, ok ? "TRUE" : "FALSE"); */
}

void
arrayTooDeep()
{
    fprintf(stderr, "%s->Array nesting too deep!\n", blanks);
}

void
#ifndef _NO_PROTO
error(char *msg, char *insert)
#else
error(msg,insert)
char *msg;
char *insert;
#endif
{
    fputs("Error (texbreak): ", stderr);
    fputs(msg, stderr);
    fputs(insert, stderr);
    fputc('\n', stderr);

    fputs("% Error (texbreak): ", stdout);
    fputs(msg, stdout);
    fputs(insert, stdout);
    fputc('\n', stdout);
    exit(1);
}

void
#ifndef _NO_PROTO
freeMathList(listNode * n)
#else
freeMathList(n)
listNode * n;
#endif
{
    listNode *tmpNode;

    while (n != NULL) {
        if (n->nodeType == N_NODE)
            freeMathList(n->data.node);
        else if (n->nodeType == N_TEXT)
            free(n->data.text);
        else {
            freeMathList(n->data.array->argsNode);
            freeMathList(n->data.array->entries);
            free(n->data.array);
        }
        tmpNode = n->nextListNode;
        free(n);
        n = tmpNode;
    }
}

listNode *
#ifndef _NO_PROTO
insertStringAfter(char *s, listNode * n)
#else
insertStringAfter(s,n)
char *s; 
listNode * n;
#endif
{

    /*
     * returns node after inserted string
     */
    listNode *workNode, *lastNode;

    workNode = newListNode(N_NODE);
    lastNode = string2NodeList(s, workNode);

    n->nextListNode->prevListNode = lastNode;
    lastNode->nextListNode = n->nextListNode;
    n->nextListNode = workNode->data.node;
    workNode->data.node->prevListNode = n;

    free(workNode);
    return lastNode->nextListNode;
}

listNode *
#ifndef _NO_PROTO
insertStringAtBack(char *s, listNode * n)
#else
insertStringAtBack(s,n)
char *s; 
listNode * n;
#endif
{

    /*
     * Breaks s up into a list of tokens and appends them onto the end of n.
     * n must be non-NULL.
     */

    listNode *workNode, *lastNode;

    workNode = newListNode(N_NODE);
    lastNode = string2NodeList(s, workNode);
    n->nextListNode = workNode->data.node;
    workNode->data.node->prevListNode = n;
    free(workNode);

    return lastNode;
}

listNode *
#ifndef _NO_PROTO
insertStringAtFront(char *s, listNode * n)
#else
insertStringAtFront(s,n)
char *s; 
listNode * n;
#endif
{

    /*
     * Breaks s up into a list of tokens and appends them onto the front of
     * n. n must be a node.
     */

    listNode *workNode, *lastNode;

    workNode = newListNode(N_NODE);
    lastNode = string2NodeList(s, workNode);
    lastNode->nextListNode = n->data.node;
    n->data.node->prevListNode = lastNode;
    n->data.node = workNode->data.node;
    free(workNode);

    return lastNode;
}

int
#ifndef _NO_PROTO
newLineIfNecessary(int lastWasNewLine)
#else
newLineIfNecessary(lastWasNewLine)
int lastWasNewLine;
#endif
{
    if (!lastWasNewLine || (charsOut > 0)) {
        putcBuf('\n', bufout);
        outLineNum++;
        charsOut = 0;
    }
    return TRUE;
}

listNode *
#ifndef _NO_PROTO
newListNode(enum nodeTypes nt)
#else
newListNode(nt)
enum nodeTypes nt;
#endif
{
    listNode *n;

    n = (listNode *) malloc(sizeof(listNode));
    n->nextListNode = n->prevListNode = NULL;
    n->nodeType = nt;
    n->width = -1;
    n->realWidth = -1;
    if (nt == N_NODE)
        n->data.node = NULL;
    else if (nt == N_TEXT)
        n->data.text = NULL;
    else {
        n->data.array = (arrayNode *) malloc(sizeof(arrayNode));
        n->data.array->argsNode = NULL;
        n->data.array->entries = NULL;
        n->data.array->cols = 0;
    }
    return n;
}

int
nextMathToken()
{


    /*
     * Sets mathToken. Returns 1 if ok, 0 if no more tokens.
     */

    char curChar, errChar[2];

    errChar[1] = '\0';
    mathToken[0] = '\0';
    mathTokenLen = 0;

    /*
     * Kill any blanks.
     */

    while ((mathBufferPtr < mathBufferLen) && (mathBuffer[mathBufferPtr] == ' '))
        mathBufferPtr++;

    /*
     * If at end, exit saying so.
     */

    if (mathBufferPtr >= mathBufferLen)
        return 0;

    mathToken[mathTokenLen++] = curChar = mathBuffer[mathBufferPtr++];

    if (curChar == '\\') {
        curChar = mathBuffer[mathBufferPtr++];
        switch (curChar) {
          case '\0':            /* at end of buffer */
            mathToken[mathTokenLen++] = ' ';
            goto done;
          case '\\':
          case ' ':
          case '!':
          case '#':
          case '$':
          case '%':
          case '&':
          case ',':
          case ':':
          case ';':
          case '^':
          case '_':
          case '{':
          case '}':
            mathToken[mathTokenLen++] = curChar;
            goto done;
        }
        if (isalpha(curChar) || (curChar == '@')) {
            mathToken[mathTokenLen++] = curChar;
            while ((curChar = mathBuffer[mathBufferPtr]) &&
                   (isalpha(curChar) || (curChar == '@'))) {
                mathToken[mathTokenLen++] = curChar;
                mathBufferPtr++;
            }
        }
        else {
            errChar[0] = curChar;
            errChar[1] = '\0';
            error("strange character following \\: ", errChar);
        }
    }
    else if (isdigit(curChar))  /* digits are individual tokens */
        ;
    else if (isalpha(curChar)) {
        while ((curChar = mathBuffer[mathBufferPtr]) &&
               (isalpha(curChar))) {
            mathToken[mathTokenLen++] = curChar;
            mathBufferPtr++;
        }
    }
    else if (curChar == '"') {  /* handle strings */
        while ((curChar = mathBuffer[mathBufferPtr]) &&
               (curChar != '"')) {
            mathToken[mathTokenLen++] = curChar;
            mathBufferPtr++;
        }
        mathToken[mathTokenLen++] = '"';
        mathBufferPtr++;
    }

done:
    mathToken[mathTokenLen--] = '\0';

    /*
     * Some translations.
     */
    if (0 == strcmp(mathToken, "\\sp")) {
        mathToken[0] = '^';
        mathToken[1] = '\0';
        mathTokenLen = 1;
    }
    else if (0 == strcmp(mathToken, "\\sb")) {
        mathToken[0] = '_';
        mathToken[1] = '\0';
        mathTokenLen = 1;
    }

    return 1;
}

int
#ifndef _NO_PROTO
printChar(char c)
#else
printChar(c)
char c;
#endif
{
    if ((charsOut > MAXCHARSINLINE) &&
        isdigit(lastPrinted) && isdigit(c)) {
        putcBuf('\n', bufout);
        outLineNum++;
        charsOut = 0;
    }

    putcBuf(c, bufout);
    lastPrinted = c;
    charsOut++;

    /*
     * break lines after following characters
     */

    if ((charsOut > MAXCHARSINLINE) && strchr("+- ,_^", c)) {
        putcBuf('\n', bufout);
        outLineNum++;
        charsOut = 0;
        lastPrinted = '\0';
        return TRUE;
    }
    return FALSE;
}

int
#ifndef _NO_PROTO
printMathList(listNode * n, int lastWasNewLine)
#else
printMathList(n,lastWasNewLine)
listNode * n; 
int lastWasNewLine;
#endif
{
    listNode *tmpNode, *rowNode, *colNode;
    int begin, group, r, c;

    while (n != NULL) {
        if (n->nodeType == N_NODE) {
            lastWasNewLine = printChar('{');
            lastWasNewLine = printMathList(n->data.node, lastWasNewLine);
            lastWasNewLine = printChar('}');
        }
        else if (n->nodeType == N_ARRAY) {
            lastWasNewLine = newLineIfNecessary(lastWasNewLine);
            lastWasNewLine = printString("\\begin{array}");
            lastWasNewLine = printMathList(n->data.array->argsNode, lastWasNewLine);
            lastWasNewLine = printString("\\displaystyle");
            lastWasNewLine = newLineIfNecessary(lastWasNewLine);

            rowNode = n->data.array->entries;   /* node pointing to first row */
            while (rowNode) {
                colNode = rowNode->data.node;
                while (colNode) {
                    if (colNode->prevListNode) {        /* if not first column */
                        lastWasNewLine = printString(" & ");
                        lastWasNewLine = newLineIfNecessary(lastWasNewLine);
                    }
                    lastWasNewLine = printMathList(colNode->data.node, lastWasNewLine);
                    colNode = colNode->nextListNode;
                }
                if (rowNode->nextListNode)      /* if not last row */
                    lastWasNewLine = printString(" \\\\");

                lastWasNewLine = newLineIfNecessary(lastWasNewLine);
                rowNode = rowNode->nextListNode;
            }

            lastWasNewLine = printString("\\end{array}");
            lastWasNewLine = newLineIfNecessary(lastWasNewLine);
        }
        else if (n->nodeType == N_TEXT) {

            /*
             * handle keywords that might appear in math mode
             */

            if ((0 == strcmp(n->data.text, "by")) ||
                (0 == strcmp(n->data.text, "if")) ||
                (0 == strcmp(n->data.text, "then")) ||
                (0 == strcmp(n->data.text, "else"))) {
                lastWasNewLine = printString(" \\hbox{ ");
                lastWasNewLine = printString(n->data.text);
                lastWasNewLine = printString(" } ");
            }

            /*
             * handle things that should be in a special font
             */

            else if ((0 == strcmp(n->data.text, "true")) ||
                     (0 == strcmp(n->data.text, "false")) ||
                     (0 == strcmp(n->data.text, "table")) ||
                     (0 == strcmp(n->data.text, "Aleph"))
                ) {
                lastWasNewLine = printString(" \\mbox{\\rm ");
                lastWasNewLine = printString(n->data.text);
                lastWasNewLine = printString("} ");
            }

            /*
             * handle things that should always be on their own line
             */

            else if ((0 == strcmp(n->data.text, "\\\\")) ||
                     (0 == strcmp(n->data.text, "\\displaystyle"))) {
                lastWasNewLine = newLineIfNecessary(lastWasNewLine);
                lastWasNewLine = printString(n->data.text);
                lastWasNewLine = newLineIfNecessary(lastWasNewLine);
            }

            /*
             * handle phrases that should be on their own line.
             */

            else if ((0 == strcmp(n->data.text, "\\begin")) ||
                     (0 == strcmp(n->data.text, "\\end"))) {
                lastWasNewLine = newLineIfNecessary(lastWasNewLine);
                lastWasNewLine = printString(n->data.text);
                begin = (n->data.text[1] == 'b') ? TRUE : FALSE;

                n = n->nextListNode;    /* had better be a node */
                tmpNode = n->data.node;
                lastWasNewLine = printChar('{');
                lastWasNewLine = printMathList(tmpNode, lastWasNewLine);
                lastWasNewLine = printChar('}');

                if (begin) {

                    /*
                     * if array, print the argument.
                     */

                    if (0 == strcmp(tmpNode->data.text, "array")) {
                        n = n->nextListNode;    /* had better be a node */
                        lastWasNewLine = printChar('{');
                        lastWasNewLine = printMathList(n->data.node, lastWasNewLine);
                        lastWasNewLine = printChar('}');
                    }
                }
                lastWasNewLine = newLineIfNecessary(FALSE);
            }

            /*
             * handle everything else, paying attention as to whether we
             * should include a trailing blank.
             */

            else {
                group = 0;
                /* guess whether next word is part of a type */
                if ((strlen(n->data.text) > 2) &&
                    ('A' <= n->data.text[0]) &&
                    ('Z' >= n->data.text[0])) {
                    group = 1;
                    lastWasNewLine = printString("\\hbox{\\axiomType{");
                }
                lastWasNewLine = printString(n->data.text);
                if (group) {
                    lastWasNewLine = printString("}\\ }");
                    group = 0;
                }
                tmpNode = n->nextListNode;
                if ((n->data.text[0] == '_') ||
                    (n->data.text[0] == '^') ||
                    (n->data.text[0] == '.') ||
                    (n->data.text[0] == '(') ||
                    (0 == strcmp(n->data.text, "\\left")) ||
                    (0 == strcmp(n->data.text, "\\right")) ||
                    (0 == strcmp(n->data.text, "\\%")));
                else if (tmpNode && (tmpNode->nodeType == N_TEXT)) {
                    if (((isdigit(n->data.text[0])) &&
                         (isdigit(tmpNode->data.text[0]))) ||
                        ((isdigit(n->data.text[0])) &&
                         (',' == tmpNode->data.text[0])) ||
                        (tmpNode->data.text[0] == '\'') ||
                        (tmpNode->data.text[0] == '_') ||
                        (tmpNode->data.text[0] == '^') ||
                        (tmpNode->data.text[0] == '.') ||
                        (tmpNode->data.text[0] == ')'));
                    else
                        lastWasNewLine = printChar(' ');
                }
            }
        }
        n = n->nextListNode;
    }
    return lastWasNewLine;
}

int
#ifndef _NO_PROTO
printString(char *s)
#else
printString(s)
char *s;
#endif
{
    if (s[0]) {
        if (!s[1])
            return printChar(s[0]);
        else {
            putsBuf(s,bufout);
            charsOut += strlen(s);
        }
    }
    return FALSE;
}

void
#ifndef _NO_PROTO
putsBuf(char s[], char buf[])
#else
putsBuf(s,buf)
char s[], buf[]
#endif
{
  strcat(buf,s);
}

void
#ifndef _NO_PROTO
putcBuf(char c,char buf[])
#else
putcBuf(c,buf)
char c, buf[]
#endif
{
  char s[2];

  s[0] = c;
  s[1] = '\0';
  strcat(buf,s);
}

listNode *
#ifndef _NO_PROTO
string2NodeList(char *s, listNode * n)
#else
string2NodeList(s,n)
char *s;
listNode * n;
#endif
{

    /*
     * First argument is string to be broken up, second is a node. Return
     * value is last item in list.
     */

    mathBufferPtr = 0;
    strcpy(mathBuffer, s);
    mathBufferLen = strlen(s);
    buildMathList(n);
    n = n->data.node;
    while (n->nextListNode) {

        /*
         * set width to 0: other funs will have to set for real
         */
        n->width = 0;
        n = n->nextListNode;
    }
    n->width = 0;
    return n;
}

void
resetCharMults()
{

    /*
     * this is a ratio by which the standard \mit should be multiplied to get
     * other fonts, roughly
     */

    charMultNum = charMultDenom = 1;
}

void
ttCharMults()
{

    /*
     * this is a ratio by which the standard \mit should be multiplied to get
     * the \tt font, roughly
     */

    charMultNum = 11;
    charMultDenom = 10;
}

int
#ifndef _NO_PROTO
charWidth(char c)
#else
charWidth(c)
char c;
#endif
{
    return (charMultNum * charTable[c]) / charMultDenom;
}

void
#ifndef _NO_PROTO
#else
#endif
initCharTable()
{
    int i;

    avgCharWidth = 95;          /* where 1000 = 1 inch */

    spaceWidths[0] = 51;        /* \  */
    spaceWidths[1] = 25;        /* \, */
    spaceWidths[2] = -25;       /* \! */
    spaceWidths[3] = 37;        /* \: */
    spaceWidths[4] = 42;        /* \; */

    extraOverWidth = 33;        /* extra space in fraction bar */

    sinWidth = 186;             /* width of \sin */
    cosWidth = 203;
    tanWidth = 219;
    erfWidth = 185;

    for (i = 0; i < 256; i++)
        charTable[i] = avgCharWidth;

    charTable['!'] = 42;
    charTable['"'] = 76;
    charTable['%'] = 126;
    charTable['('] = 59;
    charTable[')'] = 59;
    charTable['+'] = 185;
    charTable[','] = 42;
    charTable['-'] = 185;
    charTable['.'] = 42;
    charTable['/'] = 76;
    charTable['0'] = 76;
    charTable['1'] = 76;
    charTable['2'] = 76;
    charTable['3'] = 76;
    charTable['4'] = 76;
    charTable['5'] = 76;
    charTable['6'] = 76;
    charTable['7'] = 76;
    charTable['8'] = 76;
    charTable['9'] = 76;
    charTable[':'] = 42;
    charTable[';'] = 42;
    charTable['<'] = 202;
    charTable['='] = 202;
    charTable['>'] = 202;
    charTable['A'] = 114;
    charTable['B'] = 123;
    charTable['C'] = 119;
    charTable['D'] = 130;
    charTable['E'] = 121;
    charTable['F'] = 119;
    charTable['G'] = 119;
    charTable['H'] = 138;
    charTable['I'] = 79;
    charTable['J'] = 99;
    charTable['K'] = 140;
    charTable['L'] = 103;
    charTable['M'] = 164;
    charTable['N'] = 138;
    charTable['O'] = 120;
    charTable['P'] = 118;
    charTable['Q'] = 120;
    charTable['R'] = 116;
    charTable['S'] = 102;
    charTable['T'] = 110;
    charTable['U'] = 120;
    charTable['V'] = 122;
    charTable['W'] = 164;
    charTable['X'] = 137;
    charTable['Y'] = 122;
    charTable['Z'] = 114;
    charTable['['] = 42;
    charTable[']'] = 42;
    charTable['a'] = 80;
    charTable['b'] = 65;
    charTable['c'] = 66;
    charTable['d'] = 79;
    charTable['e'] = 71;
    charTable['f'] = 91;
    charTable['g'] = 78;
    charTable['h'] = 87;
    charTable['i'] = 52;
    charTable['j'] = 71;
    charTable['k'] = 84;
    charTable['l'] = 48;
    charTable['m'] = 133;
    charTable['n'] = 91;
    charTable['o'] = 73;
    charTable['p'] = 76;
    charTable['q'] = 73;
    charTable['r'] = 73;
    charTable['s'] = 71;
    charTable['t'] = 55;
    charTable['u'] = 87;
    charTable['v'] = 79;
    charTable['w'] = 113;
    charTable['x'] = 87;
    charTable['y'] = 80;
    charTable['z'] = 77;
    charTable['{'] = 76;
    charTable['|'] = 42;
    charTable['}'] = 76;
}
