The R Project SVN R

Rev

Rev 1160 | Rev 1926 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

/*
 *  R : A Computer Language for Statistical Data Analysis
 *  Copyright (C) 1995, 1996, 1997 Robert Gentleman and Ross Ihaka
 *
 *  This source code module:
 *  Copyright (C) 1997 Paul Murrell and Ross Ihaka
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "Mathlib.h"
#include "Graphics.h"
#include "Defn.h"
#ifdef max
#undef max
#endif
#ifdef Unix

static DevDesc *mathDevice;

/* Return maximum of two doubles. */

static double max(double x, double y)
{
    if (x > y) return x;
    else return y;
}

/* Determine match between symbol name and string. */

static int symbolMatch(SEXP expr, char *aString)
{
    return !strcmp(CHAR(PRINTNAME(expr)), aString);
}

/* Code to determine the ascii code corresponding */
/* to an element of a mathematical expression. */

static int hatAscii()
{
    return 94;
}

static int tildeAscii()
{
    return 126;
}

static int accentAscii(SEXP expr)
{
    int result = 0;

    if (symbolMatch(expr, "hat"))
    result = hatAscii();
    else if (symbolMatch(expr, "tilde"))
    result = tildeAscii();
    return result;
}

static int operatorAscii(SEXP expr)
{
    int result = 0;
    if (symbolMatch(expr, "sum"))
    result = 229;
    else if (symbolMatch(expr, "integral"))
    result = 242;
    else if (symbolMatch(expr, "product"))
    result = 213;
    return result;
}

static int integralAscii(int section)
{
    if (section == 1)
    return 243;
    else if (section == 2)
    return 244;
    else
    return 245;
}

static int groupOpenAscii()
{
    return 40;
}

static int groupCloseAscii()
{
    return 41;
}

static int commaAscii()
{
    return 44;
}

static int spaceAscii()
{
    return 32;
}

static int radicalAscii()
{
    return 214;
}

static int radicalExAscii()
{
    return 96;
}

static struct {
    char *name;
    int code;
}
GreekTable[] = {

    "Alpha",    65,
    "Beta",     66,
    "Chi",      67,
    "Delta",    68,
    "Epsilon",  69,
    "Phi",      70,
    "Gamma",    71,
    "Eta",      72,
    "Iota",     73,
    "Phi1",     74,
    "Kappa",    75,
    "Lambda",   76,
    "Mu",       77,
    "Nu",       78,
    "Omicron",  79,
    "Pi",       80,
    "Theta",    81,
    "Rho",      82,
    "Sigma",    83,
    "Tau",      84,
    "Upsilon",  85,
    "sigma1",   86,
    "Omega",    87,
    "Xi",       88,
    "Psi",      89,
    "Zeta",     90,

    "alpha",    97,
    "beta",     98,
    "chi",      99,
    "delta",   100,
    "epsilon", 101,
    "phi",     102,
    "gamma",   103,
    "eta",     104,
    "iota",    105,
    "phi1",    106,
    "kappa",   107,
    "lambda",  108,
    "mu",      109,
    "nu",      110,
    "omicron", 111,
    "pi",      112,
    "theta",   113,
    "rho",     114,
    "sigma",   115,
    "tau",     116,
    "upsilon", 117,
    "omega1",  118,
    "omega",   119,
    "xi",      120,
    "psi",     121,
    "zeta",    122,

    NULL,      0,
};

static int greekAscii(SEXP expr)
{
    int i;
    for (i = 0; GreekTable[i].code; i++)
    if (symbolMatch(expr, GreekTable[i].name))
        return GreekTable[i].code;
    return 0;
}

static int relAscii()
{
    return 61;
}

/* Initialisation code for mathematical notation. */

static double ratioScale = 0.8;
static double scriptScale = 0.65;
static int ratioDepth = 0;
static int metricUnit = INCHES;

static SEXP plusSymbol;
static SEXP minusSymbol;
static SEXP timesSymbol;
static SEXP divideSymbol;
static SEXP equalSymbol;
static SEXP superSymbol;
static SEXP subSymbol;
static SEXP groupSymbol;

static void initFormulaSymbols()
{
    plusSymbol = install("+");
    minusSymbol = install("-");
    timesSymbol = install("*");
    divideSymbol = install("/");
    equalSymbol = install("==");
    superSymbol = install("^");
    subSymbol = install("[");
    groupSymbol = install("(");
}

/* Code to determine the nature of an expression. */

static int formulaExpression(SEXP expr)
{
    return (TYPEOF(expr) == LANGSXP);
}

static int symbolAtom(SEXP expr)
{
    return (TYPEOF(expr) == SYMSXP);
}

static int numberAtom(SEXP expr)
{
    return ((TYPEOF(expr) == REALSXP) ||
        (TYPEOF(expr) == INTSXP)  ||
        (TYPEOF(expr) == CPLXSXP));
}

static int stringAtom(SEXP expr)
{
    return (TYPEOF(expr) == STRSXP);
}

static int binAtom(SEXP expr)
{
    int result = symbolAtom(expr) &&
    ((expr == plusSymbol)  ||
     (expr == minusSymbol) ||
     (expr == timesSymbol) ||
     (expr == divideSymbol));
    return result;
}

static int relAtom(SEXP expr)
{
    return symbolAtom(expr) &&
    (expr == equalSymbol);
}

static int multiplicationOperator(SEXP expr)
{
    return binAtom(expr) &&
    (expr == timesSymbol);
}

static int superAtom(SEXP expr)
{
    return symbolAtom(expr) &&
    (expr == superSymbol);
}

static int subAtom(SEXP expr)
{
    return symbolAtom(expr) &&
    (expr == subSymbol);
}

static int hatAtom(SEXP expr)
{
    return symbolMatch(expr, "hat");
}

static int barAtom(SEXP expr)
{
    return symbolMatch(expr, "bar");
}

static int accentAtom(SEXP expr)
{
    return symbolAtom(expr) &&
    (hatAtom(expr) || barAtom(expr) || symbolMatch(expr, "tilde"));
}

static int fractionAtom(SEXP expr)
{
    return symbolAtom(expr) &&
    (symbolMatch(expr, "over") || symbolMatch(expr, "frac"));
}

static int groupAtom(SEXP expr)
{
    return symbolAtom(expr) && (expr == groupSymbol);
}

static int operatorAtom(SEXP expr)
{
    return symbolAtom(expr) &&
    (symbolMatch(expr, "sum") ||
     symbolMatch(expr, "integral") ||
     symbolMatch(expr, "product"));
}

static int integralOperator(SEXP expr)
{
    return symbolAtom(expr) && symbolMatch(expr, "integral");
}

static int radicalAtom(SEXP expr)
{
    return symbolAtom(expr) &&
    (symbolMatch(expr, "root") || symbolMatch(expr, "sqrt"));
}

static int absAtom(SEXP expr)
{
    return symbolAtom(expr) && symbolMatch(expr, "abs");
}

static int curlyAtom(SEXP expr)
{
    return symbolAtom(expr) && symbolMatch(expr, "{");
}

static int boldAtom(SEXP expr)
{
    return symbolAtom(expr) && symbolMatch(expr, "bold");
}

static int italicAtom(SEXP expr)
{
    return symbolAtom(expr) && symbolMatch(expr, "italic");
}

static int plainAtom(SEXP expr)
{
    return symbolAtom(expr) && symbolMatch(expr, "plain");
}

static int boldItalicAtom(SEXP expr)
{
    return symbolAtom(expr) && symbolMatch(expr, "bolditalic");
}

static int italicExpression(SEXP expr)
{
    return formulaExpression(expr) &&
    (italicAtom(CAR(expr)) || boldItalicAtom(CAR(expr)));
}

static int nonItalicExpression(SEXP expr)
{
    return formulaExpression(expr) &&
    (boldAtom(CAR(expr)) || plainAtom(CAR(expr)));
}

static int concatenateAtom(SEXP expr)
{
    return symbolAtom(expr) && symbolMatch(expr, "paste");
}

static int greekSymbol(SEXP expr)
{
    int i;
    if (symbolAtom(expr)) {
    for (i = 0; GreekTable[i].code; i++)
        if (symbolMatch(expr, GreekTable[i].name))
        return 1;
    }
    return 0;
}

/* Code to determine a font from the */
/* nature of the expression */

static int currentFont = 3;

static int getFont() { return currentFont; }

static void setFont(font) { currentFont = font; }

static void boldFont() { setFont(2); }
static void italicFont() { setFont(3); }
static void plainFont() { setFont(1); }
static void boldItalicFont() { setFont(4); }

static int isItalic() { return (getFont() == 3 || getFont() == 4); }

static int atomFontFace(SEXP expr)
{
    int fontFace = 1;
    if (symbolAtom(expr)) {
    if (greekSymbol(expr) ||
        binAtom(expr) ||
        relAtom(expr) ||
        groupAtom(expr) ||
        operatorAtom(expr) ||
        radicalAtom(expr))
        fontFace = 5;
    else
#ifdef OLD
        fontFace = 3;
#else
    fontFace = getFont();
#endif
    }
    return fontFace;
}

/* a forward declaration */
static double fontHeight();

/* some forward declarations */
typedef struct {
    double height;
    double depth;
    double width;
} BBOX;

static double bboxHeight(BBOX bbox)
{
    return bbox.height;
}
static double bboxDepth(BBOX bbox)
{
    return bbox.depth;
}
static double bboxWidth(BBOX bbox)
{
    return bbox.width;
}
static BBOX asciiBBox(int ascii);
static BBOX elementBBox(SEXP expr);
static void drawElement(SEXP expr);


/* code to determine superscript offsets, etc ... */

/* these "twiddle factors" are given as  */
/* proportions of the current font height */
/* NOTE that metric information is obtained */
/* in INCHES so that the information will */
/* be useful for rotated math.text */


static float SuperDrop = 0.3;
static float Superscript = 0.3;
static float SubDrop = 0.1;
static float Subscript = -0.3;
static float LineWidth = 0.05;

static float CustomAccentGap = 0.2;
static float CustomHatHeight = 0.3;
static float CustomRadicalWidth = 0.6;
static float CustomRadicalSpace = 0.1;
static float CustomRadicalGap = 0.2;
static float AbsSpace = 0.2;
static float OperatorSpace[] = { 0.1, 0.15, 0.2, 0.6, 0.1 };

static double fontHeight()
{
    double height, depth, width;
    GMetricInfo(0, &height, &depth, &width, metricUnit, mathDevice);
    return height + depth;
}

static double xHeight()
{
    int x = 'x';
    double xheight, depth, width;
    GMetricInfo(x, &xheight, &depth, &width, metricUnit, mathDevice);
    return xheight;
}

static double axisHeight()
{
    int plus = '+';
    double plusHeight, depth, width;
    GMetricInfo(plus, &plusHeight, &depth, &width, metricUnit, mathDevice);
    return 0.5 * plusHeight;
}

static double superscriptDrop()
{
    return SuperDrop * fontHeight();
}
static double subscriptDrop()
{
    return SubDrop * fontHeight();
}
static double superscript()
{
    return Superscript * fontHeight();
}
static double subscript()
{
    return Subscript * fontHeight();
}
static double lineWidth()
{
    return LineWidth * fontHeight();
}
static double numeratorShift()
{
    return axisHeight() + 3 * lineWidth();
}
static double denominatorShift()
{
    return axisHeight() - 3 * lineWidth() - xHeight();
}
static double radicalDrop()
{
    return 1.25 * lineWidth();
}
static double customAccentGap()
{
    return CustomAccentGap * fontHeight();
}
static double customHatHeight()
{
    return CustomHatHeight * fontHeight();
}
static double customRadicalWidth()
{
    return CustomRadicalWidth * fontHeight();
}
static double customRadicalSpace()
{
    return CustomRadicalSpace * fontHeight();
}
static double customRadicalGap()
{
    return CustomRadicalGap * fontHeight();
}
static double absSpace()
{
    return AbsSpace * fontHeight();
}
static double operatorSpace(int i)
{
    return OperatorSpace[i] * fontHeight();
}


/* Should only be called when font=5 (symbol) */

static double radicalExWidth()
{
    int radicalEx = 96;
    double height, depth, REWidth;
    GMetricInfo(radicalEx, &height, &depth, &REWidth, metricUnit, mathDevice);
    return REWidth;
}

static double superscriptShift(SEXP body, SEXP sup)
{
    BBOX bodyBBox = elementBBox(body);
    BBOX superscriptBBox;
    float cexSaved = mathDevice->gp.cex;
    double temp1 = bboxHeight(bodyBBox) - superscriptDrop();
    double temp2 = superscript();
    double temp3;

    mathDevice->gp.cex = mathDevice->gp.cex * scriptScale;
    superscriptBBox = elementBBox(sup);
    mathDevice->gp.cex = cexSaved;
    temp3 = bboxDepth(superscriptBBox) + 0.25 * xHeight();

    return max(temp1, max(temp2, temp3));
}

static double subscriptShift(SEXP body, SEXP sub, int subOnly)
{
    BBOX bodyBBox = elementBBox(body);
    BBOX subscriptBBox;
    float cexSaved = mathDevice->gp.cex;
    double temp1 = bboxDepth(bodyBBox) + subscriptDrop();
    double temp2 = subscript();
    double temp3;

    mathDevice->gp.cex = mathDevice->gp.cex * scriptScale;
    subscriptBBox = elementBBox(sub);
    mathDevice->gp.cex = cexSaved;
    temp3 = bboxHeight(subscriptBBox) - (4 * xHeight() / 5);

    if (subOnly)
    return max(temp1, max(temp2, temp3));
    else
    return max(temp1, temp2);
}

static void supsubShift(SEXP body, SEXP sup, SEXP sub,
            double *supShift, double *subShift)
{
    BBOX superscriptBBox, subscriptBBox;
    float cexSaved = mathDevice->gp.cex;
    double temp1, temp2;

    mathDevice->gp.cex = mathDevice->gp.cex * scriptScale;
    superscriptBBox = elementBBox(sup);
    subscriptBBox = elementBBox(sub);
    mathDevice->gp.cex = cexSaved;
    *supShift = superscriptShift(body, sup);
    *subShift = subscriptShift(body, sub, 0);

    temp1 = (*supShift - bboxDepth(superscriptBBox)) -
    (bboxHeight(subscriptBBox) - *subShift);
    if (temp1 < (4 * lineWidth()))
    *subShift = bboxHeight(subscriptBBox) -
        *supShift +
        bboxDepth(superscriptBBox) +
        (4 * lineWidth());

    temp2 = (4 * xHeight() / 5) -
    (*supShift - bboxDepth(superscriptBBox));
    if (temp2 > 0) {
    *supShift = *supShift + temp2;
    *subShift = *subShift - temp2;
    }
}

static double accentVShift(SEXP body)
{
    double temp = xHeight();
    double bodyHeight = bboxHeight(elementBBox(body));

    if (bodyHeight > temp)
    return bodyHeight - temp;
    else
    return 0;
}

static double accentHShift(SEXP body, SEXP accent)
{
    return (bboxWidth(elementBBox(body)) -
        bboxWidth(elementBBox(accent))) / 2;
}

static double numeratorVShift(SEXP num)
{
    BBOX numBBox;
    float cexSaved = mathDevice->gp.cex;
    double theShift, theClearance, minClearance;

    mathDevice->gp.cex = mathDevice->gp.cex * scriptScale;
    numBBox = elementBBox(num);
    mathDevice->gp.cex = cexSaved;

    theShift = numeratorShift();
    theClearance = numeratorShift() - bboxDepth(numBBox) -
    (axisHeight() + 0.5 * lineWidth());
    minClearance = 3 * lineWidth();

    if (theClearance < minClearance)
    theShift += minClearance - theClearance;

    return theShift;
}

/* RATIO */
static double denominatorVShift(SEXP denom)
{
    BBOX denomBBox;
    float cexSaved = mathDevice->gp.cex;
    double theShift, theClearance, minClearance;

#ifdef OLD
    mathDevice->gp.cex = mathDevice->gp.cex * scriptScale;
#else
    mathDevice->gp.cex = mathDevice->gp.cex * ratioScale;
#endif
    denomBBox = elementBBox(denom);
#ifdef OLD
    mathDevice->gp.cex = cexSaved;
#else
    mathDevice->gp.cex = cexSaved;
#endif

    theShift = denominatorShift();
    theClearance = axisHeight() - 0.5 * lineWidth() -
    (bboxHeight(denomBBox) - denominatorShift());
    minClearance = 3 * lineWidth();

    if (theClearance < minClearance)
    theShift += minClearance - theClearance;

    return theShift;
}

/* RATIO */
static double fractionWidth(SEXP num, SEXP denom)
{
    BBOX numBBox;
    BBOX denomBBox;
    float cexSaved = mathDevice->gp.cex;
    double temp1, temp2;

#ifdef OLD
    mathDevice->gp.cex = mathDevice->gp.cex * scriptScale;
#else
    mathDevice->gp.cex = mathDevice->gp.cex * ratioScale;
#endif
    numBBox = elementBBox(num);
    denomBBox = elementBBox(denom);
#ifdef OLD
    mathDevice->gp.cex = cexSaved;
#else
    mathDevice->gp.cex = cexSaved;
#endif
    temp1 = bboxWidth(numBBox);
    temp2 = bboxWidth(denomBBox);

    return max(temp1, temp2);
}

/* RATIO */
static void numdenomHShift(SEXP num, SEXP denom,
               double *numShift, double *denomShift)
{
    BBOX numBBox;
    BBOX denomBBox;
    float cexSaved = mathDevice->gp.cex;
    double temp1, temp2;

#ifdef OLD
    mathDevice->gp.cex = mathDevice->gp.cex * scriptScale;
#else
    mathDevice->gp.cex = mathDevice->gp.cex * ratioScale;
#endif
    numBBox = elementBBox(num);
    denomBBox = elementBBox(denom);
#ifdef OLD
    mathDevice->gp.cex = cexSaved;
#else
    mathDevice->gp.cex = cexSaved;
#endif
    temp1 = bboxWidth(numBBox);
    temp2 = bboxWidth(denomBBox);

    if (temp1 > temp2) {
    *numShift = 0;
    *denomShift = (temp1 - temp2) / 2;
    }
    else {
    *numShift = (temp2 - temp1) / 2;
    *denomShift = 0;
    }
}

static int normalRadical(SEXP body)
{
    BBOX bodyBBox = elementBBox(body);
    BBOX radicalBBox = asciiBBox(radicalAscii());

    return ((bboxHeight(bodyBBox) + radicalDrop())
        <= bboxHeight(radicalBBox)) &&
    (bboxDepth(bodyBBox) <= bboxDepth(radicalBBox));
}

static double radicalVShift(SEXP body)
{
    BBOX bodyBBox = elementBBox(body);
    return bboxHeight(bodyBBox) + radicalDrop();
}


static BBOX theOperatorBBox(SEXP operator);

static BBOX operatorLimitBBox(SEXP operator);

static double operatorLowerShift(SEXP operator, SEXP lower)
{
    BBOX opBBox = theOperatorBBox(operator);
    BBOX lowerBBox;
    double lowerHeight;
    double spacing2 = operatorSpace(1);
    double spacing4 = operatorSpace(3);

    lowerBBox = operatorLimitBBox(lower);
    lowerHeight = bboxHeight(lowerBBox);
    if (spacing2 > (spacing4 - lowerHeight))
    return bboxDepth(opBBox) + spacing2 + lowerHeight;
    else
    return bboxDepth(opBBox) + spacing4;
}

static double operatorUpperShift(SEXP operator, SEXP upper)
{
    BBOX opBBox = theOperatorBBox(operator);
    BBOX upperBBox;
    double upperDepth;
    double spacing1 = operatorSpace(0);
    double spacing3 = operatorSpace(2);

    upperBBox = operatorLimitBBox(upper);
    upperDepth = bboxDepth(upperBBox);
    if (spacing1 > (spacing3 - upperDepth))
    return bboxHeight(opBBox) + spacing1 + upperDepth;
    else
    return bboxHeight(opBBox) + spacing3;
}

static double operatorLowerHShift(SEXP operator, SEXP lower)
{
    BBOX opBBox = theOperatorBBox(operator);
    BBOX lowerBBox;
    double maxWidth = bboxWidth(opBBox);

    lowerBBox = operatorLimitBBox(lower);
    if (bboxWidth(lowerBBox) < maxWidth)
    return (maxWidth - bboxWidth(lowerBBox))/2;
    else
    return 0;
}

static double operatorHShift(SEXP operator, SEXP lower)
{
    BBOX opBBox = theOperatorBBox(operator);
    BBOX lowerBBox;
    double maxWidth = bboxWidth(opBBox);

    lowerBBox = operatorLimitBBox(lower);
    if (bboxWidth(lowerBBox) > maxWidth)
    return (bboxWidth(lowerBBox) - maxWidth)/2;
    else
    return 0;
}

static double operatorLowerHShiftAll(SEXP operator, SEXP lower, SEXP upper)
{
    BBOX opBBox = theOperatorBBox(operator);
    BBOX lowerBBox, upperBBox;
    double maxWidth = bboxWidth(opBBox);

    lowerBBox = operatorLimitBBox(lower);
    upperBBox = operatorLimitBBox(upper);
    maxWidth = max(maxWidth, max(bboxWidth(lowerBBox), bboxWidth(upperBBox)));

    if (bboxWidth(lowerBBox) < maxWidth)
    return (maxWidth - bboxWidth(lowerBBox))/2;
    else
    return 0;
}

static double operatorUpperHShiftAll(SEXP operator, SEXP lower, SEXP upper)
{
    BBOX opBBox = theOperatorBBox(operator);
    BBOX lowerBBox, upperBBox;
    double maxWidth = bboxWidth(opBBox);

    lowerBBox = operatorLimitBBox(lower);
    upperBBox = operatorLimitBBox(upper);
    maxWidth = max(maxWidth, max(bboxWidth(lowerBBox), bboxWidth(upperBBox)));

    if (bboxWidth(upperBBox) < maxWidth)
    return (maxWidth - bboxWidth(upperBBox))/2;
    else
    return 0;
}

static double operatorHShiftAll(SEXP operator, SEXP lower, SEXP upper)
{
    BBOX opBBox = theOperatorBBox(operator);
    BBOX lowerBBox, upperBBox;
    double maxWidth = bboxWidth(opBBox);

    lowerBBox = operatorLimitBBox(lower);
    upperBBox = operatorLimitBBox(upper);
    maxWidth = max(maxWidth, max(bboxWidth(lowerBBox), bboxWidth(upperBBox)));

    if (bboxWidth(opBBox) < maxWidth)
    return (maxWidth - bboxWidth(opBBox))/2;
    else
    return 0;
}

/* Code to generate bounding boxes and draw formulae. */

/* Bounding box basics. */

static BBOX makeBBox(double height, double depth, double width)
{
    BBOX bbox;
    bbox.height = height;
    bbox.depth = depth;
    bbox.width = width;
    return bbox;
}

static BBOX nullBBox()
{
    return makeBBox(0.0, 0.0, 0.0);
}

static BBOX makeBBoxFromChar(int chr)
{
    double height, depth, width;
    GMetricInfo(chr, &height, &depth, &width, metricUnit, mathDevice);
    return makeBBox(height, depth, width);
}

static BBOX shiftBBox(BBOX bbox, double shiftV)
{
    return makeBBox(bboxHeight(bbox) + shiftV,
            bboxDepth(bbox) - shiftV,
            bboxWidth(bbox));
}

static BBOX combineBBoxes(BBOX bbox1, BBOX bbox2)
{
    return makeBBox(max(bboxHeight(bbox1), bboxHeight(bbox2)),
            max(bboxDepth(bbox1), bboxDepth(bbox2)),
            bboxWidth(bbox1) + bboxWidth(bbox2));
}

static BBOX combineAlignedBBoxes(BBOX bbox1, BBOX bbox2)
{
    return makeBBox(max(bboxHeight(bbox1), bboxHeight(bbox2)),
            max(bboxDepth(bbox1), bboxDepth(bbox2)),
            max(bboxWidth(bbox1), bboxWidth(bbox2)));
}

/* Drawing basics */

static double referenceX;
static double referenceY;
static double currentX;
static double currentY;
static double currentAngle;
static double cosAngle;
static double sinAngle;

/* Convert currentX and currentY from 0 angle */
/* to and currentAngle */

static double convertedX()
{
    double rotatedX = referenceX +
    (currentX - referenceX) * cosAngle -
    (currentY - referenceY) * sinAngle;
    return rotatedX;
}

static double convertedY()
{
    double rotatedY = referenceY +
    (currentY - referenceY) * cosAngle +
    (currentX - referenceX) * sinAngle;
    return rotatedY;
}

static void moveAcross(double xamount)
{
    currentX += xamount;
}

static void moveUp(double yamount)
{
    currentY += yamount;
}

static void moveTo(double x, double y)
{
    currentX = x;
    currentY = y;
}

/* Code for ascii atoms. */

/* NOTE that I assume that all symbols which have */
/* been converted to ascii are in the symbol font. */

static BBOX asciiBBox(int ascii)
{
    if ((ascii == hatAscii()) || (ascii == tildeAscii()))
    mathDevice->gp.font = 1;
    else
    mathDevice->gp.font = 5;
    return makeBBoxFromChar(ascii);
}

static void drawAscii(int ascii)
{
    char asciiStr[2];

    if ((ascii == hatAscii()) || (ascii == tildeAscii()))
    mathDevice->gp.font = 1;
    else
    mathDevice->gp.font = 5;
    asciiStr[0] = ascii;
    asciiStr[1] = '\0';
    GText(convertedX(), convertedY(), INCHES, asciiStr,
      0.0, 0.0, currentAngle, mathDevice);
    moveAcross(GStrWidth(asciiStr, metricUnit, mathDevice));
}

/* Code for character atoms. */

static BBOX charBBox(char *str, SEXP expr)
{
    BBOX resultBBox = nullBBox();
    int i;

    mathDevice->gp.font = atomFontFace(expr);
    for (i = 0; i < strlen(str); i++)
    resultBBox = combineBBoxes(resultBBox, makeBBoxFromChar(str[i]));

    return resultBBox;
}

static void drawChar(char *str, SEXP expr)
{
    mathDevice->gp.font = atomFontFace(expr);
    GText(convertedX(), convertedY(), INCHES, str,
      0.0, 0.0, currentAngle, mathDevice);
    moveAcross(GStrWidth(str, metricUnit, mathDevice));
}

/* code for symbol atoms */

static BBOX symbolBBox(SEXP expr)
{
    if (greekSymbol(expr))
    return asciiBBox(greekAscii(expr));
    else
    return charBBox(CHAR(PRINTNAME(expr)), expr);
}

static void drawSymbol(SEXP expr)
{
    if (greekSymbol(expr))
    drawAscii(greekAscii(expr));
    else
    drawChar(CHAR(PRINTNAME(expr)), expr);
}

/* code for numeric atoms */

static BBOX numberBBox(SEXP expr)
{
    return charBBox(CHAR(asChar(expr)), expr);
}

static void drawNumber(SEXP expr)
{
    drawChar(CHAR(asChar(expr)), expr);
}

/* code for string atoms */

static BBOX stringBBox(SEXP expr)
{
    return charBBox(CHAR(STRING(expr)[0]), expr);
}

static void drawString(SEXP expr)
{
    drawChar(CHAR(STRING(expr)[0]), expr);
}

/* code for atoms */

static BBOX atomBBox(SEXP expr)
{
    if (symbolAtom(expr))
    return symbolBBox(expr);
    else if (numberAtom(expr))
    return numberBBox(expr);
    else if (stringAtom(expr))
    return stringBBox(expr);
}

static void drawAtom(SEXP expr)
{
    if (symbolAtom(expr))
    drawSymbol(expr);
    else if (numberAtom(expr))
    drawNumber(expr);
    else if (stringAtom(expr))
    drawString(expr);
}

/* code for italic corrections */

static double half_pi = 1.57079632679489661922;

static double italicCorrection(SEXP expr)
{
    BBOX exprBBox = elementBBox(expr);
    return bboxHeight(exprBBox) * tan(half_pi / 6);
}

    /* correction within expression checks for current font italic */

static BBOX correctionWithinBBox(SEXP expr)
{
    if (isItalic() && !nonItalicExpression(expr))
    return makeBBox(0, 0, italicCorrection(expr));
    else
    return nullBBox();
}

static void drawCorrectionWithin(SEXP expr)
{
    if (isItalic() && !nonItalicExpression(expr))
    moveAcross(italicCorrection(expr));
}

    /* correction between expressions checks current font and font */
    /* of each expression */

static BBOX correctionBetweenBBox(SEXP expr1, SEXP expr2)
{
    if (((isItalic() && !nonItalicExpression(expr1)) ||
     italicExpression(expr1)) &&
    ((!isItalic() && !italicExpression(expr2)) ||
     nonItalicExpression(expr2)))
    return makeBBox(0, 0, italicCorrection(expr1));
    else
    return nullBBox();
}

static void drawCorrectionBetween(SEXP expr1, SEXP expr2)
{
    if (((isItalic() && !nonItalicExpression(expr1)) ||
     italicExpression(expr1)) &&
    ((!isItalic() && !italicExpression(expr2)) ||
     nonItalicExpression(expr2)))
    moveAcross(italicCorrection(expr1));
}

/* code for gaps */

static int cexGap = 1;

static void setGapCEX()
{
  cexGap = mathDevice->gp.cex;
}

static BBOX gapBBox(double gap)
{
    double cexSaved = mathDevice->gp.cex;
    BBOX theBBox;

    mathDevice->gp.cex = cexGap;
    theBBox = makeBBox(0, 0, gap * fontHeight());
    mathDevice->gp.cex = cexSaved;

    return theBBox;
}

static BBOX smallgapBBox(double gap)
{
    double cexSaved = mathDevice->gp.cex;
    BBOX theBBox;

    mathDevice->gp.cex = cexGap;
    theBBox = makeBBox(0, 0, 0.5 * gap * fontHeight());
    mathDevice->gp.cex = cexSaved;

    return theBBox;
}

static void drawGap(double gap)
{
    double cexSaved = mathDevice->gp.cex;

    mathDevice->gp.cex = cexGap;
    moveAcross(gap * fontHeight());
    mathDevice->gp.cex = cexSaved;
}

static void drawSmallGap(double gap)
{
    double cexSaved = mathDevice->gp.cex;

    mathDevice->gp.cex = cexGap;
    moveAcross(0.5 * gap * fontHeight());
    mathDevice->gp.cex = cexSaved;
}

/* Code for binary operator (+, -, *, /) expressions */

/* NOTE that gaps are specified as proportions */
/* of the current font height */

static double binGapBefore(SEXP beforeOperand)
{
    return 0.2;
}

static double binGapBetween(SEXP operand1, SEXP operand2)
{
    return 0;
}

static double binGapAfter(SEXP afterOperand)
{
    return 0.2;
}

static BBOX binBBox(SEXP expr)
{
    SEXP operator, operand1, operand2;
    BBOX middleBBox;

    operator = CAR(expr);
    operand1 = CADR(expr);
    setGapCEX();
    if(length(expr) == 3) {
    operand2 = CADDR(expr);
    if (multiplicationOperator(operator))
        middleBBox =
        correctionBetweenBBox(operand1, operand2);
    else
        middleBBox =
        combineBBoxes(
                  gapBBox(binGapBefore(operand1)),
                  combineBBoxes(atomBBox(operator),
                        gapBBox(binGapAfter(operand2))));

    return combineBBoxes(elementBBox(operand1),
                 combineBBoxes(middleBBox,
                       elementBBox(operand2)));
    }
    else if(length(expr) == 2) {
    middleBBox = combineBBoxes(atomBBox(operator),
                   smallgapBBox(binGapAfter(operand1)));
    return combineBBoxes(middleBBox, elementBBox(operand1));
    }
    else error("invalid formula\n");
}

static void drawBin(SEXP expr)
{
    SEXP operator, operand1, operand2;

    operator = CAR(expr);
    operand1 = CADR(expr);
    setGapCEX();
    if(length(expr) == 3) {
    operand2 = CADDR(expr);
    drawElement(operand1);
    if (multiplicationOperator(operator))
        drawGap(binGapBetween(operand1, operand2));
    else {
        drawGap(binGapBefore(operand1));
        drawAtom(operator);
        drawGap(binGapAfter(operand2));
    }
    drawElement(operand2);
    }
    else {
    drawAtom(operator);
    drawSmallGap(binGapAfter(operand1));
    drawElement(operand1);
    }
}

/* Code for superscript and subscript expressions */

static BBOX supsubBBox(SEXP body, SEXP superscript, SEXP subscript);

static BBOX superscriptBBox(SEXP superscript)
{
    BBOX result;
    float cexSaved = mathDevice->gp.cex;

    mathDevice->gp.cex = mathDevice->gp.cex * scriptScale;
    result = elementBBox(superscript);
    mathDevice->gp.cex = cexSaved;

    return result;
}

static BBOX supBBox(SEXP expr)
{
    SEXP body = CADR(expr);
    SEXP superscript = CADDR(expr);
    int supsub = 0;

    if (formulaExpression(body))
    if (subAtom(CAR(body)))
        supsub = 1;

    if (supsub)
    return supsubBBox(CADR(body), superscript, (CADDR(body)));
    else
    return combineBBoxes(elementBBox(body),
                 combineBBoxes(correctionWithinBBox(body),
                       shiftBBox(superscriptBBox(superscript),
                             superscriptShift(body, superscript))));
}

static BBOX subscriptBBox(SEXP subscript)
{
    BBOX result;
    float cexSaved = mathDevice->gp.cex;

    mathDevice->gp.cex = mathDevice->gp.cex * scriptScale;
    result = elementBBox(subscript);
    mathDevice->gp.cex = cexSaved;

    return result;
}

static BBOX subBBox(SEXP expr)
{
    SEXP body = CADR(expr);
    SEXP subscript = CADDR(expr);

    return combineBBoxes(elementBBox(body),
             shiftBBox(subscriptBBox(subscript),
                   subscriptShift(body, subscript, 1)));
}

static BBOX supsubBBox(SEXP body, SEXP superscript, SEXP subscript)
{
    double supShift, subShift;
    supsubShift(body, superscript, subscript, &supShift, &subShift);

    return combineBBoxes(elementBBox(body),
           combineAlignedBBoxes(
               combineBBoxes(correctionWithinBBox(body),
                shiftBBox(superscriptBBox(superscript), supShift)),
           shiftBBox(subscriptBBox(subscript), subShift)));
}

static void drawScriptElement(SEXP expr)
{
    float cexSaved = mathDevice->gp.cex;

    mathDevice->gp.cex = mathDevice->gp.cex * scriptScale;
    drawElement(expr);
    mathDevice->gp.cex = cexSaved;
}

static void drawSupSub(SEXP body, SEXP superscript, SEXP subscript);

static void drawSuper(SEXP expr)
{
    SEXP body = CADR(expr);
    SEXP superscript = CADDR(expr);
    double supShift = superscriptShift(body, superscript);
    int supsub = 0;

    if (formulaExpression(body))
    if (subAtom(CAR(body)))
        supsub = 1;

    if (supsub)
    drawSupSub(CADR(body), superscript, CADDR(body));
    else {
    drawElement(body);
    drawCorrectionWithin(body);
    moveUp(supShift);
    drawScriptElement(superscript);
    moveUp(-supShift);
    }
}

static void drawSub(SEXP expr)
{
    SEXP body = CADR(expr);
    SEXP subscript = CADDR(expr);
    double subShift = subscriptShift(body, subscript, 1);

    drawElement(body);
    moveUp(-subShift);
    drawScriptElement(subscript);
    moveUp(subShift);
}

static void drawSupSub(SEXP body, SEXP superscript, SEXP subscript)
{
    double supShift, subShift;
    double savedX, savedY;
    BBOX supBBox = elementBBox(superscript);
    BBOX subBBox = elementBBox(subscript);

    supsubShift(body, superscript, subscript, &supShift, &subShift);
    drawElement(body);
    savedX = currentX;
    savedY = currentY;
    drawCorrectionWithin(body);
    moveUp(supShift);
    drawScriptElement(superscript);
    moveTo(savedX, savedY);
    moveUp(-subShift);
    drawScriptElement(subscript);
    moveTo(savedX, savedY);
    moveAcross(max(bboxWidth(supBBox), bboxWidth(subBBox)));
}

/* code for accented expressions (hat, bar, ...) */

static BBOX hatBBox(SEXP body)
{
    BBOX bodyBBox = elementBBox(body);
    return combineAlignedBBoxes(bodyBBox,
                makeBBox(bboxHeight(bodyBBox) +
                     customAccentGap() +
                     customHatHeight(), 0, 0));
}

static BBOX barBBox(SEXP body)
{
    BBOX bodyBBox = elementBBox(body);
    return combineAlignedBBoxes(bodyBBox,
                makeBBox(bboxHeight(bodyBBox) +
                     customAccentGap(), 0, 0));
}

static BBOX accentBBox(SEXP expr)
{
    SEXP accent = CAR(expr);
    SEXP body = CADR(expr);

    if (hatAtom(accent))
    return hatBBox(body);
    else if (barAtom(accent))
    return barBBox(body);
    else
    return combineAlignedBBoxes(elementBBox(body),
            combineBBoxes(makeBBox(accentHShift(body, accent), 0, 0),
                  shiftBBox(asciiBBox(accentAscii(accent)),
                        accentVShift(body))));
}

static void drawHat(SEXP body)
{
    BBOX bodyBBox = elementBBox(body);
    double width = bboxWidth(bodyBBox);
    double savedX = currentX;
    double savedY = currentY;
    double x[3], y[3];

    moveUp(bboxHeight(bodyBBox) + customAccentGap());
    x[0] = convertedX(); y[0] = convertedY();
    moveUp(customHatHeight());
    moveAcross(width / 2);
    x[1] = convertedX(); y[1] = convertedY();
    moveUp(-customHatHeight());
    moveAcross(width / 2);
    x[2] = convertedX(); y[2] = convertedY();
    GPolyline(3, x, y, INCHES, mathDevice);
    moveTo(savedX, savedY);
    drawElement(body);
}

static void drawBar(SEXP body)
{
    BBOX bodyBBox = elementBBox(body);
    double savedX = currentX;
    double savedY = currentY;
    double x[2], y[2];

    moveUp(bboxHeight(bodyBBox) + customAccentGap());
    x[0] = convertedX(); y[0] = convertedY();
    moveAcross(bboxWidth(bodyBBox));
    x[1] = convertedX(); y[1] = convertedY();
    GPolyline(2, x, y, INCHES, mathDevice);
    moveTo(savedX, savedY);
    drawElement(body);
}

static void drawAccent(SEXP expr)
{
    SEXP accent = CAR(expr);
    SEXP body = CADR(expr);
    double savedX = currentX;
    double savedY = currentY;

    if (hatAtom(accent))
    drawHat(body);
    else if (barAtom(accent))
    drawBar(body);
    else {
    moveAcross(accentHShift(body, accent));
    moveUp(accentVShift(body));
    drawAscii(accentAscii(accent));
    moveTo(savedX, savedY);
    drawElement(body);
    }
}

/* Code for fraction expressions (over) */

static BBOX fractionBBox(SEXP expr)
{
    SEXP numerator = CADR(expr);
    SEXP denominator = CADDR(expr);
    BBOX numBBox, denomBBox;
    double numHShift, denomHShift;
    float cexSaved = mathDevice->gp.cex;

#ifdef OLD
    mathDevice->gp.cex = mathDevice->gp.cex * scriptScale;
#else
    mathDevice->gp.cex = mathDevice->gp.cex * ratioScale;
#endif
    numBBox = elementBBox(numerator);
    denomBBox = elementBBox(denominator);
#ifdef OLD
    mathDevice->gp.cex = cexSaved;
#else
    mathDevice->gp.cex = cexSaved;
#endif
    numdenomHShift(numerator, denominator, &numHShift, &denomHShift);

    return combineAlignedBBoxes(
          shiftBBox(combineBBoxes(makeBBox(numHShift, 0, 0), numBBox),
            numeratorVShift(numerator)),
          shiftBBox(combineBBoxes(makeBBox(denomHShift, 0, 0), denomBBox),
            -denominatorVShift(denominator)));
}

static void drawRatioElement(SEXP expr)
{
    float cexSaved = mathDevice->gp.cex;
    mathDevice->gp.cex = mathDevice->gp.cex * ratioScale;
    drawElement(expr);
    mathDevice->gp.cex = cexSaved;
}

static void drawFraction(SEXP expr)
{
    SEXP numerator = CADR(expr);
    SEXP denominator = CADDR(expr);
    double savedX = currentX;
    double savedY = currentY;
    double fWidth = fractionWidth(numerator, denominator);
    double numHShift, denomHShift;
    double x[2], y[2];

    numdenomHShift(numerator, denominator, &numHShift, &denomHShift);
    moveAcross(numHShift);
    moveUp(numeratorVShift(numerator));
#ifdef OLD
    drawScriptElement(numerator);
#else
    drawRatioElement(numerator);
#endif
    moveTo(savedX, savedY);
    moveUp(axisHeight());
    x[0] = convertedX(); y[0] = convertedY();
    moveAcross(fWidth);
    x[1] = convertedX(); y[1] = convertedY();
    GPolyline(2, x, y, INCHES, mathDevice);
    moveTo(savedX, savedY);
    moveAcross(denomHShift);
    moveUp(-denominatorVShift(denominator));
#ifdef OLD
    drawScriptElement(denominator);
#else
    drawRatioElement(denominator);
#endif
    moveTo(savedX + fWidth, savedY);
}

/* code for group expressions (expressions within parentheses) */

static BBOX groupBBox(SEXP expr)
{
    return combineBBoxes(
          asciiBBox(groupOpenAscii()),
          combineBBoxes(elementBBox(CADR(expr)),
                combineBBoxes(correctionWithinBBox(CADR(expr)),
                      asciiBBox(groupCloseAscii()))));
}

static void drawGroup(SEXP expr)
{
    drawAscii(groupOpenAscii());
    drawElement(CADR(expr));
    drawCorrectionWithin(CADR(expr));
    drawAscii(groupCloseAscii());
}

/* code for operator expressions (sum, product, integral) */

/* NOTE that gaps are specified as proportions of the current font height */

static double operatorGap(SEXP body)
{
    return 0.1;
}

static double integralTopShift() { return 0.5 * fontHeight(); }

static double integralBottomShift() { return -0.5 * fontHeight(); }

static BBOX theOperatorBBox(SEXP operator)
{
    if (integralOperator(operator))
    return combineAlignedBBoxes(
          shiftBBox(asciiBBox(integralAscii(1)), integralTopShift()),
          combineAlignedBBoxes(asciiBBox(integralAscii(2)),
                       shiftBBox(asciiBBox(integralAscii(3)),
                         integralBottomShift())));
    else
    return asciiBBox(operatorAscii(operator));
}

static int useRelGap = 1;

static BBOX operatorLimitBBox(SEXP limit)
{
    float cexSaved = mathDevice->gp.cex;
    BBOX limitBBox;

    mathDevice->gp.cex = mathDevice->gp.cex * scriptScale;
    useRelGap = 0;
    limitBBox = elementBBox(limit);
    useRelGap = 1;
    mathDevice->gp.cex = cexSaved;

    return limitBBox;
}

static BBOX operatorBBox(SEXP expr)
{
    SEXP operator = CAR(expr);
    SEXP body, lower, upper;
    BBOX opBBox = theOperatorBBox(operator);
    BBOX bodyBBox, lowerBBox, upperBBox;
    setGapCEX();

    if (length(expr) > 1) {
    body = CADR(expr);
    bodyBBox = combineBBoxes(
              opBBox,
              combineBBoxes(gapBBox(operatorGap(body)),
                    elementBBox(body)));

    if (length(expr) > 2) {
        lower = CADDR(expr);
        lowerBBox = operatorLimitBBox(lower);

        if (length(expr) > 3) {
        upper = CADDDR(expr);
        upperBBox = operatorLimitBBox(upper);

        return combineAlignedBBoxes(
             combineBBoxes(
               makeBBox(
                 operatorHShiftAll(operator,lower, upper),
                 0, 0),
               bodyBBox),
             combineAlignedBBoxes(
               shiftBBox(
                             combineBBoxes(
                   makeBBox(
                                 operatorUpperHShiftAll(
                   operator,lower, upper),
                 0, 0),
                   upperBBox),
                 operatorUpperShift(operator, upper)),
               shiftBBox(
                             combineBBoxes(
                   makeBBox(
                 operatorLowerHShiftAll(
                   operator, lower, upper),
                 0, 0),
                   lowerBBox),
                 operatorLowerShift(operator, lower))));
        }
        else
        return combineAlignedBBoxes(
             combineBBoxes(
               makeBBox(
                 operatorHShift(operator, lower), 0, 0),
               bodyBBox),
             shiftBBox(
               combineBBoxes(
                 makeBBox(operatorLowerHShift(operator, lower),
                      0, 0),
                 lowerBBox),
               operatorLowerShift(operator, lower)));
    }
    else
        return bodyBBox;
    }
    else
    error("Invalid Formula\n");
}

static void drawTheOperator(SEXP operator)
{
    if (integralOperator(operator)) {
    double savedX = currentX;
    double savedY = currentY;
    moveUp(integralTopShift());
    drawAscii(integralAscii(1));
    moveTo(savedX, savedY);
    moveUp(integralBottomShift());
    drawAscii(integralAscii(3));
    moveTo(savedX, savedY);
    drawAscii(integralAscii(2));
    }
    else
    drawAscii(operatorAscii(operator));
}

static void drawOperatorLimit(SEXP limit)
{
    useRelGap = 0;
    drawScriptElement(limit);
    useRelGap = 1;
}

static void drawOperator(SEXP expr)
{
    SEXP operator = CAR(expr);
    SEXP body = CADR(expr);
    SEXP lower, upper;
    double savedX = currentX;
    double savedY = currentY;

    setGapCEX();

    if (length(expr) > 2) {
    lower = CADDR(expr);

    if (length(expr) > 3) {
        upper = CADDDR(expr);
        moveUp(operatorUpperShift(operator, upper));
        moveAcross(operatorUpperHShiftAll(operator, lower, upper));
        drawOperatorLimit(upper);
        moveTo(savedX, savedY);
        moveUp(-operatorLowerShift(operator, lower));
        moveAcross(operatorLowerHShiftAll(operator, lower, upper));
        drawOperatorLimit(lower);
        moveTo(savedX, savedY);
        moveAcross(operatorHShiftAll(operator, lower, upper));
    }

    else{
        moveUp(-operatorLowerShift(operator, lower));
        moveAcross(operatorLowerHShift(operator, lower));
        drawOperatorLimit(lower);
        moveTo(savedX, savedY);
        moveAcross(operatorHShift(operator, lower));
    }
    }

    drawTheOperator(operator);
    drawGap(operatorGap(body));
    drawElement(body);
}

/* Code for radical expressions (root) */

static BBOX customRadicalBBox(SEXP body)
{
    BBOX bodyBBox = elementBBox(body);
    return combineBBoxes(makeBBox(bboxHeight(bodyBBox) + customRadicalGap(),
                  0, customRadicalWidth()),
             combineBBoxes(makeBBox(0, 0, customRadicalSpace()),
                       bodyBBox));
}

static BBOX radicalBBox(SEXP expr)
{
    SEXP body = CADR(expr);
    return customRadicalBBox(body);
}

static void drawCustomRadical(SEXP body)
{
    BBOX bodyBBox = elementBBox(body);
    double height = bboxHeight(bodyBBox);
    double depth = bboxDepth(bodyBBox);
    double width = bboxWidth(bodyBBox);
    double twiddleHeight = (height - depth) / 2;
    double savedX = currentX;
    double savedY = currentY;
    double x[5], y[5];

    moveUp(0.8 * twiddleHeight);
    x[0] = convertedX(); y[0] = convertedY();
    moveUp(0.2 * twiddleHeight);
    moveAcross(0.3 * customRadicalWidth());
    x[1] = convertedX(); y[1] = convertedY();
    moveUp(-(twiddleHeight + depth));
    moveAcross(0.3 * customRadicalWidth());
    x[2] = convertedX(); y[2] = convertedY();
    moveUp(depth + height + customRadicalGap());
    moveAcross(0.4 * customRadicalWidth());
    x[3] = convertedX(); y[3] = convertedY();
    moveAcross(customRadicalSpace() + width);
    x[4] = convertedX(); y[4] = convertedY();
    GPolyline(5, x, y, INCHES, mathDevice);
    moveTo(savedX, savedY);
    moveAcross(customRadicalWidth() + customRadicalSpace());
    drawElement(body);
}

static void drawRadical(SEXP expr)
{
    SEXP body = CADR(expr);
    drawCustomRadical(body);
}

/* Code for absolute expressions (abs). */

static BBOX absBBox(SEXP expr)
{
    SEXP body = CADR(expr);
    return combineBBoxes(makeBBox(0, 0, absSpace()),
             combineBBoxes(elementBBox(body),
                       makeBBox(0, 0, absSpace())));
}

static void drawAbs(SEXP expr)
{
    SEXP body = CADR(expr);
    BBOX bodyBBox = elementBBox(expr);
    double height = bboxHeight(bodyBBox);
    double depth = bboxDepth(bodyBBox);
    double x[2], y[2];

    moveUp(-depth);
    x[0] = convertedX(); y[0] = convertedY();
    moveUp(depth + height);
    x[1] = convertedX(); y[1] = convertedY();
    GPolyline(2, x, y, INCHES, mathDevice);
    moveUp(-height);
    moveAcross(absSpace());
    drawElement(body);
    moveAcross(absSpace());
    moveUp(-depth);
    x[0] = convertedX(); y[0] = convertedY();
    moveUp(depth + height);
    x[1] = convertedX(); y[1] = convertedY();
    GPolyline(2, x, y, INCHES, mathDevice);
    moveUp(-height);
}

/* Code for general expressions with no special meaning */
/* in mathematical notation syntax (e.g., f(x)) */

static BBOX expressionBBox(SEXP expr)
{
    int i;
    int numParams = length(expr) - 1;
    BBOX resultBBox = elementBBox(CAR(expr));
    SEXP lastTerm;

    lastTerm = CAR(expr);
    expr = CDR(expr);
    resultBBox = combineBBoxes(resultBBox, asciiBBox(groupOpenAscii()));
    for (i = 0; i < numParams; i++) {
    resultBBox = combineBBoxes(resultBBox, elementBBox(CAR(expr)));
    lastTerm = CAR(expr);
    expr = CDR(expr);
    if (i < numParams - 1)
        resultBBox = combineBBoxes(resultBBox,
                       combineBBoxes(asciiBBox(commaAscii()),
                             asciiBBox(spaceAscii())));
    }
    return combineBBoxes(resultBBox,
             combineBBoxes(correctionWithinBBox(lastTerm),
                       asciiBBox(groupCloseAscii())));
}

static void drawExpression(SEXP expr)
{
    int i;
    int numParams = length(expr) - 1;
    SEXP lastTerm;

    drawElement(CAR(expr));
    lastTerm = CAR(expr);
    expr = CDR(expr);
    drawAscii(groupOpenAscii());
    for (i = 0; i < numParams; i++) {
    drawElement(CAR(expr));
    lastTerm = CAR(expr);
    expr = CDR(expr);
    if (i < numParams - 1) {
        drawAscii(commaAscii());
        drawAscii(spaceAscii());
    }
    }
    drawCorrectionWithin(lastTerm);
    drawAscii(groupCloseAscii());
}

/* Code for curly expressions (i.e., { ... } ) */

static BBOX curlyBBox(SEXP expr)
{
    return expressionBBox(CADR(expr));
}

static void drawFormula(SEXP);

static void drawCurly(SEXP expr)
{
    drawFormula(CADR(expr));
}

/* code for relation expressions (i.e. ... == ...) */

static double relGap()
{
    if (useRelGap)
    return 0.3;
    else
    return 0.1;
}

static BBOX relBBox(SEXP expr)
{
    SEXP arg1 = CADR(expr);
    SEXP arg2 = CADDR(expr);

    return combineBBoxes(
          elementBBox(arg1),
          combineBBoxes(
             gapBBox(relGap()),
             combineBBoxes(
            asciiBBox(relAscii()),
            combineBBoxes(
               gapBBox(relGap()),
               elementBBox(arg2)))));
}

static void drawRel(SEXP expr)
{
    SEXP arg1 = CADR(expr);
    SEXP arg2 = CADDR(expr);

    drawElement(arg1);
    drawGap(relGap());
    drawAscii(relAscii());
    drawGap(relGap());
    drawElement(arg2);
}

/* code for bold expressions */

static BBOX boldBBox(SEXP expr)
{
    BBOX result;
    int savedFont = getFont();

    boldFont();
    result = elementBBox(CADR(expr));
    setFont(savedFont);

    return result;
}

static void drawBold(SEXP expr)
{
    int savedFont = getFont();

    boldFont();
    drawElement(CADR(expr));
    setFont(savedFont);
}

/* code for italic expressions */

static BBOX italicBBox(SEXP expr)
{
    BBOX result;
    SEXP body = CADR(expr);
    int savedFont = getFont();

    italicFont();
    result = elementBBox(body);
    setFont(savedFont);

    return result;
}

static void drawItalic(SEXP expr)
{
    SEXP body = CADR(expr);
    int savedFont = getFont();

    italicFont();
    drawElement(body);
    setFont(savedFont);
}

/* code for plain expressions */

static BBOX plainBBox(SEXP expr)
{
    BBOX result = nullBBox();
    int savedFont = getFont();

    plainFont();
    result = elementBBox(CADR(expr));
    setFont(savedFont);

    return result;
}

static void drawPlain(SEXP expr)
{
    int savedFont = getFont();

    plainFont();
    drawElement(CADR(expr));
    setFont(savedFont);
}

/* code for bolditalic expressions */

static BBOX boldItalicBBox(SEXP expr)
{
    BBOX result;
    int savedFont = getFont();

    boldItalicFont();
    result = elementBBox(CADR(expr));
    setFont(savedFont);

    return result;
}

static void drawBoldItalic(SEXP expr)
{
    int savedFont = getFont();

    boldItalicFont();
    drawElement(CADR(expr));
    setFont(savedFont);
}

/* code for concatenating expressions c(...) */

static BBOX concatenateBBox(SEXP expr)
{
    SEXP args = CDR(expr);
    SEXP lastArg;
    int i;
    int numArgs = length(args);
    BBOX result = nullBBox();

    if (numArgs > 0)
    result = elementBBox(CAR(args));
    lastArg = CAR(args);
    args = CDR(args);

    for (i=1; i<numArgs; i++) {
    result = combineBBoxes(result,
            combineBBoxes(
               correctionBetweenBBox(lastArg, CAR(args)),
               elementBBox(CAR(args))));
    lastArg = CAR(args);
    args = CDR(args);
    }

    return result;
}

static void drawConcatenate(SEXP expr)
{
    SEXP args = CDR(expr);
    SEXP lastArg;
    int i;
    int numArgs = length(args);

    for (i=0; i<numArgs; i++) {
    if (i > 0)
        drawCorrectionBetween(lastArg, CAR(args));
    drawElement(CAR(args));
    lastArg = CAR(args);
    args = CDR(args);
    }
}

/* Dispatching procedure which determines nature of expression. */

static BBOX formulaBBox(SEXP expr)
{
    SEXP head = CAR(expr);

    if (binAtom(head))
    return binBBox(expr);
    else if (superAtom(head))
    return supBBox(expr);
    else if (subAtom(head))
    return subBBox(expr);
    else if (accentAtom(head))
    return accentBBox(expr);
    else if (fractionAtom(head))
    return fractionBBox(expr);
    else if (groupAtom(head))
    return groupBBox(expr);
    else if (operatorAtom(head))
    return operatorBBox(expr);
    else if (radicalAtom(head))
    return radicalBBox(expr);
    else if (absAtom(head))
    return absBBox(expr);
    else if (curlyAtom(head))
    return curlyBBox(expr);
    else if (relAtom(head))
    return relBBox(expr);
    else if (boldAtom(head))
    return boldBBox(expr);
    else if (italicAtom(head))
    return italicBBox(expr);
    else if (plainAtom(head))
    return plainBBox(expr);
    else if (boldItalicAtom(head))
    return boldItalicBBox(expr);
    else if (concatenateAtom(head))
    return concatenateBBox(expr);
    else
    return expressionBBox(expr);
}

static void drawFormula(SEXP expr)
{
    SEXP head = CAR(expr);

    if (binAtom(head))
    drawBin(expr);
    else if (superAtom(head))
    drawSuper(expr);
    else if (subAtom(head))
    drawSub(expr);
    else if (accentAtom(head))
    drawAccent(expr);
    else if (fractionAtom(head))
    drawFraction(expr);
    else if (groupAtom(head))
    drawGroup(expr);
    else if (operatorAtom(head))
    drawOperator(expr);
    else if (radicalAtom(head))
    drawRadical(expr);
    else if (absAtom(head))
    drawAbs(expr);
    else if (curlyAtom(head))
    drawCurly(expr);
    else if (relAtom(head))
    drawRel(expr);
    else if (boldAtom(head))
    drawBold(expr);
    else if (italicAtom(head))
    drawItalic(expr);
    else if (plainAtom(head))
    drawPlain(expr);
    else if (boldItalicAtom(head))
    drawBoldItalic(expr);
    else if (concatenateAtom(head))
    drawConcatenate(expr);

    /* if expression is not a special mathematical notation */
    /* function then just reconstruct expression */

    else
    drawExpression(expr);
}

/* top-level:  dispatch on whether atom (symbol, string, number, ...) */
/* or formula (some sort of expression) */

static BBOX elementBBox(SEXP expr)
{
    if (formulaExpression(expr))
    return formulaBBox(expr);
    else
    return atomBBox(expr);
}

static void drawElement(SEXP expr)
{
    if (formulaExpression(expr))
    drawFormula(expr);
    else
    drawAtom(expr);
}

        /* calculate width of expression */
        /* BBOXes are in INCHES (see metricUnit) */
double GExpressionWidth(SEXP expr, int units, DevDesc *dd)
{
    BBOX exprBBox = elementBBox(expr);
    double w  = exprBBox.width;
    if (units == INCHES)
    return w;
    else
    return GConvertXUnits(w, INCHES, units, dd);
}

#define ABS(a)  ((a)>=0 ? (a) : -(a))

double GExpressionHeight(SEXP expr, int units, DevDesc *dd)
{
    BBOX exprBBox = elementBBox(expr);
    double h = exprBBox.height + exprBBox.depth;
    if (units == INCHES)
    return h;
    else
    return GConvertYUnits(h, INCHES, units, dd);
}

/* Functions forming the API */

void GMathText(double x, double y, int coords, SEXP expr,
           double xc, double yc, double rot, DevDesc *dd)
{
    BBOX expressionBBox;

    mathDevice = dd;

    initFormulaSymbols();
    expressionBBox = elementBBox(expr);

    referenceX = x;
    referenceY = y;
    GConvert(&referenceX, &referenceY, coords, INCHES, dd);

    currentX = referenceX - xc * bboxWidth(expressionBBox);
    currentY = referenceY - yc * bboxHeight(expressionBBox);
    currentAngle = rot;
    cosAngle = cos(rot / 90 * half_pi);
    sinAngle = sin(rot / 90 * half_pi);
    drawElement(expr);
}


void GMMathText(SEXP str, int side, double line, int outer, double at, int las,
        DevDesc *dd)
{
    int coords;
    double a, xadj, yadj;

    mathDevice = dd;

    if (outer) {
    switch (side) {
    case 1:
        line = line + 1;
        coords = MAR1;
        a = 0.0;
        xadj = mathDevice->gp.adj;
        yadj = 0.0;
        break;
    case 2:
        coords = MAR2;
        a = 90.0;
        xadj = mathDevice->gp.adj;
        yadj = 0.0;
        break;
    case 3:
        coords = MAR3;
        a = 0.0;
        xadj = mathDevice->gp.adj;
        yadj = 0.0;
        break;
    case 4:
        line = line + 1;
        coords = MAR4;
        a = 90.0;
        xadj = mathDevice->gp.adj;
        yadj = 0.0;
        break;
    }
    GMathText(at, line, coords, str, xadj, yadj, a, dd);
    }
    else {
    switch (side) {
    case 1:
        if (las == 2) {
        at = at - GConvertXUnits(dd->gp.yLineBias,
                     LINES, USER, dd);
        line = line + dd->gp.yLineBias;
        a = 90.0;
        xadj = 1.0;
        yadj = 0.5;
        }
        else {
        line = line + 1 - dd->gp.yLineBias;
        a = 0.0;
        xadj = mathDevice->gp.adj;
        yadj = 0.0;
        }
        coords = MAR1;
        break;
    case 2:
        if (las == 1 || las == 2) {
        at = at + GConvertYUnits(dd->gp.yLineBias,
                     LINES, USER, dd);
        line = line + dd->gp.yLineBias;
        a = 0.0;
        xadj = 1.0;
        yadj = 0.5;
        }
        else {
        line = line + dd->gp.yLineBias;
        a = 90.0;
        xadj = mathDevice->gp.adj;
        yadj = 0.0;
        }
        coords = MAR2;
        break;
    case 3:
        if (las == 2) {
        at = at - GConvertXUnits(dd->gp.yLineBias,
                     LINES, USER, dd);
        line = line + dd->gp.yLineBias;
        a = 90.0;
        xadj = 0.0;
        yadj = 0.5;
        }
        else {
        line = line + dd->gp.yLineBias;
        a = 0.0;
        xadj = mathDevice->gp.adj;
        yadj = 0.0;
        }
        coords = MAR3;
        break;
    case 4:
        if (las == 1 || las == 2) {
        at = at + GConvertYUnits(dd->gp.yLineBias,
                     LINES, USER, dd);
        line = line + dd->gp.yLineBias;
        a = 0.0;
        xadj = 0.0;
        yadj = 0.5;
        }
        else {
        line = line + 1 - dd->gp.yLineBias;
        a = 90.0;
        xadj = mathDevice->gp.adj;
        yadj = 0.0;
        }
        coords = MAR4;
        break;
    }
    GMathText(at, line, coords, str, xadj, yadj, a, dd);
    }
}

#else

void GMMathText(SEXP str, int side, double line, int outer, double at, int las)
{
    error("Can't print math under Windows ... yet\n");
}

void GMathText(double x, double y, SEXP expr, double xc, double yc, double rot)
{
    error("Can't print math under Windows ... yet\n");
}
#endif