/*
 * rbcGrPs.c --
 *
 *      This module implements the "postscript" operation for rbc
 *      graph widget.
 *
 * Copyright (c) 2001 BLT was created by George Howlett.
 * Copyright (c) 2009 RBC was created by Samuel Green, Nicholas Hudson, Stanton Sievers, Jarrod Stormo
 * Copyright (c) 2018 Rene Zaumseil

 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tkoGraph.h"
#include <X11/Xatom.h>


#define PS_MAXPATH	1500    /* Maximum number of components in a PostScript
                             * (level 1) path. */
#define PS_PREVIEW_EPSI	0
#define PS_PREVIEW_WMF	1
#define PS_PREVIEW_TIFF	2

static Tk_OptionParseProc StringToColorMode;
static Tk_OptionPrintProc ColorModeToString;
static Tk_CustomOption colorModeOption = {
    StringToColorMode, ColorModeToString, (ClientData) 0,
};

static Tk_OptionParseProc StringToFormat;
static Tk_OptionPrintProc FormatToString;
static Tk_CustomOption formatOption = {
    StringToFormat, FormatToString, (ClientData) 0,
};

extern Tk_CustomOption rbcDistanceOption;
extern Tk_CustomOption rbcPositiveDistanceOption;
extern Tk_CustomOption rbcPadOption;

#define DEF_PS_CENTER		"yes"
#define DEF_PS_COLOR_MAP	(char *)NULL
#define DEF_PS_COLOR_MODE	"color"
#define DEF_PS_DECORATIONS	"yes"
#define DEF_PS_FONT_MAP		(char *)NULL
#define DEF_PS_FOOTER		"no"
#define DEF_PS_HEIGHT		"0"
#define DEF_PS_LANDSCAPE	"no"
#define DEF_PS_MAXPECT		"no"
#define DEF_PS_PADX		"1.0i"
#define DEF_PS_PADY		"1.0i"
#define DEF_PS_PAPERHEIGHT	"11.0i"
#define DEF_PS_PAPERWIDTH	"8.5i"
#define DEF_PS_PREVIEW		"no"
#define DEF_PS_PREVIEW_FORMAT   "epsi"
#define DEF_PS_WIDTH		"0"

static Tk_ConfigSpec configSpecs[] = {
    {TK_CONFIG_BOOLEAN, "-center", "center", "Center",
            DEF_PS_CENTER, offsetof(RbcPostScript, center),
        TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_STRING, "-colormap", "colorMap", "ColorMap",
            DEF_PS_COLOR_MAP, offsetof(RbcPostScript, colorVarName),
        TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-colormode", "colorMode", "ColorMode",
            DEF_PS_COLOR_MODE, offsetof(RbcPostScript, colorMode),
        TK_CONFIG_DONT_SET_DEFAULT, &colorModeOption},
    {TK_CONFIG_BOOLEAN, "-decorations", "decorations", "Decorations",
            DEF_PS_DECORATIONS, offsetof(RbcPostScript, decorations),
        TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_STRING, "-fontmap", "fontMap", "FontMap",
            DEF_PS_FONT_MAP, offsetof(RbcPostScript, fontVarName),
        TK_CONFIG_NULL_OK},
    {TK_CONFIG_BOOLEAN, "-footer", "footer", "Footer",
            DEF_PS_FOOTER, offsetof(RbcPostScript, footer),
        TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-height", "height", "Height",
            DEF_PS_HEIGHT, offsetof(RbcPostScript, reqHeight),
        TK_CONFIG_DONT_SET_DEFAULT, &rbcDistanceOption},
    {TK_CONFIG_BOOLEAN, "-landscape", "landscape", "Landscape",
            DEF_PS_LANDSCAPE, offsetof(RbcPostScript, landscape),
        TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_BOOLEAN, "-maxpect", "maxpect", "Maxpect",
            DEF_PS_MAXPECT, offsetof(RbcPostScript, maxpect),
        TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-padx", "padX", "PadX",
        DEF_PS_PADX, offsetof(RbcPostScript, padX), 0, &rbcPadOption},
    {TK_CONFIG_CUSTOM, "-pady", "padY", "PadY",
        DEF_PS_PADY, offsetof(RbcPostScript, padY), 0, &rbcPadOption},
    {TK_CONFIG_CUSTOM, "-paperheight", "paperHeight", "PaperHeight",
            DEF_PS_PAPERHEIGHT, offsetof(RbcPostScript, reqPaperHeight),
        0, &rbcPositiveDistanceOption},
    {TK_CONFIG_CUSTOM, "-paperwidth", "paperWidth", "PaperWidth",
            DEF_PS_PAPERWIDTH, offsetof(RbcPostScript, reqPaperWidth),
        0, &rbcPositiveDistanceOption},
    {TK_CONFIG_BOOLEAN, "-preview", "preview", "Preview",
            DEF_PS_PREVIEW, offsetof(RbcPostScript, addPreview),
        TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-previewformat", "previewFormat", "PreviewFormat",
            DEF_PS_PREVIEW_FORMAT, offsetof(RbcPostScript, previewFormat),
        TK_CONFIG_DONT_SET_DEFAULT, &formatOption},
    {TK_CONFIG_CUSTOM, "-width", "width", "Width",
            DEF_PS_WIDTH, offsetof(RbcPostScript, reqWidth),
        TK_CONFIG_DONT_SET_DEFAULT, &rbcDistanceOption},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

static const char *NameOfColorMode(
    RbcPsColorMode colorMode);
static int CgetOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int ConfigureOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int OutputOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int ComputeBoundingBox(
    RbcGraph * graph,
    RbcPostScript * psPtr);
static void PreviewImage(
    RbcGraph * graph,
    RbcPsToken * psToken);
static int PostScriptPreamble(
    RbcGraph * graph,
    const char *fileName,
    RbcPsToken * psToken);
static void MarginsToPostScript(
    RbcGraph * graph,
    RbcPsToken * psToken);
static int GraphToPostScript(
    RbcGraph * graph,
    const char *ident,
    RbcPsToken * psToken);
static void XColorToPostScript(
	RbcPsToken * tokenPtr,
	XColor * colorPtr);
static unsigned char ReverseBits(
	register unsigned char byte);
static void ByteToHex(
	register unsigned char byte,
	char *string);
static void TextLayoutToPostScript(
	RbcPsToken * tokenPtr,
	int x,
	int y,
	RbcTextLayout * textPtr);
static char *PostscriptFromToken(
	RbcPsToken * psToken);
static int ColorImageToPsData(
	RbcColorImage * image,
	int nComponents,
	Tcl_DString * resultPtr,
	const char *prefix);

#ifdef _WIN32
static int CreateWindowsEPS(
    RbcGraph * graph,
    RbcPsToken * psToken,
    FILE * f);
static char * ReturnLastError();
#else
static const char *NameOfAtom(
	Tk_Window tkwin,
	Atom atom);
#endif

/*
 *----------------------------------------------------------------------
 *
 * StringToColorMode --
 *
 *      Convert the string representation of a PostScript color mode
 *      into the enumerated type representing the color level:
 *
 *          PS_MODE_COLOR 	- Full color
 *          PS_MODE_GREYSCALE  	- Color converted to greyscale
 *          PS_MODE_MONOCHROME 	- Only black and white
 *
 * Results:
 *      A standard Tcl result.  The color level is written into the
 *      page layout information structure.
 *
 * Side Effects:
 *      Future invocations of the "postscript" option will use this
 *      variable to determine how color information will be displayed
 *      in the PostScript output it produces.
 *
 *----------------------------------------------------------------------
 */
static int
StringToColorMode(
    ClientData clientData,     /* Not used. */
    Tcl_Interp * interp,       /* Interpreter to send results back to */
    Tk_Window tkwin,           /* Not used. */
    const char *string,        /* New value. */
    char *widgRec,             /* Widget record */
    Tcl_Size offset)
{              /* Offset of field in record */
    RbcPsColorMode *modePtr = (RbcPsColorMode *) (widgRec + offset);
    unsigned int length;
    char c;

    c = string[0];
    length = strlen(string);
    if((c == 'c') && (strncmp(string, "color", length) == 0)) {
        *modePtr = PS_MODE_COLOR;
    } else if((c == 'g') && (strncmp(string, "grayscale", length) == 0)) {
        *modePtr = PS_MODE_GREYSCALE;
    } else if((c == 'g') && (strncmp(string, "greyscale", length) == 0)) {
        *modePtr = PS_MODE_GREYSCALE;
    } else if((c == 'm') && (strncmp(string, "monochrome", length) == 0)) {
        *modePtr = PS_MODE_MONOCHROME;
    } else {
        Tcl_AppendResult(interp, "bad color mode \"", string, "\": should be \
\"color\", \"greyscale\", or \"monochrome\"", (char *)NULL);
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NameOfColorMode --
 *
 *      Convert the PostScript mode value into the string representing
 *      a valid color mode.
 *
 * Results:
 *      The static string representing the color mode is returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static const char *
NameOfColorMode(
    RbcPsColorMode colorMode)
{
    switch (colorMode) {
    case PS_MODE_COLOR:
        return "color";
    case PS_MODE_GREYSCALE:
        return "greyscale";
    case PS_MODE_MONOCHROME:
        return "monochrome";
    default:
        return "unknown color mode";
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ColorModeToString --
 *
 *      Convert the current color mode into the string representing a
 *      valid color mode.
 *
 * Results:
 *      The string representing the color mode is returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static const char *
ColorModeToString(
    ClientData clientData,     /* Not used. */
    Tk_Window tkwin,           /* Not used. */
    char *widgRec,             /* Widget record. */
    Tcl_Size offset,                /* field of colorMode in record */
    Tcl_FreeProc ** freeProcPtr)
{              /* Not used. */
    RbcPsColorMode mode = *(RbcPsColorMode *) (widgRec + offset);

    return NameOfColorMode(mode);
}

/*
 *----------------------------------------------------------------------
 *
 * StringToFormat --
 *
 *      Convert the string of the PostScript preview format into
 *      an enumerated type representing the desired format.  The
 *      available formats are:
 *
 *        PS_PREVIEW_WMF 	- Windows Metafile.
 *        PS_PREVIEW_TIFF  	- TIFF bitmap image.
 *        PS_PREVIEW_EPSI 	- Device independent ASCII preview
 *
 * Results:
 *      A standard Tcl result.  The format is written into the
 *      page layout information structure.
 *
 * Side Effects:
 *      Future invocations of the "postscript" option will use this
 *      variable to determine how to format a preview image (if one
 *      is selected) when the PostScript output is produced.
 *
 *----------------------------------------------------------------------
 */
static int
StringToFormat(
    ClientData clientData,     /* Not used. */
    Tcl_Interp * interp,       /* Interpreter to send results back to */
    Tk_Window tkwin,           /* Not used. */
    const char *string,        /* New value. */
    char *widgRec,             /* Widget record */
    Tcl_Size offset)
{              /* Offset of field in record */
    int *formatPtr = (int *)(widgRec + offset);
    unsigned int length;
    char c;

    c = string[0];
    length = strlen(string);
    if((c == 'c') && (strncmp(string, "epsi", length) == 0)) {
        *formatPtr = PS_PREVIEW_EPSI;
#ifdef _WIN32
#ifdef HAVE_TIFF_H
    } else if((c == 't') && (strncmp(string, "tiff", length) == 0)) {
        *formatPtr = PS_PREVIEW_TIFF;
#endif /* HAVE_TIFF_H */
    } else if((c == 'w') && (strncmp(string, "wmf", length) == 0)) {
        *formatPtr = PS_PREVIEW_WMF;
#endif /* _WIN32 */
    } else {
        Tcl_AppendResult(interp, "bad format \"", string, "\": should be ",
#ifdef _WIN32
#ifdef HAVE_TIFF_H
            "\"tiff\" or ",
#endif /* HAVE_TIFF_H */
            "\"wmf\" or ",
#endif /* _WIN32 */
            "\"epsi\"", (char *)NULL);
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * FormatToString --
 *
 *      Convert the preview format into the string representing its
 *      type.
 *
 * Results:
 *      The string representing the preview format is returned.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static const char *
FormatToString(
    ClientData clientData,     /* Not used. */
    Tk_Window tkwin,           /* Not used. */
    char *widgRec,             /* PostScript structure record */
    Tcl_Size offset,                /* field of colorMode in record */
    Tcl_FreeProc ** freeProcPtr)
{              /* Not used. */
    int format = *(int *)(widgRec + offset);

    switch (format) {
    case PS_PREVIEW_EPSI:
        return "epsi";
    case PS_PREVIEW_WMF:
        return "wmf";
    case PS_PREVIEW_TIFF:
        return "tiff";
    }
    return "?unknown preview format?";
}

/*
 *--------------------------------------------------------------
 *
 * RbcDestroyPostScript --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
void
RbcDestroyPostScript(
    RbcGraph * graph)
{
    Tk_FreeOptions(configSpecs, (char *)graph->postscript, graph->widget.display, 0);
    ckfree((char *)graph->postscript);
}

/*
 *--------------------------------------------------------------
 *
 * CgetOp --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
static int
CgetOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    RbcPostScript *psPtr = (RbcPostScript *) graph->postscript;
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return TCL_ERROR;
	}

    if(Tk_ConfigureValue(interp, tkWin, configSpecs, (char *)psPtr,
            Tcl_GetString(objv[3]), 0) != TCL_OK) {
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * ConfigureOp --
 *
 *      This procedure is invoked to print the graph in a file.
 *
 * Results:
 *      A standard TCL result.
 *
 * Side effects:
 *      A new PostScript file is created.
 *
 * ----------------------------------------------------------------------
 */
static int
ConfigureOp(
    RbcGraph * graph,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{              /* Option vector */
    int flags = TK_CONFIG_ARGV_ONLY;
    RbcPostScript *psPtr = (RbcPostScript *) graph->postscript;
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return TCL_ERROR;
	}

    if(objc == 3) {
        return Tk_ConfigureInfo(interp, tkWin, configSpecs,
            (char *)psPtr, (char *)NULL, flags);
    } else if(objc == 4) {
        return Tk_ConfigureInfo(interp, tkWin, configSpecs,
            (char *)psPtr, Tcl_GetString(objv[3]), flags);
    }
    if(Tk_ConfigureWidget(interp, tkWin, configSpecs, objc - 3,
            (void *)(objv + 3), (char *)psPtr, flags|TK_CONFIG_OBJS) != TCL_OK) {
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 * --------------------------------------------------------------------------
 *
 * ComputeBoundingBox --
 *
 *      Computes the bounding box for the PostScript plot.  First get
 *      the size of the plot (by default, it's the size of graph's X
 *      window).  If the plot plus the page border is bigger than the
 *      designated paper size, or if the "-maxpect" option is turned
 *      on, scale the plot to the page.
 *
 *      Note: All coordinates/sizes are in screen coordinates, not
 *            PostScript coordinates.  This includes the computed
 *            bounding box and paper size.  They will be scaled to
 *            printer points later.
 *
 * Results:
 *      Returns the height of the paper in screen coordinates.
 *
 * Side Effects:
 *      The graph dimensions (width and height) are changed to the
 *      requested PostScript plot size.
 *
 * --------------------------------------------------------------------------
 */
static int
ComputeBoundingBox(
    RbcGraph * graph,
    RbcPostScript * psPtr)
{
int paperWidth, paperHeight;
int x, y, hSize, vSize, hBorder, vBorder;
double hScale, vScale, scale;

    x = psPtr->padX.side1;      /*left */
    y = psPtr->padY.side1;      /*top */
    hBorder = RbcPadding(psPtr->padX);
    vBorder = RbcPadding(psPtr->padY);

    if(psPtr->reqWidth > 0) {
        graph->width = psPtr->reqWidth;
    }
    if(psPtr->reqHeight > 0) {
        graph->height = psPtr->reqHeight;
    }
    if(psPtr->landscape) {
        hSize = graph->height;
        vSize = graph->width;
    } else {
        hSize = graph->width;
        vSize = graph->height;
    }
    /*
     * If the paper size wasn't specified, set it to the graph size plus
     * the paper border.
     */
    paperWidth = psPtr->reqPaperWidth;
    paperHeight = psPtr->reqPaperHeight;
    if(paperWidth < 1) {
        paperWidth = hSize + hBorder;
    }
    if(paperHeight < 1) {
        paperHeight = vSize + vBorder;
    }
    hScale = vScale = 1.0;
    /*
     * Scale the plot size (the graph itself doesn't change size) if
     * it's bigger than the paper or if -maxpect was set.
     */
    if((psPtr->maxpect) || ((hSize + hBorder) > paperWidth)) {
        hScale = (double)(paperWidth - hBorder) / (double)hSize;
    }
    if((psPtr->maxpect) || ((vSize + vBorder) > paperHeight)) {
        vScale = (double)(paperHeight - vBorder) / (double)vSize;
    }
    scale = MIN(hScale, vScale);
    if(scale != 1.0) {
        hSize = (int)((hSize * scale) + 0.5);
        vSize = (int)((vSize * scale) + 0.5);
    }
    psPtr->pageScale = scale;
    if(psPtr->center) {
        if(paperWidth > hSize) {
            x = (paperWidth - hSize) / 2;
        }
        if(paperHeight > vSize) {
            y = (paperHeight - vSize) / 2;
        }
    }
    psPtr->left = x;
    psPtr->bottom = y;
    psPtr->right = x + hSize - 1;
    psPtr->top = y + vSize - 1;

    graph->flags |= RBC_LAYOUT_NEEDED | RBC_MAP_WORLD;
    RbcLayoutGraph(graph);
    return paperHeight;
}

/*
 * --------------------------------------------------------------------------
 *
 * PreviewImage --
 *
 *      Generates a EPSI thumbnail of the graph.  The thumbnail is
 *      restricted to a certain size.  This is to keep the size of the
 *      PostScript file small and the processing time low.
 *
 *      The graph is drawn into a pixmap.  We then take a snapshot
 *      of that pixmap, and rescale it to a smaller image.  Finally,
 *       the image is dumped to PostScript.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 * --------------------------------------------------------------------------
 */
static void
PreviewImage(
    RbcGraph * graph,
    RbcPsToken * psToken)
{
RbcPostScript *psPtr = (RbcPostScript *) graph->postscript;
int noBackingStore = 0;
Pixmap drawable;
RbcColorImage *image;
int nLines;
Tcl_DString dString;
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return;
	}

    /* Create a pixmap and draw the graph into it. */

    drawable = Tk_GetPixmap(graph->widget.display, Tk_WindowId(tkWin),
        graph->width, graph->height, Tk_Depth(tkWin));
    RbcDrawGraph(graph, drawable, noBackingStore);

    /* Get a color image from the pixmap */
    image =
        RbcDrawableToColorImage(tkWin, drawable, 0, 0,
        graph->width, graph->height, 1.0);
    Tk_FreePixmap(graph->widget.display, drawable);
    if(image == NULL) {
        return; /* Can't grab pixmap? */
    }
#ifdef THUMBNAIL_PREVIEW
    {
double scale, xScale, yScale;
int width, height;
RbcColorImage *destImage;

        /* Scale the source image into a size appropriate for a thumbnail. */
#define PS_MAX_PREVIEW_WIDTH	300.0
#define PS_MAX_PREVIEW_HEIGHT	300.0
        xScale = PS_MAX_PREVIEW_WIDTH / (double)graph->width;
        yScale = PS_MAX_PREVIEW_HEIGHT / (double)graph->height;
        scale = MIN(xScale, yScale);

        width = (int)(scale * graph->width + 0.5);
        height = (int)(scale * graph->height + 0.5);
        destImage = RbcResampleColorImage(image, width, height,
            rbcBoxFilterPtr, rbcBoxFilterPtr);
        RbcFreeColorImage(image);
        image = destImage;
    }
#endif /* THUMBNAIL_PREVIEW */
    RbcColorImageToGreyscale(image);
    if(psPtr->landscape) {
RbcColorImage *oldImage;

        oldImage = image;
        image = RbcRotateColorImage(image, 90.0);
        RbcFreeColorImage(oldImage);
    }
    Tcl_DStringInit(&dString);
    /* Finally, we can generate PostScript for the image */
    nLines = ColorImageToPsData(image, 1, &dString, "%");

    RbcAppendToPostScript(psToken, "%%BeginPreview: ", (char *)NULL);
    RbcFormatToPostScript(psToken, "%d %d 8 %d\n", image->width,
        image->height, nLines);
    RbcAppendToPostScript(psToken, Tcl_DStringValue(&dString), (char *)NULL);
    RbcAppendToPostScript(psToken, "%%EndPreview\n\n", (char *)NULL);
    Tcl_DStringFree(&dString);
    RbcFreeColorImage(image);
}

#ifdef TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif /* HAVE_SYS_TIME_H */
#endif /* TIME_WITH_SYS_TIME */

/*
 *--------------------------------------------------------------
 *
 * PostScriptPreamble --
 *
 *      The PostScript preamble calculates the needed
 *      translation and scaling to make X11 coordinates
 *      compatible with PostScript.
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
static int
PostScriptPreamble(
    RbcGraph * graph,
    const char *fileName,
    RbcPsToken * psToken)
{
    RbcPostScript *psPtr = (RbcPostScript *) graph->postscript;
    time_t ticks;
    char date[200];            /* Hold the date string from ctime() */
    const char *version;
    double dpiX, dpiY;
    double xPixelsToPica, yPixelsToPica;        /* Scales to convert pixels to pica */
    Screen *screenPtr;
    char *nl;
    int paperHeightPixels;
    Tcl_Obj *preambleObj;
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return TCL_ERROR;
	}

    paperHeightPixels = ComputeBoundingBox(graph, psPtr);
    if(fileName == NULL) {
        fileName = Tk_PathName(tkWin);
    }
    RbcAppendToPostScript(psToken, "%!PS-Adobe-3.0 EPSF-3.0\n", (char *)NULL);

    /*
     * Compute the scale factors to convert PostScript to X11 coordinates.
     * Round the pixels per inch (dpi) to an integral value before computing
     * the scale.
     */
#define MM_INCH		25.4
#define PICA_INCH	72.0
    screenPtr = Tk_Screen(tkWin);
    dpiX = (WidthOfScreen(screenPtr) * MM_INCH) / WidthMMOfScreen(screenPtr);
    xPixelsToPica = PICA_INCH / dpiX;
    dpiY = (HeightOfScreen(screenPtr) * MM_INCH) / HeightMMOfScreen(screenPtr);
    yPixelsToPica = PICA_INCH / dpiY;

    /*
     * The "BoundingBox" comment is required for EPS files. The box
     * coordinates are integers, so we need round away from the
     * center of the box.
     */
    RbcFormatToPostScript(psToken, "%%%%BoundingBox: %d %d %d %d\n",
        (int)floor(psPtr->left * xPixelsToPica),
        (int)floor((paperHeightPixels - psPtr->top) * yPixelsToPica),
        (int)ceil(psPtr->right * xPixelsToPica),
        (int)ceil((paperHeightPixels - psPtr->bottom) * yPixelsToPica));

    RbcAppendToPostScript(psToken, "%%Pages: 0\n", (char *)NULL);

    version = Tcl_GetVar(graph->widget.interp, "rbc_version", TCL_GLOBAL_ONLY);
    if(version == NULL) {
        version = "???";
    }
    RbcFormatToPostScript(psToken, "%%%%Creator: (Rbc %s %s)\n", version,
        Tk_Class(tkWin));

    ticks = time((time_t *) NULL);
    strcpy(date, ctime(&ticks));
    nl = date + strlen(date) - 1;
    if(*nl == '\n') {
        *nl = '\0';
    }
    RbcFormatToPostScript(psToken, "%%%%CreationDate: (%s)\n", date);
    RbcFormatToPostScript(psToken, "%%%%Title: (%s)\n", fileName);
    RbcAppendToPostScript(psToken, "%%DocumentData: Clean7Bit\n", (char *)NULL);
    if(psPtr->landscape) {
        RbcAppendToPostScript(psToken, "%%Orientation: Landscape\n",
            (char *)NULL);
    } else {
        RbcAppendToPostScript(psToken, "%%Orientation: Portrait\n",
            (char *)NULL);
    }
    RbcAppendToPostScript(psToken,
        "%%DocumentNeededResources: font Helvetica Courier\n", (char *)NULL);
    RbcAppendToPostScript(psToken, "%%EndComments\n\n", (char *)NULL);
    if((psPtr->addPreview) && (psPtr->previewFormat == PS_PREVIEW_EPSI)) {
        PreviewImage(graph, psToken);
    }
    preambleObj = Tcl_GetVar2Ex(graph->widget.interp, "::tko::graph::ps_preamble", NULL,
        TCL_LEAVE_ERR_MSG);
    if(preambleObj == NULL) {
        return TCL_ERROR;
    }
    RbcAppendToPostScript(psToken, Tcl_GetString(preambleObj), (char *)NULL);
    if(psPtr->footer) {
    const char *who;

        who = getenv("LOGNAME");
        if(who == NULL) {
            who = "???";
        }
        RbcAppendToPostScript(psToken,
            "8 /Helvetica SetFont\n",
            "10 30 moveto\n",
            "(Date: ", date, ") show\n",
            "10 20 moveto\n",
            "(File: ", fileName, ") show\n",
            "10 10 moveto\n",
            "(Created by: ", who, "@", Tcl_GetHostName(), ") show\n",
            "0 0 moveto\n", (char *)NULL);
    }
    /*
     * Set the conversion from PostScript to X11 coordinates.  Scale
     * pica to pixels and flip the y-axis (the origin is the upperleft
     * corner).
     */
    RbcAppendToPostScript(psToken,
        "% Transform coordinate system to use X11 coordinates\n\n",
        "% 1. Flip y-axis over by reversing the scale,\n",
        "% 2. Translate the origin to the other side of the page,\n",
        "%    making the origin the upper left corner\n", (char *)NULL);
    RbcFormatToPostScript(psToken, "%g -%g scale\n", xPixelsToPica,
        yPixelsToPica);
    /* Papersize is in pixels.  Translate the new origin *after*
     * changing the scale. */
    RbcFormatToPostScript(psToken, "0 %d translate\n\n", -paperHeightPixels);
    RbcAppendToPostScript(psToken, "% User defined page layout\n\n",
        "% Set color level\n", (char *)NULL);
    RbcFormatToPostScript(psToken, "/CL %d def\n\n", psPtr->colorMode);
    RbcFormatToPostScript(psToken, "%% Set origin\n%d %d translate\n\n",
        psPtr->left, psPtr->bottom);
    if(psPtr->landscape) {
        RbcFormatToPostScript(psToken,
            "%% Landscape orientation\n0 %g translate\n-90 rotate\n",
            ((double)graph->width * psPtr->pageScale));
    }
    if(psPtr->pageScale != 1.0) {
        RbcAppendToPostScript(psToken, "\n% Setting graph scale factor\n",
            (char *)NULL);
        RbcFormatToPostScript(psToken, " %g %g scale\n", psPtr->pageScale,
            psPtr->pageScale);
    }
    RbcAppendToPostScript(psToken, "\n%%EndSetup\n\n", (char *)NULL);
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * MarginsToPostScript --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
static void
MarginsToPostScript(
    RbcGraph * graph,
    RbcPsToken * psToken)
{
RbcPostScript *psPtr = (RbcPostScript *) graph->postscript;
XRectangle margin[4];

    margin[0].x = margin[0].y = margin[3].x = margin[1].x = 0;
    margin[0].width = margin[3].width = graph->width;
    margin[0].height = graph->top;
    margin[3].y = graph->bottom;
    margin[3].height = graph->height - graph->bottom;
    margin[2].y = margin[1].y = graph->top;
    margin[1].width = graph->left;
    margin[2].height = margin[1].height = graph->bottom - graph->top;
    margin[2].x = graph->right;
    margin[2].width = graph->width - graph->right;

    /* Clear the surrounding margins and clip the plotting surface */
    if(psPtr->decorations) {
        RbcBackgroundToPostScript(psToken, Tk_3DBorderColor(graph->border));
    } else {
        RbcClearBackgroundToPostScript(psToken);
    }
    RbcRectanglesToPostScript(psToken, margin, 4);

    /* Interior 3D border */
    if((psPtr->decorations) && (graph->plotBorderWidth > 0)) {
int x, y, width, height;

        x = graph->left - graph->plotBorderWidth;
        y = graph->top - graph->plotBorderWidth;
        width = (graph->right - graph->left) + (2 * graph->plotBorderWidth);
        height = (graph->bottom - graph->top) + (2 * graph->plotBorderWidth);
        RbcDraw3DRectangleToPostScript(psToken, graph->border,
            (double)x, (double)y, width, height, graph->plotBorderWidth,
            graph->plotRelief);
    }
    if(RbcLegendSite(graph->legend) & RBC_LEGEND_IN_MARGIN) {
        /*
         * Print the legend if we're using a site which lies in one
         * of the margins (left, right, top, or bottom) of the graph.
         */
        RbcLegendToPostScript(graph->legend, psToken);
    }
    if(graph->title != NULL) {
        RbcTextToPostScript(psToken, graph->title,
            &graph->titleTextStyle, (double)graph->titleX,
            (double)graph->titleY);
    }
    RbcAxesToPostScript(graph, psToken);
}

/*
 *--------------------------------------------------------------
 *
 * GraphToPostScript --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
static int
GraphToPostScript(
    RbcGraph * graph,
    const char *ident,         /* Identifier string (usually the filename) */
    RbcPsToken * psToken)
{
    int x, y, width, height;
    int result;
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return TCL_ERROR;
	}

    /*
     * We need to know how big a graph to print.  If the graph hasn't
     * been drawn yet, the width and height will be 1.  Instead use
     * the requested size of the widget.  The user can still override
     * this with the -width and -height postscript options.
     */
    if(graph->height <= 1) {
        graph->height = Tk_ReqHeight(tkWin);
    }
    if(graph->width <= 1) {
        graph->width = Tk_ReqWidth(tkWin);
    }
    result = PostScriptPreamble(graph, ident, psToken);
    if(result != TCL_OK) {
        goto error;
    }
    /*
     * Determine rectangle of the plotting area for the graph window
     */
    x = graph->left - graph->plotBorderWidth;
    y = graph->top - graph->plotBorderWidth;

    width = (graph->right - graph->left + 1) + (2 * graph->plotBorderWidth);
    height = (graph->bottom - graph->top + 1) + (2 * graph->plotBorderWidth);

    RbcFontToPostScript(psToken, graph->titleTextStyle.font);
    RbcRegionToPostScript(psToken, (double)x, (double)y, width, height);
    if(graph->postscript->decorations) {
        RbcBackgroundToPostScript(psToken, graph->plotBg);
    } else {
        RbcClearBackgroundToPostScript(psToken);
    }
    RbcAppendToPostScript(psToken, "Fill\n", (char *)NULL);
    RbcAppendToPostScript(psToken, "gsave clip\n\n", (char *)NULL);
    /* Draw the grid, elements, and markers in the plotting area. */
    if(!graph->gridPtr->hidden) {
        RbcGridToPostScript(graph, psToken);
    }
    RbcMarkersToPostScript(graph, psToken, TRUE);
    if((RbcLegendSite(graph->legend) & RBC_LEGEND_IN_PLOT) &&
        (!RbcLegendIsRaised(graph->legend))) {
        /* Print legend underneath elements and markers */
        RbcLegendToPostScript(graph->legend, psToken);
    }
    RbcAxisLimitsToPostScript(graph, psToken);
    RbcElementsToPostScript(graph, psToken);
    if((RbcLegendSite(graph->legend) & RBC_LEGEND_IN_PLOT) &&
        (RbcLegendIsRaised(graph->legend))) {
        /* Print legend above elements (but not markers) */
        RbcLegendToPostScript(graph->legend, psToken);
    }
    RbcMarkersToPostScript(graph, psToken, FALSE);
    RbcActiveElementsToPostScript(graph, psToken);
    RbcAppendToPostScript(psToken, "\n",
        "% Unset clipping\n", "grestore\n\n", (char *)NULL);
    MarginsToPostScript(graph, psToken);
    RbcAppendToPostScript(psToken,
        "showpage\n",
        "%Trailer\n", "grestore\n", "end\n", "%EOF\n", (char *)NULL);
  error:
    /* Reset height and width of graph window */
    graph->width = Tk_Width(tkWin);
    graph->height = Tk_Height(tkWin);
    graph->flags = RBC_MAP_WORLD;

    /*
     * Redraw the graph in order to re-calculate the layout as soon as
     * possible. This is in the case the crosshairs are active.
     */
    RbcEventuallyRedrawGraph(graph);
    return result;
}

#ifdef _WIN32

/*
*--------------------------------------------------------------
*
* ReturnLastError --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static char *
ReturnLastError(
)
{
	static char buffer[1024];
	int length;

	FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
		buffer, 1024, NULL);
	length = strlen(buffer);
	if (buffer[length - 2] == '\r') {
		buffer[length - 2] = '\0';
	}
	return buffer;
}

/*
 * --------------------------------------------------------------------------
 *
 * CreateWindowEPS --
 *
 *      Generates an EPS file with a Window metafile preview.
 *
 *      Windows metafiles aren't very robust.  Including exactly the
 *      same metafile (one embedded in a DOS EPS, the other as .wmf
 *      file) will play back differently.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      TODO: Side Effects
 *
 * --------------------------------------------------------------------------
 */
static int
CreateWindowsEPS(
    RbcGraph * graph,
    RbcPsToken * psToken,
    FILE * f)
{
DWORD size;
DOSEPSHEADER epsHeader;
HANDLE hMem;
HDC hRefDC, hDC;
HENHMETAFILE hMetaFile;
Tcl_DString dString;
TkWinDC drawableDC;
TkWinDCState state;
int result;
unsigned char *buffer;
char *psBuffer;
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return TCL_ERROR;
	}

    RbcAppendToPostScript(psToken, "\n", (char *)NULL);
    psBuffer = PostscriptFromToken(psToken);
    /*
     * Fill out as much information as we can into the DOS EPS header.
     * We won't know the start of the length of the WMF segment until
     * we create the metafile.
     */
    epsHeader.magic[0] = 0xC5;
    epsHeader.magic[1] = 0xD0;
    epsHeader.magic[2] = 0xD3;
    epsHeader.magic[3] = 0xC6;
    epsHeader.psStart = sizeof(epsHeader);
    epsHeader.psLength = strlen(psBuffer) + 1;
    epsHeader.wmfStart = epsHeader.psStart + epsHeader.psLength;
    epsHeader.wmfLength = 0L;   /* Fill in later. */
    epsHeader.tiffStart = 0L;
    epsHeader.tiffLength = 0L;
    epsHeader.checksum = 0xFFFF;

    result = TCL_ERROR;
    hRefDC = TkWinGetDrawableDC(graph->widget.display,
        Tk_WindowId(tkWin), &state);

    /* Build a description string. */
    Tcl_DStringInit(&dString);
    Tcl_DStringAppend(&dString, "Rbc Graph ", -1);
    Tcl_DStringAppend(&dString, "\0", -1);
    Tcl_DStringAppend(&dString, Tk_PathName(tkWin), -1);
    Tcl_DStringAppend(&dString, "\0", -1);

    hDC = CreateEnhMetaFileA(hRefDC, NULL, NULL, Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);

    if(hDC == NULL) {
        Tcl_AppendResult(graph->widget.interp, "can't create metafile: ",
            ReturnLastError(), (char *)NULL);
        return TCL_ERROR;
    }
    /* Assemble a Tk drawable that points to the metafile and let the
     * graph's drawing routine draw into it. */
    drawableDC.hdc = hDC;
    drawableDC.type = TWD_WINDC;

    graph->width = Tk_Width(tkWin);
    graph->height = Tk_Height(tkWin);
    graph->flags |= RBC_RESET_WORLD;
    RbcLayoutGraph(graph);
    RbcDrawGraph(graph, (Drawable) & drawableDC, FALSE);
    GdiFlush();
    hMetaFile = CloseEnhMetaFile(hDC);

    size = GetWinMetaFileBits(hMetaFile, 0, NULL, MM_ANISOTROPIC, hRefDC);
    hMem = GlobalAlloc(GHND, size);
    if(hMem == NULL) {
        Tcl_AppendResult(graph->widget.interp, "can't allocate global memory:",
            ReturnLastError(), (char *)NULL);
        goto error;
    }
    buffer = (LPVOID) GlobalLock(hMem);
    if(!GetWinMetaFileBits(hMetaFile, size, buffer, MM_ANISOTROPIC, hRefDC)) {
        Tcl_AppendResult(graph->widget.interp, "can't get metafile data:",
            ReturnLastError(), (char *)NULL);
        goto error;
    }

    /*
     * Fix up the EPS header with the correct metafile length and PS
     * offset (now that we what they are).
     */
    epsHeader.wmfLength = size;
    epsHeader.wmfStart = epsHeader.psStart + epsHeader.psLength;

    /* Write out the eps header, */
    if(fwrite(&epsHeader, 1, sizeof(epsHeader), f) != sizeof(epsHeader)) {
        Tcl_AppendResult(graph->widget.interp, "error writing eps header:",
            ReturnLastError(), (char *)NULL);
        goto error;
    }
    /* the PostScript, */
    if(fwrite(psBuffer, 1, epsHeader.psLength, f) != epsHeader.psLength) {
        Tcl_AppendResult(graph->widget.interp, "error writing PostScript data:",
            ReturnLastError(), (char *)NULL);
        goto error;
    }
    /* and finally the metadata itself. */
    if(fwrite(buffer, 1, size, f) != size) {
        Tcl_AppendResult(graph->widget.interp, "error writing metafile data:",
            ReturnLastError(), (char *)NULL);
        goto error;
    }
    result = TCL_OK;

  error:
    DeleteEnhMetaFile(hMetaFile);
    TkWinReleaseDrawableDC(Tk_WindowId(tkWin), hRefDC, &state);
    fclose(f);
    if(hMem != NULL) {
        GlobalUnlock(hMem);
        GlobalFree(hMem);
    }
    graph->flags = RBC_MAP_WORLD;
    RbcEventuallyRedrawGraph(graph);
    return result;
}

#endif /*_WIN32*/

/*
 *----------------------------------------------------------------------
 *
 * OutputOp --
 *
 *      This procedure is invoked to print the graph in a file.
 *
 * Results:
 *      Standard TCL result.  TCL_OK if plot was successfully printed,
 *      TCL_ERROR otherwise.
 *
 * Side effects:
 *      A new PostScript file is created.
 *
 *----------------------------------------------------------------------
 */
static int
OutputOp(
    RbcGraph * graph,          /* Graph widget record */
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{              /* Option vector */
    RbcPostScript *psPtr = (RbcPostScript *) graph->postscript;
    FILE *f = NULL;
    RbcPsToken *psToken;
    const char *fileName;      /* Name of file to write PostScript output
                                * If NULL, output is returned via
                                * interp->result. */
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return TCL_ERROR;
	}

    fileName = NULL;
    if(objc > 3) {
		char* c = Tcl_GetString(objv[3]);
        if(c[0] != '-') {
            fileName = c; /* First argument is the file name. */
            objv++, objc--;
        }
        if(Tk_ConfigureWidget(interp, tkWin, configSpecs, objc - 3,
                (void *)(objv + 3), (char *)psPtr, TK_CONFIG_ARGV_ONLY|TK_CONFIG_OBJS) != TCL_OK) {
            return TCL_ERROR;
        }
        if(fileName != NULL) {
#ifdef _WIN32
            f = fopen(fileName, "wb");
#else
            f = fopen(fileName, "w");
#endif
            if(f == NULL) {
                Tcl_AppendResult(interp, "can't create \"", fileName, "\": ",
                    Tcl_PosixError(interp), (char *)NULL);
                return TCL_ERROR;
            }
        }
    }
	psToken = (RbcPsToken *)ckalloc(sizeof(RbcPsToken));
	assert(psToken);

	psToken->fontVarName = NULL;
	psToken->colorVarName = NULL;
	psToken->interp = graph->widget.interp;
	psToken->tkwin = tkWin;
	psToken->colorMode = PS_MODE_COLOR;
	Tcl_DStringInit(&(psToken->dString));
    psToken->fontVarName = psPtr->fontVarName;
    psToken->colorVarName = psPtr->colorVarName;
    psToken->colorMode = psPtr->colorMode;

    if(GraphToPostScript(graph, fileName, psToken) != TCL_OK) {
        goto error;
    }
    /*
     * If a file name was given, write the results to that file
     */
    if(f != NULL) {
#ifdef _WIN32
        if((psPtr->addPreview) && (psPtr->previewFormat != PS_PREVIEW_EPSI)) {
            if(CreateWindowsEPS(graph, psToken, f)) {
                return TCL_ERROR;
            }
        } else {
            fputs(PostscriptFromToken(psToken), f);
            if(ferror(f)) {
                Tcl_AppendResult(interp, "error writing file \"", fileName,
                    "\": ", Tcl_PosixError(interp), (char *)NULL);
                goto error;
            }
        }
#else
        fputs(PostscriptFromToken(psToken), f);
        if(ferror(f)) {
            Tcl_AppendResult(interp, "error writing file \"", fileName, "\": ",
                Tcl_PosixError(interp), (char *)NULL);
            goto error;
        }
#endif /* _WIN32 */
        fclose(f);
    } else {
        Tcl_SetObjResult(interp,
            Tcl_NewStringObj(PostscriptFromToken(psToken), -1));
    }
	Tcl_DStringFree(&(psToken->dString));
	ckfree((char *)psToken);
    return TCL_OK;

  error:
    if(f != NULL) {
        fclose(f);
    }
	Tcl_DStringFree(&(psToken->dString));
	ckfree((char *)psToken);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * RbcCreatePostScript --
 *
 *      Creates a postscript structure.
 *
 * Results:
 *      Always TCL_OK.
 *
 * Side effects:
 *      A new PostScript structure is created.
 *
 *----------------------------------------------------------------------
 */
int
RbcCreatePostScript(
    RbcGraph * graph)
{
RbcPostScript *psPtr;
	Tk_Window tkWin = graph->widget.tkWin;
	if (tkWin == NULL) {
		return TCL_ERROR;
	}

    psPtr=ckalloc(sizeof(RbcPostScript));
    assert(psPtr);
    memset(psPtr,0,sizeof(RbcPostScript));
    psPtr->colorMode = PS_MODE_COLOR;
    psPtr->center = TRUE;
    psPtr->decorations = TRUE;
    graph->postscript = psPtr;

    if(RbcConfigureWidgetComponent(graph->widget.interp, tkWin,
            "postscript", "Postscript", configSpecs, 0, NULL,
            (char *)psPtr, 0) != TCL_OK) {
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * RbcPostScriptOp --
 *
 *      This procedure is invoked to process the Tcl command
 *      that corresponds to a widget managed by this module.
 *      See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *--------------------------------------------------------------
 */
static RbcOpSpec psOps[] = {
    {"cget", 2, (RbcOp) CgetOp, 4, 4, "option",},
    {"configure", 2, (RbcOp) ConfigureOp, 3, 0, "?option value?...",},
    {"output", 1, (RbcOp) OutputOp, 3, 0,
        "?fileName? ?option value?...",},
};

static int nPsOps = sizeof(psOps) / sizeof(RbcOpSpec);

/*
 *--------------------------------------------------------------
 *
 * RbcPostScriptOp --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side effects:
 *      TODO: Side Effects
 *
 *--------------------------------------------------------------
 */
int
RbcPostScriptOp(
    RbcGraph * graph,          /* Graph widget record */
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{              /* Argument list */
    RbcOp proc;
    int result;

    proc = RbcGetOp(interp, nPsOps, psOps, RBC_OP_ARG2, objc, objv, 0);
    if(proc == NULL) {
        return TCL_ERROR;
    }
    result = (*proc) (graph, interp, objc, objv);
    return result;
}

/*
*--------------------------------------------------------------
*
* RbcGetPsToken --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
RbcPsToken *
RbcGetPsToken(
	Tcl_Interp * interp,
	Tk_Window tkwin)
{
	RbcPsToken *tokenPtr;

	tokenPtr = (RbcPsToken *)ckalloc(sizeof(RbcPsToken));
	assert(tokenPtr);

	tokenPtr->fontVarName = tokenPtr->colorVarName = NULL;
	tokenPtr->interp = interp;
	tokenPtr->tkwin = tkwin;
	tokenPtr->colorMode = PS_MODE_COLOR;
	Tcl_DStringInit(&(tokenPtr->dString));
	return tokenPtr;
}

/*
*--------------------------------------------------------------
*
* PostscriptFromToken --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static char *
PostscriptFromToken(
	RbcPsToken * tokenPtr)
{
	return Tcl_DStringValue(&(tokenPtr->dString));
}

/*
*--------------------------------------------------------------
*
* RbcAppendToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcAppendToPostScript(
	RbcPsToken * psToken,
	...)
{
	va_list argList;
	RbcPsToken *tokenPtr;
	char *string;

	tokenPtr = psToken;
	va_start(argList, psToken);
	for (;;) {
		string = va_arg(argList, char *);
		if (string == NULL) {
			break;
		}
		Tcl_DStringAppend(&(tokenPtr->dString), string, -1);
	}
}

/*
*--------------------------------------------------------------
*
* RbcFormatToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcFormatToPostScript(
	RbcPsToken * psToken,
	...)
{
	va_list argList;
	RbcPsToken *tokenPtr;
	char *fmt;

	tokenPtr = psToken;
	va_start(argList, psToken);
	fmt = va_arg(argList, char *);
	vsprintf(tokenPtr->scratchArr, fmt, argList);
	va_end(argList);
	Tcl_DStringAppend(&(tokenPtr->dString), tokenPtr->scratchArr, -1);
}

/*
*----------------------------------------------------------------------
*
* XColorToPostScript --
*
*      Convert the a XColor (from its RGB values) to a PostScript
*      command.  If a Tk color map variable exists, it will be
*      consulted for a PostScript translation based upon the color
*      name.
*
*      Maps an X color intensity (0 to 2^16-1) to a floating point
*      value [0..1].  Many versions of Tk don't properly handle the
*      the lower 8 bits of the color intensity, so we can only
*      consider the upper 8 bits.
*
* Results:
*      The string representing the color mode is returned.
*
* Side effects:
*      TODO: Side Effects
*
*----------------------------------------------------------------------
*/
static void
XColorToPostScript(
	RbcPsToken * tokenPtr,
	XColor * colorPtr)
{              /* Color value to be converted */
			   /*
			   * Shift off the lower byte before dividing because some versions
			   * of Tk don't fill the lower byte correctly.
			   */
	RbcFormatToPostScript(tokenPtr, "%g %g %g",
		((double)(colorPtr->red >> 8) / 255.0),
		((double)(colorPtr->green >> 8) / 255.0),
		((double)(colorPtr->blue >> 8) / 255.0));
}

/*
*--------------------------------------------------------------
*
* RbcBackgroundToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcBackgroundToPostScript(
	RbcPsToken * tokenPtr,
	XColor * colorPtr)
{
	/* If the color name exists in Tcl array variable, use that translation */
	if (tokenPtr->colorVarName != NULL) {
		const char *psColor;

		psColor = Tcl_GetVar2(tokenPtr->interp, tokenPtr->colorVarName,
			Tk_NameOfColor(colorPtr), 0);
		if (psColor != NULL) {
			RbcAppendToPostScript(tokenPtr, " ", psColor, "\n", (char *)NULL);
			return;
		}
	}
	XColorToPostScript(tokenPtr, colorPtr);
	RbcAppendToPostScript(tokenPtr, " SetBgColor\n", (char *)NULL);
}

/*
*--------------------------------------------------------------
*
* RbcForegroundToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcForegroundToPostScript(
	RbcPsToken * tokenPtr,
	XColor * colorPtr)
{
	/* If the color name exists in Tcl array variable, use that translation */
	if (tokenPtr->colorVarName != NULL) {
		const char *psColor;

		psColor = Tcl_GetVar2(tokenPtr->interp, tokenPtr->colorVarName,
			Tk_NameOfColor(colorPtr), 0);
		if (psColor != NULL) {
			RbcAppendToPostScript(tokenPtr, " ", psColor, "\n", (char *)NULL);
			return;
		}
	}
	XColorToPostScript(tokenPtr, colorPtr);
	RbcAppendToPostScript(tokenPtr, " SetFgColor\n", (char *)NULL);
}

/*
*----------------------------------------------------------------------
*
* ReverseBits --
*
*      Convert a byte from a X image into PostScript image order.
*      This requires not only the nybbles to be reversed but also
*      their bit values.
*
* Results:
*      The converted byte is returned.
*
* Side effects:
*      TODO: Side Effects
*
*----------------------------------------------------------------------
*/
static unsigned char
ReverseBits(
	register unsigned char byte)
{
	byte = ((byte >> 1) & 0x55) | ((byte << 1) & 0xaa);
	byte = ((byte >> 2) & 0x33) | ((byte << 2) & 0xcc);
	byte = ((byte >> 4) & 0x0f) | ((byte << 4) & 0xf0);
	return byte;
}

/*
*----------------------------------------------------------------------
*
* ByteToHex --
*
*      Convert a byte to its ASCII hexidecimal equivalent.
*
* Results:
*      The converted 2 ASCII character string is returned.
*
* Side effects:
*      TODO: Side Effects
*
*----------------------------------------------------------------------
*/
static void
ByteToHex(
	register unsigned char byte,
	char *string)
{
	static char hexDigits[] = "0123456789ABCDEF";

	string[0] = hexDigits[byte >> 4];
	string[1] = hexDigits[byte & 0x0F];
}

#ifdef _WIN32

/*
* -------------------------------------------------------------------------
*
* RbcBitmapDataToPostScript --
*
*      Output a PostScript image string of the given bitmap image.
*      It is assumed the image is one bit deep and a zero value
*      indicates an off-pixel.  To convert to PostScript, the bits
*      need to be reversed from the X11 image order.
*
* Results:
*      None.
*
* Side Effects:
*      The PostScript image string is appended.
*
* -------------------------------------------------------------------------
*/
void
RbcBitmapDataToPostScript(
	RbcPsToken * tokenPtr,
	Display * display,
	Pixmap bitmap,
	int width,
	int height)
{
	register unsigned char byte;
	register int x, y, bitPos;
	unsigned long pixel;
	int byteCount;
	char string[10];
	unsigned char *srcBits, *srcPtr;
	int bytesPerRow;

	srcBits = RbcGetBitmapData(display, bitmap, width, height, &bytesPerRow);
	if (srcBits == NULL) {
		OutputDebugStringA("Can't get bitmap data");
		return;
	}
	RbcAppendToPostScript(tokenPtr, "\t<", (char *)NULL);
	byteCount = bitPos = 0;     /* Suppress compiler warning */
	for (y = height - 1; y >= 0; y--) {
		srcPtr = srcBits + (bytesPerRow * y);
		byte = 0;
		for (x = 0; x < width; x++) {
			bitPos = x % 8;
			pixel = (*srcPtr & (0x80 >> bitPos));
			if (pixel) {
				byte |= (unsigned char)(1 << bitPos);
			}
			if (bitPos == 7) {
				byte = ReverseBits(byte);
				ByteToHex(byte, string);
				string[2] = '\0';
				byteCount++;
				srcPtr++;
				byte = 0;
				if (byteCount >= 30) {
					string[2] = '\n';
					string[3] = '\t';
					string[4] = '\0';
					byteCount = 0;
				}
				RbcAppendToPostScript(tokenPtr, string, (char *)NULL);
			}
		}      /* x */
		if (bitPos != 7) {
			byte = ReverseBits(byte);
			ByteToHex(byte, string);
			string[2] = '\0';
			RbcAppendToPostScript(tokenPtr, string, (char *)NULL);
			byteCount++;
		}
	}          /* y */
	ckfree((char *)srcBits);
	RbcAppendToPostScript(tokenPtr, ">\n", (char *)NULL);
}

#else

/*
* -------------------------------------------------------------------------
*
* RbcBitmapDataToPostScript --
*
*      Output a PostScript image string of the given bitmap image.
*      It is assumed the image is one bit deep and a zero value
*      indicates an off-pixel.  To convert to PostScript, the bits
*      need to be reversed from the X11 image order.
*
* Results:
*      None.
*
* Side Effects:
*      The PostScript image string is appended to interp->result.
*
* -------------------------------------------------------------------------
*/
void
RbcBitmapDataToPostScript(
	RbcPsToken * psToken,
	Display * display,
	Pixmap bitmap,
	int width,
	int height)
{
	register unsigned char byte = 0;
	register int x, y, bitPos;
	unsigned long pixel;
	XImage *imagePtr;
	int byteCount;
	char string[10];

	imagePtr = XGetImage(display, bitmap, 0, 0, width, height, 1, ZPixmap);
	RbcAppendToPostScript(psToken, "\t<", (char *)NULL);
	byteCount = bitPos = 0;     /* Suppress compiler warning */
	for (y = 0; y < height; y++) {
		byte = 0;
		for (x = 0; x < width; x++) {
			pixel = XGetPixel(imagePtr, x, y);
			bitPos = x % 8;
			byte |= (unsigned char)(pixel << bitPos);
			if (bitPos == 7) {
				byte = ReverseBits(byte);
				ByteToHex(byte, string);
				string[2] = '\0';
				byteCount++;
				byte = 0;
				if (byteCount >= 30) {
					string[2] = '\n';
					string[3] = '\t';
					string[4] = '\0';
					byteCount = 0;
				}
				RbcAppendToPostScript(psToken, string, (char *)NULL);
			}
		}      /* x */
		if (bitPos != 7) {
			byte = ReverseBits(byte);
			ByteToHex(byte, string);
			string[2] = '\0';
			RbcAppendToPostScript(psToken, string, (char *)NULL);
			byteCount++;
		}
	}          /* y */
	RbcAppendToPostScript(psToken, ">\n", (char *)NULL);
	XDestroyImage(imagePtr);
}

#endif /* _WIN32 */

/*
*----------------------------------------------------------------------
*
* ColorImageToPsData --
*
*      Converts a color image to PostScript RGB (3 components)
*      or Greyscale (1 component) output.  With 3 components, we
*      assume the "colorimage" operator is available.
*
*      Note that the image converted from bottom to top, to conform
*      to the PostScript coordinate system.
*
* Results:
*      The PostScript data comprising the color image is written
*      into the dynamic string.
*
* Side effects:
*      TODO: Side Effects
*
*----------------------------------------------------------------------
*/
static int
ColorImageToPsData(
	RbcColorImage * image,
	int nComponents,
	Tcl_DString * resultPtr,
	const char *prefix)
{
	char string[10];
	register int count;
	register int x, y;
	register RbcPix32 *pixelPtr;
	unsigned char byte;
	int width, height;
	int offset;
	int nLines;
	width = image->width;
	height = image->height;

	nLines = 0;
	count = 0;
	offset = (height - 1) * width;
	if (nComponents == 3) {
		for (y = (height - 1); y >= 0; y--) {
			pixelPtr = image->bits + offset;
			for (x = 0; x < width; x++, pixelPtr++) {
				if (count == 0) {
					Tcl_DStringAppend(resultPtr, prefix, -1);
					Tcl_DStringAppend(resultPtr, " ", -1);
				}
				count += 6;
				ByteToHex(pixelPtr->rgba.red, string);
				ByteToHex(pixelPtr->rgba.green, string + 2);
				ByteToHex(pixelPtr->rgba.blue, string + 4);
				string[6] = '\0';
				if (count >= 60) {
					string[6] = '\n';
					string[7] = '\0';
					count = 0;
					nLines++;
				}
				Tcl_DStringAppend(resultPtr, string, -1);
			}
			offset -= width;
		}
	}
	else if (nComponents == 1) {
		for (y = (height - 1); y >= 0; y--) {
			pixelPtr = image->bits + offset;
			for (x = 0; x < width; x++, pixelPtr++) {
				if (count == 0) {
					Tcl_DStringAppend(resultPtr, prefix, -1);
					Tcl_DStringAppend(resultPtr, " ", -1);
				}
				count += 2;
				byte = ~(pixelPtr->rgba.red);
				ByteToHex(byte, string);
				string[2] = '\0';
				if (count >= 60) {
					string[2] = '\n';
					string[3] = '\0';
					count = 0;
					nLines++;
				}
				Tcl_DStringAppend(resultPtr, string, -1);
			}
			offset -= width;
		}
	}
	if (count != 0) {
		Tcl_DStringAppend(resultPtr, "\n", -1);
		nLines++;
	}
	return nLines;
}

#ifndef  _WIN32

/*
*----------------------------------------------------------------------
*
* NameOfAtom --
*
*      Wrapper routine for Tk_GetAtomName.  Returns NULL instead of
*      "?bad atom?" if the atom can't be found.
*
* Results:
*      The name of the atom is returned if found. Otherwise NULL.
*
* Side effects:
*      TODO: Side Effects
*
*----------------------------------------------------------------------
*/
static const char *
NameOfAtom(
	Tk_Window tkwin,
	Atom atom)
{
	char *result;

	result = Tk_GetAtomName(tkwin, atom);
	if ((result[0] == '?') && (strcmp(result, "?bad atom?") == 0)) {
		return NULL;
	}
	return result;
}
#endif

typedef struct {
	const char *alias;
	const char *fontName;
} FontMap;

#ifndef  _WIN32

static FontMap psFontMap[] = {
        { "Arial", "Helvetica", },
        { "AvantGarde", "AvantGarde", },
        { "Courier New", "Courier", },
        { "Courier", "Courier", },
        { "Geneva", "Helvetica", },
        { "Helvetica", "Helvetica", },
        { "Monaco", "Courier", },
        { "New Century Schoolbook", "NewCenturySchlbk", },
        { "New York", "Times", },
        { "Palatino", "Palatino", },
        { "Symbol", "Symbol", },
        { "Times New Roman", "Times", },
        { "Times Roman", "Times", },
        { "Times", "Times", },
        { "Utopia", "Utopia", },
        { "ZapfChancery", "ZapfChancery", },
        { "ZapfDingbats", "ZapfDingbats", },
};

static int nFontNames = (sizeof(psFontMap) / sizeof(FontMap));

/*
* -----------------------------------------------------------------
*
* XFontStructToPostScript --
*
*      Map X11 font to a PostScript font. Currently, only fonts whose
*      FOUNDRY property are "Adobe" are converted. Simply gets the
*      XA_FULL_NAME and XA_FAMILY properties and pieces together a
*      PostScript fontname.
*
* Results:
*      Returns the mapped PostScript font name if one is possible.
*      Otherwise returns NULL.
*
* Side effects:
*      TODO: Side Effects
*
* -----------------------------------------------------------------
*/
static char *
XFontStructToPostScript(
	Tk_Window tkwin,           /* Window to query for atoms */
	XFontStruct * fontPtr)
{              /* Font structure to map to name */
	Atom atom;
	char *fullName, *family, *foundry;
	register char *src, *dest;
	int familyLen;
	char *start;
	static char string[200];       /* What size? */

	if (XGetFontProperty(fontPtr, XA_FULL_NAME, &atom) == False) {
		return NULL;
	}
	fullName = NameOfAtom(tkwin, atom);
	if (fullName == NULL) {
		return NULL;
	}
	family = foundry = NULL;
	if (XGetFontProperty(fontPtr, Tk_InternAtom(tkwin, "FOUNDRY"), &atom)) {
		foundry = NameOfAtom(tkwin, atom);
	}
	if (XGetFontProperty(fontPtr, XA_FAMILY_NAME, &atom)) {
		family = NameOfAtom(tkwin, atom);
	}
	/*
	* Try to map the font only if the foundry is Adobe
	*/
	if ((foundry == NULL) || (family == NULL)) {
		return NULL;
	}
	src = NULL;
	familyLen = strlen(family);
	if (strncasecmp(fullName, family, familyLen) == 0) {
		src = fullName + familyLen;
	}
	if (strcmp(foundry, "Adobe") != 0) {
		register int i;

		if (strncasecmp(family, "itc ", 4) == 0) {
			family += 4;        /* Throw out the "itc" prefix */
		}
		for (i = 0; i < nFontNames; i++) {
			if (strcasecmp(family, psFontMap[i].alias) == 0) {
				family = psFontMap[i].fontName;
			}
		}
		if (i == nFontNames) {
			family = "Helvetica";       /* Default to a known font */
		}
	}
	/*
	* PostScript font name is in the form <family>-<type face>
	*/
	sprintf(string, "%s-", family);
	dest = start = string + strlen(string);

	/*
	* Append the type face (part of the full name trailing the family name)
	* to the the PostScript font name, removing any spaces or dashes
	*
	* ex. " Bold Italic" ==> "BoldItalic"
	*/
	if (src != NULL) {
		while (*src != '\0') {
			if ((*src != ' ') && (*src != '-')) {
				*dest++ = *src;
			}
			src++;
		}
	}
	if (dest == start) {
		--dest; /* Remove '-' to leave just the family name */
	}
	*dest = '\0';       /* Make a valid string */
	return string;
}

#endif /* !_WIN32 */

/*
* -------------------------------------------------------------------
* Routines to convert X drawing functions to PostScript commands.
* -------------------------------------------------------------------
*/

/*
*--------------------------------------------------------------
*
* RbcClearBackgroundToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcClearBackgroundToPostScript(
	RbcPsToken * tokenPtr)
{
	RbcAppendToPostScript(tokenPtr, " 1.0 1.0 1.0 SetBgColor\n", (char *)NULL);
}

/*
*--------------------------------------------------------------
*
* CapStyleToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
CapStyleToPostScript(
	RbcPsToken * tokenPtr,
	int capStyle)
{
	/*
	* X11:not last = 0, butt = 1, round = 2, projecting = 3
	* PS: butt = 0, round = 1, projecting = 2
	*/
	if (capStyle > 0) {
		capStyle--;
	}
	RbcFormatToPostScript(tokenPtr, "%d setlinecap\n", capStyle);
}

/*
*--------------------------------------------------------------
*
* JoinStyleToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
JoinStyleToPostScript(
	RbcPsToken * tokenPtr,
	int joinStyle)
{
	/*
	* miter = 0, round = 1, bevel = 2
	*/
	RbcFormatToPostScript(tokenPtr, "%d setlinejoin\n", joinStyle);
}

/*
*--------------------------------------------------------------
*
* RbcLineWidthToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcLineWidthToPostScript(
	RbcPsToken * tokenPtr,
	int lineWidth)
{
	if (lineWidth < 1) {
		lineWidth = 1;
	}
	RbcFormatToPostScript(tokenPtr, "%d setlinewidth\n", lineWidth);
}

/*
*--------------------------------------------------------------
*
* RbcLineDashesToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcLineDashesToPostScript(
	RbcPsToken * tokenPtr,
	RbcDashes * dashesPtr)
{

	RbcAppendToPostScript(tokenPtr, "[ ", (char *)NULL);
	if (dashesPtr != NULL) {
		char *p;

		for (p = dashesPtr->values; *p != 0; p++) {
			RbcFormatToPostScript(tokenPtr, " %d", *p);
		}
	}
	RbcAppendToPostScript(tokenPtr, "] 0 setdash\n", (char *)NULL);
}

/*
*--------------------------------------------------------------
*
* RbcLineAttributesToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcLineAttributesToPostScript(
	RbcPsToken * tokenPtr,
	XColor * colorPtr,
	int lineWidth,
	RbcDashes * dashesPtr,
	int capStyle,
	int joinStyle)
{
	JoinStyleToPostScript(tokenPtr, joinStyle);
	CapStyleToPostScript(tokenPtr, capStyle);
	RbcForegroundToPostScript(tokenPtr, colorPtr);
	RbcLineWidthToPostScript(tokenPtr, lineWidth);
	RbcLineDashesToPostScript(tokenPtr, dashesPtr);
	RbcAppendToPostScript(tokenPtr, "/DashesProc {} def\n", (char *)NULL);
}

/*
*--------------------------------------------------------------
*
* RbcRectangleToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcRectangleToPostScript(
	RbcPsToken * tokenPtr,
	double x,
	double y,
	int width,
	int height)
{
	RbcFormatToPostScript(tokenPtr,
		"%g %g %d %d Box fill\n\n", x, y, width, height);
}

/*
*--------------------------------------------------------------
*
* RbcRegionToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcRegionToPostScript(
	RbcPsToken * tokenPtr,
	double x,
	double y,
	int width,
	int height)
{
	RbcFormatToPostScript(tokenPtr, "%g %g %d %d Box\n\n", x, y, width, height);
}

/*
*--------------------------------------------------------------
*
* RbcPathToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcPathToPostScript(
	RbcPsToken * tokenPtr,
	register RbcPoint2D * screenPts,
	int nScreenPts)
{
	register RbcPoint2D *pointPtr, *endPtr;

	pointPtr = screenPts;
	RbcFormatToPostScript(tokenPtr, "newpath %g %g moveto\n",
		pointPtr->x, pointPtr->y);
	pointPtr++;
	endPtr = screenPts + nScreenPts;
	while (pointPtr < endPtr) {
		RbcFormatToPostScript(tokenPtr, "%g %g lineto\n",
			pointPtr->x, pointPtr->y);
		pointPtr++;
	}
}

/*
*--------------------------------------------------------------
*
* RbcPolygonToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcPolygonToPostScript(
	RbcPsToken * tokenPtr,
	RbcPoint2D * screenPts,
	int nScreenPts)
{
	RbcPathToPostScript(tokenPtr, screenPts, nScreenPts);
	RbcFormatToPostScript(tokenPtr, "%g %g ", screenPts[0].x, screenPts[0].y);
	RbcAppendToPostScript(tokenPtr, " lineto closepath Fill\n", (char *)NULL);
}

/*
*--------------------------------------------------------------
*
* RbcSegmentsToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcSegmentsToPostScript(
	RbcPsToken * tokenPtr,
	register XSegment * segPtr,
	int nSegments)
{
	register int i;

	for (i = 0; i < nSegments; i++, segPtr++) {
		RbcFormatToPostScript(tokenPtr, "%d %d moveto\n",
			segPtr->x1, segPtr->y1);
		RbcFormatToPostScript(tokenPtr, " %d %d lineto\n",
			segPtr->x2, segPtr->y2);
		RbcAppendToPostScript(tokenPtr, "DashesProc stroke\n", (char *)NULL);
	}
}

/*
*--------------------------------------------------------------
*
* RbcRectanglesToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcRectanglesToPostScript(
	RbcPsToken * tokenPtr,
	XRectangle rectArr[],
	int nRects)
{
	register int i;

	for (i = 0; i < nRects; i++) {
		RbcRectangleToPostScript(tokenPtr,
			(double)rectArr[i].x, (double)rectArr[i].y,
			(int)rectArr[i].width, (int)rectArr[i].height);
	}
}

#ifndef TK_RELIEF_SOLID
#define TK_RELIEF_SOLID		-1      /* Set the an impossible value. */
#endif /* TK_RELIEF_SOLID */

/*
*--------------------------------------------------------------
*
* RbcDraw3DRectangleToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcDraw3DRectangleToPostScript(
	RbcPsToken * tokenPtr,
	Tk_3DBorder border,        /* Token for border to draw. */
	double x,                  /* Coordinates of rectangle */
	double y,                  /* Coordinates of rectangle */
	int width,                 /* Region to be drawn. */
	int height,                /* Region to be drawn. */
	int borderWidth,           /* Desired width for border, in pixels. */
	int relief)
{              /* Should be either TK_RELIEF_RAISED or
			   * TK_RELIEF_SUNKEN;  indicates position of
			   * interior of window relative to exterior. */
	TkBorder *borderPtr = (TkBorder *)border;
	XColor lightColor, darkColor;
	XColor *lightColorPtr, *darkColorPtr;
	XColor *topColor, *bottomColor;
	RbcPoint2D points[7];
	int twiceWidth = (borderWidth * 2);

	if ((width < twiceWidth) || (height < twiceWidth)) {
		return;
	}
	if ((relief == TK_RELIEF_SOLID) || (borderPtr->lightColorPtr == NULL)
		|| (borderPtr->darkColorPtr == NULL)) {
		if (relief == TK_RELIEF_SOLID) {
			darkColor.red = darkColor.blue = darkColor.green = 0x00;
			lightColor.red = lightColor.blue = lightColor.green = 0x00;
			relief = TK_RELIEF_SUNKEN;
		}
		else {
			Screen *screenPtr;

			lightColor = *borderPtr->bgColorPtr;
			screenPtr = Tk_Screen(tokenPtr->tkwin);
			if (lightColor.pixel == WhitePixelOfScreen(screenPtr)) {
				darkColor.red = darkColor.blue = darkColor.green = 0x00;
			}
			else {
				darkColor.red = darkColor.blue = darkColor.green = 0xFF;
			}
		}
		lightColorPtr = &lightColor;
		darkColorPtr = &darkColor;
	}
	else {
		lightColorPtr = borderPtr->lightColorPtr;
		darkColorPtr = borderPtr->darkColorPtr;
	}

	/*
	* Handle grooves and ridges with recursive calls.
	*/

	if ((relief == TK_RELIEF_GROOVE) || (relief == TK_RELIEF_RIDGE)) {
		int halfWidth, insideOffset;

		halfWidth = borderWidth / 2;
		insideOffset = borderWidth - halfWidth;
		RbcDraw3DRectangleToPostScript(tokenPtr, border, (double)x, (double)y,
			width, height, halfWidth,
			(relief == TK_RELIEF_GROOVE) ? TK_RELIEF_SUNKEN : TK_RELIEF_RAISED);
		RbcDraw3DRectangleToPostScript(tokenPtr, border,
			(double)(x + insideOffset), (double)(y + insideOffset),
			width - insideOffset * 2, height - insideOffset * 2, halfWidth,
			(relief == TK_RELIEF_GROOVE) ? TK_RELIEF_RAISED : TK_RELIEF_SUNKEN);
		return;
	}
	if (relief == TK_RELIEF_RAISED) {
		topColor = lightColorPtr;
		bottomColor = darkColorPtr;
	}
	else if (relief == TK_RELIEF_SUNKEN) {
		topColor = darkColorPtr;
		bottomColor = lightColorPtr;
	}
	else {
		topColor = bottomColor = borderPtr->bgColorPtr;
	}
	RbcBackgroundToPostScript(tokenPtr, bottomColor);
	RbcRectangleToPostScript(tokenPtr, x, y + height - borderWidth, width,
		borderWidth);
	RbcRectangleToPostScript(tokenPtr, x + width - borderWidth, y,
		borderWidth, height);
	points[0].x = points[1].x = points[6].x = x;
	points[0].y = points[6].y = y + height;
	points[1].y = points[2].y = y;
	points[2].x = x + width;
	points[3].x = x + width - borderWidth;
	points[3].y = points[4].y = y + borderWidth;
	points[4].x = points[5].x = x + borderWidth;
	points[5].y = y + height - borderWidth;
	if (relief != TK_RELIEF_FLAT) {
		RbcBackgroundToPostScript(tokenPtr, topColor);
	}
	RbcPolygonToPostScript(tokenPtr, points, 7);
}

/*
*--------------------------------------------------------------
*
* RbcFill3DRectangleToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcFill3DRectangleToPostScript(
	RbcPsToken * tokenPtr,
	Tk_3DBorder border,        /* Token for border to draw. */
	double x,                  /* Coordinates of top-left of border area */
	double y,                  /* Coordinates of top-left of border area */
	int width,                 /* Dimension of border to be drawn. */
	int height,                /* Dimension of border to be drawn. */
	int borderWidth,           /* Desired width for border, in pixels. */
	int relief)
{              /* Should be either TK_RELIEF_RAISED or
			   * TK_RELIEF_SUNKEN;  indicates position of
			   * interior of window relative to exterior. */
	TkBorder *borderPtr = (TkBorder *)border;

	/*
	* I'm assuming that the rectangle is to be drawn as a background.
	* Setting the pen color as foreground or background only affects
	* the plot when the colormode option is "monochrome".
	*/
	RbcBackgroundToPostScript(tokenPtr, borderPtr->bgColorPtr);
	RbcRectangleToPostScript(tokenPtr, x, y, width, height);
	RbcDraw3DRectangleToPostScript(tokenPtr, border, x, y, width, height,
		borderWidth, relief);
}

/*
*--------------------------------------------------------------
*
* RbcStippleToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcStippleToPostScript(
	RbcPsToken * tokenPtr,
	Display * display,
	Pixmap bitmap)
{
	int width, height;

	Tk_SizeOfBitmap(display, bitmap, &width, &height);
	RbcFormatToPostScript(tokenPtr, "gsave\n  clip\n  %d %d\n", width, height);
	RbcBitmapDataToPostScript(tokenPtr, display, bitmap, width, height);
	RbcAppendToPostScript(tokenPtr, "  StippleFill\ngrestore\n", (char *)NULL);
}

/*
*----------------------------------------------------------------------
*
* RbcColorImageToPostScript --
*
*      Translates a color image into 3 component RGB PostScript output.
*      Uses PS Language Level 2 operator "colorimage".
*
* Results:
*      The dynamic string will contain the PostScript output.
*
* Side effects:
*      TODO: Side Effects
*
*----------------------------------------------------------------------
*/
void
RbcColorImageToPostScript(
	RbcPsToken * tokenPtr,
	RbcColorImage * image,
	double x,
	double y)
{
	int width, height;
	int tmpSize;

	width = image->width;
	height = image->height;

	tmpSize = width;
	if (tokenPtr->colorMode == PS_MODE_COLOR) {
		tmpSize *= 3;
	}
	RbcFormatToPostScript(tokenPtr, "\n/tmpStr %d string def\n", tmpSize);
	RbcAppendToPostScript(tokenPtr, "gsave\n", (char *)NULL);
	RbcFormatToPostScript(tokenPtr, "  %g %g translate\n", x, y);
	RbcFormatToPostScript(tokenPtr, "  %d %d scale\n", width, height);
	RbcFormatToPostScript(tokenPtr, "  %d %d 8\n", width, height);
	RbcFormatToPostScript(tokenPtr, "  [%d 0 0 %d 0 %d] ", width, -height,
		height);
	RbcAppendToPostScript(tokenPtr,
		"{\n    currentfile tmpStr readhexstring pop\n  } ", (char *)NULL);
	if (tokenPtr->colorMode != PS_MODE_COLOR) {
		RbcAppendToPostScript(tokenPtr, "image\n", (char *)NULL);
		RbcColorImageToGreyscale(image);
		ColorImageToPsData(image, 1, &(tokenPtr->dString), " ");
	}
	else {
		RbcAppendToPostScript(tokenPtr, "false 3 colorimage\n", (char *)NULL);
		ColorImageToPsData(image, 3, &(tokenPtr->dString), " ");
	}
	RbcAppendToPostScript(tokenPtr, "\ngrestore\n\n", (char *)NULL);
}

/*
*----------------------------------------------------------------------
*
* RbcWindowToPostScript --
*
*      Converts a Tk window to PostScript.  If the window could not
*      be "snapped", then a grey rectangle is drawn in its place.
*
* Results:
*      None.
*
* Side effects:
*      TODO: Side Effects
*
*----------------------------------------------------------------------
*/
void
RbcWindowToPostScript(
	RbcPsToken * tokenPtr,
	Tk_Window tkwin,
	double x,
	double y)
{
	RbcColorImage *image;
	int width, height;

	width = Tk_Width(tkwin);
	height = Tk_Height(tkwin);
	image =
		RbcDrawableToColorImage(tkwin, Tk_WindowId(tkwin), 0, 0, width, height,
			1.0 /*gamma */);
	if (image == NULL) {
		/* Can't grab window image so paint the window area grey */
		RbcAppendToPostScript(tokenPtr, "% Can't grab window \"",
			Tk_PathName(tkwin), "\"\n", (char *)NULL);
		RbcAppendToPostScript(tokenPtr, "0.5 0.5 0.5 SetBgColor\n",
			(char *)NULL);
		RbcRectangleToPostScript(tokenPtr, x, y, width, height);
		return;
	}
	RbcColorImageToPostScript(tokenPtr, image, x, y);
	RbcFreeColorImage(image);
}

/*
* -------------------------------------------------------------------------
*
* RbcPhotoToPostScript --
*
*      Output a PostScript image string of the given photo image.
*      The photo is first converted into a color image and then
*      translated into PostScript.
*
* Results:
*      None.
*
* Side Effects:
*      The PostScript output representing the photo is appended to
*      the tokenPtr's dynamic string.
*
* -------------------------------------------------------------------------
*/
void
RbcPhotoToPostScript(
	RbcPsToken * tokenPtr,
	Tk_PhotoHandle photo,
	double x,                  /* Origin of photo image */
	double y)
{              /* Origin of photo image */
	RbcColorImage *image;

	image = RbcPhotoToColorImage(photo);
	RbcColorImageToPostScript(tokenPtr, image, x, y);
	RbcFreeColorImage(image);
}

/*
* -----------------------------------------------------------------
*
* RbcFontToPostScript --
*
*      Map the Tk font to a PostScript font and point size.
*
*      If a Tcl array variable was specified, each element should be
*      indexed by the X11 font name and contain a list of 1-2
*      elements; the PostScript font name and the desired point size.
*      The point size may be omitted and the X font point size will
*      be used.
*
*      Otherwise, if the foundry is "Adobe", we try to do a plausible
*      mapping looking at the full name of the font and building a
*      string in the form of "Family-TypeFace".
*
* Returns:
*      None.
*
* Side Effects:
*      PostScript commands are output to change the type and the
*      point size of the current font.
*
* -----------------------------------------------------------------
*/
void
RbcFontToPostScript(
	RbcPsToken * tokenPtr,
	Tk_Font font)
{              /* Tk font to query about */
	Tcl_Interp *interp = tokenPtr->interp;
	const char *fontName;
	double pointSize;
	Tcl_DString dString;

	fontName = Tk_NameOfFont(font);
	pointSize = 12.0;
	/*
	* Use the font variable information if it exists.
	*/
	if (tokenPtr->fontVarName != NULL) {
		char *fontInfo;

		fontInfo = (char *)Tcl_GetVar2(interp, tokenPtr->fontVarName, fontName,
			0);
		if (fontInfo != NULL) {
			Tcl_Size nProps;
			const char **propArr = NULL;

			if (Tcl_SplitList(interp, fontInfo, &nProps, &propArr) == TCL_OK) {
				int newSize;

				fontName = propArr[0];
				if ((nProps == 2) &&
					(Tcl_GetInt(interp, propArr[1], &newSize) == TCL_OK)) {
					pointSize = (double)newSize;
				}
			}
			RbcFormatToPostScript(tokenPtr,
				"%g /%s SetFont\n", pointSize, fontName);
			if (propArr != (const char **)NULL) {
				ckfree((char *)propArr);        /*TODO really delete here? */
			}
			return;
		}
	}
    /*
     * Tk_PostScriptFontName will silently generate a bogus PostScript
	 * font description. we ignore it so far because we can not use TkFont here.
     */
	Tcl_DStringInit(&dString);
	pointSize = (double)Tk_PostscriptFontName(font, &dString);
	fontName = Tcl_DStringValue(&dString);
	RbcFormatToPostScript(tokenPtr, "%g /%s SetFont\n", pointSize, fontName);
	Tcl_DStringFree(&dString);
	return;
}

/*
*--------------------------------------------------------------
*
* TextLayoutToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static void
TextLayoutToPostScript(
	RbcPsToken * tokenPtr,
	int x,
	int y,
	RbcTextLayout * textPtr)
{
	char *src, *dst, *end;
	int count;                 /* Counts the # of bytes written to
							   * the intermediate scratch buffer. */
	RbcTextFragment *fragPtr;
	int i;
	unsigned char c;
#if HAVE_UTF
	Tcl_UniChar ch;
#endif
	int limit;

	limit = (BUFSIZ * 2) - 5;   /* High water mark for the scratch
								* buffer. */
	fragPtr = textPtr->fragArr;
	for (i = 0; i < textPtr->nFrags; i++, fragPtr++) {
		if (fragPtr->count < 1) {
			continue;
		}
		RbcAppendToPostScript(tokenPtr, "(", (char *)NULL);
		count = 0;
		dst = tokenPtr->scratchArr;
		src = fragPtr->text;
		end = fragPtr->text + fragPtr->count;
		while (src < end) {
			if (count > limit) {
				/* Don't let the scatch buffer overflow */
				dst = tokenPtr->scratchArr;
				dst[count] = '\0';
				RbcAppendToPostScript(tokenPtr, dst, (char *)NULL);
				count = 0;
			}
#if HAVE_UTF
			/*
			* INTL: For now we just treat the characters as binary
			* data and display the lower byte.  Eventually this should
			* be revised to handle international postscript fonts.
			*/
			src += Tcl_UtfToUniChar(src, &ch);
			c = (unsigned char)(ch & 0xff);
#else
			c = *src++;
#endif

			if ((c == '\\') || (c == '(') || (c == ')')) {
				/*
				* If special PostScript characters characters "\", "(",
				* and ")" are contained in the text string, prepend
				* backslashes to them.
				*/
				*dst++ = '\\';
				*dst++ = c;
				count += 2;
			}
			else if ((c < ' ') || (c > '~')) {
				/*
				* Present non-printable characters in their octal
				* representation.
				*/
				sprintf(dst, "\\%03o", c);
				dst += 4;
				count += 4;
			}
			else {
				*dst++ = c;
				count++;
			}
		}
		tokenPtr->scratchArr[count] = '\0';
		RbcAppendToPostScript(tokenPtr, tokenPtr->scratchArr, (char *)NULL);
		RbcFormatToPostScript(tokenPtr, ") %d %d %d DrawAdjText\n",
			fragPtr->width, x + fragPtr->x, y + fragPtr->y);
	}
}

/*
* -----------------------------------------------------------------
*
* RbcTextToPostScript --
*
*      Output PostScript commands to print a text string. The string
*      may be rotated at any arbitrary angle, and placed according
*      the anchor type given. The anchor indicates how to interpret
*      the window coordinates as an anchor for the text bounding box.
*
* Results:
*      None.
*
* Side Effects:
*      Text string is drawn using the given font and GC on the graph
*      window at the given coordinates, anchor, and rotation
*
* -----------------------------------------------------------------
*/
void
RbcTextToPostScript(
	RbcPsToken * tokenPtr,
	char *string,              /* String to convert to PostScript */
	RbcTextStyle * tsPtr,      /* Text attribute information */
	double x,                  /* Window coordinates where to print text */
	double y)
{              /* Window coordinates where to print text */
	double theta;
	double rotWidth, rotHeight;
	RbcTextLayout *textPtr;
	RbcPoint2D anchorPos;

	if ((string == NULL) || (*string == '\0')) { /* Empty string, do nothing */
		return;
	}
	theta = FMOD(tsPtr->theta, (double)360.0);
	textPtr = RbcGetTextLayout(string, tsPtr);
	RbcGetBoundingBox(textPtr->width, textPtr->height, theta, &rotWidth,
		&rotHeight, (RbcPoint2D *)NULL);
	/*
	* Find the center of the bounding box
	*/
	anchorPos.x = x, anchorPos.y = y;
	anchorPos =
		RbcTranslatePoint(&anchorPos, ROUND(rotWidth), ROUND(rotHeight),
			tsPtr->anchor);
	anchorPos.x += (rotWidth * 0.5);
	anchorPos.y += (rotHeight * 0.5);

	/* Initialize text (sets translation and rotation) */
	RbcFormatToPostScript(tokenPtr, "%d %d %g %g %g BeginText\n",
		textPtr->width, textPtr->height, tsPtr->theta, anchorPos.x,
		anchorPos.y);

	RbcFontToPostScript(tokenPtr, tsPtr->font);

	/* All coordinates are now relative to what was set by BeginText */
	if ((tsPtr->shadow.offset > 0) && (tsPtr->shadow.color != NULL)) {
		RbcForegroundToPostScript(tokenPtr, tsPtr->shadow.color);
		TextLayoutToPostScript(tokenPtr, tsPtr->shadow.offset,
			tsPtr->shadow.offset, textPtr);
	}
	RbcForegroundToPostScript(tokenPtr,
		(tsPtr->state & RBC_STATE_ACTIVE) ? tsPtr->activeColor : tsPtr->color);
	TextLayoutToPostScript(tokenPtr, 0, 0, textPtr);
	ckfree((char *)textPtr);
	RbcAppendToPostScript(tokenPtr, "EndText\n", (char *)NULL);
}

/*
* -----------------------------------------------------------------
*
* RbcLineToPostScript --
*
*      Outputs PostScript commands to print a multi-segmented line.
*      It assumes a procedure DashesProc was previously defined.
*
* Results:
*      None.
*
* Side Effects:
*      Segmented line is printed.
*
* -----------------------------------------------------------------
*/
void
RbcLineToPostScript(
	RbcPsToken * tokenPtr,
	register XPoint * pointPtr,
	int nPoints)
{
	register int i;

	if (nPoints <= 0) {
		return;
	}
	RbcFormatToPostScript(tokenPtr, " newpath %d %d moveto\n",
		pointPtr->x, pointPtr->y);
	pointPtr++;
	for (i = 1; i < (nPoints - 1); i++, pointPtr++) {
		RbcFormatToPostScript(tokenPtr, " %d %d lineto\n",
			pointPtr->x, pointPtr->y);
		if ((i % PS_MAXPATH) == 0) {
			RbcFormatToPostScript(tokenPtr,
				"DashesProc stroke\n newpath  %d %d moveto\n",
				pointPtr->x, pointPtr->y);
		}
	}
	RbcFormatToPostScript(tokenPtr, " %d %d lineto\n",
		pointPtr->x, pointPtr->y);
	RbcAppendToPostScript(tokenPtr, "DashesProc stroke\n", (char *)NULL);
}

/*
*--------------------------------------------------------------
*
* RbcBitmapToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
RbcBitmapToPostScript(
	RbcPsToken * tokenPtr,
	Display * display,
	Pixmap bitmap,             /* Bitmap to be converted to PostScript */
	double scaleX,
	double scaleY)
{
	int width, height;
	double scaledWidth, scaledHeight;

	Tk_SizeOfBitmap(display, bitmap, &width, &height);
	scaledWidth = (double)width *scaleX;
	scaledHeight = (double)height *scaleY;
	RbcAppendToPostScript(tokenPtr, "  gsave\n", (char *)NULL);
	RbcFormatToPostScript(tokenPtr, "    %g %g translate\n",
		scaledWidth * -0.5, scaledHeight * 0.5);
	RbcFormatToPostScript(tokenPtr, "    %g %g scale\n",
		scaledWidth, -scaledHeight);
	RbcFormatToPostScript(tokenPtr, "    %d %d true [%d 0 0 %d 0 %d] {",
		width, height, width, -height, height);
	RbcBitmapDataToPostScript(tokenPtr, display, bitmap, width, height);
	RbcAppendToPostScript(tokenPtr, "    } imagemask\n  grestore\n",
		(char *)NULL);
}

/*
*--------------------------------------------------------------
*
* Rbc2DSegmentsToPostScript --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
void
Rbc2DSegmentsToPostScript(
	RbcPsToken * psToken,
	register RbcSegment2D * segPtr,
	int nSegments)
{
	register RbcSegment2D *endPtr;

	for (endPtr = segPtr + nSegments; segPtr < endPtr; segPtr++) {
		RbcFormatToPostScript(psToken, "%g %g moveto\n",
			segPtr->p.x, segPtr->p.y);
		RbcFormatToPostScript(psToken, " %g %g lineto\n",
			segPtr->q.x, segPtr->q.y);
		RbcAppendToPostScript(psToken, "DashesProc stroke\n", (char *)NULL);
	}
}

/* vim: set ts=4 sw=4 sts=4 ff=unix et : */
