/*
 *  R : A Computer Language for Statistical Data Analysis
 *  Copyright (C) 1997--2018  The R Core Team
 *  Copyright (C) 1995, 1996  Robert Gentleman and Ross Ihaka
 *  Copyright (C) 2002--2011  The R Foundation
 *
 *  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, a copy is available at
 *  https://www.R-project.org/Licenses/


 *  This is an extensive reworking by Paul Murrell of an original
 *  quick hack by Ross Ihaka designed to give a superset of the
 *  functionality in the AT&T Bell Laboratories GRZ library.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <Defn.h>
#include <float.h> /* for DBL_EPSILON etc */
#include <Graphics.h>
// --> R_ext/GraphicsEngine.h + Rgraphics.h
#include <GraphicsBase.h>       /* setBaseDevice */
#include <Rmath.h>		/* eg. fmax2() */

#ifdef ENABLE_NLS
#include <libintl.h>
#undef _
#define _(String) dgettext ("grDevices", String)
#else
#define _(String) (String)
#endif

/*--->> Documentation now in  ../include/Rgraphics.h  "API" ----- */

double R_Log10(double x)
{
    return (R_FINITE(x) && x > 0.0) ? log10(x) : NA_REAL;
}

/*-------------------------------------------------------------------
 *
 *  TRANSFORMATIONS
 *
 *    There are five major regions on a device, for any
 *    particular figure:  the outer margins, which "stick"
 *    to the edges of the device;  the inner region, which
 *    is defined as the total device less the outer margins;
 *    the figure region, which defaults from the current
 *    layout (mfrow, mfcol, layout) unless the user specifies
 *    it directly (fig, fin);  the figure margins, which
 *    "stick" to the edges of the plot region;	and thed
 *    plot region, which is the figure region less the figure
 *    margins by default unless the user specifies it directly
 *    (plt, pin)
 *
 *  COORDINATE SYSTEMS
 *
 *    DEVICE  = devices natural coordinate system
 *		(e.g., pixels, 1/72", ...)
 *    NDC     = normalised device coordinates (0..1 on device)
 *    INCHES  = inches
 *    OMA1..4 = outer margin coordinates
 *    NIC     = normalised inner region coordinates
 *		(0..1 on inner region)
 *    NFC     = normalised figure coordinates
 *		(0..1 on figure region)
 *    MAR1..4 = figure margin coordinates
 *    NPC     = normalised plot coordinates
 *		(0..1 on plot region)
 *    USER    = world or data coordinates
 *
 *
 *  UNITS
 *
 *    All of the above, except OMA1..4 and MAR1..4, plus ...
 *
 *	LINES = line coordinates (lines of margin;  based on mex)
 *	CHARS = char coordinates (lines of text;  based on cex)
 *
 *    The function Convert(value, from, to) is provided
 *    to transform between any pair of coordinate systems
 *    (for transforming locations)
 *
 *    The functions ConvertXUnits(value, from, to) and
 *    ConvertYUnits(value, from, to) are provided to transform
 *    between any pair of units (for transforming dimensions)
 *
 *    IMPORTANT:  if user coordinates are logged, then the
 *    conversion to/from USER units will not work.  in this
 *    case it is necessary to use convert(x1) - convert(x2)
 *    rather than convert(x1 - x2)
 *
 */


/* In interpreted R, units are as follows:
 *	1 = "user"
 *	2 = "figure"
 *	3 = "inches"
 * the function GMapUnits provides a mapping
 * between interpreted units and internal units.
 */
GUnit GMapUnits(int Runits)
{
    switch (Runits) {
    case 1:	return USER;
    case 2:	return NFC;
    case 3:	return INCHES;
    default:	return 0;
    }
}

	/* Conversions Between Units*/

/* Used to be global (non-static) -- but are nowhere declared.
 * The public interface is through G[XY]ConvertUnits() */

static double xNDCtoDevUnits(double x, pGEDevDesc dd)
{
    return x*fabs(gpptr(dd)->ndc2dev.bx);
}

static double yNDCtoDevUnits(double y, pGEDevDesc dd)
{
    return y*fabs(gpptr(dd)->ndc2dev.by);
}

static double xNICtoDevUnits(double x, pGEDevDesc dd)
{
    return x*fabs(gpptr(dd)->inner2dev.bx);
}

static double yNICtoDevUnits(double y, pGEDevDesc dd)
{
    return y*fabs(gpptr(dd)->inner2dev.by);
}

static double xNFCtoDevUnits(double x, pGEDevDesc dd)
{
    return x*fabs(gpptr(dd)->fig2dev.bx);
}

static double yNFCtoDevUnits(double y, pGEDevDesc dd)
{
    return y*fabs(gpptr(dd)->fig2dev.by);
}

static double xNPCtoDevUnits(double x, pGEDevDesc dd)
{
    return xNFCtoDevUnits(x*(gpptr(dd)->plt[1] - gpptr(dd)->plt[0]), dd);
}

static double yNPCtoDevUnits(double y, pGEDevDesc dd)
{
    return yNFCtoDevUnits(y*(gpptr(dd)->plt[3] - gpptr(dd)->plt[2]), dd);
}

static double xUsrtoDevUnits(double x, pGEDevDesc dd)
{
    return xNFCtoDevUnits(x*gpptr(dd)->win2fig.bx, dd);
}

static double yUsrtoDevUnits(double y, pGEDevDesc dd)
{
    return yNFCtoDevUnits(y*gpptr(dd)->win2fig.by, dd);
}

static double xInchtoDevUnits(double x, pGEDevDesc dd)
{
    return xNDCtoDevUnits(x*gpptr(dd)->xNDCPerInch, dd);
}

static double yInchtoDevUnits(double y, pGEDevDesc dd)
{
    return yNDCtoDevUnits(y*gpptr(dd)->yNDCPerInch, dd);
}

static double xLinetoDevUnits(double x, pGEDevDesc dd)
{
    return xNDCtoDevUnits(x*gpptr(dd)->xNDCPerLine, dd);
}

static double yLinetoDevUnits(double y, pGEDevDesc dd)
{
    return yNDCtoDevUnits(y*gpptr(dd)->yNDCPerLine, dd);
}

static double xChartoDevUnits(double x, pGEDevDesc dd)
{
    return xNDCtoDevUnits(x*gpptr(dd)->cex*gpptr(dd)->xNDCPerChar, dd);
}

static double yChartoDevUnits(double y, pGEDevDesc dd)
{
    return yNDCtoDevUnits(y*gpptr(dd)->cex*gpptr(dd)->yNDCPerChar, dd);
}

static double xDevtoNDCUnits(double x, pGEDevDesc dd)
{
    return x/fabs(gpptr(dd)->ndc2dev.bx);
}

static double yDevtoNDCUnits(double y, pGEDevDesc dd)
{
    return y/fabs(gpptr(dd)->ndc2dev.by);
}

static double xDevtoNICUnits(double x, pGEDevDesc dd)
{
    return x/fabs(gpptr(dd)->inner2dev.bx);
}

static double yDevtoNICUnits(double y, pGEDevDesc dd)
{
    return y/fabs(gpptr(dd)->inner2dev.by);
}

static double xDevtoNFCUnits(double x, pGEDevDesc dd)
{
    return x/fabs(gpptr(dd)->fig2dev.bx);
}

static double yDevtoNFCUnits(double y, pGEDevDesc dd)
{
    return y/fabs(gpptr(dd)->fig2dev.by);
}

static double xDevtoNPCUnits(double x, pGEDevDesc dd)
{
    return xDevtoNFCUnits(x, dd)/(gpptr(dd)->plt[1] - gpptr(dd)->plt[0]);
}

static double yDevtoNPCUnits(double y, pGEDevDesc dd)
{
    return yDevtoNFCUnits(y, dd)/(gpptr(dd)->plt[3] - gpptr(dd)->plt[2]);
}

static double xDevtoUsrUnits(double x, pGEDevDesc dd)
{
    return xDevtoNFCUnits(x, dd)/gpptr(dd)->win2fig.bx;
}

static double yDevtoUsrUnits(double y, pGEDevDesc dd)
{
    return yDevtoNFCUnits(y, dd)/gpptr(dd)->win2fig.by;
}

static double xDevtoInchUnits(double x, pGEDevDesc dd)
{
    return xDevtoNDCUnits(x, dd)/gpptr(dd)->xNDCPerInch;
}

static double yDevtoInchUnits(double y, pGEDevDesc dd)
{
    return yDevtoNDCUnits(y, dd)/gpptr(dd)->yNDCPerInch;
}

static double xDevtoLineUnits(double x, pGEDevDesc dd)
{
    return xDevtoNDCUnits(x, dd)/gpptr(dd)->xNDCPerLine;
}

static double yDevtoLineUnits(double y, pGEDevDesc dd)
{
    return yDevtoNDCUnits(y, dd)/gpptr(dd)->yNDCPerLine;
}

/* NOTE that use the _current_ gpptr(dd)->cex here */
/* the conversion for lines doesn't have to worry about */
/* this because gpptr(dd)->mex can only be set once per plot */

static double xDevtoCharUnits(double x, pGEDevDesc dd)
{
    return xDevtoNDCUnits(x, dd)/(gpptr(dd)->cex * gpptr(dd)->xNDCPerChar);
}

static double yDevtoCharUnits(double y, pGEDevDesc dd)
{
    return yDevtoNDCUnits(y, dd)/(gpptr(dd)->cex * gpptr(dd)->yNDCPerChar);
}

static void NORET BadUnitsError(const char *where)
{
    error(_("bad units specified in '%s'"), where);
}

/* GConvertXUnits() and GConvertYUnits() convert
   a single value fromUnits toUnits : */

double GConvertXUnits(double x, GUnit fromUnits, GUnit toUnits, pGEDevDesc dd)
{
    double dev, final;
    switch (fromUnits) {
    case DEVICE: dev = x; break;
    case NDC:	 dev = xNDCtoDevUnits(x, dd); break;
    case NIC:	 dev = xNICtoDevUnits(x, dd); break;
    case NFC:	 dev = xNFCtoDevUnits(x, dd); break;
    case NPC:	 dev = xNPCtoDevUnits(x, dd); break;
    case USER:	 dev = xUsrtoDevUnits(x, dd); break;
    case INCHES: dev = xInchtoDevUnits(x, dd); break;
    case LINES:	 dev = xLinetoDevUnits(x, dd); break;
    case CHARS:	 dev = xChartoDevUnits(x, dd); break;
    default:	 dev = 0; BadUnitsError("GConvertXUnits");

    }
    switch (toUnits) {
    case DEVICE: final = dev; break;
    case NDC:	 final = xDevtoNDCUnits(dev, dd); break;
    case NIC:	 final = xDevtoNICUnits(dev, dd); break;
    case NFC:	 final = xDevtoNFCUnits(dev, dd); break;
    case NPC:	 final = xDevtoNPCUnits(dev, dd); break;
    case USER:	 final = xDevtoUsrUnits(dev, dd); break;
    case INCHES: final = xDevtoInchUnits(dev, dd); break;
    case LINES:	 final = xDevtoLineUnits(dev, dd); break;
    case CHARS:	 final = xDevtoCharUnits(dev, dd); break;
    default:	 final = 0; BadUnitsError("GConvertXUnits");
    }
    return final;
}

double GConvertYUnits(double y, GUnit fromUnits, GUnit toUnits, pGEDevDesc dd)
{
    double dev, final;
    switch (fromUnits) {
    case DEVICE: dev = y; break;
    case NDC:	 dev = yNDCtoDevUnits(y, dd); break;
    case NIC:	 dev = yNICtoDevUnits(y, dd); break;
    case NFC:	 dev = yNFCtoDevUnits(y, dd); break;
    case NPC:	 dev = yNPCtoDevUnits(y, dd); break;
    case USER:	 dev = yUsrtoDevUnits(y, dd); break;
    case INCHES: dev = yInchtoDevUnits(y, dd); break;
    case LINES:	 dev = yLinetoDevUnits(y, dd); break;
    case CHARS:	 dev = yChartoDevUnits(y, dd); break;
    default:	 dev = 0; BadUnitsError("GConvertYUnits");
    }
    switch (toUnits) {
    case DEVICE: final = dev; break;
    case NDC:	 final = yDevtoNDCUnits(dev, dd); break;
    case NIC:	 final = yDevtoNICUnits(dev, dd); break;
    case NFC:	 final = yDevtoNFCUnits(dev, dd); break;
    case NPC:	 final = yDevtoNPCUnits(dev, dd); break;
    case USER:	 final = yDevtoUsrUnits(dev, dd); break;
    case INCHES: final = yDevtoInchUnits(dev, dd); break;
    case LINES:	 final = yDevtoLineUnits(dev, dd); break;
    case CHARS:	 final = yDevtoCharUnits(dev, dd); break;
    default:	 final = 0; BadUnitsError("GConvertYUnits");
    }
    return final;
}

/* Functions to convert locations from one coordinate system to another */

/* OTHER coordinate systems to DEVICE */

/* Used to be global (non-static) -- but are nowhere declared.
 * The public interface is  GConvert(), GConvertX(), GConvertY() */
static double xNDCtoDev(double x, pGEDevDesc dd)
{
    return gpptr(dd)->ndc2dev.ax + x*gpptr(dd)->ndc2dev.bx;
}

static double yNDCtoDev(double y, pGEDevDesc dd)
{
    return gpptr(dd)->ndc2dev.ay + y*gpptr(dd)->ndc2dev.by;
}

static double xInchtoDev(double x, pGEDevDesc dd)
{
    return xNDCtoDev(x*gpptr(dd)->xNDCPerInch, dd);
}

static double yInchtoDev(double y, pGEDevDesc dd)
{
    return yNDCtoDev(y*gpptr(dd)->yNDCPerInch, dd);
}

static double xLinetoDev(double x, pGEDevDesc dd)
{
    return xNDCtoDev(x*gpptr(dd)->xNDCPerLine, dd);
}

static double yLinetoDev(double y, pGEDevDesc dd)
{
    return yNDCtoDev(y*gpptr(dd)->yNDCPerLine, dd);
}

static double xChartoDev(double x, pGEDevDesc dd)
{
    return xNDCtoDev(x*gpptr(dd)->cex*gpptr(dd)->xNDCPerChar, dd);
}

static double yChartoDev(double y, pGEDevDesc dd)
{
    return yNDCtoDev(y*gpptr(dd)->cex*gpptr(dd)->yNDCPerChar, dd);
}

static double xNICtoDev(double x, pGEDevDesc dd)
{
    return gpptr(dd)->inner2dev.ax + x*gpptr(dd)->inner2dev.bx;
}

static double yNICtoDev(double y, pGEDevDesc dd)
{
    return gpptr(dd)->inner2dev.ay + y*gpptr(dd)->inner2dev.by;
}
/* NOTE that an x-coordinate in OMA2 or OMA4 converts to a */
/* y-coordinate in Dev and a y-coordinate in OMA2 or OMA4 */
/* converts to an x-coordinate in Dev */

static double xOMA1toDev(double x, pGEDevDesc dd)
{
    return xNICtoDev(x, dd);
}

static double yOMA1toDev(double y, pGEDevDesc dd)
{
    return yLinetoDev((gpptr(dd)->oma[0] - y), dd);
}

static double xOMA2toyDev(double x, pGEDevDesc dd)
{
    return yNICtoDev(x, dd);
}

static double yOMA2toxDev(double y, pGEDevDesc dd)
{
    return xLinetoDev((gpptr(dd)->oma[1] - y), dd);
}

static double xOMA3toDev(double x, pGEDevDesc dd)
{
    return xNICtoDev(x, dd);
}

static double yOMA3toDev(double y, pGEDevDesc dd)
{
    double ndc = 1.0-yDevtoNDC(yLinetoDev((gpptr(dd)->oma[2] - y), dd), dd);
    return yNDCtoDev(ndc, dd);
}

static double xOMA4toyDev(double x, pGEDevDesc dd)
{
    return yNICtoDev(x, dd);
}

static double yOMA4toxDev(double y, pGEDevDesc dd)
{
    double ndc = 1.0-xDevtoNDC(xLinetoDev(gpptr(dd)->oma[3]-y, dd), dd);
    return xNDCtoDev(ndc, dd);
}

static double xNFCtoDev(double x, pGEDevDesc dd)
{
    return gpptr(dd)->fig2dev.ax + x*gpptr(dd)->fig2dev.bx;
}

static double yNFCtoDev(double y, pGEDevDesc dd)
{
    return gpptr(dd)->fig2dev.ay + y*gpptr(dd)->fig2dev.by;
}

static double xNPCtoDev(double x, pGEDevDesc dd)
{
    return xNFCtoDev(gpptr(dd)->plt[0] +
		     x*(gpptr(dd)->plt[1] - gpptr(dd)->plt[0]), dd);
}

static double yNPCtoDev(double y, pGEDevDesc dd)
{
    return yNFCtoDev(gpptr(dd)->plt[2] +
		     y*(gpptr(dd)->plt[3] - gpptr(dd)->plt[2]), dd);
}

static double xUsrtoDev(double x, pGEDevDesc dd)
{
    if (gpptr(dd)->xlog)
	x = R_Log10(x);
    return xNFCtoDev(gpptr(dd)->win2fig.ax + x*gpptr(dd)->win2fig.bx, dd);
}

static double yUsrtoDev(double y, pGEDevDesc dd)
{
    if (gpptr(dd)->ylog)
	y = R_Log10(y);
    return yNFCtoDev(gpptr(dd)->win2fig.ay + y*gpptr(dd)->win2fig.by, dd);
}

/* NOTE that an x-coordinate in MAR2 or MAR4 converts to a */
/* y-coordinate in Dev and a y-coordinate in MAR2 or MAR4 */
/* converts to an x-coordinate in Dev */

static double xMAR1toDev(double x, pGEDevDesc dd)
{
    return xUsrtoDev(x, dd);
}

static double yMAR1toDev(double y, pGEDevDesc dd)
{
    double nfc = GConvertYUnits(y, LINES, NFC, dd);
    return yNFCtoDev(gpptr(dd)->plt[2] - nfc, dd);
}

static double xMAR2toyDev(double x, pGEDevDesc dd)
{
    return yUsrtoDev(x, dd);
}

static double yMAR2toxDev(double y, pGEDevDesc dd)
{
    double nfc = GConvertXUnits(y, LINES, NFC, dd);
    return xNFCtoDev(gpptr(dd)->plt[0] - nfc, dd);
}

static double xMAR3toDev(double x, pGEDevDesc dd)
{
    return xUsrtoDev(x, dd);
}

static double yMAR3toDev(double y, pGEDevDesc dd)
{
    double nfc = GConvertYUnits(y, LINES, NFC, dd);
    return yNFCtoDev(gpptr(dd)->plt[3] + nfc, dd);
}

static double xMAR4toyDev(double x, pGEDevDesc dd)
{
    return yUsrtoDev(x, dd);
}

static double yMAR4toxDev(double y, pGEDevDesc dd)
{
    double nfc = GConvertXUnits(y, LINES, NFC, dd);
    return xNFCtoDev(gpptr(dd)->plt[1] + nfc, dd);
}

/* DEVICE coordinates to OTHER */

double xDevtoNDC(double x, pGEDevDesc dd)
{
    return (x - gpptr(dd)->ndc2dev.ax)/gpptr(dd)->ndc2dev.bx;
}

double yDevtoNDC(double y, pGEDevDesc dd)
{
    return (y - gpptr(dd)->ndc2dev.ay)/gpptr(dd)->ndc2dev.by;
}

static double xDevtoInch(double x, pGEDevDesc dd)
{
    return xDevtoNDC(x, dd)/gpptr(dd)->xNDCPerInch;
}

static double yDevtoInch(double y, pGEDevDesc dd)
{
    return yDevtoNDC(y, dd)/gpptr(dd)->yNDCPerInch;
}

static double xDevtoLine(double x, pGEDevDesc dd)
{
  return xDevtoNDC(x, dd)/gpptr(dd)->xNDCPerLine;
}

static double yDevtoLine(double y, pGEDevDesc dd)
{
    return yDevtoNDC(y, dd)/gpptr(dd)->yNDCPerLine;
}

static double xDevtoChar(double x, pGEDevDesc dd)
{
    return xDevtoNDC(x, dd)/(gpptr(dd)->cex * gpptr(dd)->xNDCPerChar);
}

static double yDevtoChar(double y, pGEDevDesc dd)
{
    return yDevtoNDC(y, dd)/(gpptr(dd)->cex * gpptr(dd)->yNDCPerChar);
}

static double xDevtoNIC(double x, pGEDevDesc dd)
{
    return (x - gpptr(dd)->inner2dev.ax)/gpptr(dd)->inner2dev.bx;
}

static double yDevtoNIC(double y, pGEDevDesc dd)
{
    return (y - gpptr(dd)->inner2dev.ay)/gpptr(dd)->inner2dev.by;
}

static double xDevtoOMA1(double x, pGEDevDesc dd)
{
    return xDevtoNIC(x, dd);
}

static double yDevtoOMA1(double y, pGEDevDesc dd)
{
    return gpptr(dd)->oma[0] - yDevtoLine(y, dd);
}

static double xDevtoyOMA2(double x, pGEDevDesc dd)
{
    return gpptr(dd)->oma[1] - xDevtoLine(x, dd);
}

static double yDevtoxOMA2(double y, pGEDevDesc dd)
{
    return yDevtoNIC(y, dd);
}

static double xDevtoOMA3(double x, pGEDevDesc dd)
{
    return xDevtoNIC(x, dd);
}

static double yDevtoOMA3(double y, pGEDevDesc dd)
{
    double line = (1.0 - yDevtoNDC(y, dd))/gpptr(dd)->yNDCPerLine;
    return gpptr(dd)->oma[2] - line;
}

static double xDevtoyOMA4(double x, pGEDevDesc dd)
{
    double line = (1.0 - xDevtoNDC(x, dd))/gpptr(dd)->xNDCPerLine;
    return gpptr(dd)->oma[3] - line;
}

static double yDevtoxOMA4(double y, pGEDevDesc dd)
{
    return yDevtoNIC(y, dd);
}

double xDevtoNFC(double x, pGEDevDesc dd)
{
    return (x - gpptr(dd)->fig2dev.ax)/gpptr(dd)->fig2dev.bx;
}

double yDevtoNFC(double y, pGEDevDesc dd)
{
    return (y - gpptr(dd)->fig2dev.ay)/gpptr(dd)->fig2dev.by;
}

double xDevtoNPC(double x, pGEDevDesc dd)
{
    return (xDevtoNFC(x, dd) - gpptr(dd)->plt[0])/
	(gpptr(dd)->plt[1] - gpptr(dd)->plt[0]);
}

double yDevtoNPC(double y, pGEDevDesc dd)
{
    return (yDevtoNFC(y, dd) - gpptr(dd)->plt[2])/
	(gpptr(dd)->plt[3] - gpptr(dd)->plt[2]);
}

/* a special case (NPC = normalised plot region coordinates) */

double xNPCtoUsr(double x, pGEDevDesc dd)
{
    if (gpptr(dd)->xlog)
	return Rexp10(gpptr(dd)->logusr[0] +
		   x*(gpptr(dd)->logusr[1] - gpptr(dd)->logusr[0]));
    else
	return gpptr(dd)->usr[0] + x*(gpptr(dd)->usr[1] - gpptr(dd)->usr[0]);
}

double yNPCtoUsr(double y, pGEDevDesc dd)
{
    if (gpptr(dd)->ylog)
	return Rexp10(gpptr(dd)->logusr[2] +
		   y*(gpptr(dd)->logusr[3]-gpptr(dd)->logusr[2]));
    else
	return gpptr(dd)->usr[2] + y*(gpptr(dd)->usr[3] - gpptr(dd)->usr[2]);
}

double xDevtoUsr(double x, pGEDevDesc dd)
{
    double nfc = xDevtoNFC(x, dd);
    if (gpptr(dd)->xlog)
	return Rexp10((nfc - gpptr(dd)->win2fig.ax)/gpptr(dd)->win2fig.bx);
    else
	return (nfc - gpptr(dd)->win2fig.ax)/gpptr(dd)->win2fig.bx;
}

double yDevtoUsr(double y, pGEDevDesc dd)
{
  double nfc = yDevtoNFC(y, dd);
  if (gpptr(dd)->ylog)
    return Rexp10((nfc - gpptr(dd)->win2fig.ay)/gpptr(dd)->win2fig.by);
  else
    return (nfc - gpptr(dd)->win2fig.ay)/gpptr(dd)->win2fig.by;
}

static double xDevtoMAR1(double x, pGEDevDesc dd)
{
    return xDevtoUsr(x, dd);
}

static double yDevtoMAR1(double y, pGEDevDesc dd)
{
    return gpptr(dd)->oma[0] + gpptr(dd)->mar[0] - yDevtoLine(y, dd);
}

static double xDevtoyMAR2(double x, pGEDevDesc dd)
{
    return gpptr(dd)->oma[1] + gpptr(dd)->mar[1] - xDevtoLine(x, dd);
}

static double yDevtoxMAR2(double y, pGEDevDesc dd)
{
    return yDevtoUsr(y, dd);
}

static double xDevtoMAR3(double x, pGEDevDesc dd)
{
    return xDevtoUsr(x, dd);
}

static double yDevtoMAR3(double y, pGEDevDesc dd)
{
    double line = GConvertYUnits(1.0 - yDevtoNFC(y, dd), NFC, LINES, dd);
    return gpptr(dd)->mar[2] - line;
}

static double xDevtoyMAR4(double x, pGEDevDesc dd)
{
    double line = GConvertXUnits(1.0 - xDevtoNFC(x, dd), NFC, LINES, dd);
    return gpptr(dd)->mar[3] - line;
}

static double yDevtoxMAR4(double y, pGEDevDesc dd)
{
    return yDevtoUsr(y, dd);
}

/* the Convert function converts a LOCATION in the FROM coordinate */
/* system to a LOCATION in the TO coordinate system */

void GConvert(double *x, double *y, GUnit from, GUnit to, pGEDevDesc dd)
{
    double devx, devy;

    switch (from) {
    case DEVICE:
	devx = *x;
	devy = *y;
	break;
    case NDC:
	devx = xNDCtoDev(*x, dd);
	devy = yNDCtoDev(*y, dd);
	break;
    case INCHES:
	devx = xInchtoDev(*x, dd);
	devy = yInchtoDev(*y, dd);
	break;
    case OMA1:
	devx = xOMA1toDev(*x, dd);
	devy = yOMA1toDev(*y, dd);
	break;
    case OMA2:
	devx = yOMA2toxDev(*y, dd);
	devy = xOMA2toyDev(*x, dd);
	break;
    case OMA3:
	devx = xOMA3toDev(*x, dd);
	devy = yOMA3toDev(*y, dd);
	break;
    case OMA4:
	devx = yOMA4toxDev(*y, dd);
	devy = xOMA4toyDev(*x, dd);
	break;
    case NIC:
	devx = xNICtoDev(*x, dd);
	devy = yNICtoDev(*y, dd);
	break;
    case NFC:
	devx = xNFCtoDev(*x, dd);
	devy = yNFCtoDev(*y, dd);
	break;
    case MAR1:
	devx = xMAR1toDev(*x, dd);
	devy = yMAR1toDev(*y, dd);
	break;
    case MAR2:
	devx = yMAR2toxDev(*y, dd);
	devy = xMAR2toyDev(*x, dd);
	break;
    case MAR3:
	devx = xMAR3toDev(*x, dd);
	devy = yMAR3toDev(*y, dd);
	break;
    case MAR4:
	devx = yMAR4toxDev(*y, dd);
	devy = xMAR4toyDev(*x, dd);
	break;
    case NPC:
	devx = xNPCtoDev(*x, dd);
	devy = yNPCtoDev(*y, dd);
	break;
    case USER:
	devx = xUsrtoDev(*x, dd);
	devy = yUsrtoDev(*y, dd);
	break;
    default:
	devx = 0;	/* for -Wall */
	devy = 0;
	BadUnitsError("GConvert");
    }

    switch (to) {
    case DEVICE:
	*x = devx;
	*y = devy;
	break;
    case NDC:
	*x = xDevtoNDC(devx, dd);
	*y = yDevtoNDC(devy, dd);
	break;
    case INCHES:
	*x = xDevtoInch(devx, dd);
	*y = yDevtoInch(devy, dd);
	break;
    case LINES:
	*x = xDevtoLine(devx, dd);
	*y = yDevtoLine(devy, dd);
	break;
    case CHARS:
	*x = xDevtoChar(devx, dd);
	*y = yDevtoChar(devy, dd);
    case NIC:
	*x = xDevtoNIC(devx, dd);
	*y = yDevtoNIC(devy, dd);
	break;
    case OMA1:
	*x = xDevtoOMA1(devx, dd);
	*y = yDevtoOMA1(devy, dd);
	break;
    case OMA2:
	*x = yDevtoxOMA2(devy, dd);
	*y = xDevtoyOMA2(devx, dd);
	break;
    case OMA3:
	*x = xDevtoOMA3(devx, dd);
	*y = yDevtoOMA3(devy, dd);
	break;
    case OMA4:
	*x = yDevtoxOMA4(devy, dd);
	*y = xDevtoyOMA4(devx, dd);
	break;
    case NFC:
	*x = xDevtoNFC(devx, dd);
	*y = yDevtoNFC(devy, dd);
	break;
    case NPC:
	*x = xDevtoNPC(devx, dd);
	*y = yDevtoNPC(devy, dd);
	break;
    case USER:
	*x = xDevtoUsr(devx, dd);
	*y = yDevtoUsr(devy, dd);
	break;
    case MAR1:
	*x = xDevtoMAR1(devx, dd);
	*y = yDevtoMAR1(devy, dd);
	break;
    case MAR2:
	*x = yDevtoxMAR2(devy, dd);
	*y = xDevtoyMAR2(devx, dd);
	break;
    case MAR3:
	*x = xDevtoMAR3(devx, dd);
	*y = yDevtoMAR3(devy, dd);
	break;
    case MAR4:
	*x = yDevtoxMAR4(devy, dd);
	*y = xDevtoyMAR4(devx, dd);
	break;
    default:
	BadUnitsError("GConvert");
    }
}

double GConvertX(double x, GUnit from, GUnit to, pGEDevDesc dd)
{
    double devx;
    switch (from) {
    case DEVICE:devx = x;	break;
    case NDC:	devx = xNDCtoDev(x, dd);	break;
    case INCHES:devx = xInchtoDev(x, dd);	break;
    case LINES: devx = xLinetoDev(x, dd);       break;
    case CHARS: devx = xChartoDev(x, dd);       break;
    case OMA1:	devx = xOMA1toDev(x, dd);	break;
    /*case OMA2:	x <--> y */
    case OMA3:	devx = xOMA3toDev(x, dd);	break;
    /*case OMA4:	x <--> y */
    case NIC:	devx = xNICtoDev(x, dd);	break;
    case NFC:	devx = xNFCtoDev(x, dd);	break;
    case MAR1:	devx = xMAR1toDev(x, dd);	break;
    /*case MAR2:	x <--> y */
    case MAR3:	devx = xMAR3toDev(x, dd);	break;
    /*case MAR4:	x <--> y */
    case NPC:	devx = xNPCtoDev(x, dd);	break;
    case USER:	devx = xUsrtoDev(x, dd);	break;
    default:	devx = 0;/* for -Wall */ BadUnitsError("GConvertX");
    }

    switch (to) {
    case DEVICE:x = devx;	break;
    case NDC:	x = xDevtoNDC(devx, dd);	break;
    case INCHES:x = xDevtoInch(devx, dd);	break;
    case LINES:	x = xDevtoLine(devx, dd);	break;
    case CHARS:	x = xDevtoChar(devx, dd);	break;
    case NIC:	x = xDevtoNIC(devx, dd);	break;
    case OMA1:	x = xDevtoOMA1(devx, dd);	break;
    /*case OMA2:	x <--> y */
    case OMA3:	x = xDevtoOMA3(devx, dd);	break;
    /*case OMA4:	x <--> y */
    case NFC:	x = xDevtoNFC(devx, dd);	break;
    case USER:	x = xDevtoUsr(devx, dd);	break;
    case MAR1:	x = xDevtoMAR1(devx, dd);	break;
    /*case MAR2:	x <--> y */
    case MAR3:	x = xDevtoMAR3(devx, dd);	break;
    /*case MAR4:	x <--> y */
    case NPC:	x = xDevtoNPC(devx, dd);	break;
    default:	BadUnitsError("GConvertX");
    }
    return x;
}

double GConvertY(double y, GUnit from, GUnit to, pGEDevDesc dd)
{
    double devy;
    switch (from) {
    case DEVICE:devy = y;	break;
    case NDC:	devy = yNDCtoDev(y, dd);	break;
    case INCHES:devy = yInchtoDev(y, dd);	break;
    case LINES: devy = yLinetoDev(y, dd);       break;
    case CHARS: devy = yChartoDev(y, dd);       break;
    case OMA1:	devy = yOMA1toDev(y, dd);	break;
    /*case OMA2:	x <--> y */
    case OMA3:	devy = yOMA3toDev(y, dd);	break;
    /*case OMA4:	x <--> y */
    case NIC:	devy = yNICtoDev(y, dd);	break;
    case NFC:	devy = yNFCtoDev(y, dd);	break;
    case MAR1:	devy = yMAR1toDev(y, dd);	break;
    /*case MAR2:	x <--> y */
    case MAR3:	devy = yMAR3toDev(y, dd);	break;
    /*case MAR4:	x <--> y */
    case NPC:	devy = yNPCtoDev(y, dd);	break;
    case USER:	devy = yUsrtoDev(y, dd);	break;
    default:	devy = 0;/* for -Wall */ BadUnitsError("GConvertY");
    }

    switch (to) {
    case DEVICE:y = devy;	break;
    case NDC:	y = yDevtoNDC(devy, dd);	break;
    case INCHES:y = yDevtoInch(devy, dd);	break;
    case LINES:	y = yDevtoLine(devy, dd);	break;
    case CHARS:	y = yDevtoChar(devy, dd);	break;
    case NIC:	y = yDevtoNIC(devy, dd);	break;
    case OMA1:	y = yDevtoOMA1(devy, dd);	break;
    /*case OMA2:	x <--> y */
    case OMA3:	y = yDevtoOMA3(devy, dd);	break;
    /*case OMA4:	x <--> y */
    case NFC:	y = yDevtoNFC(devy, dd);	break;
    case USER:	y = yDevtoUsr(devy, dd);	break;
    case MAR1:	y = yDevtoMAR1(devy, dd);	break;
    /*case MAR2:	x <--> y */
    case MAR3:	y = yDevtoMAR3(devy, dd);	break;
    /*case MAR4:	x <--> y */
    case NPC:   y = yDevtoNPC(devy, dd);	break;
    default:	BadUnitsError("GConvertY");
    }
    return y;
}

/* Code for layouts */

static double sum(double values[], int n, int cmValues[], int cmSum)
{
    int i;
    double s = 0;
    for (i = 0; i < n; i++)
	if ((cmSum && cmValues[i]) || (!cmSum && !cmValues[i]))
	    s = s + values[i];
    return s;
}

static double sumWidths(pGEDevDesc dd)
{
    return sum(gpptr(dd)->widths, gpptr(dd)->numcols, gpptr(dd)->cmWidths, 0);
}

static double sumCmWidths(pGEDevDesc dd)
{
    return sum(gpptr(dd)->widths, gpptr(dd)->numcols,  gpptr(dd)->cmWidths, 1);
}

static double sumHeights(pGEDevDesc dd)
{
    return sum(gpptr(dd)->heights, gpptr(dd)->numrows, gpptr(dd)->cmHeights, 0);
}

static double sumCmHeights(pGEDevDesc dd)
{
    return sum(gpptr(dd)->heights, gpptr(dd)->numrows, gpptr(dd)->cmHeights, 1);
}

static int tallLayout(double cmWidth, double cmHeight, pGEDevDesc dd)
{
    return (cmHeight/sumHeights(dd)) > (cmWidth/sumWidths(dd));
}

static void figureExtent(int *minCol, int *maxCol, int *minRow, int *maxRow,
			 int figureNum, pGEDevDesc dd)
{
    int minc = -1;
    int maxc = -1;
    int minr = -1;
    int maxr = -1;
    int i, j;
    int nr = gpptr(dd)->numrows;
    for (i = 0; i < nr; i++)
	for (j = 0; j < gpptr(dd)->numcols; j++)
	    if (gpptr(dd)->order[i + j*nr] == figureNum) {
		if ((minc == -1) || (j < minc))
		    minc = j;
		if ((maxc == -1) || (j > maxc))
		    maxc = j;
		if ((minr == -1) || (i < minr))
		    minr = i;
		if ((maxr == -1) || (i > maxr))
		    maxr = i;
	    }
    *minCol = minc;
    *maxCol = maxc;
    *minRow = minr;
    *maxRow = maxr;
}

static double sumRegions(double regions[], int from, int to)
{
    int i;
    double s = 0;
    for (i = from; i < to + 1; i++)
	s = s + regions[i];
    return s;
}

static void largestRegion(double *width, double *height,
			  double layoutAspectRatio, double innerAspectRatio)
{
    if (layoutAspectRatio < innerAspectRatio) {
	*width = 1.0;
	*height = layoutAspectRatio/innerAspectRatio;
    }
    else {
	*width = innerAspectRatio/layoutAspectRatio;
	*height = 1.0;
    }
}

static void layoutRegion(double *width, double *height,
			 double widths[], double heights[],
			 double cmWidth, double cmHeight, pGEDevDesc dd)
{
  largestRegion(width, height,
		sum(heights, gpptr(dd)->numrows, gpptr(dd)->cmHeights, 0)/
		sum(widths, gpptr(dd)->numcols, gpptr(dd)->cmWidths, 0),
		cmHeight/cmWidth);
}


	/* allocate one dimension (width or height) for either */
	/* relative or cm units */

static void allocDimension(double dimensions[], double sumDimensions, int n,
			   int cmDimensions[], int cmDimension)
{
    int i;
    for (i = 0; i < n; i++)
	if ((cmDimension && cmDimensions[i]) ||
	    (!cmDimension && !cmDimensions[i]))
	    dimensions[i] = dimensions[i]/sumDimensions;
}

static void allCmRegions(double widths[], double heights[],
			 double cmWidth, double cmHeight, pGEDevDesc dd)
{
    allocDimension(widths, cmWidth, gpptr(dd)->numcols, gpptr(dd)->cmWidths, 1);
    allocDimension(heights, cmHeight, gpptr(dd)->numrows, gpptr(dd)->cmHeights, 1);
}

static void modifyDimension(double dimension[], double multiplier, double n,
			    int cmDimensions[])
{
    int i;
    for (i = 0; i < n; i++)
	if (!cmDimensions[i])
	    dimension[i] = dimension[i] * multiplier;
}

static void modifyRegions(double widths[], double heights[],
		   double colMultiplier, double rowMultiplier,
		   pGEDevDesc dd)
{
    modifyDimension(widths, colMultiplier, gpptr(dd)->numcols, gpptr(dd)->cmWidths);
    modifyDimension(heights, rowMultiplier, gpptr(dd)->numrows, gpptr(dd)->cmHeights);
}

static void regionsWithoutRespect(double widths[], double heights[], pGEDevDesc dd)
{
    allocDimension(widths,
		   sum(widths, gpptr(dd)->numcols, gpptr(dd)->cmWidths, 0),
		   gpptr(dd)->numcols, gpptr(dd)->cmWidths, 0);
    allocDimension(heights,
		   sum(heights, gpptr(dd)->numrows, gpptr(dd)->cmHeights, 0),
		   gpptr(dd)->numrows, gpptr(dd)->cmHeights, 0);
}

static void regionsWithRespect(double widths[], double heights[],
			       double cmWidth, double cmHeight, pGEDevDesc dd)
{
    double cm, rm;
    layoutRegion(&cm, &rm, widths, heights, cmWidth, cmHeight, dd);
    regionsWithoutRespect(widths, heights, dd);
    modifyRegions(widths, heights, cm, rm, dd);
}

static void widthsRespectingHeights(double widths[],
				    double cmWidth, double cmHeight,
				    pGEDevDesc dd)
{
    int i, j;
    int respectedCols[MAX_LAYOUT_COLS];
    double widthLeft;
    double disrespectedWidth = 0;
    int nr = gpptr(dd)->numrows;
    for (j = 0; j < gpptr(dd)->numcols; j++) {
	respectedCols[j] = 0;
	widths[j] = gpptr(dd)->widths[j];
    }
    for (i = 0; i < nr; i++)
	for (j = 0; j < gpptr(dd)->numcols; j++)
	    if (gpptr(dd)->respect[i + j * nr] &&
		!gpptr(dd)->cmWidths[j]) respectedCols[j] = 1;
    for (j = 0; j < gpptr(dd)->numcols; j++)
	if (!respectedCols[j])
	    disrespectedWidth += gpptr(dd)->widths[j];
    widthLeft = sumHeights(dd) * cmWidth/cmHeight -
	sumWidths(dd) + disrespectedWidth;
    for (j = 0; j < gpptr(dd)->numcols; j++)
	if (!respectedCols[j])
	    widths[j] = widthLeft * widths[j]/disrespectedWidth;
}

static void regionsRespectingHeight(double widths[], double heights[],
			     double cmWidth, double cmHeight,
			     pGEDevDesc dd)
{
    widthsRespectingHeights(widths, cmWidth, cmHeight, dd);
    regionsWithRespect(widths, heights, cmWidth, cmHeight, dd);
}

static void heightsRespectingWidths(double heights[],
			     double cmWidth, double cmHeight,
			     pGEDevDesc dd)
{
    int i, j;
    int respectedRows[MAX_LAYOUT_ROWS];
    double heightLeft;
    double disrespectedHeight = 0;
    int nr = gpptr(dd)->numrows;
    for (i = 0; i < nr; i++) {
	respectedRows[i] = 0;
	heights[i] = gpptr(dd)->heights[i];
    }
    for (i = 0; i < nr; i++)
	for (j = 0; j < gpptr(dd)->numcols; j++)
	    if (gpptr(dd)->respect[i + j*nr] &&
		!gpptr(dd)->cmHeights[i]) respectedRows[i] = 1;
    for (i = 0; i < gpptr(dd)->numrows; i++)
	if (!respectedRows[i])
	    disrespectedHeight += gpptr(dd)->heights[i];
    heightLeft = sumWidths(dd) * cmHeight/cmWidth -
	sumHeights(dd) + disrespectedHeight;
    for (i = 0; i < gpptr(dd)->numrows; i++)
	if (!respectedRows[i])
	    heights[i] = heightLeft * heights[i]/disrespectedHeight;
}

static void regionsRespectingWidth(double widths[], double heights[],
			    double cmWidth, double cmHeight,
			    pGEDevDesc dd)
{
    heightsRespectingWidths(heights, cmWidth, cmHeight, dd);
    regionsWithRespect(widths, heights, cmWidth, cmHeight, dd);
}

static void noCmRegions(double widths[], double heights[],
		 double cmWidth, double cmHeight, pGEDevDesc dd)
{
    switch (gpptr(dd)->rspct) {
    case 0:
	regionsWithoutRespect(widths, heights, dd);
	break;
    case 1:
	regionsWithRespect(widths, heights, cmWidth, cmHeight, dd);
	break;
    case 2:
	if (tallLayout(cmWidth, cmHeight, dd))
	    regionsRespectingWidth(widths, heights, cmWidth, cmHeight, dd);
	else
	    regionsRespectingHeight(widths, heights, cmWidth, cmHeight, dd);
    }
}

static void notAllCmRegions(double widths[], double heights[],
			    double cmWidth, double cmHeight, pGEDevDesc dd)
{
    double newCmWidth, newCmHeight;
    newCmWidth = cmWidth - sumCmWidths(dd);
    newCmHeight = cmHeight - sumCmHeights(dd);
    noCmRegions(widths, heights, newCmWidth, newCmHeight, dd);
    allocDimension(widths, cmWidth, gpptr(dd)->numcols, gpptr(dd)->cmWidths, 1);
    allocDimension(heights, cmHeight, gpptr(dd)->numrows, gpptr(dd)->cmHeights, 1);
    modifyDimension(widths, newCmWidth/cmWidth, gpptr(dd)->numcols,
		    gpptr(dd)->cmWidths);
    modifyDimension(heights, newCmHeight/cmHeight, gpptr(dd)->numrows,
		    gpptr(dd)->cmHeights);
}

static void widthCmRegions(double widths[], double heights[],
			   double cmWidth, double cmHeight, pGEDevDesc dd)
{
    allocDimension(widths, cmWidth, gpptr(dd)->numcols, gpptr(dd)->cmWidths, 1);
    allocDimension(heights, sumHeights(dd), gpptr(dd)->numrows,
		   gpptr(dd)->cmHeights, 0);
    modifyDimension(heights, (cmHeight - sumCmHeights(dd))/cmHeight,
		    gpptr(dd)->numrows, gpptr(dd)->cmHeights);
    allocDimension(heights, cmHeight, gpptr(dd)->numrows,
		   gpptr(dd)->cmHeights, 1);
}

static void heightCmRegions(double widths[], double heights[],
			    double cmWidth, double cmHeight, pGEDevDesc dd)
{
    allocDimension(heights, cmHeight, gpptr(dd)->numrows, gpptr(dd)->cmHeights, 1);
    allocDimension(widths, sumWidths(dd), gpptr(dd)->numcols,
		   gpptr(dd)->cmWidths, 0);
    modifyDimension(widths, (cmWidth - sumCmWidths(dd))/cmWidth,
		    gpptr(dd)->numcols, gpptr(dd)->cmWidths);
    allocDimension(widths, cmWidth, gpptr(dd)->numcols,
		   gpptr(dd)->cmWidths, 1);
}

static Rboolean allCmWidths(pGEDevDesc dd)
{
    int j;
    for (j = 0; j < gpptr(dd)->numcols; j++)
	if (!gpptr(dd)->cmWidths[j])
	    return FALSE;
    return TRUE;
}

static Rboolean allCmHeights(pGEDevDesc dd)
{
    int i;
    for (i = 0; i < gpptr(dd)->numrows; i++)
	if (!gpptr(dd)->cmHeights[i])
	    return FALSE;
    return TRUE;
}

static Rboolean noCmWidths(pGEDevDesc dd)
{
    int j;
    for (j = 0; j < gpptr(dd)->numcols; j++)
	if (gpptr(dd)->cmWidths[j])
	    return FALSE;
    return TRUE;
}

static Rboolean noCmHeights(pGEDevDesc dd)
{
    int i;
    for (i = 0; i < gpptr(dd)->numrows; i++)
	if (gpptr(dd)->cmHeights[i])
	    return FALSE;
    return TRUE;
}

static void someCmRegions(double widths[], double heights[],
		   double cmWidth, double cmHeight, pGEDevDesc dd)
{
    if (allCmWidths(dd))
	widthCmRegions(widths, heights, cmWidth, cmHeight, dd);
    else if (allCmHeights(dd))
	heightCmRegions(widths, heights, cmWidth, cmHeight, dd);
    else
	notAllCmRegions(widths, heights, cmWidth, cmHeight, dd);
}

static Rboolean allCm(pGEDevDesc dd)
{
    return allCmWidths(dd) && allCmHeights(dd);
}

static Rboolean noCm(pGEDevDesc dd)
{
    return noCmWidths(dd) && noCmHeights(dd);
}

static void layoutRegions(double widths[], double heights[],
		   double cmWidth, double cmHeight, pGEDevDesc dd)
{
    int i, j;
    for (j = 0; j < gpptr(dd)->numcols; j++)
	widths[j] = gpptr(dd)->widths[j];
    for (i = 0; i < gpptr(dd)->numrows; i++)
	heights[i] = gpptr(dd)->heights[i];

    if (allCm(dd))
	allCmRegions(widths, heights, cmWidth, cmHeight, dd);
    else if (noCm(dd))
	noCmRegions(widths, heights, cmWidth, cmHeight, dd);
    else
	someCmRegions(widths, heights, cmWidth, cmHeight, dd);
}

static void subRegion(double *left, double *right, double *bottom, double *top,
		      int mincol, int maxcol,
		      int minrow, int maxrow,
		      double widths[], double heights[], pGEDevDesc dd)
{
    double totalWidth = sumRegions(widths, 0, gpptr(dd)->numcols-1);
    double totalHeight = sumRegions(heights, 0, gpptr(dd)->numrows-1);
    *left = (0.5 - totalWidth/2) + sumRegions(widths, 0, mincol-1);
    *right = (0.5 - totalWidth/2) + sumRegions(widths, 0, maxcol);
    *bottom = (0.5 - totalHeight/2) + totalHeight
	- sumRegions(heights, 0, maxrow);
    *top = (0.5 - totalHeight/2) + totalHeight
	- sumRegions(heights, 0, minrow-1);
}

/* a fudge for backwards compatibility (of sorts) with par(mfg) */
/* return the top-left-most row/col that the current figure */
/* occupies in the current layout */

void currentFigureLocation(int *row, int *col, pGEDevDesc dd)
{
    int maxcol, maxrow;
    if (gpptr(dd)->layout)
	figureExtent(col, &maxcol, row, &maxrow, gpptr(dd)->currentFigure, dd);
    else if (gpptr(dd)->mfind) { /* mfcol */
	*row = (gpptr(dd)->currentFigure - 1)%gpptr(dd)->numrows;
	*col = (gpptr(dd)->currentFigure - 1)/gpptr(dd)->numrows;
    }
    else { /* mfrow */
	*row = (gpptr(dd)->currentFigure - 1)/gpptr(dd)->numcols;
	*col = (gpptr(dd)->currentFigure - 1)%gpptr(dd)->numcols;
    }
}

/*  mapNDC2Dev -- transformation from NDC to Dev  */
/*  Use this coordinate system for outer margin coordinates  */
/*  This must be called if the device is resized */

static void mapNDC2Dev(pGEDevDesc dd)
{
    /* For new devices, have to check the device's idea of its size
     * in case there has been a resize.
     */
    double asp = dd->dev->ipr[1] / dd->dev->ipr[0];

    gpptr(dd)->ndc2dev.bx = dpptr(dd)->ndc2dev.bx =
	dd->dev->right - dd->dev->left;
    gpptr(dd)->ndc2dev.ax = dpptr(dd)->ndc2dev.ax = dd->dev->left;
    gpptr(dd)->ndc2dev.by = dpptr(dd)->ndc2dev.by =
	dd->dev->top - dd->dev->bottom;
    gpptr(dd)->ndc2dev.ay = dpptr(dd)->ndc2dev.ay = dd->dev->bottom;
    /* Units Conversion */

    gpptr(dd)->xNDCPerInch = dpptr(dd)->xNDCPerInch =
	1.0/fabs(gpptr(dd)->ndc2dev.bx * dd->dev->ipr[0]);
    gpptr(dd)->yNDCPerInch = dpptr(dd)->yNDCPerInch =
	1.0/fabs(gpptr(dd)->ndc2dev.by * dd->dev->ipr[1]);
    gpptr(dd)->xNDCPerChar = dpptr(dd)->xNDCPerChar =
	fabs(gpptr(dd)->cexbase * gpptr(dd)->scale *
	     dd->dev->cra[1] * asp / gpptr(dd)->ndc2dev.bx);
    gpptr(dd)->yNDCPerChar = dpptr(dd)->yNDCPerChar =
	fabs(gpptr(dd)->cexbase * gpptr(dd)->scale *
	     dd->dev->cra[1] / gpptr(dd)->ndc2dev.by);
    gpptr(dd)->xNDCPerLine = dpptr(dd)->xNDCPerLine =
	fabs(gpptr(dd)->mex * gpptr(dd)->cexbase * gpptr(dd)->scale *
	     dd->dev->cra[1] * asp / gpptr(dd)->ndc2dev.bx);
    gpptr(dd)->yNDCPerLine = dpptr(dd)->yNDCPerLine =
	fabs(gpptr(dd)->mex * gpptr(dd)->cexbase * gpptr(dd)->scale *
	     dd->dev->cra[1] / gpptr(dd)->ndc2dev.by);
}

static void updateOuterMargins(pGEDevDesc dd)
{
    switch (gpptr(dd)->oUnits) {
    case LINES:
	gpptr(dd)->omi[0] = dpptr(dd)->omi[0] =
	    GConvertYUnits(gpptr(dd)->oma[0], LINES, INCHES, dd);
	gpptr(dd)->omi[1] = dpptr(dd)->omi[1] =
	    GConvertXUnits(gpptr(dd)->oma[1], LINES, INCHES, dd);
	gpptr(dd)->omi[2] = dpptr(dd)->omi[2] =
	    GConvertYUnits(gpptr(dd)->oma[2], LINES, INCHES, dd);
	gpptr(dd)->omi[3] = dpptr(dd)->omi[3] =
	    GConvertXUnits(gpptr(dd)->oma[3], LINES, INCHES, dd);
	gpptr(dd)->omd[0] = dpptr(dd)->omd[0] =
	    GConvertXUnits(gpptr(dd)->oma[1], LINES, NDC, dd);
	gpptr(dd)->omd[1] = dpptr(dd)->omd[1] =
	    1 - GConvertXUnits(gpptr(dd)->oma[3], LINES, NDC, dd);
	gpptr(dd)->omd[2] = dpptr(dd)->omd[2] =
	    GConvertYUnits(gpptr(dd)->oma[0], LINES, NDC, dd);
	gpptr(dd)->omd[3] = dpptr(dd)->omd[3] =
	    1 - GConvertYUnits(gpptr(dd)->oma[2], LINES, NDC, dd);
	break;
    case INCHES:
	gpptr(dd)->oma[0] = dpptr(dd)->oma[0] =
	    GConvertYUnits(gpptr(dd)->omi[0], INCHES, LINES, dd);
	gpptr(dd)->oma[1] = dpptr(dd)->oma[1] =
	    GConvertXUnits(gpptr(dd)->omi[1], INCHES, LINES, dd);
	gpptr(dd)->oma[2] = dpptr(dd)->oma[2] =
	    GConvertYUnits(gpptr(dd)->omi[2], INCHES, LINES, dd);
	gpptr(dd)->oma[3] = dpptr(dd)->oma[3] =
	    GConvertXUnits(gpptr(dd)->omi[3], INCHES, LINES, dd);
	gpptr(dd)->omd[0] = dpptr(dd)->omd[0] =
	    GConvertXUnits(gpptr(dd)->omi[1], INCHES, NDC, dd);
	gpptr(dd)->omd[1] = dpptr(dd)->omd[1] =
	    1 - GConvertXUnits(gpptr(dd)->omi[3], INCHES, NDC, dd);
	gpptr(dd)->omd[2] = dpptr(dd)->omd[2] =
	    GConvertYUnits(gpptr(dd)->omi[0], INCHES, NDC, dd);
	gpptr(dd)->omd[3] = dpptr(dd)->omd[3] =
	    1 - GConvertYUnits(gpptr(dd)->omi[2], INCHES, NDC, dd);
	break;
    case NDC:
	gpptr(dd)->oma[0] = dpptr(dd)->oma[0] =
	    GConvertYUnits(gpptr(dd)->omd[2], NDC, LINES, dd);
	gpptr(dd)->oma[1] = dpptr(dd)->oma[1] =
	    GConvertXUnits(gpptr(dd)->omd[0], NDC, LINES, dd);
	gpptr(dd)->oma[2] = dpptr(dd)->oma[2] =
	    GConvertYUnits(1 - gpptr(dd)->omd[3], NDC, LINES, dd);
	gpptr(dd)->oma[3] = dpptr(dd)->oma[3] =
	    GConvertXUnits(1 - gpptr(dd)->omd[1], NDC, LINES, dd);
	gpptr(dd)->omi[0] = dpptr(dd)->omi[0] =
	    GConvertYUnits(gpptr(dd)->omd[2], NDC, INCHES, dd);
	gpptr(dd)->omi[1] = dpptr(dd)->omi[1] =
	    GConvertXUnits(gpptr(dd)->omd[0], NDC, INCHES, dd);
	gpptr(dd)->omi[2] = dpptr(dd)->omi[2] =
	    GConvertYUnits(1 - gpptr(dd)->omd[3], NDC, INCHES, dd);
	gpptr(dd)->omi[3] = dpptr(dd)->omi[3] =
	    GConvertXUnits(1 - gpptr(dd)->omd[1], NDC, INCHES, dd);
	break;
    default: break; /*nothing (-Wall) */
    }
}

/*  mapInner2Dev -- transformation from NIC to Dev  */
/*  Use this coordinate system for setting up multiple figures	*/
/*  This is also used when specifying the figure region directly  */
/*  Note that this is incompatible with S which uses then entire  */
/*  device surface for such a plot  */
/*  This must be called per DevNewPlot, if the NDCtoDev transformation */
/*  changes, and if oma changes */

static void mapInner2Dev(pGEDevDesc dd)
{
    double x0, x1, y0, y1;
    x0 = xLinetoDev(gpptr(dd)->oma[1], dd);
    y0 = yLinetoDev(gpptr(dd)->oma[0], dd);
    x1 = GConvertXUnits(gpptr(dd)->oma[3], LINES, NDC, dd);
    x1 = xNDCtoDev(1.0 - x1, dd);
    y1 = GConvertYUnits(gpptr(dd)->oma[2], LINES, NDC, dd);
    y1 = yNDCtoDev(1.0 - y1, dd);
    gpptr(dd)->inner2dev.bx = dpptr(dd)->inner2dev.bx = x1 - x0;
    gpptr(dd)->inner2dev.ax = dpptr(dd)->inner2dev.ax = x0;
    gpptr(dd)->inner2dev.by = dpptr(dd)->inner2dev.by = y1 - y0;
    gpptr(dd)->inner2dev.ay = dpptr(dd)->inner2dev.ay = y0;
}

/* mapFigureRegion -- calculate figure region in NIC  */

static void mapFigureRegion(pGEDevDesc dd)
{
    int mincol, maxcol, minrow, maxrow;
    double x0, x1, y0, y1;
    double widths[MAX_LAYOUT_COLS], heights[MAX_LAYOUT_ROWS];
    if (gpptr(dd)->layout) {
	layoutRegions(widths, heights,
		      GConvertXUnits(1.0, NIC, INCHES, dd)*2.54,
		      GConvertYUnits(1.0, NIC, INCHES, dd)*2.54, dd);
	figureExtent(&mincol, &maxcol, &minrow, &maxrow,
		     gpptr(dd)->currentFigure, dd);
	subRegion(&x0, &x1, &y0, &y1,
		  mincol, maxcol, minrow, maxrow,
		  widths, heights, dd);
    }
    else {
	int row, col;
	if (gpptr(dd)->mfind) {
	    col = (gpptr(dd)->currentFigure-1) / gpptr(dd)->numrows + 1;
	    row = gpptr(dd)->currentFigure - (col-1)*gpptr(dd)->numrows;
	}
	else {
	    row = (gpptr(dd)->currentFigure-1) / gpptr(dd)->numcols + 1;
	    col = gpptr(dd)->currentFigure - (row-1)*gpptr(dd)->numcols;
	}
	x0 = (double) (col-1) / gpptr(dd)->numcols;
	x1 = (double) col / gpptr(dd)->numcols;
	y0 = (double) (gpptr(dd)->numrows - row) / gpptr(dd)->numrows;
	y1 = (double) (gpptr(dd)->numrows - row + 1) / gpptr(dd)->numrows;
    }
    gpptr(dd)->fig[0] = dpptr(dd)->fig[0] = x0;
    gpptr(dd)->fig[1] = dpptr(dd)->fig[1] = x1;
    gpptr(dd)->fig[2] = dpptr(dd)->fig[2] = y0;
    gpptr(dd)->fig[3] = dpptr(dd)->fig[3] = y1;
    gpptr(dd)->fUnits = dpptr(dd)->fUnits = NIC;
}

static void updateFigureRegion(pGEDevDesc dd)
{
    double nicWidth, nicHeight;
    switch (gpptr(dd)->fUnits) {
    case NIC:
	gpptr(dd)->fin[0] = dpptr(dd)->fin[0] =
	    GConvertXUnits(gpptr(dd)->fig[1] - gpptr(dd)->fig[0], NIC, INCHES, dd);
	gpptr(dd)->fin[1] = dpptr(dd)->fin[1] =
	    GConvertYUnits(gpptr(dd)->fig[3] - gpptr(dd)->fig[2], NIC, INCHES, dd);
	break;
    case INCHES:
	nicWidth = GConvertXUnits(gpptr(dd)->fin[0], INCHES, NIC, dd);
	nicHeight = GConvertYUnits(gpptr(dd)->fin[1], INCHES, NIC, dd);
	gpptr(dd)->fig[0] = dpptr(dd)->fig[0] = 0.5 - nicWidth/2;
	gpptr(dd)->fig[1] = dpptr(dd)->fig[1] = gpptr(dd)->fig[0] + nicWidth;
	gpptr(dd)->fig[2] = dpptr(dd)->fig[2] = 0.5 - nicHeight/2;
	gpptr(dd)->fig[3] = dpptr(dd)->fig[3] = gpptr(dd)->fig[2] + nicHeight;
	break;
    default: /*nothing*/ break;
    }
}

/*  mapFig2Dev -- Transformation from NFC to Dev  */
/* This must be called per plot.new and if the NICtoDev transformation */
/* changes */

static void mapFig2Dev(pGEDevDesc dd)
{
    double x0, x1, y0, y1;
    y0 = yNICtoDev(gpptr(dd)->fig[2], dd);
    y1 = yNICtoDev(gpptr(dd)->fig[3], dd);
    x0 = xNICtoDev(gpptr(dd)->fig[0], dd);
    x1 = xNICtoDev(gpptr(dd)->fig[1], dd);
    gpptr(dd)->fig2dev.bx = dpptr(dd)->fig2dev.bx = x1 - x0;
    gpptr(dd)->fig2dev.ax = dpptr(dd)->fig2dev.ax = x0;
    gpptr(dd)->fig2dev.by = dpptr(dd)->fig2dev.by = y1 - y0;
    gpptr(dd)->fig2dev.ay = dpptr(dd)->fig2dev.ay = y0;
}

static void updateFigureMargins(pGEDevDesc dd)
{
    switch (gpptr(dd)->mUnits) {
    case LINES:
	gpptr(dd)->mai[0] = dpptr(dd)->mai[0] =
	    GConvertYUnits(gpptr(dd)->mar[0], LINES, INCHES, dd);
	gpptr(dd)->mai[1] = dpptr(dd)->mai[1] =
	    GConvertXUnits(gpptr(dd)->mar[1], LINES, INCHES, dd);
	gpptr(dd)->mai[2] = dpptr(dd)->mai[2] =
	    GConvertYUnits(gpptr(dd)->mar[2], LINES, INCHES, dd);
	gpptr(dd)->mai[3] = dpptr(dd)->mai[3] =
	    GConvertXUnits(gpptr(dd)->mar[3], LINES, INCHES, dd);
	break;
    case INCHES:
	gpptr(dd)->mar[0] = dpptr(dd)->mar[0] =
	    GConvertYUnits(gpptr(dd)->mai[0], INCHES, LINES, dd);
	gpptr(dd)->mar[1] = dpptr(dd)->mar[1] =
	    GConvertXUnits(gpptr(dd)->mai[1], INCHES, LINES, dd);
	gpptr(dd)->mar[2] = dpptr(dd)->mar[2] =
	    GConvertYUnits(gpptr(dd)->mai[2], INCHES, LINES, dd);
	gpptr(dd)->mar[3] = dpptr(dd)->mar[3] =
	    GConvertXUnits(gpptr(dd)->mai[3], INCHES, LINES, dd);
	break;
    default: /*nothing*/ break;
    }
}

/* mapPlotRegion -- plot region in NFC */

static void mapPlotRegion(pGEDevDesc dd)
{
    double x0, x1, y0, y1;
    x0 = GConvertXUnits(gpptr(dd)->mar[1], LINES, NFC, dd);
    y0 = GConvertYUnits(gpptr(dd)->mar[0], LINES, NFC, dd);
    x1 = 1.0 - GConvertXUnits(gpptr(dd)->mar[3], LINES, NFC, dd);
    y1 = 1.0 - GConvertYUnits(gpptr(dd)->mar[2], LINES, NFC, dd);
    if(gpptr(dd)->pty == 's') {
	/* maximal plot size in inches */
	double center, width, height;
	double inchWidth = GConvertXUnits(x1 - x0, NFC, INCHES, dd);
	double inchHeight = GConvertYUnits(y1 - y0, NFC, INCHES, dd);
	/* shrink the longer side */
	if (inchWidth > inchHeight) {
	    width = 0.5*GConvertXUnits(inchHeight, INCHES, NFC, dd);
	    center = 0.5*(x1 + x0);
	    x0 = center-width;
	    x1 = center+width;
	}
	else {
	    height = 0.5*GConvertYUnits(inchWidth, INCHES, NFC, dd);
	    center = 0.5*(y1 + y0);
	    y0 = center-height;
	    y1 = center+height;
	}
    }
    gpptr(dd)->plt[0] = dpptr(dd)->plt[0] = x0;
    gpptr(dd)->plt[1] = dpptr(dd)->plt[1] = x1;
    gpptr(dd)->plt[2] = dpptr(dd)->plt[2] = y0;
    gpptr(dd)->plt[3] = dpptr(dd)->plt[3] = y1;
    gpptr(dd)->pUnits = dpptr(dd)->pUnits = NFC;
}

static void updatePlotRegion(pGEDevDesc dd)
{
    double nfcWidth, nfcHeight;
    switch (gpptr(dd)->pUnits) {
    case NFC:
	gpptr(dd)->pin[0] = dpptr(dd)->pin[0] =
	    GConvertXUnits(gpptr(dd)->plt[1] - gpptr(dd)->plt[0], NFC, INCHES, dd);
	gpptr(dd)->pin[1] = dpptr(dd)->pin[1] =
	    GConvertYUnits(gpptr(dd)->plt[3] - gpptr(dd)->plt[2], NFC, INCHES, dd);
	break;
    case INCHES:
	nfcWidth = GConvertXUnits(gpptr(dd)->pin[0], INCHES, NFC, dd);
	nfcHeight = GConvertYUnits(gpptr(dd)->pin[1], INCHES, NFC, dd);
	gpptr(dd)->plt[0] = dpptr(dd)->plt[0] = 0.5 - nfcWidth/2;
	gpptr(dd)->plt[1] = dpptr(dd)->plt[1] = gpptr(dd)->plt[0] + nfcWidth;
	gpptr(dd)->plt[2] = dpptr(dd)->plt[2] = 0.5 - nfcHeight/2;
	gpptr(dd)->plt[3] = dpptr(dd)->plt[3] = gpptr(dd)->plt[2] + nfcHeight;
	break;
    default: /*nothing*/ break;
    }
}

/*  GMapWin2Fig -- transformation from Usr to NFC */

void GMapWin2Fig(pGEDevDesc dd)
{
    if (gpptr(dd)->xlog) {
	gpptr(dd)->win2fig.bx = dpptr(dd)->win2fig.bx =
	    (gpptr(dd)->plt[1] - gpptr(dd)->plt[0])/
	    (gpptr(dd)->logusr[1] - gpptr(dd)->logusr[0]);
	gpptr(dd)->win2fig.ax = dpptr(dd)->win2fig.ax =
	    gpptr(dd)->plt[0] - gpptr(dd)->win2fig.bx * gpptr(dd)->logusr[0];
    }
    else {
	gpptr(dd)->win2fig.bx = dpptr(dd)->win2fig.bx =
	    (gpptr(dd)->plt[1] - gpptr(dd)->plt[0])/
	    (gpptr(dd)->usr[1] - gpptr(dd)->usr[0]);
	gpptr(dd)->win2fig.ax = dpptr(dd)->win2fig.ax =
	    gpptr(dd)->plt[0] - gpptr(dd)->win2fig.bx * gpptr(dd)->usr[0];
    }
    if (gpptr(dd)->ylog) {
	gpptr(dd)->win2fig.by = dpptr(dd)->win2fig.by =
	    (gpptr(dd)->plt[3] - gpptr(dd)->plt[2])/
	    (gpptr(dd)->logusr[3] - gpptr(dd)->logusr[2]);
	gpptr(dd)->win2fig.ay = dpptr(dd)->win2fig.ay =
	    gpptr(dd)->plt[2] - gpptr(dd)->win2fig.by * gpptr(dd)->logusr[2];
    }
    else {
	gpptr(dd)->win2fig.by = dpptr(dd)->win2fig.by =
	    (gpptr(dd)->plt[3] - gpptr(dd)->plt[2])/
	    (gpptr(dd)->usr[3] - gpptr(dd)->usr[2]);
	gpptr(dd)->win2fig.ay = dpptr(dd)->win2fig.ay =
	    gpptr(dd)->plt[2] - gpptr(dd)->win2fig.by * gpptr(dd)->usr[2];
    }
}

/*  mapping -- Set up mappings between coordinate systems  */
/*  This is the user's interface to the mapping routines above */

static
void mapping(pGEDevDesc dd, int which)
{
    switch(which) {
    case 0:
	mapNDC2Dev(dd);
    case 1:
	updateOuterMargins(dd);
	mapInner2Dev(dd);
    case 2:
	if (gpptr(dd)->defaultFigure)
	    mapFigureRegion(dd);
	updateFigureRegion(dd);
	mapFig2Dev(dd);
    case 3:
	updateFigureMargins(dd);
	if (gpptr(dd)->defaultPlot)
	    mapPlotRegion(dd);
	updatePlotRegion(dd);
    }
}

/*  GReset -- Reset coordinate systems mappings and unit yardsticks */

void GReset(pGEDevDesc dd)
{
    /* Character extents are based on the raster size */
    gpptr(dd)->mkh = gpptr(dd)->scale * dd->dev->cra[0]
	* dd->dev->ipr[0];

    /* Recompute Mappings */
    mapping(dd, 0);
}

/*  Is the figure region too big ? */

/* Why is this FLT_EPSILON? */
static Rboolean validFigureRegion(pGEDevDesc dd)
{
    return ((gpptr(dd)->fig[0] > 0-FLT_EPSILON) &&
	    (gpptr(dd)->fig[1] < 1+FLT_EPSILON) &&
	    (gpptr(dd)->fig[2] > 0-FLT_EPSILON) &&
	    (gpptr(dd)->fig[3] < 1+FLT_EPSILON));
}

/*  Is the figure region too small ? */

static Rboolean validOuterMargins(pGEDevDesc dd)
{
    return ((gpptr(dd)->fig[0] < gpptr(dd)->fig[1]) &&
	    (gpptr(dd)->fig[2] < gpptr(dd)->fig[3]));
}

/* Is the plot region too big ? */

static Rboolean validPlotRegion(pGEDevDesc dd)
{
    return ((gpptr(dd)->plt[0] > 0-FLT_EPSILON) &&
	    (gpptr(dd)->plt[1] < 1+FLT_EPSILON) &&
	    (gpptr(dd)->plt[2] > 0-FLT_EPSILON) &&
	    (gpptr(dd)->plt[3] < 1+FLT_EPSILON));
}

/* Is the plot region too small ? */

static Rboolean validFigureMargins(pGEDevDesc dd)
{
    return ((gpptr(dd)->plt[0] < gpptr(dd)->plt[1]) &&
	    (gpptr(dd)->plt[2] < gpptr(dd)->plt[3]));
}

static void NORET invalidError(const char *message, pGEDevDesc dd)
{
    dpptr(dd)->currentFigure -= 1;
    if (dpptr(dd)->currentFigure < 1)
	dpptr(dd)->currentFigure = dpptr(dd)->lastFigure;
    gpptr(dd)->currentFigure = dpptr(dd)->currentFigure;
    error(message);
}

Rboolean GRecording(SEXP call, pGEDevDesc dd)
{
    return GErecording(call, dd);
}

/*  GNewPlot -- Begin a new plot (advance to new frame if needed)  */
pGEDevDesc GNewPlot(Rboolean recording)
{
    pGEDevDesc dd;

    /* Restore Default Parameters */

    dd = GEcurrentDevice();
    GRestore(dd);

    /* GNewPlot always starts a new plot UNLESS the user has set
     * gpptr(dd)->new to TRUE by par(new=TRUE)
     * If gpptr(dd)->new is FALSE, we leave it that way (further GNewPlot's
     * will move on to subsequent plots)
     * If gpptr(dd)->new is TRUE, any subsequent drawing will dirty the plot
     * and reset gpptr(dd)->new to FALSE
     */

    /* we can call par(mfg) before any plotting.
       That sets new = TRUE and also sets currentFigure <= lastFigure
       so treat separately. */

    /* The logic for when to start a new page is mimiced in the
     * read-only par("page") in par.c, SO if you make changes
     * to the logic here, you will need to change that as well
     */
    if (!gpptr(dd)->new) {
	R_GE_gcontext gc;
	gcontextFromGP(&gc, dd);
	dpptr(dd)->currentFigure += 1;
	gpptr(dd)->currentFigure = dpptr(dd)->currentFigure;
	if (gpptr(dd)->currentFigure > gpptr(dd)->lastFigure) {
	    if (recording) {
		if (dd->ask) {
		    NewFrameConfirm(dd->dev);
		    /*
		     * User may have killed device during pause for prompt
		     */
		    if (NoDevices())
			error(_("attempt to plot on null device"));
		    else
			dd = GEcurrentDevice();
		}
		GEinitDisplayList(dd);
	    }
	    GENewPage(&gc, dd);
	    dpptr(dd)->currentFigure = gpptr(dd)->currentFigure = 1;
	}

	GReset(dd);
	GForceClip(dd);
    } else if(!gpptr(dd)->state) { /* device is unused */
	R_GE_gcontext gc;
	gcontextFromGP(&gc, dd);
	if (recording) {
	    if (dd->ask) {
		NewFrameConfirm(dd->dev);
		/*
		 * User may have killed device during pause for prompt
		 */
		if (NoDevices())
		    error(_("attempt to plot on null device"));
		else
		    dd = GEcurrentDevice();
	    }
	    GEinitDisplayList(dd);
	}
	GENewPage(&gc, dd);
	dpptr(dd)->currentFigure = gpptr(dd)->currentFigure = 1;
	GReset(dd);
	GForceClip(dd);
    }

    /* IF the division of the device into separate regions */
    /* has resulted in any invalid regions ... */
    /* IF this was a user command (i.e., we are recording) */
    /* send an error message to the command line */
    /* IF we are replaying then draw a message in the output */

#define G_ERR_MSG(msg)			\
	if (recording)			\
	    invalidError(msg, dd);	\
	else {				\
	    int xpdsaved = gpptr(dd)->xpd; \
	    gpptr(dd)->xpd = 2; \
	    GText(0.5,0.5, NFC, msg, -1, 0.5,0.5,  0, dd);  \
	    gpptr(dd)->xpd = xpdsaved; \
	}

    dpptr(dd)->valid = gpptr(dd)->valid = FALSE;
    if (!validOuterMargins(dd)) {
	G_ERR_MSG(_("outer margins too large (figure region too small)"));
    } else if (!validFigureRegion(dd)) {
	G_ERR_MSG(_("figure region too large"));
    } else if (!validFigureMargins(dd)) {
	G_ERR_MSG(_("figure margins too large"));
    } else if (!validPlotRegion(dd)) {
	G_ERR_MSG(_("plot region too large"));
    } else {
	dpptr(dd)->valid = gpptr(dd)->valid = TRUE;
	/*
	 * At this point, base output has been successfully
	 * produced on the device, so mark the device "dirty"
	 * with respect to base graphics.
	 * This is used when checking whether the device is
	 * "valid" with respect to base graphics
	 */
	Rf_setBaseDevice(TRUE, dd);
	GEdirtyDevice(dd);
    }

    return dd;
}
#undef G_ERR_MSG

/*
// (usr, log, n_inp) |--> (axp = (min, max), n_out) :

void GAxisPars(double *min, double *max, int *n, Rboolean log, int axis)

* ----> in src/main/graphics.c (as used in grDevices too)
*          ------------------- */

void GScale(double min, double max, int axis, pGEDevDesc dd)
{
/* GScale: used to default axis information
 *	   i.e., if user has NOT specified par(usr=...)
 * NB: can have min > max !
 */
#define EPS_FAC_1  16

    Rboolean is_xaxis = (axis == 1 || axis == 3);
    int log, n, style;
    double temp, min_o = 0., max_o = 0., tmp2 = 0.;/*-Wall*/

    if(is_xaxis) {
	n = gpptr(dd)->lab[0];
	style = gpptr(dd)->xaxs;
	log = gpptr(dd)->xlog;
    }
    else {
	n = gpptr(dd)->lab[1];
	style = gpptr(dd)->yaxs;
	log = gpptr(dd)->ylog;
    }

    if (log) {
	/*  keep original  min, max - to use in extremis */
	min_o = min; max_o = max;
	min = log10(min);
	max = log10(max);
    }
    if(!R_FINITE(min) || !R_FINITE(max)) {
	warning(_("nonfinite axis limits [GScale(%g,%g,%d, .); log=%d]"),
		min, max, axis, log);
	if(!R_FINITE(min)) min = - .45 * DBL_MAX;
	if(!R_FINITE(max)) max = + .45 * DBL_MAX;
	/* max - min is now finite */
    }
    /* Version <= 1.2.0 had
       if (min == max)	 -- exact equality for real numbers */
    temp = fmax2(fabs(max), fabs(min));
    if(temp == 0) {/* min = max = 0 */
	min = -1;
	max =  1;
    }
    else if(fabs(max - min) < temp * EPS_FAC_1 * DBL_EPSILON) {
	temp *= (min == max) ? .4 : 1e-2;
	min -= temp;
	max += temp;
    }

    switch(style) {
    case 'r':
	temp = 0.04 * (max-min);
	min -= temp;
	max += temp;
	break;
    case 'i':
	break;
    case 's':/* FIXME --- implement  's' and 'e' axis styles ! */
    case 'e':
    default:
	error(_("axis style \"%c\" unimplemented"), style);
    }

    if (log) { /* 10^max may have gotten +Inf ; or  10^min has become 0 */
	if((temp = Rexp10(min)) == 0.) {/* or < 1.01*DBL_MIN */
	    temp = fmin2(min_o, 1.01* DBL_MIN); /* allow smaller non 0 */
	    min = log10(temp);
	}
	if(max >= 308.25) { /* overflows */
	    tmp2 = fmax2(max_o, .99 * DBL_MAX);
	    max = log10(tmp2);
	} else tmp2 = Rexp10(max);
    }
    if(is_xaxis) {
	if (log) {
	    gpptr(dd)->usr[0] = dpptr(dd)->usr[0] = temp;
	    gpptr(dd)->usr[1] = dpptr(dd)->usr[1] = tmp2;
	    gpptr(dd)->logusr[0] = dpptr(dd)->logusr[0] = min;
	    gpptr(dd)->logusr[1] = dpptr(dd)->logusr[1] = max;
	} else {
	    gpptr(dd)->usr[0] = dpptr(dd)->usr[0] = min;
	    gpptr(dd)->usr[1] = dpptr(dd)->usr[1] = max;
	}
    } else {
	if (log) {
	    gpptr(dd)->usr[2] = dpptr(dd)->usr[2] = temp;
	    gpptr(dd)->usr[3] = dpptr(dd)->usr[3] = tmp2;
	    gpptr(dd)->logusr[2] = dpptr(dd)->logusr[2] = min;
	    gpptr(dd)->logusr[3] = dpptr(dd)->logusr[3] = max;
	} else {
	    gpptr(dd)->usr[2] = dpptr(dd)->usr[2] = min;
	    gpptr(dd)->usr[3] = dpptr(dd)->usr[3] = max;
	}
    }

    /* This is not directly needed when [xy]axt = "n",
     * but may later be different in another call to axis(), e.g.:
      > plot(1, xaxt = "n");  axis(1)
     * In that case, do_axis() should do the following:
     */

    // Computation of [xy]axp[0:2] == (min,max,n) :
    GAxisPars(&min, &max, &n, log, axis);

#define G_Store_AXP(is_X)			\
    if(is_X) {					\
	gpptr(dd)->xaxp[0] = dpptr(dd)->xaxp[0] = min;	\
	gpptr(dd)->xaxp[1] = dpptr(dd)->xaxp[1] = max;	\
	gpptr(dd)->xaxp[2] = dpptr(dd)->xaxp[2] = n;	\
    }						\
    else {					\
	gpptr(dd)->yaxp[0] = dpptr(dd)->yaxp[0] = min;	\
	gpptr(dd)->yaxp[1] = dpptr(dd)->yaxp[1] = max;	\
	gpptr(dd)->yaxp[2] = dpptr(dd)->yaxp[2] = n;	\
    }

    G_Store_AXP(is_xaxis);
}
#undef EPS_FAC_1
#undef EPS_FAC_2

void GSetupAxis(int axis, pGEDevDesc dd)
{
/*  GSetupAxis -- Set up the default axis information
 *		  called when user specifies	par(usr =...) */
/*  What should happen if			------------
 *   xlog or ylog = TRUE ? */
    double min, max;
    int n;
    Rboolean is_xaxis = (axis == 1 || axis == 3);

    if(is_xaxis) {
	n = gpptr(dd)->lab[0];
	min = gpptr(dd)->usr[0];
	max = gpptr(dd)->usr[1];
    }
    else {
	n = gpptr(dd)->lab[1];
	min = gpptr(dd)->usr[2];
	max = gpptr(dd)->usr[3];
    }

    GPretty(&min, &max, &n);

    G_Store_AXP(is_xaxis);
}
#undef G_Store_AXP

/*-------------------------------------------------------------------
 *
 *  GPAR FUNCTIONS
 *
 */


/* Set default graphics parameter values in a GPar.
 * This initialises the plot state, plus the graphical
 * parameters that are not the responsibility of the device initialisation.

 * Called from baseCallback.
 */

void GInit(GPar *dp)
{
    dp->state = 0;
    dp->valid = FALSE;

    dp->ann = TRUE;
    dp->err = 0;
    dp->bty = 'o';

    dp->mkh = .001;/* dummy value > 0  --- set in GReset : unused in R */
    dp->cex = 1.0;
    dp->lheight = 1.0;
    dp->cexbase = 1.0;
    dp->cexmain = 1.2;
    dp->cexlab = 1.0;
    dp->cexsub = 1.0;
    dp->cexaxis = 1.0;

    dp->col = R_RGB(0, 0, 0);
    dp->colmain = R_RGB(0, 0, 0);
    dp->collab = R_RGB(0, 0, 0);
    dp->colsub = R_RGB(0, 0, 0);
    dp->colaxis = R_RGB(0, 0, 0);
    dp->gamma = 1;

    dp->scale = 1.0;
    strcpy(dp->family, "");
    dp->font = 1;
    dp->fontmain = 2;
    dp->fontlab = 1;
    dp->fontsub = 1;
    dp->fontaxis = 1;

    dp->pch = 1;
    dp->lty = LTY_SOLID;
    dp->lend = GE_ROUND_CAP;
    dp->ljoin = GE_ROUND_JOIN;
    dp->lmitre = 10.0;
    dp->smo = 1;

    /* String Adjustment and rotation */
    dp->adj = 0.5;
    dp->crt = 0.0;
    dp->srt = 0.0;

    /* Positioning of margin text */
    dp->mgp[0] = 3;
    dp->mgp[1] = 1;
    dp->mgp[2] = 0;

    /* Axis annotation parameters */
    dp->lab[0] = 5;
    dp->lab[1] = 5;
    dp->lab[2] = 7;
    dp->las = 0;
    dp->tck = NA_REAL;
    dp->tcl = -0.5;
    dp->xaxp[0] = 0.0;
    dp->xaxp[1] = 1.0;
    dp->xaxp[2] = 5.0;
    dp->xaxs = 'r';
    dp->xaxt = 's';
    dp->xlog = FALSE;
    dp->xpd = 0;
    dp->oldxpd = -99;
    dp->yaxp[0] = 0.0;
    dp->yaxp[1] = 1.0;
    dp->yaxp[2] = 5.0;
    dp->yaxs = 'r';
    dp->yaxt = 's';
    dp->ylog = FALSE;

    /* Outer Margins */
    dp->mex = 1.0;
    dp->oma[0] = 0.0;
    dp->oma[1] = 0.0;
    dp->oma[2] = 0.0;
    dp->oma[3] = 0.0;
    dp->oUnits = LINES;
    dp->fig[0] = 0.0;
    dp->fig[1] = 1.0;
    dp->fig[2] = 0.0;
    dp->fig[3] = 1.0;
    dp->fUnits = NIC;
    dp->defaultFigure = TRUE;	/* the figure region is calculated from */
				/* the layout by default */
    dp->pUnits = NFC;
    dp->defaultPlot = TRUE;	/* the plot region is calculated as */
				/* figure-margin by default */

    /* Inner Margins */
    dp->mar[0] = 5.1;
    dp->mar[1] = 4.1;
    dp->mar[2] = 4.1;
    dp->mar[3] = 2.1;
    dp->mUnits = LINES;

    /* Multi-figure parameters */
    dp->layout = FALSE;
    dp->mfind  = 0;

    dp->numrows = 1;
    dp->numcols = 1;
    dp->currentFigure = 1;
    dp->lastFigure = 1;
    dp->heights[0] = 1;
    dp->widths[0] = 1;
    dp->cmHeights[0] = 0;
    dp->cmWidths[0] = 0;
    dp->order[0] = 1;
    dp->rspct = 0;
    dp->respect[0] = 0;

    /* Misc plotting parameters */
    dp->new = FALSE;
    dp->devmode = -99;
    dp->pty = 'm';
    dp->lwd = 1;

    /* Data window */
    dp->usr[0] = 0.0;
    dp->usr[1] = 1.0;
    dp->usr[2] = 0.0;
    dp->usr[3] = 1.0;
}

/* Copy a GPar structure from source to dest. */
void copyGPar(GPar *source, GPar *dest)
{
    memcpy(dest, source, sizeof(GPar));
}


/* Restore the graphics parameters from the device copy. */
void GRestore(pGEDevDesc dd)
{
    if (NoDevices()) error(_("no graphics device is active"));
    copyGPar(dpptr(dd), gpptr(dd));
}


/* FIXME: reorganize this as a memcpy */

/*  Saving and restoring of "inline" graphical	*/
/*  parameters.	 These are the ones which can be  */
/*  specified as a arguments to high-level  */
/*  graphics functions.	 */

static double	adjsave;	/* adj */
static int	annsave;	/* ann */
static char	btysave;	/* bty */
static double	cexsave;	/* cex */
static double   lheightsave;
static double	cexbasesave;	/* cexbase */
static double	cexmainsave;	/* cex.main */
static double	cexlabsave;	/* cex.lab */
static double	cexsubsave;	/* cex.sub */
static double	cexaxissave;	/* cex.axis */
static int	colsave;	/* col */
static int	fgsave;		/* fg */
static int	bgsave;		/* bg */
static int	colmainsave;	/* col.main */
static int	collabsave;	/* col.lab */
static int	colsubsave;	/* col.sub */
static int	colaxissave;	/* col.axis */
static double	crtsave;	/* character rotation */
static char     familysave[201];
static int	fontsave;	/* font */
static int	fontmainsave;	/* font.main */
static int	fontlabsave;	/* font.lab */
static int	fontsubsave;	/* font.sub */
static int	fontaxissave;	/* font.axis */
static int	errsave;	/* error mode */
static int	labsave[3];	/* axis labelling parameters */
static int	lassave;	/* label style */
static int	ltysave;	/* line type */
static double	lwdsave;	/* line width */
static R_GE_lineend lendsave;
static R_GE_linejoin ljoinsave;
static double   lmitresave;
static double	mgpsave[3];	/* margin position for annotation */
static double	mkhsave;	/* mark height */
static int	pchsave;	/* plotting character */
static double	srtsave;	/* string rotation */
static double	tcksave;	/* tick mark length */
static double	tclsave;	/* tick mark length in LINES */
static double	xaxpsave[3];	/* x axis parameters */
static char	xaxssave;	/* x axis calculation style */
static char	xaxtsave;	/* x axis type */
static int	xpdsave;	/* clipping control */
static double	yaxpsave[3];	/* y axis parameters */
static char	yaxssave;	/* y axis calculation style */
static char	yaxtsave;	/* y axis type */


/* Make a temporary copy of the inline parameter values. */
void GSavePars(pGEDevDesc dd)
{
    adjsave = gpptr(dd)->adj;
    annsave = gpptr(dd)->ann;
    btysave = gpptr(dd)->bty;
    cexsave = gpptr(dd)->cex;
    lheightsave = gpptr(dd)->lheight;
    cexbasesave = gpptr(dd)->cexbase;
    cexlabsave = gpptr(dd)->cexlab;
    cexmainsave = gpptr(dd)->cexmain;
    cexsubsave = gpptr(dd)->cexsub;
    cexaxissave = gpptr(dd)->cexaxis;
    colsave = gpptr(dd)->col;
    fgsave = gpptr(dd)->fg;
    bgsave = gpptr(dd)->bg;
    collabsave = gpptr(dd)->collab;
    colmainsave = gpptr(dd)->colmain;
    colsubsave = gpptr(dd)->colsub;
    colaxissave = gpptr(dd)->colaxis;
    crtsave = gpptr(dd)->crt;
    errsave = gpptr(dd)->err;
    strncpy(familysave, gpptr(dd)->family, 201);
    fontsave = gpptr(dd)->font;
    fontmainsave = gpptr(dd)->fontmain;
    fontlabsave = gpptr(dd)->fontlab;
    fontsubsave = gpptr(dd)->fontsub;
    fontaxissave = gpptr(dd)->fontaxis;
    labsave[0] = gpptr(dd)->lab[0];
    labsave[1] = gpptr(dd)->lab[1];
    labsave[2] = gpptr(dd)->lab[2];
    lassave = gpptr(dd)->las;
    ltysave = gpptr(dd)->lty;
    lwdsave = gpptr(dd)->lwd;
    lendsave = gpptr(dd)->lend;
    ljoinsave = gpptr(dd)->ljoin;
    lmitresave = gpptr(dd)->lmitre;
    mgpsave[0] = gpptr(dd)->mgp[0];
    mgpsave[1] = gpptr(dd)->mgp[1];
    mgpsave[2] = gpptr(dd)->mgp[2];
    mkhsave = gpptr(dd)->mkh;
    pchsave = gpptr(dd)->pch;
    srtsave = gpptr(dd)->srt;
    tcksave = gpptr(dd)->tck;
    tclsave = gpptr(dd)->tcl;
    xaxpsave[0] = gpptr(dd)->xaxp[0];
    xaxpsave[1] = gpptr(dd)->xaxp[1];
    xaxpsave[2] = gpptr(dd)->xaxp[2];
    xaxssave = gpptr(dd)->xaxs;
    xaxtsave = gpptr(dd)->xaxt;
    xpdsave = gpptr(dd)->xpd;
    yaxpsave[0] = gpptr(dd)->yaxp[0];
    yaxpsave[1] = gpptr(dd)->yaxp[1];
    yaxpsave[2] = gpptr(dd)->yaxp[2];
    yaxssave = gpptr(dd)->yaxs;
    yaxtsave = gpptr(dd)->yaxt;
}


/*  Restore temporarily saved inline parameter values	*/
void GRestorePars(pGEDevDesc dd)
{
    gpptr(dd)->adj = adjsave;
    gpptr(dd)->ann = annsave;
    gpptr(dd)->bty = btysave;
    gpptr(dd)->cex = cexsave;
    gpptr(dd)->lheight = lheightsave;
    gpptr(dd)->cexbase = cexbasesave;
    gpptr(dd)->cexlab = cexlabsave;
    gpptr(dd)->cexmain = cexmainsave;
    gpptr(dd)->cexsub = cexsubsave;
    gpptr(dd)->cexaxis = cexaxissave;
    gpptr(dd)->col = colsave;
    gpptr(dd)->fg = fgsave;
    gpptr(dd)->bg = bgsave;
    gpptr(dd)->collab = collabsave;
    gpptr(dd)->colmain = colmainsave;
    gpptr(dd)->colsub = colsubsave;
    gpptr(dd)->colaxis = colaxissave;
    gpptr(dd)->crt = crtsave;
    gpptr(dd)->err = errsave;
    strncpy(gpptr(dd)->family, familysave, 201);
    gpptr(dd)->font = fontsave;
    gpptr(dd)->fontmain = fontmainsave;
    gpptr(dd)->fontlab = fontlabsave;
    gpptr(dd)->fontsub = fontsubsave;
    gpptr(dd)->fontaxis = fontaxissave;
    gpptr(dd)->lab[0] = labsave[0];
    gpptr(dd)->lab[1] = labsave[1];
    gpptr(dd)->lab[2] = labsave[2];
    gpptr(dd)->las = lassave;
    gpptr(dd)->lty = ltysave;
    gpptr(dd)->lwd = lwdsave;
    gpptr(dd)->lend = lendsave;
    gpptr(dd)->ljoin = ljoinsave;
    gpptr(dd)->lmitre = lmitresave;
    gpptr(dd)->mgp[0] = mgpsave[0];
    gpptr(dd)->mgp[1] = mgpsave[1];
    gpptr(dd)->mgp[2] = mgpsave[2];
    gpptr(dd)->mkh = mkhsave;
    gpptr(dd)->pch = pchsave;
    gpptr(dd)->srt = srtsave;
    gpptr(dd)->tck = tcksave;
    gpptr(dd)->tcl = tclsave;
    gpptr(dd)->xaxp[0] = xaxpsave[0];
    gpptr(dd)->xaxp[1] = xaxpsave[1];
    gpptr(dd)->xaxp[2] = xaxpsave[2];
    gpptr(dd)->xaxs = xaxssave;
    gpptr(dd)->xaxt = xaxtsave;
    gpptr(dd)->xpd = xpdsave;
    gpptr(dd)->yaxp[0] = yaxpsave[0];
    gpptr(dd)->yaxp[1] = yaxpsave[1];
    gpptr(dd)->yaxp[2] = yaxpsave[2];
    gpptr(dd)->yaxs = yaxssave;
    gpptr(dd)->yaxt = yaxtsave;
}

/*-------------------------------------------------------------------
 *
 *  DEVICE STATE FUNCTIONS
 *
 */


/* This records whether GNewPlot has been called. */
void GSetState(int newstate, pGEDevDesc dd)
{
    dpptr(dd)->state = gpptr(dd)->state = newstate;
}



/* Enquire whether GNewPlot has been called. */
void GCheckState(pGEDevDesc dd)
{
    if(gpptr(dd)->state == 0)
	error(_("plot.new has not been called yet"));
    if (!gpptr(dd)->valid)
	error(_("invalid graphics state"));
}

/*-------------------------------------------------------------------
 * GRAPHICAL PRIMITIVES
 *
 */

/* CLIPPING paradigm:

   R uses both the clipping capabilities of the device (if present)
   and its own internal clipping algorithms.
   If the device has no clipping capabilities (canClip = FALSE) then R
   does all of the clipping internally.
   If the device has clipping capabilities, R still does some internal
   clipping (to the device extent).  This is to avoid "silly" values
   being sent to the device (e.g., X11 and Ghostview will barf if you
   send a ridiculously large number to them).  Call this silly-clipping.

       The problem with getting R to do some of the clipping is that it is
       not necessarily as good as the device at clipping (e.g., R's text
       clipping is very crude).  This is the motivation for leaving as much
       of the clipping as possible to the device.
       R does different amounts of silly-clipping for different primitives.
       See the individual routines for more info.
*/


static void setClipRect(double *x1, double *y1, double *x2, double *y2,
			int coords, pGEDevDesc dd)
{
    /*
     * xpd = 0 means clip to current plot region
     * xpd = 1 means clip to current figure region
     * xpd = 2 means clip to device region
     */
    *x1 = 0.0;
    *y1 = 0.0;
    *x2 = 1.0;
    *y2 = 1.0;
    switch (gpptr(dd)->xpd) {
    case 0:
	GConvert(x1, y1, NPC, coords, dd);
	GConvert(x2, y2, NPC, coords, dd);
	break;
    case 1:
	GConvert(x1, y1, NFC, coords, dd);
	GConvert(x2, y2, NFC, coords, dd);
	break;
    case 2:
	GConvert(x1, y1, NDC, coords, dd);
	GConvert(x2, y2, NDC, coords, dd);
	break;
    }
}

/* Update the device clipping region (depends on GP->xpd). */
void GClip(pGEDevDesc dd)
{
    if (gpptr(dd)->xpd != gpptr(dd)->oldxpd) {
	double x1, y1, x2, y2;
	setClipRect(&x1, &y1, &x2, &y2, DEVICE, dd);
	GESetClip(x1, y1, x2, y2, dd);
	gpptr(dd)->oldxpd = gpptr(dd)->xpd;
    }
}


/*  Forced update of the device clipping region. */
void GForceClip(pGEDevDesc dd)
{
    double x1, y1, x2, y2;
    if (gpptr(dd)->state == 0) return;
    setClipRect(&x1, &y1, &x2, &y2, DEVICE, dd);
    GESetClip(x1, y1, x2, y2, dd);
    gpptr(dd)->oldxpd = gpptr(dd)->xpd;
}

/*
 * Function to generate an R_GE_gcontext from gpptr info
 *
 * In some cases, the settings made here will need to be overridden
 * (eps. the fill setting)
 */
/* Used here and in do_xspline */
void gcontextFromGP(pGEcontext gc, pGEDevDesc dd)
{
    gc->col = gpptr(dd)->col;
    gc->fill = gpptr(dd)->bg;  /* This may need manual adjusting */
    gc->gamma = gpptr(dd)->gamma;
    /*
     * Scale by "zoom" factor to allow for fit-to-window resizing in Windows
     */
    gc->lwd = gpptr(dd)->lwd * gpptr(dd)->scale;
    gc->lty = gpptr(dd)->lty;
    gc->lend = gpptr(dd)->lend;
    gc->ljoin = gpptr(dd)->ljoin;
    gc->lmitre = gpptr(dd)->lmitre;
    gc->cex = gpptr(dd)->cex;
    /*
     * Scale by "zoom" factor to allow for fit-to-window resizing in Windows
     */
    gc->ps = (double) gpptr(dd)->ps * gpptr(dd)->scale;
    gc->lineheight = gpptr(dd)->lheight;
    gc->fontface = gpptr(dd)->font;
    strncpy(gc->fontfamily, gpptr(dd)->family, 201);
}

/* Draw a line. */
/* If the device canClip, R clips line to device extent and
   device does all other clipping. */
void GLine(double x1, double y1, double x2, double y2, int coords, pGEDevDesc dd)
{
    R_GE_gcontext gc; gcontextFromGP(&gc, dd);
    if (gpptr(dd)->lty == LTY_BLANK) return;
    /*
     * Work in device coordinates because that is what the
     * graphics engine needs.
     */
    GConvert(&x1, &y1, coords, DEVICE, dd);
    GConvert(&x2, &y2, coords, DEVICE, dd);
    /*
     * Ensure that the base clipping region is set on the device
     */
    GClip(dd);
    if(R_FINITE(x1) && R_FINITE(y1) && R_FINITE(x2) && R_FINITE(y2))
	GELine(x1, y1, x2, y2, &gc, dd);
}

/* We need extra graphics device closure handling
   when inside a call to locator (it should raise
   an error and return).  PR#15253

   This assume that locator is running on only one device at a time,
   which is currently safe.
*/
static void (*old_close)(pDevDesc) = NULL;

static void
#ifndef WIN32
NORET
#endif
locator_close(pDevDesc dd)
{
    if(old_close) old_close(dd);
    dd->close = old_close;
    old_close = NULL;
    /* It's not safe to call error() in a Windows event handler, so
       the GA_Close method records the close event separately.
    */
#ifndef WIN32
    error(_("graphics device closed during call to locator or identify"));
#endif
}


/* Read the current "pen" position. */
Rboolean GLocator(double *x, double *y, int coords, pGEDevDesc dd)
{
  Rboolean ret;
  /* store original close handler (it will still be called on
     closure) and assign new handler that throws an error
  */
  old_close = (dd->dev)->close;
  dd->dev->close = &locator_close;

  if(dd->dev->locator && dd->dev->locator(x, y, dd->dev)) {
      GConvert(x, y, DEVICE, coords, dd);
      ret =  TRUE;
  } else ret =  FALSE;
  /* restore original close handler */
  dd->dev->close = old_close;
  old_close = NULL;
  return ret;

}

/* Access character font metric information.  */
void GMetricInfo(int c, double *ascent, double *descent, double *width,
		 GUnit units, pGEDevDesc dd)
{
    R_GE_gcontext gc;
    gcontextFromGP(&gc, dd);
    dd->dev->metricInfo(c & 0xFF, &gc, ascent, descent, width, dd->dev);
    if (units != DEVICE) {
	*ascent = GConvertYUnits(*ascent, DEVICE, units, dd);
	*descent = GConvertYUnits(*descent, DEVICE, units, dd);
	*width = GConvertXUnits(*width, DEVICE, units, dd);
    }
}


/* Check that everything is initialized :
	Interpretation :
	mode = 0, graphics off
	mode = 1, graphics on
	mode = 2, graphical input on (ignored by most drivers)
*/
void GMode(int mode, pGEDevDesc dd)
{
    if (NoDevices())
	error(_("No graphics device is active"));
    if(mode != gpptr(dd)->devmode) GEMode(mode, dd); /* dd->dev->mode(mode, dd->dev); */
    gpptr(dd)->new = dpptr(dd)->new = FALSE;
    gpptr(dd)->devmode = dpptr(dd)->devmode = mode;
}


/*
***********************************
* START GClipPolygon code
*
* Everything up to END GClipPolygon code
* is just here to support GClipPolygon
* which only exists to satisfy the
* Rgraphics.h API (which should be
* superceded by the API provided by
* GraphicsDevice.h and GraphicsEngine.h)
***********************************
*/
/*
 * If device can't clip we should use something like Sutherland-Hodgman here
 *
 * NOTE:  most of this code (up to GPolygon) is only now used by
 * GClipPolygon -- GPolygon runs the new GEPolygon in engine.c
 */
typedef enum {
    Left = 0,
    Right = 1,
    Bottom = 2,
    Top = 3
} Edge;

/* Clipper State Variables */
typedef struct {
    int first;    /* true if we have seen the first point */
    double fx;    /* x coord of the first point */
    double fy;    /* y coord of the first point */
    double sx;    /* x coord of the most recent point */
    double sy;    /* y coord of the most recent point */
}
GClipState;

/* The Clipping Rectangle */
typedef struct {
    double xmin;
    double xmax;
    double ymin;
    double ymax;
}
GClipRect;

static
int inside (Edge b, double px, double py, GClipRect *clip)
{
    switch (b) {
    case Left:   if (px < clip->xmin) return 0; break;
    case Right:  if (px > clip->xmax) return 0; break;
    case Bottom: if (py < clip->ymin) return 0; break;
    case Top:    if (py > clip->ymax) return 0; break;
    }
    return 1;
}

static
int cross (Edge b, double x1, double y1, double x2, double y2,
	   GClipRect *clip)
{
    if (inside (b, x1, y1, clip) == inside (b, x2, y2, clip))
	return 0;
    else return 1;
}

static
void intersect (Edge b, double x1, double y1, double x2, double y2,
		double *ix, double *iy, GClipRect *clip)
{
    double m = 0;

    if (x1 != x2) m = (y1 - y2) / (x1 - x2);
    switch (b) {
    case Left:
	*ix = clip->xmin;
	*iy = y2 + (clip->xmin - x2) * m;
	break;
    case Right:
	*ix = clip->xmax;
	*iy = y2 + (clip->xmax - x2) * m;
	break;
    case Bottom:
	*iy = clip->ymin;
	if (x1 != x2) *ix = x2 + (clip->ymin - y2) / m;
	else *ix = x2;
	break;
    case Top:
	*iy = clip->ymax;
	if (x1 != x2) *ix = x2 + (clip->ymax - y2) / m;
	else *ix = x2;
	break;
    }
}

static
void clipPoint (Edge b, double x, double y,
		double *xout, double *yout, int *cnt, int store,
		GClipRect *clip, GClipState *cs)
{
    double ix = 0.0, iy = 0.0 /* -Wall */;

    if (!cs[b].first) {
	/* No previous point exists for this edge. */
	/* Save this point. */
	cs[b].first = 1;
	cs[b].fx = x;
	cs[b].fy = y;
    }
    else
	/* A previous point exists.  */
	/* If 'p' and previous point cross edge, find intersection.  */
	/* Clip against next boundary, if any.  */
	/* If no more edges, add intersection to output list. */
	if (cross (b, x, y, cs[b].sx, cs[b].sy, clip)) {
	    intersect (b, x, y, cs[b].sx, cs[b].sy, &ix, &iy, clip);
	    if (b < Top)
		clipPoint (b + 1, ix, iy, xout, yout, cnt, store,
			   clip, cs);
	    else {
		if (store) {
		    xout[*cnt] = ix;
		    yout[*cnt] = iy;
		}
		(*cnt)++;
	    }
	}

    /* Save as most recent point for this edge */
    cs[b].sx = x;
    cs[b].sy = y;

    /* For all, if point is 'inside' */
    /* proceed to next clip edge, if any */
    if (inside (b, x, y, clip)) {
	if (b < Top)
	    clipPoint (b + 1, x, y, xout, yout, cnt, store, clip, cs);
	else {
	    if (store) {
		xout[*cnt] = x;
		yout[*cnt] = y;
	    }
	    (*cnt)++;
	}
    }
}

static
void closeClip (double *xout, double *yout, int *cnt, int store,
		GClipRect *clip, GClipState *cs)
{
    double ix = 0.0, iy = 0.0 /* -Wall */;
    Edge b;

    for (b = Left; b <= Top; b++) {
	if (cross (b, cs[b].sx, cs[b].sy, cs[b].fx, cs[b].fy, clip)) {
	    intersect (b, cs[b].sx, cs[b].sy,
		       cs[b].fx, cs[b].fy, &ix, &iy, clip);
	    if (b < Top)
		clipPoint (b + 1, ix, iy, xout, yout, cnt, store, clip, cs);
	    else {
		if (store) {
		    xout[*cnt] = ix;
		    yout[*cnt] = iy;
		}
		(*cnt)++;
	    }
	}
    }
}

int GClipPolygon(double *x, double *y, int n, int coords, int store,
		 double *xout, double *yout, pGEDevDesc dd)
{
    int i, cnt = 0;
    GClipState cs[4];
    GClipRect clip;
    for (i = 0; i < 4; i++)
	cs[i].first = 0;
    /* Set up the cliprect here for R. */
    setClipRect(&clip.xmin, &clip.ymin, &clip.xmax, &clip.ymax, coords, dd);
    /* If necessary, swap the clip region extremes */
    if (clip.xmax < clip.xmin) {
	double swap = clip.xmax;
	clip.xmax = clip.xmin;
	clip.xmin = swap;
    }
    if (clip.ymax < clip.ymin) {
	double swap = clip.ymax;
	clip.ymax = clip.ymin;
	clip.ymin = swap;
    }
    for (i = 0; i < n; i++)
	clipPoint (Left, x[i], y[i], xout, yout, &cnt, store, &clip, cs);
    closeClip (xout, yout, &cnt, store, &clip, cs);
    return (cnt);
}
/*
***********************************
* END GClipPolygon code
***********************************
*/

/*
 * This is just here to satisfy the Rgraphics.h API.
 * This allows new graphics API (GraphicsDevice.h, GraphicsEngine.h)
 * to be developed alongside.
 * Could be removed if Rgraphics.h ever gets REPLACED by new API
 * NOTE that base graphics code (in plot.c) still calls this.
 */
/* GPolygon -- Draw a polygon
 *	Filled with color bg and outlined with color fg
 *	These may both be NA_INTEGER
 */
void GPolygon(int n, double *x, double *y, int coords,
	      int bg, int fg, pGEDevDesc dd)
{
    int i;
    double *xx;
    double *yy;
    const void *vmaxsave = vmaxget();
    R_GE_gcontext gc; gcontextFromGP(&gc, dd);

    if (gpptr(dd)->lty == LTY_BLANK)
	fg = R_TRANWHITE; /* transparent for the border */

    /*
     * Work in device coordinates because that is what the
     * graphics engine needs.
     */
    xx = (double*) R_alloc(n, sizeof(double));
    yy = (double*) R_alloc(n, sizeof(double));
    if (!xx || !yy)
	error("unable to allocate memory (in GPolygon)");
    for (i=0; i<n; i++) {
	xx[i] = x[i];
	yy[i] = y[i];
	GConvert(&(xx[i]), &(yy[i]), coords, DEVICE, dd);
    }
    /*
     * Ensure that the base clipping region is set on the device
     */
    GClip(dd);
    gc.col = fg;
    gc.fill = bg;
    GEPolygon(n, xx, yy, &gc, dd);
    vmaxset(vmaxsave);
}

#include <stdio.h>

/* Draw a series of line segments. */
/* If the device canClip, R clips to the device extent and the device
   does all other clipping */
void GPolyline(int n, double *x, double *y, int coords, pGEDevDesc dd)
{
    int i;
    double *xx;
    double *yy;
    const void *vmaxsave = vmaxget();
    R_GE_gcontext gc; gcontextFromGP(&gc, dd);

    /*
     * Work in device coordinates because that is what the
     * graphics engine needs.
     */
    xx = (double*) R_alloc(n, sizeof(double));
    yy = (double*) R_alloc(n, sizeof(double));
    if (!xx || !yy)
	error("unable to allocate memory (in GPolyline)");
    for (i=0; i<n; i++) {
	xx[i] = x[i];
	yy[i] = y[i];
	GConvert(&(xx[i]), &(yy[i]), coords, DEVICE, dd);
    }
    /*
     * Ensure that the base clipping region is set on the device
     */
    GClip(dd);
    GEPolyline(n, xx, yy, &gc, dd);
    vmaxset(vmaxsave);
}


/*
 * This is just here to satisfy the Rgraphics.h API.
 * This allows new graphics API (GraphicsDevice.h, GraphicsEngine.h)
 * to be developed alongside.
 * Could be removed if Rgraphics.h ever gets REPLACED by new API
 * NOTE that base graphics code (do_symbol in plot.c) still calls this.
 *
 * NB: this fiddles with radius = 0.
 */
void GCircle(double x, double y, int coords,
	     double radius, int bg, int fg, pGEDevDesc dd)
{
    double ir;
    R_GE_gcontext gc; gcontextFromGP(&gc, dd);

    ir = radius/dd->dev->ipr[0];
    ir = (ir > 0) ? ir : 1;

    if (gpptr(dd)->lty == LTY_BLANK)
	fg = R_TRANWHITE; /* transparent for the border */

    /*
     * Work in device coordinates because that is what the
     * graphics engine needs.
     */
    GConvert(&x, &y, coords, DEVICE, dd);
    /*
     * Ensure that the base clipping region is set on the device
     */
    GClip(dd);
    gc.col = fg;
    gc.fill = bg;
    GECircle(x, y, ir, &gc, dd);
}

/* Draw a rectangle	*/
/* Filled with color bg and outlined with color fg  */
/* These may both be NA_INTEGER	 */
void GRect(double x0, double y0, double x1, double y1, int coords,
	   int bg, int fg, pGEDevDesc dd)
{
    R_GE_gcontext gc; gcontextFromGP(&gc, dd);

    if (gpptr(dd)->lty == LTY_BLANK)
	fg = R_TRANWHITE; /* transparent for the border */

    /*
     * Work in device coordinates because that is what the
     * graphics engine needs.
     */
    GConvert(&x0, &y0, coords, DEVICE, dd);
    GConvert(&x1, &y1, coords, DEVICE, dd);
    /*
     * Ensure that the base clipping region is set on the device
     */
    GClip(dd);
    gc.col = fg;
    gc.fill = bg;
    GERect(x0, y0, x1, y1, &gc, dd);
}

void GPath(double *x, double *y,
           int npoly, int *nper,
           Rboolean winding,
           int bg, int fg, pGEDevDesc dd)
{
    R_GE_gcontext gc; gcontextFromGP(&gc, dd);

    if (gpptr(dd)->lty == LTY_BLANK)
	fg = R_TRANWHITE; /* transparent for the border */

    /*
     * Ensure that the base clipping region is set on the device
     */
    GClip(dd);
    gc.col = fg;
    gc.fill = bg;
    GEPath(x, y, npoly, nper, winding, &gc, dd);
}

void GRaster(unsigned int* image, int w, int h,
             double x0, double y0, double x1, double y1,
             double angle, Rboolean interpolate,
             pGEDevDesc dd)
{
    R_GE_gcontext gc; gcontextFromGP(&gc, dd);

    /*
     * Ensure that the base clipping region is set on the device
     */
    GClip(dd);

    GERaster(image, w, h, x0, y0, x1, y1, angle, interpolate,
             &gc, dd);
}

/* Compute string width. */
double GStrWidth(const char *str, cetype_t enc, GUnit units, pGEDevDesc dd)
{
    double w;
    R_GE_gcontext gc; gcontextFromGP(&gc, dd);
    w = GEStrWidth(str, (gc.fontface == 5) ? CE_SYMBOL:enc, &gc, dd);
    if (units != DEVICE)
	w = GConvertXUnits(w, DEVICE, units, dd);
    return w;
}


/* Compute string height. */
double GStrHeight(const char *str, cetype_t enc, GUnit units, pGEDevDesc dd)
{
    double h;
    R_GE_gcontext gc; gcontextFromGP(&gc, dd);
    h = GEStrHeight(str, (gc.fontface == 5) ? CE_SYMBOL:enc, &gc, dd);
    if (units != DEVICE)
	h = GConvertYUnits(h, DEVICE, units, dd);
    return h;
}

/* Draw text in a plot. */
/* If you want EXACT centering of text (e.g., like in GSymbol) */
/* then pass NA_REAL for xc and yc */
void GText(double x, double y, int coords, const char *str, cetype_t enc,
	   double xc, double yc, double rot, pGEDevDesc dd)
{
    R_GE_gcontext gc; gcontextFromGP(&gc, dd);
    /*
     * Work in device coordinates because that is what the
     * graphics engine needs.
     */
    GConvert(&x, &y, coords, DEVICE, dd);
    /*
     * Ensure that the base clipping region is set on the device
     */
    GClip(dd);
    GEText(x, y, str, (gc.fontface == 5) ? CE_SYMBOL:enc, xc, yc, rot, &gc, dd);
}

/*-------------------------------------------------------------------
 *
 *  GRAPHICAL UTILITIES
 *
 */


/* GArrow -- Draw an arrow. */
/* NOTE that the length parameter is in inches. */
void GArrow(double xfrom, double yfrom, double xto, double yto, int coords,
	    double length, double angle, int code, pGEDevDesc dd)
{

    double xfromInch = xfrom;
    double yfromInch = yfrom;
    double xtoInch = xto;
    double ytoInch = yto;
    double rot, xc, yc;
    double x[3], y[3];
    double eps = 1.e-3;

    GLine(xfrom, yfrom, xto, yto, coords, dd);

    GConvert(&xfromInch, &yfromInch, coords, INCHES, dd);
    GConvert(&xtoInch, &ytoInch, coords, INCHES, dd);
    if((code & 3) == 0) return; /* no arrows specified */
    if(length == 0) return; /* zero-length arrow heads */

    if(hypot(xfromInch - xtoInch, yfromInch - ytoInch) < eps) {
	/* effectively 0-length arrow */
	warning(_("zero-length arrow is of indeterminate angle and so skipped"));
	return;
    }
    angle *= DEG2RAD;
    if(code & 1) {
	xc = xtoInch - xfromInch;
	yc = ytoInch - yfromInch;
	rot= atan2(yc, xc);
	x[0] = xfromInch + length * cos(rot+angle);
	y[0] = yfromInch + length * sin(rot+angle);
	x[1] = xfromInch;
	y[1] = yfromInch;
	x[2] = xfromInch + length * cos(rot-angle);
	y[2] = yfromInch + length * sin(rot-angle);
	GPolyline(3, x, y, INCHES, dd);
    }
    if(code & 2) {
	xc = xfromInch - xtoInch;
	yc = yfromInch - ytoInch;
	rot= atan2(yc, xc);
	x[0] = xtoInch + length * cos(rot+angle);
	y[0] = ytoInch + length * sin(rot+angle);
	x[1] = xtoInch;
	y[1] = ytoInch;
	x[2] = xtoInch + length * cos(rot-angle);
	y[2] = ytoInch + length * sin(rot-angle);
	GPolyline(3, x, y, INCHES, dd);
    }
}


/* Draw a box about one of several regions:  box(which) */
void GBox(int which, pGEDevDesc dd)
{
    double x[7], y[7];
    if (which == 1) {/* plot */
	x[0] = gpptr(dd)->plt[0]; y[0] = gpptr(dd)->plt[2];/* <- , __ */
	x[1] = gpptr(dd)->plt[1]; y[1] = gpptr(dd)->plt[2];/* -> , __ */
	x[2] = gpptr(dd)->plt[1]; y[2] = gpptr(dd)->plt[3];/* -> , ^  */
	x[3] = gpptr(dd)->plt[0]; y[3] = gpptr(dd)->plt[3];/* <- , ^  */
	x[4] = x[0];	      y[4] = y[0];	   /* <- , __ */
	x[5] = x[1];	      y[5] = y[1];	   /* -> , __ */
	x[6] = x[2];	      y[6] = y[2];	   /* -> , __ */
    }
    else {/* "figure", "inner", or "outer" */
	x[0] = 0.; y[0] = 0.;
	x[1] = 1.; y[1] = 0.;
	x[2] = 1.; y[2] = 1.;
	x[3] = 0.; y[3] = 1.;
    }
    switch(which) {
    case 1: /* Plot */
	switch(gpptr(dd)->bty) {
	case 'o':
	case 'O':
	    GPolygon(4, x, y, NFC,
		     R_TRANWHITE, gpptr(dd)->col, dd);
	    break;
	case 'l':
	case 'L':
	    GPolyline(3, x+3, y+3, NFC, dd);
	    break;
	case '7':
	    GPolyline(3, x+1, y+1, NFC, dd);
	    break;
	case 'c':
	case 'C':
	case '[':
	    GPolyline(4, x+2, y+2, NFC, dd);
	    break;
	case ']':/* new */
	    GPolyline(4, x, y, NFC, dd);
	    break;
	case 'u':
	case 'U':
	    GPolyline(4, x+3, y+3, NFC, dd);
	    break;
	case 'n':
	case 'N': /* nothing */
	    break;
	default:
	    warning(_("invalid par(\"bty\") = '%c'; no box() drawn"),
		    gpptr(dd)->bty);
	}
	break;
    case 2: /* Figure */
	GPolygon(4, x, y, NFC,
		 R_TRANWHITE, gpptr(dd)->col, dd);
	break;
    case 3: /* Inner Region */
	GPolygon(4, x, y, NIC,
		 R_TRANWHITE, gpptr(dd)->col, dd);
	break;
    case 4: /* "outer": Device border */
	GPolygon(4, x, y, NDC,
		 R_TRANWHITE, gpptr(dd)->col, dd);
	break;
    default:
	error(_("invalid argument to GBox"));
    }
}


/*
void GLPretty(double *ul, double *uh, int *n)

void GPretty(double *lo, double *up, int *ndiv)

* ----> in src/main/graphics.c (as used in grDevices too)
*          ------------------- */


#define SMALL	0.25
#define RADIUS	0.375
#define SQRC	0.88622692545275801364		/* sqrt(pi / 4) */
#define DMDC	1.25331413731550025119		/* sqrt(pi / 4) * sqrt(2) */
#define TRC0	1.55512030155621416073		/* sqrt(4 * pi/(3 * sqrt(3))) */
#define TRC1	1.34677368708859836060		/* TRC0 * sqrt(3) / 2 */
#define TRC2	0.77756015077810708036		/* TRC0 / 2 */
#define CMAG	1.0				/* Circle magnifier, now defunct */
#define GSTR_0  dpptr(dd)->scale * dd->dev->cra[1] * 0.5 * dd->dev->ipr[1] * gpptr(dd)->cex
/* NOTE: This cex is already multiplied with cexbase */

/* Draw one of the R special symbols. */
void GSymbol(double x, double y, int coords, int pch, pGEDevDesc dd)
{
    double size = GConvertYUnits(GSTR_0, INCHES, DEVICE, dd);
    R_GE_gcontext gc; gcontextFromGP(&gc, dd);
    /*
     * Work in device coordinates because that is what the
     * graphics engine needs.
     */
    GConvert(&x, &y, coords, DEVICE, dd);
    /*
     * Ensure that the base clipping region is set on the device
     */
    GClip(dd);
    /*
     * Force line type LTY_SOLID
     * i.e., current par(lty) is ignored when drawing symbols
     */
    gc.lty = LTY_SOLID;
    /*
     * special case for pch = "."
     */
    if(pch == 46) size = gpptr(dd)->cex;
    GESymbol(x, y, pch, size, &gc, dd);
}


/* Draw text in plot margins. */
void GMtext(const char *str, cetype_t enc, int side, double line, int outer,
	    double at, int las, double yadj, pGEDevDesc dd)
{
/* "las" gives the style of axis labels:
	 0 = always parallel to the axis [= default],
	 1 = always horizontal,
	 2 = always perpendicular to the axis.
	 3 = always vertical.
*/
    double angle, xadj;
    int coords;

    /* Init to keep -Wall happy: */
    angle = 0.;
    coords = 0;

    xadj = gpptr(dd)->adj;	/* ALL cases */
    if(outer) {
	switch(side) {
	case 1:	    coords = OMA1;	break;
	case 2:	    coords = OMA2;	break;
	case 3:	    coords = OMA3;	break;
	case 4:	    coords = OMA4;	break;
	}
    }
    else {
	switch(side) {
	case 1:	    coords = MAR1;	break;
	case 2:	    coords = MAR2;	break;
	case 3:	    coords = MAR3;	break;
	case 4:	    coords = MAR4;	break;
	}
    }
    /* Note: I changed gpptr(dd)->yLineBias to 0.3 here. */
    /* Purely visual tuning. RI */
    /* This has been replaced by a new argument padj (=yadj here) to axis()
       and mtext() and that can either be set manually or is determined in
       a somehow fuzzy manner with respect to current side and las settings.
       Uwe L.
    */
    /* Note from PR#14532:
       yLineBias is the proportion of line height that is white
       space. The manipulation of "line" below is pure visual tuning
       such that when we plot horizontal text on side 1 (or vertical
       text on side 4) with padj=0 (i.e. text written *above* the
       specified y-value), it is symmetric w.r.t text written on sides
       1 and 2 with padj=0.
    */
    switch(side) {
    case 1:
	if(las == 2 || las == 3) {
	    angle = 90;
	}
	else {
	    line += (1/gpptr(dd)->mex)*(1 - dd->dev->yLineBias);
	    angle = 0;
	}
	break;
    case 2:
	if(las == 1 || las == 2) {
	    angle = 0;
	}
	else {
	    line += (1/gpptr(dd)->mex)*dd->dev->yLineBias;
	    angle = 90;
	}
	break;
    case 3:
	if(las == 2 || las == 3) {
	    angle = 90;
	}
	else {
	    line += (1/gpptr(dd)->mex)*dd->dev->yLineBias;
	    angle = 0;
	}
	break;
    case 4:
	if(las == 1 || las == 2) {
	    angle = 0;
	}
	else {
	    line += (1/gpptr(dd)->mex)*(1 - dd->dev->yLineBias);
	    angle = 90;
	}
	break;
    }
    GText(at, line, coords, str, enc, xadj, yadj, angle, dd);
}/* GMtext */

/* ------------------------------------------------------------
   code below here moved from plotmath.c, which said

 *  This source code module:
 *  Copyright (C) 1997, 1998 Paul Murrell and Ross Ihaka
 *  Copyright (C) 1998-2008  The R Core Team

 */

double GExpressionWidth(SEXP expr, GUnit units, pGEDevDesc dd)
{
    R_GE_gcontext gc;
    double width;
    gcontextFromGP(&gc, dd);
    width = GEExpressionWidth(expr, &gc, dd);
    if (units == DEVICE)
	return width;
    else
	return GConvertXUnits(width, DEVICE, units, dd);
}

double GExpressionHeight(SEXP expr, GUnit units, pGEDevDesc dd)
{
    R_GE_gcontext gc;
    double height;
    gcontextFromGP(&gc, dd);
    height = GEExpressionHeight(expr, &gc, dd);
    if (units == DEVICE)
	return height;
    else
	return GConvertYUnits(height, DEVICE, units, dd);
}

/* Comment is NOT true: used in plot.c for strwidth and strheight.
 *
 * This is just here to satisfy the Rgraphics.h API.
 * This allows new graphics API (GraphicsDevice.h, GraphicsEngine.h)
 * to be developed alongside.
 * Could be removed if Rgraphics.h ever gets REPLACED by new API
 * NOTE that base graphics code no longer calls this -- the base
 * graphics system directly calls the graphics engine for mathematical
 * annotation (GEMathText in ../../../main/plotmath.c )
 */
void GMathText(double x, double y, int coords, SEXP expr,
	       double xc, double yc, double rot,
	       pGEDevDesc dd)
{
    R_GE_gcontext gc;
    gcontextFromGP(&gc, dd);
    GConvert(&x, &y, coords, DEVICE, dd);
    GClip(dd);
    GEMathText(x, y, expr, xc, yc, rot, &gc, dd);
}

void GMMathText(SEXP str, int side, double line, int outer,
		double at, int las, double yadj, pGEDevDesc dd)
{
    int coords = 0;
    double xadj, angle = 0;

    /* IF font metric information is not available for device */
    /* then bail out */
    double ascent, descent, width;
    GMetricInfo('M', &ascent, &descent, &width, DEVICE, dd);
    if ((ascent == 0) && (descent == 0) && (width == 0))
	error(_("metric information not available for this device"));

    xadj = gpptr(dd)->adj;

    /* This is MOSTLY the same as the same section of GMtext
     * BUT it differs because it sets different values for yadj for
     * different situations.
     * Paul
     */
     /* changed to unify behaviour with changes in GMText. Uwe */
    if(outer) {
	switch(side) {
	case 1:	    coords = OMA1;	break;
	case 2:	    coords = OMA2;	break;
	case 3:	    coords = OMA3;	break;
	case 4:	    coords = OMA4;	break;
	}
    }
    else {
	switch(side) {
	case 1:	    coords = MAR1;	break;
	case 2:	    coords = MAR2;	break;
	case 3:	    coords = MAR3;	break;
	case 4:	    coords = MAR4;	break;
	}
    }
    switch(side) {
    case 1:
	if(las == 2 || las == 3) {
	    angle = 90;
	}
	else {
	    /*	    line = line + 1 - gpptr(dd)->yLineBias;
		    angle = 0;
		    yadj = NA_REAL; */
	    line += (1/gpptr(dd)->mex)*(1 - dd->dev->yLineBias);
	    angle = 0;
	}
	break;
    case 2:
	if(las == 1 || las == 2) {
	    angle = 0;
	}
	else {
	    /*	    line = line + gpptr(dd)->yLineBias;
		    angle = 90;
		    yadj = NA_REAL; */
	    /* The following line is needed for symmetry with plain text
	       but changes existing output */
	    line += (1/gpptr(dd)->mex)*dd->dev->yLineBias;
	    angle = 90;
	}
	break;
    case 3:
	if(las == 2 || las == 3) {
	    angle = 90;
	}
	else {
	    /*   line = line + gpptr(dd)->yLineBias;
		 angle = 0;
		 yadj = NA_REAL; */
	    /* The following line is needed for symmetry with plain text
	       but changes existing output */
	    line += (1/gpptr(dd)->mex)*dd->dev->yLineBias;
	    angle = 0;
	}
	break;
    case 4:
	if(las == 1 || las == 2) {
	    angle = 0;
	}
	else {
	    /*   line = line + 1 - gpptr(dd)->yLineBias;
		 angle = 90;
		 yadj = NA_REAL; */
	    line += (1/gpptr(dd)->mex)*(1 - dd->dev->yLineBias);
	    angle = 90;
	}
	break;
    }
    GMathText(at, line, coords, str, xadj, yadj, angle, dd);
}/* GMMathText */

/* -------------------- end of code from plotmath ------------- */
