Tk Source Code

Documentation
Login
/*
 * tkWinGDI.c --
 *
 *      This module implements access to the Win32 GDI API.
 *
 * Copyright © 1991-2018 Microsoft Corp.
 * Copyright © 2009, Michael I. Schwartz.
 * Copyright © 1998-2019 Harald Oehlmann, Elmicron GmbH
 * Copyright © 2021 Kevin Walzer/WordTech Communications LLC.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */


#include <windows.h>
#include <math.h>
#include <wtypes.h>
#include <winspool.h>
#include <commdlg.h>
#include <wingdi.h>

#include <tcl.h>

#include "tkWinInt.h"

/*
 * Create a standard "DrawFunc" to make this more workable....
 */
#ifdef _MSC_VER
typedef BOOL (WINAPI *DrawFunc) (
	HDC, int, int, int, int, int, int, int, int); /* Arc, Chord, Pie. */
#else
typedef BOOL WINAPI (*DrawFunc) (
	HDC, int, int, int, int, int, int, int, int); /* Arc, Chord, Pie. */
#endif

/* Real functions. */
static Tcl_ObjCmdProc2 GdiArc;
static Tcl_ObjCmdProc2 GdiBitmap;
static Tcl_ObjCmdProc2 GdiCharWidths;
static Tcl_ObjCmdProc2 GdiImage;
static Tcl_ObjCmdProc2 GdiPhoto;
static Tcl_ObjCmdProc2 GdiLine;
static Tcl_ObjCmdProc2 GdiOval;
static Tcl_ObjCmdProc2 GdiPolygon;
static Tcl_ObjCmdProc2 GdiRectangle;
static Tcl_ObjCmdProc2 GdiText;
static Tcl_ObjCmdProc2 GdiMap;
static Tcl_ObjCmdProc2 GdiCopyBits;

/* Local copies of similar routines elsewhere in Tcl/Tk. */
static int		GdiGetColor(Tcl_Obj *nameObj, COLORREF *color);

/*
 * Helper functions.
 */
static int		GdiMakeLogFont(Tcl_Interp *interp, const char *str,
			    LOGFONTW *lf, HDC hDC);
static int		GdiMakePen(Tcl_Interp *interp, double dwidth,
			    int dashstyle, const char *dashstyledata,
			    int capstyle, int joinstyle,
			    int stipplestyle, const char *stippledata,
			    unsigned long color, HDC hDC, HGDIOBJ *oldPen);
static int		GdiFreePen(Tcl_Interp *interp, HDC hDC, HGDIOBJ oldPen);
static int		GdiMakeBrush(unsigned long color, long hatch,
			    LOGBRUSH *lb, HDC hDC, HBRUSH *oldBrush);
static void		GdiFreeBrush(Tcl_Interp *interp, HDC hDC,
			    HGDIOBJ oldBrush);
static int		GdiGetHdcInfo(HDC hdc,
			    LPPOINT worigin, LPSIZE wextent,
			    LPPOINT vorigin, LPSIZE vextent);

/* Helper functions for printing the window client area. */
enum PrintType { PTWindow = 0, PTClient = 1, PTScreen = 2 };

static HANDLE		CopyToDIB(HWND wnd, enum PrintType type);
static HBITMAP		CopyScreenToBitmap(LPRECT lpRect);
static HANDLE		BitmapToDIB(HBITMAP hb, HPALETTE hp);
static HANDLE		CopyScreenToDIB(LPRECT lpRect);
static int		DIBNumColors(LPBITMAPINFOHEADER lpDIB);
static int		PalEntriesOnDevice(HDC hDC);
static HPALETTE		GetSystemPalette(void);
static void		GetDisplaySize(LONG *width, LONG *height);
static int		GdiWordToWeight(const char *str);
static int		GdiParseFontWords(Tcl_Interp *interp, LOGFONTW *lf,
			    const char *str[], int numargs);
static Tcl_ObjCmdProc2 PrintSelectPrinter;
static Tcl_ObjCmdProc2 PrintOpenPrinter;
static Tcl_ObjCmdProc2 PrintClosePrinter;
static Tcl_ObjCmdProc2 PrintOpenDoc;
static Tcl_ObjCmdProc2 PrintCloseDoc;
static Tcl_ObjCmdProc2 PrintOpenPage;
static Tcl_ObjCmdProc2 PrintClosePage;

/*
 * Global state.
 */

static PRINTDLGW pd;
static DOCINFOW di;
static WCHAR *localPrinterName = NULL;
static int copies, paper_width, paper_height, dpi_x, dpi_y;
static LPDEVNAMES devnames;
static HDC printDC;

/*
 * To make the "subcommands" follow a standard convention, add them to this
 * array. The first element is the subcommand name, and the second a standard
 * Tcl command handler.
 */

static const struct gdi_command {
    const char *command_string;
    Tcl_ObjCmdProc2 *command;
} gdi_commands[] = {
    { "arc",        GdiArc },
    { "bitmap",     GdiBitmap },
    { "characters", GdiCharWidths },
    { "image",      GdiImage },
    { "line",       GdiLine },
    { "map",        GdiMap },
    { "oval",       GdiOval },
    { "photo",      GdiPhoto },
    { "polygon",    GdiPolygon },
    { "rectangle",  GdiRectangle },
    { "text",       GdiText },
    { "copybits",   GdiCopyBits },
};

/*
 *----------------------------------------------------------------------
 *
 * GdiArc --
 *
 *	Map canvas arcs to GDI context.
 *
 * Results:
 *	Renders arcs.
 *
 *----------------------------------------------------------------------
 */

static int GdiArc(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    Tcl_Size objc,
    Tcl_Obj *const *objv)
{
    static const char usage_message[] =
	"::tk::print::_gdi arc hdc x1 y1 x2 y2 "
	"-extent angle -start angle -style arcstyle "
	"-fill color -outline color "
	"-width dimension -dash dashrule "
	"-outlinestipple ignored -stipple ignored\n" ;
    double x1, y1, x2, y2;
    int xr0, yr0, xr1, yr1;
    HDC hDC;
    double extent = 0.0, start = 0.0;
    DrawFunc drawfunc;
    double width = 0.0;
    HPEN hPen;
    COLORREF linecolor = 0, fillcolor = BS_NULL;
    int dolinecolor = 0, dofillcolor = 0;
    HBRUSH hBrush = NULL;
    LOGBRUSH lbrush;
    HGDIOBJ oldobj = NULL;
    int dodash = 0;
    const char *dashdata = 0;

    drawfunc = Pie;

    /* Verrrrrry simple for now.... */
    if (objc < 6) {
	Tcl_AppendResult(interp, usage_message, (char *)NULL);
	return TCL_ERROR;
    }

    hDC = printDC;

    if ((Tcl_GetDoubleFromObj(interp, objv[2], &x1) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[3], &y1) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[4], &x2) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[5], &y2) != TCL_OK)) {
	return TCL_ERROR;
    }

    objc -= 6;
    objv += 6;
    while (objc >= 2) {
	if (strcmp(Tcl_GetString(objv[0]), "-extent") == 0) {
	    extent = atof(Tcl_GetString(objv[1]));
	} else if (strcmp(Tcl_GetString(objv[0]), "-start") == 0) {
	    start = atof(Tcl_GetString(objv[1]));
	} else if (strcmp(Tcl_GetString(objv[0]), "-style") == 0) {
	    if (strcmp(Tcl_GetString(objv[1]), "pieslice") == 0) {
		drawfunc = Pie;
	    } else if (strcmp(Tcl_GetString(objv[1]), "arc") == 0) {
		drawfunc = Arc;
	    } else if (strcmp(Tcl_GetString(objv[1]), "chord") == 0) {
		drawfunc = Chord;
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-fill") == 0) {
	    /* Handle all args, even if we don't use them yet. */
	    if (GdiGetColor(objv[1], &fillcolor)) {
		dofillcolor = 1;
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-outline") == 0) {
	    if (GdiGetColor(objv[1], &linecolor)) {
		dolinecolor = 1;
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-outlinestipple") == 0) {
	    /* ignored */
	} else if (strcmp(Tcl_GetString(objv[0]), "-stipple") == 0) {
	    /* ignored */
	} else if (strcmp(Tcl_GetString(objv[0]), "-width") == 0) {
	    if (Tcl_GetDoubleFromObj(interp, objv[1], &width)) {
		return TCL_ERROR;
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-dash") == 0) {
	    if (Tcl_GetString(objv[1])) {
		dodash = 1;
		dashdata = Tcl_GetString(objv[1]);
	    }
	} else {
	    /* Don't know that option! */
	    Tcl_AppendResult(interp, usage_message, (char *)NULL);
	    return TCL_ERROR;
	}
	objc -= 2;
	objv += 2;
    }
    xr0 = xr1 = (x1 + x2) / 2;
    yr0 = yr1 = (y1 + y2) / 2;

    /*
     * The angle used by the arc must be "warped" by the eccentricity of the
     * ellipse.  Thanks to Nigel Dodd <[email protected]> for bringing a
     * nice example.
     */

    xr0 += (int)(100.0 * (x2 - x1) * cos((start * 2.0 * 3.14159265) / 360.0));
    yr0 -= (int)(100.0 * (y2 - y1) * sin((start * 2.0 * 3.14159265) / 360.0));
    xr1 += (int)(100.0 * (x2 - x1) * cos(((start+extent) * 2.0 * 3.14159265) / 360.0));
    yr1 -= (int)(100.0 * (y2 - y1) * sin(((start+extent) * 2.0 * 3.14159265) / 360.0));

    /*
     * Under Win95, SetArcDirection isn't implemented--so we have to assume
     * that arcs are drawn counterclockwise (e.g., positive extent) So if it's
     * negative, switch the coordinates!
     */

    if (extent < 0) {
	int xr2 = xr0;
	int yr2 = yr0;

	xr0 = xr1;
	xr1 = xr2;
	yr0 = yr1;
	yr1 = yr2;
    }

    if (dofillcolor) {
	GdiMakeBrush(fillcolor, 0, &lbrush, hDC, &hBrush);
    } else {
	oldobj = SelectObject(hDC, GetStockObject(HOLLOW_BRUSH));
    }

    if (width || dolinecolor) {
	GdiMakePen(interp, width, dodash, dashdata,
		0, 0, 0, 0, linecolor, hDC, (HGDIOBJ *)&hPen);
    }

    (*drawfunc)(hDC, x1, y1, x2, y2, xr0, yr0, xr1, yr1);

    if (width || dolinecolor) {
	GdiFreePen(interp, hDC, hPen);
    }
    if (hBrush) {
	GdiFreeBrush(interp, hDC, hBrush);
    } else {
	SelectObject(hDC, oldobj);
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiBitmap --
 *
 *	Unimplemented for now. Should use the same techniques as
 *	CanvasPsBitmap (tkCanvPs.c).
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int GdiBitmap(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    TCL_UNUSED(Tcl_Size),
    Tcl_Obj *const *objv)
{
    /*
     * Skip this for now. Should be based on common code with the copybits
     * command.
     */

    Tcl_WrongNumArgs(interp, 1, objv, "hdc x y "
	    "-anchor [center|n|e|s|w] -background color "
	    "-bitmap bitmap -foreground color\n"
	    "Not implemented yet. Sorry!");
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiImage --
 *
 *	Unimplemented for now. Unimplemented for now. Should switch on image
 *	type and call either GdiPhoto or GdiBitmap. This code is similar to
 *	that in tkWinImage.c.
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int GdiImage(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    TCL_UNUSED(Tcl_Size),
    Tcl_Obj *const *objv)
{
    /* Skip this for now..... */
    /* Should be based on common code with the copybits command. */

    Tcl_WrongNumArgs(interp, 1, objv, "hdc x y -anchor [center|n|e|s|w] -image name\n"
	    "Not implemented yet. Sorry!");
    /* Normally, usage results in TCL_ERROR--but wait til' it's implemented. */
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiPhoto --
 *
 *	Contributed by Lukas Rosenthaler <[email protected]>
 *
 *	Note: The canvas doesn't directly support photos (only as images), so
 *	this is the first ::tk::print::_gdi command without an equivalent
 *	canvas command.  This code may be modified to support photo images on
 *	the canvas.
 *
 * Results:
 *	Renders a photo.
 *
 *----------------------------------------------------------------------
 */

static int GdiPhoto(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    Tcl_Size objc,
    Tcl_Obj *const *objv)
{
    static const char usage_message[] =
	"::tk::print::_gdi photo hdc [-destination x y [w [h]]] -photo name\n";
    HDC dst;
    int dst_x = 0, dst_y = 0, dst_w = 0, dst_h = 0;
    int nx, ny, sll;
    const char *photoname = 0;	/* For some reason Tk_FindPhoto takes a char *. */
    Tk_PhotoHandle photo_handle;
    Tk_PhotoImageBlock img_block;
    BITMAPINFO bitmapinfo;	/* Since we don't need the bmiColors table,
				 * there is no need for dynamic allocation. */
    int oldmode;		/* For saving the old stretch mode. */
    POINT pt;			/* For saving the brush org. */
    char *pbuf = NULL;
    int i, j, k;
    int retval = TCL_OK;

    /*
     * Parse the arguments.
     */

    /* HDC is required. */
    if (objc < 2) {
	Tcl_AppendResult(interp, usage_message, (char *)NULL);
	return TCL_ERROR;
    }

    dst = printDC;

    /*
     * Next, check to see if 'dst' can support BitBlt.
     * If not, raise an error.
     */

    if ((GetDeviceCaps(dst, RASTERCAPS) & RC_STRETCHDIB) == 0) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"::tk::print::_gdi photo not supported on device context (0x%s)",
		Tcl_GetString(objv[1])));
	return TCL_ERROR;
    }

    /* Parse the command line arguments. */
    for (j = 2; j < objc; j++) {
	if (strcmp(Tcl_GetString(objv[j]), "-destination") == 0) {
	    double x, y, w, h;
	    int count = 0;
	    char dummy;

	    if (j < objc) {
		count = sscanf(Tcl_GetString(objv[++j]), "%lf%lf%lf%lf%c",
			&x, &y, &w, &h, &dummy);
	    }

	    if (count < 2 || count > 4) {
		/* Destination must provide at least 2 arguments. */
		Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			"-destination requires a list of 2 to 4 numbers\n%s",
			usage_message));
		return TCL_ERROR;
	    }

	    dst_x = (int) x;
	    dst_y = (int) y;
	    if (count == 3) {
		dst_w = (int) w;
		dst_h = -1;
	    } else if (count == 4) {
		dst_w = (int) w;
		dst_h = (int) h;
	    }
	} else if (strcmp(Tcl_GetString(objv[j]), "-photo") == 0) {
	    photoname = Tcl_GetString(objv[++j]);
	}
    }

    if (photoname == 0) {	/* No photo provided. */
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"No photo name provided to ::tk::print::_gdi photo\n%s",
		usage_message));
	return TCL_ERROR;
    }

    photo_handle = Tk_FindPhoto(interp, photoname);
    if (photo_handle == 0) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"::tk::print::_gdi photo: Photo name %s can't be located\n%s",
		photoname, usage_message));
	return TCL_ERROR;
    }
    Tk_PhotoGetImage(photo_handle, &img_block);

    nx  = img_block.width;
    ny  = img_block.height;
    sll = ((3*nx + 3) / 4)*4; /* Must be multiple of 4. */

    /*
     * Buffer is potentially large enough that failure to allocate might be
     * recoverable.
     */

    pbuf = (char *)attemptckalloc(sll * ny * sizeof(char));
    if (pbuf == 0) { /* Memory allocation failure. */
	Tcl_AppendResult(interp,
		"::tk::print::_gdi photo failed--out of memory", (char *)NULL);
	return TCL_ERROR;
    }

    /* After this, all returns must go through retval. */

    /* BITMAP expects BGR; photo provides RGB. */
    for (k = 0; k < ny; k++) {
	for (i = 0; i < nx; i++) {
	    pbuf[k*sll + 3*i] = img_block.pixelPtr[
		    k*img_block.pitch + i*img_block.pixelSize + img_block.offset[2]];
	    pbuf[k*sll + 3*i + 1] = img_block.pixelPtr[
		    k*img_block.pitch + i*img_block.pixelSize + img_block.offset[1]];
	    pbuf[k*sll + 3*i + 2] = img_block.pixelPtr[
		    k*img_block.pitch + i*img_block.pixelSize + img_block.offset[0]];
	}
    }

    memset(&bitmapinfo, 0L, sizeof(BITMAPINFO));

    bitmapinfo.bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
    bitmapinfo.bmiHeader.biWidth         = nx;
    bitmapinfo.bmiHeader.biHeight        = -ny;
    bitmapinfo.bmiHeader.biPlanes        = 1;
    bitmapinfo.bmiHeader.biBitCount      = 24;
    bitmapinfo.bmiHeader.biCompression   = BI_RGB;
    bitmapinfo.bmiHeader.biSizeImage     = 0; /* sll*ny;. */
    bitmapinfo.bmiHeader.biXPelsPerMeter = 0;
    bitmapinfo.bmiHeader.biYPelsPerMeter = 0;
    bitmapinfo.bmiHeader.biClrUsed       = 0;
    bitmapinfo.bmiHeader.biClrImportant  = 0;

    oldmode = SetStretchBltMode(dst, HALFTONE);
    /*
     * According to the Win32 Programmer's Manual, we have to set the brush
     * org, now.
     */
    SetBrushOrgEx(dst, 0, 0, &pt);

    if (dst_w <= 0) {
	dst_w = nx;
	dst_h = ny;
    } else if (dst_h <= 0) {
	dst_h = ny*dst_w / nx;
    }

    if (StretchDIBits(dst, dst_x, dst_y, dst_w, dst_h, 0, 0, nx, ny,
	    pbuf, &bitmapinfo, DIB_RGB_COLORS, SRCCOPY) == (int)GDI_ERROR) {
	int errcode = GetLastError();

	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"::tk::print::_gdi photo internal failure: "
		"StretchDIBits error code %d", errcode));
	retval = TCL_ERROR;
    }

    /* Clean up the hDC. */
    if (oldmode != 0) {
	SetStretchBltMode(dst, oldmode);
	SetBrushOrgEx(dst, pt.x, pt.y, &pt);
    }

    ckfree(pbuf);

    if (retval == TCL_OK) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"%d %d %d %d", dst_x, dst_y, dst_w, dst_h));
    }

    return retval;
}

/*
 *----------------------------------------------------------------------
 *
 * Bezierize --
 *
 *	Interface to Tk's line smoother, used for lines and pollies.
 *	Provided by Jasper Taylor <[email protected]>.
 *
 * Results:
 *	Smooths lines.
 *
 *----------------------------------------------------------------------
 */

static int Bezierize(
    POINT* polypoints,
    int npoly,
    int nStep,
    POINT** bpointptr)
{
    /* First, translate my points into a list of doubles. */
    double *inPointList, *outPointList;
    int n;
    int nbpoints = 0;
    POINT* bpoints;

    inPointList = (double *)attemptckalloc(2 * sizeof(double) * npoly);
    if (inPointList == 0) {
	/* TODO: unreachable */
	return nbpoints; /* 0. */
    }

    for (n=0; n<npoly; n++) {
	inPointList[2*n] = polypoints[n].x;
	inPointList[2*n + 1] = polypoints[n].y;
    }

    nbpoints = 1 + npoly * nStep; /* this is the upper limit. */
    outPointList = (double *)attemptckalloc(2 * sizeof(double) * nbpoints);
    if (outPointList == 0) {
	/* TODO: unreachable */
	ckfree(inPointList);
	return 0;
    }

    nbpoints = TkMakeBezierCurve(NULL, inPointList, npoly, nStep,
	    NULL, outPointList);

    ckfree(inPointList);
    bpoints = (POINT *)attemptckalloc(sizeof(POINT)*nbpoints);
    if (bpoints == 0) {
	/* TODO: unreachable */
	ckfree(outPointList);
	return 0;
    }

    for (n=0; n<nbpoints; n++) {
	bpoints[n].x = (long)outPointList[2*n];
	bpoints[n].y = (long)outPointList[2*n + 1];
    }
    ckfree(outPointList);
    *bpointptr = bpoints;
    return nbpoints;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiLine --
 *
 *	Maps lines to GDI context.
 *
 * Results:
 *	Renders lines.
 *
 *----------------------------------------------------------------------
 */

static int GdiLine(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    Tcl_Size objc,
    Tcl_Obj *const *objv)
{
    static const char usage_message[] =
	"::tk::print::_gdi line hdc x1 y1 ... xn yn "
	"-arrow [first|last|both|none] -arrowshape {d1 d2 d3} "
	"-dash dashlist "
	"-capstyle [butt|projecting|round] -fill color "
	"-joinstyle [bevel|miter|round] -smooth [true|false|bezier] "
	"-splinesteps number -stipple bitmap -width linewid";
    char *strend;
    POINT *polypoints;
    int npoly;
    int x, y;
    HDC hDC;
    HPEN hPen;

    LOGBRUSH lbrush;
    HBRUSH hBrush = NULL;

    double width       = 0.0;
    COLORREF linecolor = 0;
    int dolinecolor    = 0;
    int dosmooth       = 0;
    int doarrow        = 0; /* 0=none; 1=end; 2=start; 3=both. */
    int arrowshape[3];

    int nStep = 12;

    int dodash = 0;
    const char *dashdata = 0;
    double p1x, p1y, p2x, p2y;

    arrowshape[0] = 8;
    arrowshape[1] = 10;
    arrowshape[2] = 3;

    /* Verrrrrry simple for now.... */
    if (objc < 6) {
	Tcl_AppendResult(interp, usage_message, (char *)NULL);
	return TCL_ERROR;
    }

    hDC = printDC;

    polypoints = (POINT *)attemptckalloc((objc - 1) * sizeof(POINT));
    if (polypoints == 0) {
	Tcl_AppendResult(interp, "Out of memory in GdiLine", (char *)NULL);
	return TCL_ERROR;
    }
    if ((Tcl_GetDoubleFromObj(interp, objv[2], &p1x) != TCL_OK)
	||	(Tcl_GetDoubleFromObj(interp, objv[3], &p1y) != TCL_OK)
	||	(Tcl_GetDoubleFromObj(interp, objv[4], &p2x) != TCL_OK)
	||	(Tcl_GetDoubleFromObj(interp, objv[5], &p2y) != TCL_OK)
    ) {
	return TCL_ERROR;
    }
    polypoints[0].x = floor(p1x+0.5);
    polypoints[0].y = floor(p1y+0.5);
    polypoints[1].x = floor(p2x+0.5);
    polypoints[1].y = floor(p2y+0.5);
    objc -= 6;
    objv += 6;
    npoly = 2;

    while (objc >= 2) {
	/* Check for a number. */
	x = strtoul(Tcl_GetString(objv[0]), &strend, 0);
	if (strend > Tcl_GetString(objv[0])) {
	    /* One number.... */
	    y = strtoul(Tcl_GetString(objv[1]), &strend, 0);
	    if (strend > Tcl_GetString(objv[1])) {
		/* TWO numbers!. */
		polypoints[npoly].x = x;
		polypoints[npoly].y = y;
		npoly++;
		objc -= 2;
		objv += 2;
	    } else {
		/* Only one number... Assume a usage error. */
		ckfree(polypoints);
		Tcl_AppendResult(interp, usage_message, (char *)NULL);
		return TCL_ERROR;
	    }
	} else {
	    if (strcmp(Tcl_GetString(*objv), "-arrow") == 0) {
		if (strcmp(Tcl_GetString(objv[1]), "none") == 0) {
		    doarrow = 0;
		} else if (strcmp(Tcl_GetString(objv[1]), "both") == 0) {
		    doarrow = 3;
		} else if (strcmp(Tcl_GetString(objv[1]), "first") == 0) {
		    doarrow = 2;
		} else if (strcmp(Tcl_GetString(objv[1]), "last") == 0) {
		    doarrow = 1;
		}
		objv += 2;
		objc -= 2;
	    } else if (strcmp(Tcl_GetString(*objv), "-arrowshape") == 0) {
		/* List of 3 numbers--set arrowshape array. */
		int a1, a2, a3;
		char dummy;

		if (sscanf(Tcl_GetString(objv[1]), "%d%d%d%c", &a1, &a2, &a3, &dummy) == 3
			&& a1 > 0 && a2 > 0 && a3 > 0) {
		    arrowshape[0] = a1;
		    arrowshape[1] = a2;
		    arrowshape[2] = a3;
		}
		/* Else the argument was bad. */

		objv += 2;
		objc -= 2;
	    } else if (strcmp(Tcl_GetString(*objv), "-capstyle") == 0) {
		objv += 2;
		objc -= 2;
	    } else if (strcmp(Tcl_GetString(*objv), "-fill") == 0) {
		if (GdiGetColor(objv[1], &linecolor)) {
		    dolinecolor = 1;
		}
		objv += 2;
		objc -= 2;
	    } else if (strcmp(Tcl_GetString(*objv), "-joinstyle") == 0) {
		objv += 2;
		objc -= 2;
	    } else if (strcmp(Tcl_GetString(*objv), "-smooth") == 0) {
		/* Argument is true/false or 1/0 or bezier. */
		if (Tcl_GetString(objv[1])) {
		    switch (Tcl_GetString(objv[1])[0]) {
		    case 't': case 'T':
		    case '1':
		    case 'b': case 'B': /* bezier. */
			dosmooth = 1;
			break;
		    default:
			dosmooth = 0;
			break;
		    }
		    objv += 2;
		    objc -= 2;
		}
	    } else if (strcmp(Tcl_GetString(*objv), "-splinesteps") == 0) {
		if (Tcl_GetIntFromObj(interp, objv[1], &nStep) != TCL_OK) {
		    return TCL_ERROR;
		}
		objv += 2;
		objc -= 2;
	    } else if (strcmp(Tcl_GetString(*objv), "-dash") == 0) {
		if (Tcl_GetString(objv[1])) {
		    dodash = 1;
		    dashdata = Tcl_GetString(objv[1]);
		}
		objv += 2;
		objc -= 2;
	    } else if (strcmp(Tcl_GetString(*objv), "-dashoffset") == 0) {
		objv += 2;
		objc -= 2;
	    } else if (strcmp(Tcl_GetString(*objv), "-stipple") == 0) {
		objv += 2;
		objc -= 2;
	    } else if (strcmp(Tcl_GetString(*objv), "-width") == 0) {
		if (Tcl_GetDoubleFromObj(interp, objv[1], &width) != TCL_OK) {
		    return TCL_ERROR;
		}
		objv += 2;
		objc -= 2;
	    } else { /* It's an unknown argument!. */
		objc--;
		objv++;
	    }
	    /* Check for arguments
	     * Most of the arguments affect the "Pen"
	     */
	}
    }

    if (width || dolinecolor || dodash) {
	GdiMakePen(interp, width, dodash, dashdata,
		0, 0, 0, 0, linecolor, hDC, (HGDIOBJ *)&hPen);
    }
    if (doarrow != 0) {
	GdiMakeBrush(linecolor, 0, &lbrush, hDC, &hBrush);
    }

    if (dosmooth) { /* Use PolyBezier. */
	int nbpoints;
	POINT *bpoints = NULL;

	nbpoints = Bezierize(polypoints,npoly,nStep,&bpoints);
	if (nbpoints > 0) {
	    Polyline(hDC, bpoints, nbpoints);
	} else {
	    Polyline(hDC, polypoints, npoly); /* Out of memory? Just draw a regular line. */
	}
	if (bpoints) {
	    ckfree(bpoints);
	}
    } else {
	Polyline(hDC, polypoints, npoly);
    }

    if (dodash && doarrow) {  /* Don't use dashed or thick pen for the arrows! */
	GdiFreePen(interp, hDC, hPen);
	GdiMakePen(interp, width, 0, 0, 0, 0, 0, 0,
		linecolor, hDC, (HGDIOBJ *)&hPen);
    }

    /* Now the arrowheads, if any. */
    if (doarrow & 1) {
	/* Arrowhead at end = polypoints[npoly-1].x, polypoints[npoly-1].y. */
	POINT ahead[6];
	double dx, dy, length;
	double sinTheta, cosTheta;
	double vertX, vertY, temp;
	double fracHeight;

	fracHeight = 2.0 / arrowshape[2];

	ahead[0].x = ahead[5].x = polypoints[npoly-1].x;
	ahead[0].y = ahead[5].y = polypoints[npoly-1].y;
	dx = ahead[0].x - polypoints[npoly-2].x;
	dy = ahead[0].y - polypoints[npoly-2].y;
	if ((length = hypot(dx, dy)) == 0) {
	    sinTheta = cosTheta = 0.0;
	} else {
	    sinTheta = dy / length;
	    cosTheta = dx / length;
	}
	vertX = ahead[0].x - arrowshape[0]*cosTheta;
	vertY = ahead[0].y - arrowshape[0]*sinTheta;
	temp = arrowshape[2]*sinTheta;
	ahead[1].x = (long)(ahead[0].x - arrowshape[1]*cosTheta + temp);
	ahead[4].x = (long)(ahead[1].x - 2 * temp);
	temp = arrowshape[2]*cosTheta;
	ahead[1].y = (long)(ahead[0].y - arrowshape[1]*sinTheta - temp);
	ahead[4].y = (long)(ahead[1].y + 2 * temp);
	ahead[2].x = (long)(ahead[1].x*fracHeight + vertX*(1.0-fracHeight));
	ahead[2].y = (long)(ahead[1].y*fracHeight + vertY*(1.0-fracHeight));
	ahead[3].x = (long)(ahead[4].x*fracHeight + vertX*(1.0-fracHeight));
	ahead[3].y = (long)(ahead[4].y*fracHeight + vertY*(1.0-fracHeight));

	Polygon(hDC, ahead, 6);
    }

    if (doarrow & 2) {
	/* Arrowhead at end = polypoints[0].x, polypoints[0].y. */
	POINT ahead[6];
	double dx, dy, length;
	double sinTheta, cosTheta;
	double vertX, vertY, temp;
	double fracHeight;

	fracHeight = 2.0 / arrowshape[2];

	ahead[0].x = ahead[5].x = polypoints[0].x;
	ahead[0].y = ahead[5].y = polypoints[0].y;
	dx = ahead[0].x - polypoints[1].x;
	dy = ahead[0].y - polypoints[1].y;
	if ((length = hypot(dx, dy)) == 0) {
	    sinTheta = cosTheta = 0.0;
	} else {
	    sinTheta = dy / length;
	    cosTheta = dx / length;
	}
	vertX = ahead[0].x - arrowshape[0]*cosTheta;
	vertY = ahead[0].y - arrowshape[0]*sinTheta;
	temp = arrowshape[2]*sinTheta;
	ahead[1].x = (long)(ahead[0].x - arrowshape[1]*cosTheta + temp);
	ahead[4].x = (long)(ahead[1].x - 2 * temp);
	temp = arrowshape[2]*cosTheta;
	ahead[1].y = (long)(ahead[0].y - arrowshape[1]*sinTheta - temp);
	ahead[4].y = (long)(ahead[1].y + 2 * temp);
	ahead[2].x = (long)(ahead[1].x*fracHeight + vertX*(1.0-fracHeight));
	ahead[2].y = (long)(ahead[1].y*fracHeight + vertY*(1.0-fracHeight));
	ahead[3].x = (long)(ahead[4].x*fracHeight + vertX*(1.0-fracHeight));
	ahead[3].y = (long)(ahead[4].y*fracHeight + vertY*(1.0-fracHeight));

	Polygon(hDC, ahead, 6);
    }

    if (width || dolinecolor || dodash) {
	GdiFreePen(interp, hDC, hPen);
    }
    if (hBrush) {
	GdiFreeBrush(interp, hDC, hBrush);
    }

    ckfree(polypoints);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiOval --
 *
 *	Maps ovals to GDI context.
 *
 * Results:
 *	Renders ovals.
 *
 *----------------------------------------------------------------------
 */

static int GdiOval(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    Tcl_Size objc,
    Tcl_Obj *const *objv)
{
    static const char usage_message[] =
	"::tk::print::_gdi oval hdc x1 y1 x2 y2 -fill color -outline color "
	"-stipple bitmap -width linewid";
    double x1, y1, x2, y2;
    HDC hDC;
    HPEN hPen;
    double width = 0.0;
    COLORREF linecolor = 0, fillcolor = 0;
    int dolinecolor = 0, dofillcolor = 0;
    HBRUSH hBrush = NULL;
    LOGBRUSH lbrush;
    HGDIOBJ oldobj = NULL;

    int dodash = 0;
    const char *dashdata = 0;

    /* Verrrrrry simple for now.... */
    if (objc < 6) {
	Tcl_AppendResult(interp, usage_message, (char *)NULL);
	return TCL_ERROR;
    }

    hDC = printDC;

    if ((Tcl_GetDoubleFromObj(interp, objv[2], &x1) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[3], &y1) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[4], &x2) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[5], &y2) != TCL_OK)) {
	return TCL_ERROR;
    }
    if (x1 > x2) {
	double x3 = x1;
	x1 = x2;
	x2 = x3;
    }
    if (y1 > y2) {
	double y3 = y1;
	y1 = y2;
	y2 = y3;
    }
    objc -= 6;
    objv += 6;

    while (objc > 0) {
	/* Now handle any other arguments that occur. */
	if (strcmp(Tcl_GetString(objv[0]), "-fill") == 0) {
	    if (Tcl_GetString(objv[1]) && GdiGetColor(objv[1], &fillcolor)) {
		dofillcolor = 1;
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-outline") == 0) {
	    if (Tcl_GetString(objv[1]) && GdiGetColor(objv[1], &linecolor)) {
		dolinecolor = 1;
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-stipple") == 0) {
	    /* Not actually implemented */
	} else if (strcmp(Tcl_GetString(objv[0]), "-width") == 0) {
	    if (Tcl_GetString(objv[1])) {
		if (Tcl_GetDoubleFromObj(interp, objv[1], &width) != TCL_OK) {
		    return TCL_ERROR;
		}
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-dash") == 0) {
	    if (Tcl_GetString(objv[1])) {
		dodash = 1;
		dashdata = Tcl_GetString(objv[1]);
	    }
	}
	objv += 2;
	objc -= 2;
    }

    if (dofillcolor) {
	GdiMakeBrush(fillcolor, 0, &lbrush, hDC, &hBrush);
    } else {
	oldobj = SelectObject(hDC, GetStockObject(HOLLOW_BRUSH));
    }

    if (width || dolinecolor) {
	GdiMakePen(interp, width, dodash, dashdata,
		0, 0, 0, 0, linecolor, hDC, (HGDIOBJ *)&hPen);
    }
    /*
     * Per Win32, Rectangle includes lower and right edges--per Tcl8.3.2 and
     * earlier documentation, canvas rectangle does not. Thus, add 1 to right
     * and lower bounds to get appropriate behavior.
     */
    Ellipse(hDC, floor(x1+0.5), floor(y1+0.5), floor(x2+1.5), floor(y2+1.5));

    if (width || dolinecolor) {
	GdiFreePen(interp, hDC, hPen);
    }
    if (hBrush) {
	GdiFreeBrush(interp, hDC, hBrush);
    } else {
	SelectObject(hDC, oldobj);
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiPolygon --
 *
 *	Maps polygons to GDI context.
 *
 * Results:
 *	Renders polygons.
 *
 *----------------------------------------------------------------------
 */

static int GdiPolygon(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    Tcl_Size objc,
    Tcl_Obj *const *objv)
{
    static const char usage_message[] =
	"::tk::print::_gdi polygon hdc x1 y1 ... xn yn "
	"-fill color -outline color -smooth [true|false|bezier] "
	"-splinesteps number -stipple bitmap -width linewid";

    char *strend;
    POINT *polypoints;
    int npoly;
    int dosmooth = 0;
    int nStep = 12;
    int x, y;
    HDC hDC;
    HPEN hPen;
    double width = 0.0;
    COLORREF linecolor = 0, fillcolor = BS_NULL;
    int dolinecolor = 0, dofillcolor = 0;
    LOGBRUSH lbrush;
    HBRUSH hBrush = NULL;
    HGDIOBJ oldobj = NULL;

    int dodash = 0;
    const char *dashdata = 0;
    double p1x, p1y, p2x, p2y;

    /* Verrrrrry simple for now.... */
    if (objc < 6) {
	Tcl_AppendResult(interp, usage_message, (char *)NULL);
	return TCL_ERROR;
    }

    hDC = printDC;

    polypoints = (POINT *)attemptckalloc((objc - 1) * sizeof(POINT));
    if (polypoints == 0) {
	/* TODO: unreachable */
	Tcl_AppendResult(interp, "Out of memory in GdiLine", (char *)NULL);
	return TCL_ERROR;
    }
    if ((Tcl_GetDoubleFromObj(interp, objv[2], &p1x) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[3], &p1y) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[4], &p2x) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[5], &p2y) != TCL_OK)) {
	return TCL_ERROR;
    }
    polypoints[0].x = floor(p1x + 0.5);
    polypoints[0].y = floor(p1y + 0.5);
    polypoints[1].x = floor(p2x + 0.5);
    polypoints[1].y = floor(p2y + 0.5);
    objc -= 6;
    objv += 6;
    npoly = 2;

    while (objc >= 2) {
	/* Check for a number */
	x = strtoul(Tcl_GetString(objv[0]), &strend, 0);
	if (strend > Tcl_GetString(objv[0])) {
	    /* One number.... */
	    y = strtoul(Tcl_GetString(objv[1]), &strend, 0);
	    if (strend > Tcl_GetString(objv[1])) {
		/* TWO numbers!. */
		polypoints[npoly].x = x;
		polypoints[npoly].y = y;
		npoly++;
		objc -= 2;
		objv += 2;
	    } else {
		/* Only one number... Assume a usage error. */
		ckfree(polypoints);
		Tcl_AppendResult(interp, usage_message, (char *)NULL);
		return TCL_ERROR;
	    }
	} else {
	    /*
	     * Check for arguments.
	     * Most of the arguments affect the "Pen" and "Brush".
	     */
	    if (strcmp(Tcl_GetString(objv[0]), "-fill") == 0) {
		if (Tcl_GetString(objv[1]) && GdiGetColor(objv[1], &fillcolor)) {
		    dofillcolor = 1;
		}
	    } else if (strcmp(Tcl_GetString(objv[0]), "-outline") == 0) {
		if (GdiGetColor(objv[1], &linecolor)) {
		    dolinecolor = 0;
		}
	    } else if (strcmp(Tcl_GetString(objv[0]), "-smooth") == 0) {
		if (Tcl_GetString(objv[1])) {
		    switch (Tcl_GetString(objv[1])[0]) {
		    case 't': case 'T':
		    case '1':
		    case 'b': case 'B': /* bezier. */
			dosmooth = 1;
			break;
		    default:
			dosmooth = 0;
			break;
		    }
		}
	    } else if (strcmp(Tcl_GetString(objv[0]), "-splinesteps") == 0) {
		if (Tcl_GetString(objv[1])) {
		    if (Tcl_GetIntFromObj(interp, objv[1], &nStep) != TCL_OK) {
			return TCL_ERROR;
		    }
		}
	    } else if (strcmp(Tcl_GetString(objv[0]), "-stipple") == 0) {
		/* Not supported */
	    } else if (strcmp(Tcl_GetString(objv[0]), "-width") == 0) {
		if (Tcl_GetString(objv[1])) {
		    if (Tcl_GetDoubleFromObj(interp, objv[1], &width) != TCL_OK) {
			return TCL_ERROR;
		    }
		}
	    } else if (strcmp(Tcl_GetString(objv[0]), "-dash") == 0) {
		if (Tcl_GetString(objv[1])) {
		    dodash = 1;
		    dashdata = Tcl_GetString(objv[1]);
		}
	    }
	    objc -= 2;
	    objv += 2;
	}
    }

    if (dofillcolor) {
	GdiMakeBrush(fillcolor, 0, &lbrush, hDC, &hBrush);
    } else {
	oldobj = SelectObject(hDC, GetStockObject(HOLLOW_BRUSH));
    }

    if (width || dolinecolor) {
	GdiMakePen(interp, width, dodash, dashdata, 0, 0, 0, 0,
		linecolor, hDC, (HGDIOBJ *)&hPen);
    }

    if (dosmooth) {
	int nbpoints;
	POINT *bpoints = NULL;

	nbpoints = Bezierize(polypoints, npoly, nStep, &bpoints);
	if (nbpoints > 0) {
	    Polygon(hDC, bpoints, nbpoints);
	} else {
	    Polygon(hDC, polypoints, npoly);
	}
	if (bpoints) {
	    ckfree(bpoints);
	}
    } else {
	Polygon(hDC, polypoints, npoly);
    }

    if (width || dolinecolor) {
	GdiFreePen(interp, hDC, hPen);
    }
    if (hBrush) {
	GdiFreeBrush(interp, hDC, hBrush);
    } else {
	SelectObject(hDC, oldobj);
    }

    ckfree(polypoints);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiRectangle --
 *
 *	Maps rectangles to GDI context.
 *
 * Results:
 *	Renders rectangles.
 *
 *----------------------------------------------------------------------
 */

static int GdiRectangle(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    Tcl_Size objc,
    Tcl_Obj *const *objv)
{
    static const char usage_message[] =
	"::tk::print::_gdi rectangle hdc x1 y1 x2 y2 "
	"-fill color -outline color "
	"-stipple bitmap -width linewid";

    double x1, y1, x2, y2;
    HDC hDC;
    HPEN hPen;
    double width = 0.0;
    COLORREF linecolor = 0, fillcolor = BS_NULL;
    int dolinecolor = 0, dofillcolor = 0;
    LOGBRUSH lbrush;
    HBRUSH hBrush = NULL;
    HGDIOBJ oldobj = NULL;

    int dodash = 0;
    const char *dashdata = 0;

    /* Verrrrrry simple for now.... */
    if (objc < 6) {
	Tcl_AppendResult(interp, usage_message, (char *)NULL);
	return TCL_ERROR;
    }

    hDC = printDC;

    if ((Tcl_GetDoubleFromObj(interp, objv[2], &x1) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[3], &y1) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[4], &x2) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[5], &y2) != TCL_OK)) {
	return TCL_ERROR;
    }
    if (x1 > x2) {
	double x3 = x1;
	x1 = x2;
	x2 = x3;
    }
    if (y1 > y2) {
	double y3 = y1;
	y1 = y2;
	y2 = y3;
    }
    objc -= 6;
    objv += 6;

    /* Now handle any other arguments that occur. */
    while (objc > 1) {
	if (strcmp(Tcl_GetString(objv[0]), "-fill") == 0) {
	    if (Tcl_GetString(objv[1]) && GdiGetColor(objv[1], &fillcolor)) {
		dofillcolor = 1;
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-outline") == 0) {
	    if (Tcl_GetString(objv[1]) && GdiGetColor(objv[1], &linecolor)) {
		dolinecolor = 1;
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-stipple") == 0) {
	    /* Not supported; ignored */
	} else if (strcmp(Tcl_GetString(objv[0]), "-width") == 0) {
	    if (Tcl_GetString(objv[1])) {
		if (Tcl_GetDoubleFromObj(interp, objv[1], &width) != TCL_OK) {
		    return TCL_ERROR;
		}
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-dash") == 0) {
	    if (Tcl_GetString(objv[1])) {
		dodash = 1;
		dashdata = Tcl_GetString(objv[1]);
	    }
	}

	objc -= 2;
	objv += 2;
    }

    /*
     * Note: If any fill is specified, the function must create a brush and
     * put the coordinates in a RECTANGLE structure, and call FillRect.
     * FillRect requires a BRUSH / color.
     * If not, the function Rectangle must be called.
     */
    if (dofillcolor) {
	GdiMakeBrush(fillcolor, 0, &lbrush, hDC, &hBrush);
    } else {
	oldobj = SelectObject(hDC, GetStockObject(HOLLOW_BRUSH));
    }

    if (width || dolinecolor) {
	GdiMakePen(interp, width, dodash, dashdata,
		0, 0, 0, 0, linecolor, hDC, (HGDIOBJ *)&hPen);
    }
    /*
     * Per Win32, Rectangle includes lower and right edges--per Tcl8.3.2 and
     * earlier documentation, canvas rectangle does not. Thus, add 1 to
     * right and lower bounds to get appropriate behavior.
     */
    Rectangle(hDC, floor(x1+0.5), floor(y1+0.5), floor(x2+1.5), floor(y2+1.5));

    if (width || dolinecolor) {
	GdiFreePen(interp, hDC, hPen);
    }
    if (hBrush) {
	GdiFreeBrush(interp, hDC, hBrush);
    } else {
	SelectObject(hDC, oldobj);
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiCharWidths --
 *
 *	Computes /character widths. This is completely inadequate for
 *	typesetting, but should work for simple text manipulation.
 *
 * Results:
 *	Returns character width.
 *
 *----------------------------------------------------------------------
 */


static int GdiCharWidths(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    Tcl_Size objc,
    Tcl_Obj *const *objv)
{
    static const char usage_message[] =
	"::tk::print::_gdi characters hdc [-font fontname] [-array ary]";
    /*
     * Returns widths of characters from font in an associative array.
     * Font is currently selected font for HDC if not specified.
     * Array name is GdiCharWidths if not specified.
     * Widths should be in the same measures as all other values (1/1000 inch).
     */

    HDC hDC;
    LOGFONTW lf;
    HFONT hfont;
    HGDIOBJ oldfont;
    int made_font = 0;
    const char *aryvarname = "GdiCharWidths";
    /* For now, assume 256 characters in the font.... */
    int widths[256];
    int retval;

    if (objc < 2) {
	Tcl_AppendResult(interp, usage_message, (char *)NULL);
	return TCL_ERROR;
    }

    hDC = printDC;

    objc -= 2;
    objv += 2;

    while (objc > 0) {
	if (strcmp(Tcl_GetString(objv[0]), "-font") == 0) {
	    objc--;
	    objv++;
	    if (GdiMakeLogFont(interp, Tcl_GetString(objv[0]), &lf, hDC)) {
		if ((hfont = CreateFontIndirectW(&lf)) != NULL) {
		    made_font = 1;
		    oldfont = SelectObject(hDC, hfont);
		}
	    }
	    /* Else leave the font alone!. */
	} else if (strcmp(Tcl_GetString(objv[0]), "-array") == 0) {
	    objv++;
	    objc--;
	    if (objc > 0) {
		aryvarname = Tcl_GetString(objv[0]);
	    }
	}
	objv++;
	objc--;
    }

    /* Now, get the widths using the correct function for font type. */
    if ((retval = GetCharWidth32W(hDC, 0, 255, widths)) == FALSE) {
	retval = GetCharWidthW(hDC, 0, 255, widths);
    }

    /*
     * Retval should be 1 (TRUE) if the function succeeded. If the function
     * fails, get the "extended" error code and return. Be sure to deallocate
     * the font if necessary.
     */
    if (retval == FALSE) {
	DWORD val = GetLastError();

	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"::tk::print::_gdi character failed with code %ld", val));
	if (made_font) {
	    SelectObject(hDC, oldfont);
	    DeleteObject(hfont);
	}
	return TCL_ERROR;
    }

    {
	int i;
	char ind[2];
	ind[1] = '\0';

	for (i = 0; i < 255; i++) {
	    /* TODO: use a bytearray for the index name so NUL works */
	    ind[0] = i;
	    Tcl_SetVar2Ex(interp, aryvarname, ind, Tcl_NewIntObj(widths[i]),
		    TCL_GLOBAL_ONLY);
	}
    }
    /* Now, remove the font if we created it only for this function. */
    if (made_font) {
	SelectObject(hDC, oldfont);
	DeleteObject(hfont);
    }

    /* The return value should be the array name(?). */
    Tcl_AppendResult(interp, aryvarname, (char *)NULL);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiText --
 *
 *	Maps text to GDI context.
 *
 * Results:
 *	Renders text.
 *
 *----------------------------------------------------------------------
 */

int GdiText(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    Tcl_Size objc,
    Tcl_Obj *const *objv)
{
    static const char usage_message[] =
	"::tk::print::_gdi text hdc x y -anchor [center|n|e|s|w] "
	"-fill color -font fontname "
	"-justify [left|right|center] "
	"-stipple bitmap -text string -width linelen "
	"-single -backfill";

    HDC hDC;
    double x, y;
    const char *string = 0;
    RECT sizerect;
    UINT format_flags = DT_EXPANDTABS|DT_NOPREFIX; /* Like the canvas. */
    Tk_Anchor anchor = TK_ANCHOR_N;
    LOGFONTW lf;
    HFONT hfont;
    HGDIOBJ oldfont;
    int made_font = 0;
    int retval;
    int dotextcolor = 0;
    int dobgmode = 0;
    int bgmode;
    COLORREF textcolor = 0;
    int usesingle = 0;
    WCHAR *wstring;
    Tcl_DString tds;

    if (objc < 4) {
	Tcl_AppendResult(interp, usage_message, (char *)NULL);
	return TCL_ERROR;
    }

    /* Parse the command. */

    hDC = printDC;

    if ((Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK)
	    || (Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK)) {
	return TCL_ERROR;
    }
    objc -= 4;
    objv += 4;

    sizerect.left = sizerect.right = floor(x+0.5);
    sizerect.top = sizerect.bottom = floor(y+0.5);

    while (objc > 0) {
	if (strcmp(Tcl_GetString(objv[0]), "-anchor") == 0) {
	    objc--;
	    objv++;
	    if (objc > 0) {
		Tk_GetAnchor(interp, Tcl_GetString(objv[0]), &anchor);
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-justify") == 0) {
	    objc--;
	    objv++;
	    if (objc > 0) {
		if (strcmp(Tcl_GetString(objv[0]), "left") == 0) {
		    format_flags |= DT_LEFT;
		} else if (strcmp(Tcl_GetString(objv[0]), "center") == 0) {
		    format_flags |= DT_CENTER;
		} else if (strcmp(Tcl_GetString(objv[0]), "right") == 0) {
		    format_flags |= DT_RIGHT;
		}
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-text") == 0) {
	    objc--;
	    objv++;
	    if (objc > 0) {
		string = Tcl_GetString(objv[0]);
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-font") == 0) {
	    objc--;
	    objv++;
	    if (GdiMakeLogFont(interp, Tcl_GetString(objv[0]), &lf, hDC)) {
		if ((hfont = CreateFontIndirectW(&lf)) != NULL) {
		    made_font = 1;
		    oldfont = SelectObject(hDC, hfont);
		}
	    }
	    /* Else leave the font alone! */
	} else if (strcmp(Tcl_GetString(objv[0]), "-stipple") == 0) {
	    objc--;
	    objv++;
	    /* Not implemented yet. */
	} else if (strcmp(Tcl_GetString(objv[0]), "-fill") == 0) {
	    objc--;
	    objv++;
	    /* Get text color. */
	    if (GdiGetColor(objv[0], &textcolor)) {
		dotextcolor = 1;
	    }
	} else if (strcmp(Tcl_GetString(objv[0]), "-width") == 0) {
	    objc--;
	    objv++;
	    if (objc > 0) {
		int value;
		if (Tcl_GetIntFromObj(interp, objv[0], &value) != TCL_OK) {
		    return TCL_ERROR;
		}
		sizerect.right += value;
	    }
	    /* If a width is specified, break at words. */
	    format_flags |= DT_WORDBREAK;
	} else if (strcmp(Tcl_GetString(objv[0]), "-single") == 0) {
	    usesingle = 1;
	} else if (strcmp(Tcl_GetString(objv[0]), "-backfill") == 0) {
	    dobgmode = 1;
	}

	objc--;
	objv++;
    }

    if (string == 0) {
	Tcl_AppendResult(interp, usage_message, (char *)NULL);
	return TCL_ERROR;
    }

    /* Set the format flags for -single: Overrides -width. */
    if (usesingle == 1) {
	format_flags |= DT_SINGLELINE;
	format_flags |= DT_NOCLIP;
	format_flags &= ~DT_WORDBREAK;
    }

    Tcl_DStringInit(&tds);
    /* Just for fun, let's try translating string to Unicode. */
    wstring = Tcl_UtfToWCharDString(string, TCL_INDEX_NONE, &tds);
    DrawTextW(hDC, wstring, Tcl_DStringLength(&tds)/2, &sizerect,
	    format_flags | DT_CALCRECT);

    /* Adjust the rectangle according to the anchor. */
    x = y = 0;
    switch (anchor) {
    case TK_ANCHOR_N:
	x = (sizerect.right - sizerect.left) / 2;
	break;
    case TK_ANCHOR_S:
	x = (sizerect.right - sizerect.left) / 2;
	y = (sizerect.bottom - sizerect.top);
	break;
    case TK_ANCHOR_E:
	x = (sizerect.right - sizerect.left);
	y = (sizerect.bottom - sizerect.top) / 2;
	break;
    case TK_ANCHOR_W:
	y = (sizerect.bottom - sizerect.top) / 2;
	break;
    case TK_ANCHOR_NE:
	x = (sizerect.right - sizerect.left);
	break;
    case TK_ANCHOR_NW:
	break;
    case TK_ANCHOR_SE:
	x = (sizerect.right - sizerect.left);
	y = (sizerect.bottom - sizerect.top);
	break;
    case TK_ANCHOR_SW:
	y = (sizerect.bottom - sizerect.top);
	break;
    default:
	x = (sizerect.right - sizerect.left) / 2;
	y = (sizerect.bottom - sizerect.top) / 2;
	break;
    }
    sizerect.right  -= x;
    sizerect.left   -= x;
    sizerect.top    -= y;
    sizerect.bottom -= y;

    /* Get the color right. */
    if (dotextcolor) {
	textcolor = SetTextColor(hDC, textcolor);
    }

    if (dobgmode) {
	bgmode = SetBkMode(hDC, OPAQUE);
    } else {
	bgmode = SetBkMode(hDC, TRANSPARENT);
    }

    /* Print the text. */
    retval = DrawTextW(hDC, wstring,
	    Tcl_DStringLength(&tds)/2, &sizerect, format_flags);
    Tcl_DStringFree(&tds);

    /* Get the color set back. */
    if (dotextcolor) {
	textcolor = SetTextColor(hDC, textcolor);
    }
    SetBkMode(hDC, bgmode);
    if (made_font) {
	SelectObject(hDC, oldfont);
	DeleteObject(hfont);
    }

    /* In this case, the return value is the height of the text. */
    Tcl_SetObjResult(interp, Tcl_NewIntObj(retval));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiGetHdcInfo --
 *
 *	Gets salient characteristics of the CTM.
 *
 * Results:
 *	The return value is 0 if any failure occurs--in which case none of the
 *	other values are meaningful.  Otherwise the return value is the
 *	current mapping mode.
 *
 *----------------------------------------------------------------------
 */

static int GdiGetHdcInfo(
    HDC hdc,
    LPPOINT worigin,
    LPSIZE wextent,
    LPPOINT vorigin,
    LPSIZE vextent)
{
    int mapmode;
    int retval;

    memset(worigin, 0, sizeof(POINT));
    memset(vorigin, 0, sizeof(POINT));
    memset(wextent, 0, sizeof(SIZE));
    memset(vextent, 0, sizeof(SIZE));

    if ((mapmode = GetMapMode(hdc)) == 0) {
	/* Failed! */
	retval = 0;
    } else {
	retval = mapmode;
    }

    if (GetWindowExtEx(hdc, wextent) == FALSE) {
	/* Failed! */
	retval = 0;
    }
    if (GetViewportExtEx(hdc, vextent) == FALSE) {
	/* Failed! */
	retval = 0;
    }
    if (GetWindowOrgEx(hdc, worigin) == FALSE) {
	/* Failed! */
	retval = 0;
    }
    if (GetViewportOrgEx(hdc, vorigin) == FALSE) {
	/* Failed! */
	retval = 0;
    }

    return retval;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiNameToMode --
 *
 *	Converts Windows mapping mode names.
 *
 * Results:
 *	Mapping modes are delineated.
 *
 *----------------------------------------------------------------------
 */

static int GdiNameToMode(
    const char *name)
{
    static const struct gdimodes {
	int mode;
	const char *name;
    } modes[] = {
	{ MM_ANISOTROPIC, "MM_ANISOTROPIC" },
	{ MM_HIENGLISH,   "MM_HIENGLISH" },
	{ MM_HIMETRIC,    "MM_HIMETRIC" },
	{ MM_ISOTROPIC,   "MM_ISOTROPIC" },
	{ MM_LOENGLISH,   "MM_LOENGLISH" },
	{ MM_LOMETRIC,    "MM_LOMETRIC" },
	{ MM_TEXT,        "MM_TEXT" },
	{ MM_TWIPS,       "MM_TWIPS" }
    };

    size_t i;
    for (i=0; i < sizeof(modes) / sizeof(struct gdimodes); i++) {
	if (strcmp(modes[i].name, name) == 0) {
	    return modes[i].mode;
	}
    }
    return atoi(name);
}

/*
 *----------------------------------------------------------------------
 *
 * GdiModeToName --
 *
 *	Converts the mode number to a printable form.
 *
 * Results:
 *	Mapping numbers are delineated.
 *
 *----------------------------------------------------------------------
 */

static const char *GdiModeToName(
    int mode)
{
    static const struct gdi_modes {
	int mode;
	const char *name;
    } modes[] = {
	{ MM_ANISOTROPIC, "Anisotropic" },
	{ MM_HIENGLISH,   "1/1000 inch" },
	{ MM_HIMETRIC,    "1/100 mm" },
	{ MM_ISOTROPIC,   "Isotropic" },
	{ MM_LOENGLISH,   "1/100 inch" },
	{ MM_LOMETRIC,    "1/10 mm" },
	{ MM_TEXT,        "1 to 1" },
	{ MM_TWIPS,       "1/1440 inch" }
    };

    size_t i;
    for (i=0; i < sizeof(modes) / sizeof(struct gdi_modes); i++) {
	if (modes[i].mode == mode) {
	    return modes[i].name;
	}
    }
    return "Unknown";
}

/*
 *----------------------------------------------------------------------
 *
 * GdiMap --
 *
 *	Sets mapping mode between logical and physical device space.
 *
 * Results:
 *	Bridges map modes.
 *
 *----------------------------------------------------------------------
 */

static int GdiMap(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    Tcl_Size objc,
    Tcl_Obj *const *objv)
{
    static const char usage_message[] =
	"::tk::print::_gdi map hdc "
	"[-logical x[y]] [-physical x[y]] "
	"[-offset {x y} ] [-default] [-mode mode]";
    HDC hdc;
    int mapmode;	/* Mapping mode. */
    SIZE wextent;	/* Device extent. */
    SIZE vextent;	/* Viewport extent. */
    POINT worigin;	/* Device origin. */
    POINT vorigin;	/* Viewport origin. */
    int argno;

    /* Keep track of what parts of the function need to be executed. */
    int need_usage   = 0;
    int use_logical  = 0;
    int use_physical = 0;
    int use_offset   = 0;
    int use_default  = 0;
    int use_mode     = 0;

    /* Required parameter: HDC for printer. */
    if (objc < 2) {
	Tcl_AppendResult(interp, usage_message, (char *)NULL);
	return TCL_ERROR;
    }

    hdc = printDC;

    if ((mapmode = GdiGetHdcInfo(hdc, &worigin, &wextent, &vorigin, &vextent)) == 0) {
	/* Failed!. */
	Tcl_AppendResult(interp, "Cannot get current HDC info", (char *)NULL);
	return TCL_ERROR;
    }

    /* Parse remaining arguments. */
    for (argno = 2; argno < objc; argno++) {
	if (strcmp(Tcl_GetString(objv[argno]), "-default") == 0) {
	    vextent.cx = vextent.cy = wextent.cx = wextent.cy = 1;
	    vorigin.x = vorigin.y = worigin.x = worigin.y = 0;
	    mapmode = MM_TEXT;
	    use_default = 1;
	} else if (strcmp(Tcl_GetString(objv[argno]), "-mode") == 0) {
	    if (argno + 1 >= objc) {
		need_usage = 1;
	    } else {
		mapmode = GdiNameToMode(Tcl_GetString(objv[argno + 1]));
		use_mode = 1;
		argno++;
	    }
	} else if (strcmp(Tcl_GetString(objv[argno]), "-offset") == 0) {
	    if (argno + 1 >= objc) {
		need_usage = 1;
	    } else {
		/* It would be nice if this parsed units as well.... */
		if (sscanf(Tcl_GetString(objv[argno + 1]), "%ld%ld",
			&vorigin.x, &vorigin.y) == 2) {
		    use_offset = 1;
		} else {
		    need_usage = 1;
		}
		argno++;
	    }
	} else if (strcmp(Tcl_GetString(objv[argno]), "-logical") == 0) {
	    if (argno + 1 >= objc) {
		need_usage = 1;
	    } else {
		int count;

		argno++;
		/* In "real-life", this should parse units as well.. */
		if ((count = sscanf(Tcl_GetString(objv[argno]), "%ld%ld",
			&wextent.cx, &wextent.cy)) != 2) {
		    if (count == 1) {
			mapmode = MM_ISOTROPIC;
			use_logical = 1;
			wextent.cy = wextent.cx;  /* Make them the same. */
		    } else {
			need_usage = 1;
		    }
		} else {
		    mapmode = MM_ANISOTROPIC;
		    use_logical = 2;
		}
	    }
	} else if (strcmp(Tcl_GetString(objv[argno]), "-physical") == 0) {
	    if (argno + 1 >= objc) {
		need_usage = 1;
	    } else {
		int count;

		argno++;
		/* In "real-life", this should parse units as well.. */
		if ((count = sscanf(Tcl_GetString(objv[argno]), "%ld%ld",
			&vextent.cx, &vextent.cy)) != 2) {
		    if (count == 1) {
			mapmode = MM_ISOTROPIC;
			use_physical = 1;
			vextent.cy = vextent.cx;  /* Make them the same. */
		    } else {
			need_usage = 1;
		    }
		} else {
		    mapmode = MM_ANISOTROPIC;
		    use_physical = 2;
		}
	    }
	}
    }

    /* Check for any impossible combinations. */
    if (use_logical != use_physical) {
	need_usage = 1;
    }
    if (use_default && (use_logical || use_offset || use_mode)) {
	need_usage = 1;
    }
    if (use_mode && use_logical &&
	    (mapmode != MM_ISOTROPIC && mapmode != MM_ANISOTROPIC)) {
	need_usage = 1;
    }

    if (need_usage) {
	Tcl_AppendResult(interp, usage_message, NULL);
	return TCL_ERROR;
    }

    /* Call Windows CTM functions. */
    if (use_logical || use_default || use_mode) { /* Don't call for offset only. */
	SetMapMode(hdc, mapmode);
    }

    if (use_offset || use_default) {
	POINT oldorg;
	SetViewportOrgEx(hdc, vorigin.x, vorigin.y, &oldorg);
	SetWindowOrgEx(hdc, worigin.x, worigin.y, &oldorg);
    }

    if (use_logical) {  /* Same as use_physical. */
	SIZE oldsiz;
	SetWindowExtEx(hdc, wextent.cx, wextent.cy, &oldsiz);
	SetViewportExtEx(hdc, vextent.cx, vextent.cy, &oldsiz);
    }

    /*
     * Since we may not have set up every parameter, get them again for the
     * report.
     */
    mapmode = GdiGetHdcInfo(hdc, &worigin, &wextent, &vorigin, &vextent);

    /*
     * Output current CTM info.
     * Note: This should really be in terms that can be used in a
     * ::tk::print::_gdi map command!
     */
    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
	    "Transform: \"(%ld, %ld) -> (%ld, %ld)\" "
	    "Origin: \"(%ld, %ld)\" "
	    "MappingMode: \"%s\"",
	    vextent.cx, vextent.cy, wextent.cx, wextent.cy,
	    vorigin.x, vorigin.y,
	    GdiModeToName(mapmode)));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiCopyBits --
 *
 *	Copies window bits from source to destination.
 *
 * Results:
 *	Copies window bits.
 *
 *----------------------------------------------------------------------
 */

static int GdiCopyBits(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    Tcl_Size objc,
    Tcl_Obj *const *objv)
{
    /* Goal: get the Tk_Window from the top-level
     * convert it to an HWND
     * get the HDC
     * Do a bitblt to the given hdc
     * Use an optional parameter to point to an arbitrary window instead of
     * the main
     * Use optional parameters to map to the width and height required for the
     * dest.
     */
    static const char usage_message[] =
	"::tk::print::_gdi copybits hdc [-window w|-screen] [-client] "
	"[-source \"a b c d\"] "
	"[-destination \"a b c d\"] [-scale number] [-calc]";

    Tk_Window mainWin;
    Tk_Window workwin;
    Window wnd;
    HDC src;
    HDC dst;
    HWND hwnd = 0;

    HANDLE hDib;    /* Handle for device-independent bitmap. */
    LPBITMAPINFOHEADER lpDIBHdr;
    LPSTR lpBits;
    enum PrintType wintype = PTWindow;

    int hgt, wid;
    char *strend;
    long errcode;
    Tcl_Size k;

    /* Variables to remember what we saw in the arguments. */
    int do_window = 0;
    int do_screen = 0;
    int do_scale = 0;
    int do_print = 1;

    /* Variables to remember the values in the arguments. */
    const char *window_spec;
    double scale = 1.0;
    int src_x = 0, src_y = 0, src_w = 0, src_h = 0;
    int dst_x = 0, dst_y = 0, dst_w = 0, dst_h = 0;
    int is_toplevel = 0;

    /*
     * The following steps are peculiar to the top level window.
     * There is likely a clever way to do the mapping of a widget pathname to
     * the proper window, to support the idea of using a parameter for this
     * purpose.
     */
    if ((workwin = mainWin = Tk_MainWindow(interp)) == 0) {
	Tcl_AppendResult(interp, "Can't find main Tk window", (char *)NULL);
	return TCL_ERROR;
    }

    /*
     * Parse the arguments.
     */
    /* HDC is required. */
    if (objc < 2) {
	Tcl_AppendResult(interp, usage_message, (char *)NULL);
	return TCL_ERROR;
    }

    dst = printDC;

    /*
     * Next, check to see if 'dst' can support BitBlt.  If not, raise an
     * error.
     */
    if ((GetDeviceCaps(dst, RASTERCAPS) & RC_BITBLT) == 0) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"Can't do bitmap operations on device context\n"));
	return TCL_ERROR;
    }

    /* Loop through the remaining arguments. */
    for (k=2; k<objc; k++) {
	if (strcmp(Tcl_GetString(objv[k]), "-window") == 0) {
	    if (Tcl_GetString(objv[k+1]) && Tcl_GetString(objv[k+1])[0] == '.') {
		do_window = 1;
		workwin = Tk_NameToWindow(interp, window_spec = Tcl_GetString(objv[++k]), mainWin);
		if (workwin == NULL) {
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			    "Can't find window %s in this application",
			    window_spec));
		    return TCL_ERROR;
		}
	    } else {
		/* Use strtoul() so octal or hex representations will be
		 * parsed. */
		hwnd = (HWND) INT2PTR(strtoul(Tcl_GetString(objv[++k]), &strend, 0));
		if (strend == 0 || strend == Tcl_GetString(objv[k])) {
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			    "Can't understand window id %s", Tcl_GetString(objv[k])));
		    return TCL_ERROR;
		}
	    }
	} else if (strcmp(Tcl_GetString(objv[k]), "-screen") == 0) {
	    do_screen = 1;
	    wintype = PTScreen;
	} else if (strcmp(Tcl_GetString(objv[k]), "-client") == 0) {
	    wintype = PTClient;
	} else if (strcmp(Tcl_GetString(objv[k]), "-source") == 0) {
	    float a, b, c, d;
	    int count = sscanf(Tcl_GetString(objv[++k]), "%f%f%f%f", &a, &b, &c, &d);

	    if (count < 2) { /* Can't make heads or tails of it.... */
		Tcl_AppendResult(interp, usage_message, (char *)NULL);
		return TCL_ERROR;
	    }
	    src_x = (int)a;
	    src_y = (int)b;
	    if (count == 4) {
		src_w = (int)c;
		src_h = (int)d;
	    }
	} else if (strcmp(Tcl_GetString(objv[k]), "-destination") == 0) {
	    float a, b, c, d;
	    int count;

	    count = sscanf(Tcl_GetString(objv[++k]), "%f%f%f%f", &a, &b, &c, &d);
	    if (count < 2) { /* Can't make heads or tails of it.... */
		Tcl_AppendResult(interp, usage_message, (char *)NULL);
		return TCL_ERROR;
	    }
	    dst_x = (int)a;
	    dst_y = (int)b;
	    if (count == 3) {
		dst_w = (int)c;
		dst_h = -1;
	    } else if (count == 4) {
		dst_w = (int)c;
		dst_h = (int)d;
	    }
	} else if (strcmp(Tcl_GetString(objv[k]), "-scale") == 0) {
	    if (Tcl_GetString(objv[++k])) {
		if (Tcl_GetDouble(interp, Tcl_GetString(objv[k]), &scale) != TCL_OK) {
		    return TCL_ERROR;
		}
		if (scale <= 0.01 || scale >= 100.0) {
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			    "Unreasonable scale specification %s", Tcl_GetString(objv[k])));
		    return TCL_ERROR;
		}
		do_scale = 1;
	    }
	} else if (strcmp(Tcl_GetString(objv[k]), "-noprint") == 0
		|| strncmp(Tcl_GetString(objv[k]), "-calc", 5) == 0) {
	    /* This option suggested by Pascal Bouvier to get sizes without
	     * printing. */
	    do_print = 0;
	}
    }

    /*
     * Check to ensure no incompatible arguments were used.
     */
    if (do_window && do_screen) {
	Tcl_AppendResult(interp, usage_message, (char *)NULL);
	return TCL_ERROR;
    }

    /*
     * Get the MS Window we want to copy.  Given the HDC, we can get the
     * "Window".
     */
    if (hwnd == 0) {
	if (Tk_IsTopLevel(workwin)) {
	    is_toplevel = 1;
	}

	if ((wnd = Tk_WindowId(workwin)) == 0) {
	    Tcl_AppendResult(interp, "Can't get id for Tk window", (char *)NULL);
	    return TCL_ERROR;
	}

	/* Given the "Window" we can get a Microsoft Windows HWND. */

	if ((hwnd = Tk_GetHWND(wnd)) == 0) {
	    Tcl_AppendResult(interp, "Can't get Windows handle for Tk window",
		    (char *)NULL);
	    return TCL_ERROR;
	}

	/*
	 * If it's a toplevel, give it special treatment: Get the top-level
	 * window instead.  If the user only wanted the client, the -client
	 * flag will take care of it.  This uses "windows" tricks rather than
	 * Tk since the obvious method of getting the wrapper window didn't
	 * seem to work.
	 */
	if (is_toplevel) {
	    HWND tmpWnd = hwnd;
	    while ((tmpWnd = GetParent(tmpWnd)) != 0) {
		hwnd = tmpWnd;
	    }
	}
    }

    /* Given the HWND, we can get the window's device context. */
    if ((src = GetWindowDC(hwnd)) == 0) {
	Tcl_AppendResult(interp, "Can't get device context for Tk window", (char *)NULL);
	return TCL_ERROR;
    }

    if (do_screen) {
	LONG w, h;
	GetDisplaySize(&w, &h);
	wid = w;
	hgt = h;
    } else if (is_toplevel) {
	RECT tl;
	GetWindowRect(hwnd, &tl);
	wid = tl.right - tl.left;
	hgt = tl.bottom - tl.top;
    } else {
	if ((hgt = Tk_Height(workwin)) <= 0) {
	    Tcl_AppendResult(interp, "Can't get height of Tk window", (char *)NULL);
	    ReleaseDC(hwnd,src);
	    return TCL_ERROR;
	}

	if ((wid = Tk_Width(workwin)) <= 0) {
	    Tcl_AppendResult(interp, "Can't get width of Tk window", (char *)NULL);
	    ReleaseDC(hwnd,src);
	    return TCL_ERROR;
	}
    }

    /*
     * Ensure all the widths and heights are set up right
     * A: No dimensions are negative
     * B: No dimensions exceed the maximums
     * C: The dimensions don't lead to a 0 width or height image.
     */
    if (src_x < 0) {
	src_x = 0;
    }
    if (src_y < 0) {
	src_y = 0;
    }
    if (dst_x < 0) {
	dst_x = 0;
    }
    if (dst_y < 0) {
	dst_y = 0;
    }

    if (src_w > wid || src_w <= 0) {
	src_w = wid;
    }

    if (src_h > hgt || src_h <= 0) {
	src_h = hgt;
    }

    if (do_scale && dst_w == 0) {
	/* Calculate destination width and height based on scale. */
	dst_w = (int)(scale * src_w);
	dst_h = (int)(scale * src_h);
    }

    if (dst_h == -1) {
	dst_h = (int) (((long)src_h * dst_w) / (src_w + 1)) + 1;
    }

    if (dst_h == 0 || dst_w == 0) {
	dst_h = src_h;
	dst_w = src_w;
    }

    if (do_print) {
	/*
	 * Based on notes from Heiko Schock and Arndt Roger Schneider, create
	 * this as a DIBitmap, to allow output to a greater range of devices.
	 * This approach will also allow selection of
	 *   a) Whole screen
	 *   b) Whole window
	 *   c) Client window only
	 * for the "grab"
	 */
	hDib = CopyToDIB(hwnd, wintype);

	/* GdiFlush();. */

	if (!hDib) {
	    Tcl_AppendResult(interp, "Can't create DIB", (char *)NULL);
	    ReleaseDC(hwnd,src);
	    return TCL_ERROR;
	}

	lpDIBHdr = (LPBITMAPINFOHEADER) GlobalLock(hDib);
	if (!lpDIBHdr) {
	    Tcl_AppendResult(interp, "Can't get DIB header", (char *)NULL);
	    ReleaseDC(hwnd,src);
	    return TCL_ERROR;
	}

	lpBits = (LPSTR) lpDIBHdr + lpDIBHdr->biSize + DIBNumColors(lpDIBHdr) * sizeof(RGBQUAD);

	/* stretch the DIBbitmap directly in the target device. */

	if (StretchDIBits(dst,
		dst_x, dst_y, dst_w, dst_h,
		src_x, src_y, src_w, src_h,
		lpBits, (LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS,
		SRCCOPY) == (int)GDI_ERROR) {
	    errcode = GetLastError();
	    GlobalUnlock(hDib);
	    GlobalFree(hDib);
	    ReleaseDC(hwnd,src);
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		    "StretchDIBits failed with code %ld", errcode));
	    return TCL_ERROR;
	}

	/* free allocated memory. */
	GlobalUnlock(hDib);
	GlobalFree(hDib);
    }

    ReleaseDC(hwnd,src);

    /*
     * The return value should relate to the size in the destination space.
     * At least the height should be returned (for page layout purposes).
     */
    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
	    "%d %d %d %d", dst_x, dst_y, dst_w, dst_h));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DIBNumColors --
 *
 *	Computes the number of colors required for a DIB palette.
 *
 * Results:
 *	Returns number of colors.

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

static int DIBNumColors(
    LPBITMAPINFOHEADER lpDIB)
{
    WORD wBitCount;	/* DIB bit count. */
    DWORD dwClrUsed;

    /*
     * If this is a Windows-style DIB, the number of colors in the color table
     * can be less than the number of bits per pixel allows for (i.e.
     * lpbi->biClrUsed can be set to some value).  If this is the case, return
     * the appropriate value..
     */

    dwClrUsed = lpDIB->biClrUsed;
    if (dwClrUsed) {
	return (WORD) dwClrUsed;
    }

    /*
     * Calculate the number of colors in the color table based on.
     * The number of bits per pixel for the DIB.
     */

    wBitCount = lpDIB->biBitCount;

    /* Return number of colors based on bits per pixel. */

    switch (wBitCount) {
    case 1:
	return 2;
    case 4:
	return 16;
    case 8:
	return 256;
    default:
	return 0;
    }
}

/*
 * Helper functions
 */

/*
 * ParseFontWords converts various keywords to modifyers of a
 * font specification.
 * For all words, later occurrences override earlier occurrences.
 * Overstrike and underline cannot be "undone" by other words
 */

/*
 *----------------------------------------------------------------------
 *
 * GdiParseFontWords --
 *
 *	Converts various keywords to modifiers of a font specification.  For
 *	all words, later occurrences override earlier occurrences.  Overstrike
 *	and underline cannot be "undone" by other words
 *
 * Results:
 *	 Keywords converted to modifiers.
 *
 *----------------------------------------------------------------------
 */

static int GdiParseFontWords(
    TCL_UNUSED(Tcl_Interp *),
    LOGFONTW *lf,
    const char *str[],
    int numargs)
{
    int i;
    int retval = 0; /* Number of words that could not be parsed. */

    for (i=0; i<numargs; i++) {
	if (str[i]) {
	    int wt;
	    if ((wt = GdiWordToWeight(str[i])) != -1) {
		lf->lfWeight = wt;
	    } else if (strcmp(str[i], "roman") == 0) {
		lf->lfItalic = FALSE;
	    } else if (strcmp(str[i], "italic") == 0) {
		lf->lfItalic = TRUE;
	    } else if (strcmp(str[i], "underline") == 0) {
		lf->lfUnderline = TRUE;
	    } else if (strcmp(str[i], "overstrike") == 0) {
		lf->lfStrikeOut = TRUE;
	    } else {
		retval++;
	    }
	}
    }
    return retval;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiWordToWeight --
 *
 *	Converts keywords to font weights.
 *
 * Results:
 *	Helps set the proper font for GDI rendering.
 *
 *----------------------------------------------------------------------
 */

static int GdiWordToWeight(
    const char *str)
{
    int retval = -1;
    size_t i;
    static const struct font_weight {
	const char *name;
	int weight;
    } font_weights[] = {
	{ "thin", FW_THIN },
	{ "extralight", FW_EXTRALIGHT },
	{ "ultralight", FW_EXTRALIGHT },
	{ "light", FW_LIGHT },
	{ "normal", FW_NORMAL },
	{ "regular", FW_NORMAL },
	{ "medium", FW_MEDIUM },
	{ "semibold", FW_SEMIBOLD },
	{ "demibold", FW_SEMIBOLD },
	{ "bold", FW_BOLD },
	{ "extrabold", FW_EXTRABOLD },
	{ "ultrabold", FW_EXTRABOLD },
	{ "heavy", FW_HEAVY },
	{ "black", FW_HEAVY },
    };

    if (str == 0) {
	return -1;
    }

    for (i=0; i<sizeof(font_weights) / sizeof(struct font_weight); i++) {
	if (strcmp(str, font_weights[i].name) == 0) {
	    retval = font_weights[i].weight;
	    break;
	}
    }

    return retval;
}

/*
 *----------------------------------------------------------------------
 *
 * MakeLogFont --
 *
 *	Takes the font description string and converts this into a logical
 *	font spec.
 *
 * Results:
 *	 Sets font weight.
 *
 *----------------------------------------------------------------------
 */

static int GdiMakeLogFont(
    Tcl_Interp *interp,
    const char *str,
    LOGFONTW *lf,
    HDC hDC)
{
    const char **list;
    Tcl_Size count;

    /* Set up defaults for logical font. */
    memset(lf, 0, sizeof(*lf));
    lf->lfWeight  = FW_NORMAL;
    lf->lfCharSet = DEFAULT_CHARSET;
    lf->lfOutPrecision = OUT_DEFAULT_PRECIS;
    lf->lfClipPrecision = CLIP_DEFAULT_PRECIS;
    lf->lfQuality = DEFAULT_QUALITY;
    lf->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;

    /* The cast to (char *) is silly, based on prototype of Tcl_SplitList. */
    if (Tcl_SplitList(interp, str, &count, &list) != TCL_OK) {
	return 0;
    }

    /* Now we have the font structure broken into name, size, weight. */
    if (count >= 1) {
	Tcl_DString ds;

	Tcl_DStringInit(&ds);
	wcsncpy(lf->lfFaceName, Tcl_UtfToWCharDString(list[0], TCL_INDEX_NONE, &ds),
		LF_FACESIZE-1);
	Tcl_DStringFree(&ds);
	lf->lfFaceName[LF_FACESIZE-1] = 0;
    } else {
	return 0;
    }

    if (count >= 2) {
	int siz;
	char *strend;
	siz = strtol(list[1], &strend, 0);

	/*
	 * Assumptions:
	 * 1) Like canvas, if a positive number is specified, it's in points.
	 * 2) Like canvas, if a negative number is specified, it's in pixels.
	 */
	if (strend > list[1]) { /* If it looks like a number, it is a number.... */
	    if (siz > 0) {  /* Size is in points. */
		SIZE wextent, vextent;
		POINT worigin, vorigin;
		double factor;

		switch (GdiGetHdcInfo(hDC, &worigin, &wextent, &vorigin, &vextent)) {
		case MM_ISOTROPIC:
		    if (vextent.cy < -1 || vextent.cy > 1) {
			factor = (double)wextent.cy / vextent.cy;
			if (factor < 0.0) {
			    factor = -factor;
			}
			lf->lfHeight = (int)(-siz * GetDeviceCaps(hDC, LOGPIXELSY) * factor / 72.0);
		    } else if (vextent.cx < -1 || vextent.cx > 1) {
			factor = (double)wextent.cx / vextent.cx;
			if (factor < 0.0) {
			    factor = -factor;
			}
			lf->lfHeight = (int)(-siz * GetDeviceCaps(hDC, LOGPIXELSY) * factor / 72.0);
		    } else {
			lf->lfHeight = -siz; /* This is bad news.... */
		    }
		    break;
		case MM_ANISOTROPIC:
		    if (vextent.cy != 0) {
			factor = (double)wextent.cy / vextent.cy;
			if (factor < 0.0) {
			    factor = -factor;
			}
			lf->lfHeight = (int)(-siz * GetDeviceCaps(hDC, LOGPIXELSY) * factor / 72.0);
		    } else {
			lf->lfHeight = -siz; /* This is bad news.... */
		    }
		    break;
		case MM_TEXT:
		default:
		    /* If mapping mode is MM_TEXT, use the documented
		     * formula. */
		    lf->lfHeight = -MulDiv(siz, GetDeviceCaps(hDC, LOGPIXELSY), 72);
		    break;
		case MM_HIENGLISH:
		    lf->lfHeight = -MulDiv(siz, 1000, 72);
		    break;
		case MM_LOENGLISH:
		    lf->lfHeight = -MulDiv(siz, 100, 72);
		    break;
		case MM_HIMETRIC:
		    lf->lfHeight = -MulDiv(siz, (int)(1000*2.54), 72);
		    break;
		case MM_LOMETRIC:
		    lf->lfHeight = -MulDiv(siz, (int)(100*2.54), 72);
		    break;
		case MM_TWIPS:
		    lf->lfHeight = -MulDiv(siz, 1440, 72);
		    break;
		}
	    } else if (siz == 0) {   /* Use default size of 12 points. */
		lf->lfHeight = -MulDiv(12, GetDeviceCaps(hDC, LOGPIXELSY), 72);
	    } else {                 /* Use pixel size. */
		lf->lfHeight = siz;  /* Leave this negative. */
	    }
	} else {
	    GdiParseFontWords(interp, lf, list+1, count-1);
	}
    }

    if (count >= 3) {
	GdiParseFontWords(interp, lf, list+2, count-2);
    }

    ckfree(list);
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiMakePen --
 *
 *	Creates a logical pen based on input parameters and selects it into
 *	the hDC.
 *
 * Results:
 *	Sets rendering pen.
 *
 *----------------------------------------------------------------------
 */

static int GdiMakePen(
    Tcl_Interp *interp,
    double dwidth,
    int dashstyle,
    const char *dashstyledata,
    TCL_UNUSED(int),		/* Ignored for now. */
    TCL_UNUSED(int),		/* Ignored for now. */
    TCL_UNUSED(int),
    TCL_UNUSED(const char *),	/* Ignored for now. */
    unsigned long color,
    HDC hDC,
    HGDIOBJ *oldPen)
{
    /*
     * The LOGPEN structure takes the following dash options:
     * PS_SOLID: a solid pen
     * PS_DASH:  a dashed pen
     * PS_DOT:   a dotted pen
     * PS_DASHDOT: a pen with a dash followed by a dot
     * PS_DASHDOTDOT: a pen with a dash followed by 2 dots
     *
     * It seems that converting to ExtCreatePen may be more advantageous, as
     * it matches the Tk canvas pens much better--but not for Win95, which
     * does not support PS_USERSTYLE. An explicit test (or storage in a static
     * after first failure) may suffice for working around this. The
     * ExtCreatePen is not supported at all under Win32.
     */
    int width = floor(dwidth + 0.5);
    HPEN hPen;
    LOGBRUSH lBrush;
    DWORD pStyle = PS_SOLID;           /* -dash should override*/
    DWORD endStyle = PS_ENDCAP_ROUND;  /* -capstyle should override. */
    DWORD joinStyle = PS_JOIN_ROUND;   /* -joinstyle should override. */
    DWORD styleCount = 0;
    DWORD *styleArray = 0;

    /*
     * To limit the propagation of allocated memory, the dashes will have a
     * maximum here.  If one wishes to remove the static allocation, please be
     * sure to update GdiFreePen and ensure that the array is NOT freed if the
     * LOGPEN option is used.
     */
    static DWORD pStyleData[24];
    if (dashstyle != 0 && dashstyledata != 0) {
	const char *cp;
	size_t i;
	char *dup = (char *) ckalloc(strlen(dashstyledata) + 1);
	strcpy(dup, dashstyledata);
	/* DEBUG. */
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"DEBUG: Found a dash spec of |%s|\n",
		dashstyledata));

	/* Parse the dash spec. */
	if (isdigit(dashstyledata[0])) {
	    cp = strtok(dup, " \t,;");
	    for (i = 0; cp && i < sizeof(pStyleData) / sizeof(DWORD); i++) {
		pStyleData[styleCount++] = atoi(cp);
		cp = strtok(NULL, " \t,;");
	    }
	} else {
	    for (i=0; dashstyledata[i] != '\0' && i< sizeof(pStyleData) / sizeof(DWORD); i++) {
		switch (dashstyledata[i]) {
		case ' ':
		    pStyleData[styleCount++] = 8;
		    break;
		case ',':
		    pStyleData[styleCount++] = 4;
		    break;
		case '_':
		    pStyleData[styleCount++] = 6;
		    break;
		case '-':
		    pStyleData[styleCount++] = 4;
		    break;
		case '.':
		    pStyleData[styleCount++] = 2;
		    break;
		default:
		    break;
		}
	    }
	}
	if (styleCount > 0) {
	    styleArray = pStyleData;
	} else {
	    dashstyle = 0;
	}
	if (dup) {
	    ckfree(dup);
	}
    }

    if (dashstyle != 0) {
	pStyle = PS_USERSTYLE;
    }

    /* -stipple could affect this.... */
    lBrush.lbStyle = BS_SOLID;
    lBrush.lbColor = color;
    lBrush.lbHatch = 0;

    /* We only use geometric pens, even for 1-pixel drawing. */
    hPen = ExtCreatePen(PS_GEOMETRIC|pStyle|endStyle|joinStyle,
	    width, &lBrush, styleCount, styleArray);

    if (hPen == 0) { /* Failed for some reason...Fall back on CreatePenIndirect. */
	LOGPEN lf;
	lf.lopnWidth.x = width;
	lf.lopnWidth.y = 0;		/* Unused in LOGPEN. */
	if (dashstyle == 0) {
	    lf.lopnStyle = PS_SOLID;	/* For now...convert 'style' in the future. */
	} else {
	    lf.lopnStyle = PS_DASH;	/* REALLLLY simple for now. */
	}
	lf.lopnColor = color;		/* Assume we're getting a COLORREF. */
	/* Now we have a logical pen. Create the "real" pen and put it in the
	 * hDC. */
	hPen = CreatePenIndirect(&lf);
    }

    *oldPen = SelectObject(hDC, hPen);
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiFreePen --
 *
 *	Wraps the protocol to delete a created pen.
 *
 * Results:
 *	Deletes pen.
 *
 *----------------------------------------------------------------------
 */

static int GdiFreePen(
    TCL_UNUSED(Tcl_Interp *),
    HDC hDC,
    HGDIOBJ oldPen)
{
    HGDIOBJ gonePen = SelectObject(hDC, oldPen);

    DeleteObject(gonePen);
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiMakeBrush--
 *
 *	Creates a logical brush based on input parameters, and selects it into
 *	the hdc.
 *
 * Results:
 *	 Creates brush.
 *
 *----------------------------------------------------------------------
 */

static int GdiMakeBrush(
    unsigned long color,
    long hatch,
    LOGBRUSH *lb,
    HDC hDC,
	HBRUSH *oldBrush)
{
    HBRUSH hBrush;
    lb->lbStyle = BS_SOLID; /* Support other styles later. */
    lb->lbColor = color;    /* Assume this is a COLORREF. */
    lb->lbHatch = hatch;    /* Ignored for now, given BS_SOLID in the Style. */

    /* Now we have the logical brush. Create the "real" brush and put it in
     * the hDC. */
    hBrush = CreateBrushIndirect(lb);
    *oldBrush = (HBRUSH)SelectObject(hDC, hBrush);
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * GdiFreeBrush --
 *
 *	Wraps the protocol to delete a created brush.
 *
 * Results:
 *	 Deletes brush.
 *
 *----------------------------------------------------------------------
 */
static void GdiFreeBrush(
    TCL_UNUSED(Tcl_Interp *),
    HDC hDC,
    HGDIOBJ oldBrush)
{
    HGDIOBJ goneBrush;

    goneBrush = SelectObject(hDC, oldBrush);
    DeleteObject(goneBrush);
}

/*
 * Utility functions from elsewhere in Tcl.
 * Functions have removed reliance on X and Tk libraries, as well as removing
 * the need for TkWindows.
 * GdiGetColor is a copy of a TkpGetColor from tkWinColor.c
 */
typedef struct {
    const char *name;
    int index;
} SystemColorEntry;

static const SystemColorEntry sysColors[] = {
    {"3dDarkShadow",		COLOR_3DDKSHADOW},
    {"3dLight",			COLOR_3DLIGHT},
    {"ActiveBorder",		COLOR_ACTIVEBORDER},
    {"ActiveCaption",		COLOR_ACTIVECAPTION},
    {"AppWorkspace",		COLOR_APPWORKSPACE},
    {"Background",		COLOR_BACKGROUND},
    {"ButtonFace",		COLOR_BTNFACE},
    {"ButtonHighlight",		COLOR_BTNHIGHLIGHT},
    {"ButtonShadow",		COLOR_BTNSHADOW},
    {"ButtonText",		COLOR_BTNTEXT},
    {"CaptionText",		COLOR_CAPTIONTEXT},
    {"DisabledText",		COLOR_GRAYTEXT},
    {"GrayText",		COLOR_GRAYTEXT},
    {"Highlight",		COLOR_HIGHLIGHT},
    {"HighlightText",		COLOR_HIGHLIGHTTEXT},
    {"InactiveBorder",		COLOR_INACTIVEBORDER},
    {"InactiveCaption",		COLOR_INACTIVECAPTION},
    {"InactiveCaptionText",	COLOR_INACTIVECAPTIONTEXT},
    {"InfoBackground",		COLOR_INFOBK},
    {"InfoText",		COLOR_INFOTEXT},
    {"Menu",			COLOR_MENU},
    {"MenuText",		COLOR_MENUTEXT},
    {"Scrollbar",		COLOR_SCROLLBAR},
    {"Window",			COLOR_WINDOW},
    {"WindowFrame",		COLOR_WINDOWFRAME},
    {"WindowText",		COLOR_WINDOWTEXT}
};

static const size_t numsyscolors = sizeof(sysColors) / sizeof(SystemColorEntry);

/*
 *----------------------------------------------------------------------
 *
 * GdiGetColor --
 *
 *	Convert color name to color specification.
 *
 * Results:
 *	 Color name converted.
 *
 *----------------------------------------------------------------------
 */

static int GdiGetColor(
    Tcl_Obj *nameObj,
    COLORREF *color)
{
    const char *name = Tcl_GetString(nameObj);

    if (_strnicmp(name, "system", 6) == 0) {
	size_t i, l, u;
	int r;

	l = 0;
	u = numsyscolors;
	while (l <= u) {
	    i = (l + u) / 2;
	    if ((r = _strcmpi(name+6, sysColors[i].name)) == 0) {
		break;
	    }
	    if (r < 0) {
		u = i - 1;
	    } else {
		l = i + 1;
	    }
	}
	if (l > u) {
	    return 0;
	}
	*color = GetSysColor(sysColors[i].index);
	return 1;
    } else {
    int result;
    XColor xcolor;
	result = XParseColor(NULL, 0, name, &xcolor);
	*color = ((xcolor.red & 0xFF00)>>8) | (xcolor.green & 0xFF00)
		| ((xcolor.blue & 0xFF00)<<8);
    return result;
    }
}

/*
 * Beginning of functions for screen-to-dib translations.
 *
 * Several of these functions are based on those in the WINCAP32 program
 * provided as a sample by Microsoft on the VC++ 5.0 disk. The copyright on
 * these functions is retained, even for those with significant changes.
 */

/*
 *----------------------------------------------------------------------
 *
 * CopyToDIB --
 *
 *	Copy window bits to a DIB.
 *
 * Results:
 *	 Color specification converted.
 *
 *----------------------------------------------------------------------
 */

static HANDLE CopyToDIB(
    HWND hWnd,
    enum PrintType type)
{
    HANDLE hDIB;
    HBITMAP hBitmap;
    HPALETTE hPalette;

    /* Check for a valid window handle. */

    if (!hWnd) {
	return NULL;
    }

    switch (type) {
    case PTWindow: {	/* Copy entire window. */
	RECT rectWnd;

	/* Get the window rectangle. */

	GetWindowRect(hWnd, &rectWnd);

	/*
	 * Get the DIB of the window by calling CopyScreenToDIB and passing it
	 * the window rect.
	 */

	hDIB = CopyScreenToDIB(&rectWnd);
	break;
    }

    case PTClient: {	/* Copy client area. */
	RECT rectClient;
	POINT pt1, pt2;

	/* Get the client area dimensions. */

	GetClientRect(hWnd, &rectClient);

	/* Convert client coords to screen coords. */

	pt1.x = rectClient.left;
	pt1.y = rectClient.top;
	pt2.x = rectClient.right;
	pt2.y = rectClient.bottom;
	ClientToScreen(hWnd, &pt1);
	ClientToScreen(hWnd, &pt2);
	rectClient.left = pt1.x;
	rectClient.top = pt1.y;
	rectClient.right = pt2.x;
	rectClient.bottom = pt2.y;

	/*
	 * Get the DIB of the client area by calling CopyScreenToDIB and
	 * passing it the client rect.
	 */

	hDIB = CopyScreenToDIB(&rectClient);
	break;
    }

    case PTScreen: { /* Entire screen. */
	RECT Rect;

	/*
	 * Get the device-dependent bitmap in lpRect by calling
	 * CopyScreenToBitmap and passing it the rectangle to grab.
	 */
	Rect.top = Rect.left = 0;
	GetDisplaySize(&Rect.right, &Rect.bottom);

	hBitmap = CopyScreenToBitmap(&Rect);

	/* Check for a valid bitmap handle. */

	if (!hBitmap) {
	    return NULL;
	}

	/* Get the current palette. */

	hPalette = GetSystemPalette();

	/* Convert the bitmap to a DIB. */

	hDIB = BitmapToDIB(hBitmap, hPalette);

	/* Clean up. */

	DeleteObject(hPalette);
	DeleteObject(hBitmap);

	/* Return handle to the packed-DIB. */
	break;
    }
    default:	/* Invalid print area. */
	return NULL;
    }

    /* Return the handle to the DIB. */
    return hDIB;
}

/*
 *----------------------------------------------------------------------
 *
 * GetDisplaySize--
 *
 *	GetDisplaySize does just that.  There may be an easier way, but it is
 *	not apparent.
 *
 * Results:
 *	Returns display size.
 *
 *----------------------------------------------------------------------
 */

static void GetDisplaySize(
    LONG *width,
    LONG *height)
{
    HDC hDC;

    hDC = CreateDCW(L"DISPLAY", 0, 0, 0);
    *width = GetDeviceCaps(hDC, HORZRES);
    *height = GetDeviceCaps(hDC, VERTRES);
    DeleteDC(hDC);
}

/*
 *----------------------------------------------------------------------
 *
 * CopyScreenToBitmap--
 *
 *	Copies screen to bitmap.
 *
 * Results:
 *	Screen is copied.
 *
 *----------------------------------------------------------------------
 */

static HBITMAP CopyScreenToBitmap(
    LPRECT lpRect)
{
    HDC     hScrDC, hMemDC;	/* Screen DC and memory DC. */
    HGDIOBJ hBitmap, hOldBitmap; /* Handles to deice-dependent bitmaps. */
    int     nX, nY, nX2, nY2;	/* Coordinates of rectangle to grab. */
    int     nWidth, nHeight;	/* DIB width and height */
    int     xScrn, yScrn;	/* Screen resolution. */

    /* Check for an empty rectangle. */

    if (IsRectEmpty(lpRect)) {
	return NULL;
    }

    /*
     * Create a DC for the screen and create a memory DC compatible to screen
     * DC.
     */

    hScrDC = CreateDCW(L"DISPLAY", NULL, NULL, NULL);
    hMemDC = CreateCompatibleDC(hScrDC);

    /* Get points of rectangle to grab. */

    nX = lpRect->left;
    nY = lpRect->top;
    nX2 = lpRect->right;
    nY2 = lpRect->bottom;

    /* Get screen resolution. */

    xScrn = GetDeviceCaps(hScrDC, HORZRES);
    yScrn = GetDeviceCaps(hScrDC, VERTRES);

    /* Make sure bitmap rectangle is visible. */

    if (nX < 0) {
	nX = 0;
    }
    if (nY < 0) {
	nY = 0;
    }
    if (nX2 > xScrn) {
	nX2 = xScrn;
    }
    if (nY2 > yScrn) {
	nY2 = yScrn;
    }

    nWidth = nX2 - nX;
    nHeight = nY2 - nY;

    /* Create a bitmap compatible with the screen DC. */
    hBitmap = CreateCompatibleBitmap(hScrDC, nWidth, nHeight);

    /* Select new bitmap into memory DC. */
    hOldBitmap = SelectObject(hMemDC, hBitmap);

    /* Bitblt screen DC to memory DC. */
    BitBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, nX, nY, SRCCOPY);

    /*
     * Select old bitmap back into memory DC and get handle to bitmap of the
     * screen.
     */

    hBitmap = SelectObject(hMemDC, hOldBitmap);

    /* Clean up. */

    DeleteDC(hScrDC);
    DeleteDC(hMemDC);

    /* Return handle to the bitmap. */

    return (HBITMAP)hBitmap;
}

/*
 *----------------------------------------------------------------------
 *
 * BitmapToDIB--
 *
 *	Converts bitmap to DIB.
 *
 * Results:
 *	Bitmap converted.
 *
 *----------------------------------------------------------------------
 */

static HANDLE BitmapToDIB(
    HBITMAP hBitmap,
    HPALETTE hPal)
{
    BITMAP              bm;
    BITMAPINFOHEADER    bi;
    LPBITMAPINFOHEADER  lpbi;
    DWORD               dwLen;
    HANDLE              hDIB;
    HANDLE              h;
    HDC                 hDC;
    WORD                biBits;

    /* Check if bitmap handle is valid. */

    if (!hBitmap) {
	return NULL;
    }

    /* Fill in BITMAP structure, return NULL if it didn't work. */

    if (!GetObjectW(hBitmap, sizeof(bm), (LPWSTR)&bm)) {
	return NULL;
    }

    /* Ff no palette is specified, use default palette. */

    if (hPal == NULL) {
	hPal = (HPALETTE)GetStockObject(DEFAULT_PALETTE);
    }

    /* Calculate bits per pixel. */

    biBits = bm.bmPlanes * bm.bmBitsPixel;

    /* Make sure bits per pixel is valid. */

    if (biBits <= 1) {
	biBits = 1;
    } else if (biBits <= 4) {
	biBits = 4;
    } else if (biBits <= 8) {
	biBits = 8;
    } else { /* If greater than 8-bit, force to 24-bit. */
	biBits = 24;
    }

    /* Initialize BITMAPINFOHEADER. */

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bm.bmWidth;
    bi.biHeight = bm.bmHeight;
    bi.biPlanes = 1;
    bi.biBitCount = biBits;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    /* Calculate size of memory block required to store BITMAPINFO. */

    dwLen = bi.biSize + DIBNumColors(&bi) * sizeof(RGBQUAD);

    /* Get a DC. */

    hDC = GetDC(NULL);

    /* Select and realize our palette. */

    hPal = SelectPalette(hDC, hPal, FALSE);
    RealizePalette(hDC);

    /* Alloc memory block to store our bitmap. */

    hDIB = GlobalAlloc(GHND, dwLen);

    /* If we couldn't get memory block. */

    if (!hDIB) {
	/* clean up and return NULL. */

	SelectPalette(hDC, hPal, TRUE);
	RealizePalette(hDC);
	ReleaseDC(NULL, hDC);
	return NULL;
    }

    /* Lock memory and get pointer to it. */

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    /* Use our bitmap info. to fill BITMAPINFOHEADER. */

    *lpbi = bi;

    /* Call GetDIBits with a NULL lpBits param, so it will calculate the
     * biSizeImage field for us
     */

    GetDIBits(hDC, hBitmap, 0, (UINT)bi.biHeight, NULL, (LPBITMAPINFO)lpbi,
	    DIB_RGB_COLORS);

    /* get the info. returned by GetDIBits and unlock memory block. */

    bi = *lpbi;
    GlobalUnlock(hDIB);

    /* If the driver did not fill in the biSizeImage field, make one up. */
    if (bi.biSizeImage == 0) {
	bi.biSizeImage = (((((DWORD)bm.bmWidth * biBits) + 31) / 32) * 4)
		* bm.bmHeight;
    }

    /* Realloc the buffer big enough to hold all the bits. */

    dwLen = bi.biSize + DIBNumColors(&bi) * sizeof(RGBQUAD) + bi.biSizeImage;

    if ((h = GlobalReAlloc(hDIB, dwLen, 0)) != 0) {
	hDIB = h;
    } else {
	/* Clean up and return NULL. */

	GlobalFree(hDIB);
	SelectPalette(hDC, hPal, TRUE);
	RealizePalette(hDC);
	ReleaseDC(NULL, hDC);
	return NULL;
    }

    /* Lock memory block and get pointer to it. */

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    /* Call GetDIBits with a NON-NULL lpBits param, and actualy get the
     * bits this time.
     */

    if (GetDIBits(hDC, hBitmap, 0, (UINT)bi.biHeight, (LPSTR)lpbi +
	    (WORD)lpbi->biSize + DIBNumColors(lpbi) * sizeof(RGBQUAD),
	    (LPBITMAPINFO)lpbi, DIB_RGB_COLORS) == 0) {
	/* Clean up and return NULL. */

	GlobalUnlock(hDIB);
	SelectPalette(hDC, hPal, TRUE);
	RealizePalette(hDC);
	ReleaseDC(NULL, hDC);
	return NULL;
    }

    bi = *lpbi;

    /* Clean up. */
    GlobalUnlock(hDIB);
    SelectPalette(hDC, hPal, TRUE);
    RealizePalette(hDC);
    ReleaseDC(NULL, hDC);

    /* Return handle to the DIB. */
    return hDIB;
}

/*
 *----------------------------------------------------------------------
 *
 * CopyScreenToDIB--
 *
 *	Copies screen to DIB.
 *
 * Results:
 *	Screen copied.
 *
 *----------------------------------------------------------------------
 */

static HANDLE CopyScreenToDIB(
    LPRECT lpRect)
{
    HBITMAP     hBitmap;
    HPALETTE    hPalette;
    HANDLE      hDIB;

    /*
     * Get the device-dependent bitmap in lpRect by calling CopyScreenToBitmap
     * and passing it the rectangle to grab.
     */

    hBitmap = CopyScreenToBitmap(lpRect);

    /* Check for a valid bitmap handle. */

    if (!hBitmap) {
	return NULL;
    }

    /* Get the current palette. */

    hPalette = GetSystemPalette();

    /* convert the bitmap to a DIB. */

    hDIB = BitmapToDIB(hBitmap, hPalette);

    /* Clean up. */

    DeleteObject(hPalette);
    DeleteObject(hBitmap);

    /* Return handle to the packed-DIB. */
    return hDIB;
}

/*
 *----------------------------------------------------------------------
 *
 * GetSystemPalette--
 *
 *	Obtains the system palette.
 *
 * Results:
 *	Returns palette.
 *
 *----------------------------------------------------------------------
 */

static HPALETTE GetSystemPalette(void)
{
    HDC hDC;                /* Handle to a DC. */
    static HPALETTE hPal = NULL;   /* Handle to a palette. */
    HANDLE hLogPal;         /* Handle to a logical palette. */
    LPLOGPALETTE lpLogPal;  /* Pointer to a logical palette. */
    int nColors;            /* Number of colors. */

    /* Find out how many palette entries we want.. */

    hDC = GetDC(NULL);
    if (!hDC) {
	return NULL;
    }

    nColors = PalEntriesOnDevice(hDC);   /* Number of palette entries. */

    /* Allocate room for the palette and lock it.. */

    hLogPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) + nColors *
	    sizeof(PALETTEENTRY));
    if (!hLogPal) {
	/* If we didn't get a logical palette, return NULL. */

	return NULL;
    }

    /* get a pointer to the logical palette. */

    lpLogPal = (LPLOGPALETTE)GlobalLock(hLogPal);

    /* Set some important fields. */

    lpLogPal->palVersion = 0x300;
    lpLogPal->palNumEntries = nColors;

    /* Copy the current system palette into our logical palette. */

    GetSystemPaletteEntries(hDC, 0, nColors,
	    (LPPALETTEENTRY) lpLogPal->palPalEntry);

    /*
     * Go ahead and create the palette.  Once it's created, we no longer need
     * the LOGPALETTE, so free it.
     */

    hPal = CreatePalette(lpLogPal);

    /* Clean up. */

    GlobalUnlock(hLogPal);
    GlobalFree(hLogPal);
    ReleaseDC(NULL, hDC);

    return hPal;
}

/*
 *----------------------------------------------------------------------
 *
 * PalEntriesOnDevice--
 *
 *	Returns the palettes on the device.
 *
 * Results:
 *	Returns palettes.
 *
 *----------------------------------------------------------------------
 */

static int PalEntriesOnDevice(
    HDC hDC)
{
    return (1 << (GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES)));
}

/*
 * --------------------------------------------------------------------------
 *
 * Winprint_Init--
 *
 *	Initializes printing module on Windows.
 *
 * Results:
 *	Module initialized.
 *
 * -------------------------------------------------------------------------
 */

int Winprint_Init(
    Tcl_Interp * interp)
{
    size_t i;
    Tcl_Namespace *namespacePtr;
    static const char *gdiName = "::tk::print::_gdi";
    static const size_t numCommands =
	    sizeof(gdi_commands) / sizeof(struct gdi_command);

    /*
     * Set up the low-level [_gdi] command.
     */

    namespacePtr = Tcl_CreateNamespace(interp, gdiName,
	    NULL, (Tcl_NamespaceDeleteProc *) NULL);
    for (i=0; i<numCommands; i++) {
	char buffer[100];

	snprintf(buffer, sizeof(buffer), "%s::%s", gdiName, gdi_commands[i].command_string);
	Tcl_CreateObjCommand2(interp, buffer, gdi_commands[i].command,
		NULL, (Tcl_CmdDeleteProc *) 0);
	Tcl_Export(interp, namespacePtr, gdi_commands[i].command_string, 0);
    }
    Tcl_CreateEnsemble(interp, gdiName, namespacePtr, 0);

    /*
     * The other printing-related commands.
     */

    Tcl_CreateObjCommand2(interp, "::tk::print::_selectprinter",
	    PrintSelectPrinter, NULL, NULL);
    Tcl_CreateObjCommand2(interp, "::tk::print::_openprinter",
	    PrintOpenPrinter, NULL, NULL);
    Tcl_CreateObjCommand2(interp, "::tk::print::_closeprinter",
	    PrintClosePrinter, NULL, NULL);
    Tcl_CreateObjCommand2(interp, "::tk::print::_opendoc",
	    PrintOpenDoc, NULL, NULL);
    Tcl_CreateObjCommand2(interp, "::tk::print::_closedoc",
	    PrintCloseDoc, NULL, NULL);
    Tcl_CreateObjCommand2(interp, "::tk::print::_openpage",
	    PrintOpenPage, NULL, NULL);
    Tcl_CreateObjCommand2(interp, "::tk::print::_closepage",
	    PrintClosePage, NULL, NULL);
    return TCL_OK;
}

/* Print API functions. */

/*----------------------------------------------------------------------
 *
 * PrintSelectPrinter--
 *
 *	Main dialog for selecting printer and initializing data for print job.
 *
 * Results:
 *	Printer selected.
 *
 *----------------------------------------------------------------------
 */

static int PrintSelectPrinter(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    TCL_UNUSED(Tcl_Size),
    TCL_UNUSED(Tcl_Obj* const*))
{
    LPCWSTR printerName = NULL;
    PDEVMODEW returnedDevmode = NULL;
    PDEVMODEW localDevmode = NULL;

    copies = 0;
    paper_width = 0;
    paper_height = 0;
    dpi_x = 0;
    dpi_y = 0;

    /* Set up print dialog and initalize property structure. */

    memset(&pd, 0, sizeof(pd));
    pd.lStructSize = sizeof(pd);
    pd.hwndOwner = GetDesktopWindow();
    pd.Flags = PD_HIDEPRINTTOFILE | PD_DISABLEPRINTTOFILE | PD_NOSELECTION;

    if (PrintDlgW(&pd) == TRUE) {

	/*Get document info.*/
	memset(&di, 0, sizeof(di));
	di.cbSize = sizeof(di);
	di.lpszDocName = L"Tk Print Output";

	/* Copy print attributes to local structure. */
	returnedDevmode = (PDEVMODEW) GlobalLock(pd.hDevMode);
	devnames = (LPDEVNAMES) GlobalLock(pd.hDevNames);
	printerName = (LPCWSTR) devnames + devnames->wDeviceOffset;
	localDevmode = (LPDEVMODEW) HeapAlloc(GetProcessHeap(),
		HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS,
		returnedDevmode->dmSize);

	if (localDevmode != NULL) {
	    memcpy((LPVOID)localDevmode, (LPVOID)returnedDevmode,
		    returnedDevmode->dmSize);

	    /* Get values from user-set and built-in properties. */
	    localPrinterName = localDevmode->dmDeviceName;
	    dpi_y = localDevmode->dmYResolution;
	    dpi_x = localDevmode->dmPrintQuality;
	    /* Convert height and width to logical points. */
	    paper_height = (int) localDevmode->dmPaperLength / 0.254;
	    paper_width = (int) localDevmode->dmPaperWidth / 0.254;
	    copies = pd.nCopies;
	    /* Set device context here for all GDI printing operations. */
	    printDC = CreateDCW(L"WINSPOOL", printerName, NULL, localDevmode);
	} else {
	    localDevmode = NULL;
	}
    } else {
	unsigned int errorcode = CommDlgExtendedError();

	/*
	 * The user cancelled, or there was an error
	 * The code on the Tcl side checks if the variable
	 * ::tk::print::printer_name is defined to determine
	 * that a valid selection was made.
	 * So we better unset this here, unconditionally.
	 */
	Tcl_UnsetVar(interp, "::tk::print::printer_name", 0);
	if (errorcode != 0) {
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf("print failed: error %04x",
		    errorcode));
	    Tcl_SetErrorCode(interp, "TK", "PRINT", "DIALOG", (char*)NULL);
	    return TCL_ERROR;
	}
	return TCL_OK;
    }

    if (pd.hDevMode != NULL) {
	GlobalFree(pd.hDevMode);
    }

    /*
     * Store print properties in variables so they can be accessed from
     * script level.
     */
    if (localPrinterName != NULL) {
	char *prname;
	int size_needed = WideCharToMultiByte(CP_UTF8, 0, localPrinterName,
		-1, NULL, 0, NULL, NULL);

	prname = (char*)ckalloc(size_needed);
	WideCharToMultiByte(CP_UTF8, 0, localPrinterName, -1, prname,
		size_needed, NULL, NULL);
	Tcl_SetVar2Ex(interp, "::tk::print::printer_name", NULL,
		Tcl_NewStringObj(prname, size_needed - 1), 0);
	Tcl_SetVar2Ex(interp, "::tk::print::copies", NULL,
		Tcl_NewIntObj(copies), 0);
	Tcl_SetVar2Ex(interp, "::tk::print::dpi_x", NULL,
		Tcl_NewIntObj(dpi_x), 0);
	Tcl_SetVar2Ex(interp, "::tk::print::dpi_y", NULL,
		Tcl_NewIntObj(dpi_y), 0);
	Tcl_SetVar2Ex(interp, "::tk::print::paper_width", NULL,
		Tcl_NewIntObj(paper_width), 0);
	Tcl_SetVar2Ex(interp, "::tk::print::paper_height", NULL,
		Tcl_NewIntObj(paper_height), 0);
	ckfree(prname);
    }

    return TCL_OK;
}

/*
 * --------------------------------------------------------------------------
 *
 * PrintOpenPrinter--
 *
 *	Open the given printer.
 *
 * Results:
 *	Opens the selected printer.
 *
 * -------------------------------------------------------------------------
 */

int PrintOpenPrinter(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    Tcl_Size objc,
    Tcl_Obj *const objv[])
{
    Tcl_DString ds;

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "printer");
	return TCL_ERROR;
    }

    /*Start an individual page.*/
    if (StartPage(printDC) <= 0) {
	return TCL_ERROR;
    }

    const char *printer = Tcl_GetString(objv[1]);

    if (printDC == NULL) {
	Tcl_AppendResult(interp, "unable to establish device context", (char *)NULL);
	return TCL_ERROR;
    }

    Tcl_DStringInit(&ds);
    if ((OpenPrinterW(Tcl_UtfToWCharDString(printer, -1, &ds),
	    (LPHANDLE)&printDC, NULL)) == FALSE) {
	Tcl_AppendResult(interp, "unable to open printer", (char *)NULL);
	Tcl_DStringFree(&ds);
	return TCL_ERROR;
    }

    Tcl_DStringFree(&ds);
    return TCL_OK;
}

/*
 * --------------------------------------------------------------------------
 *
 * PrintClosePrinter--
 *
 *	Closes the given printer.
 *
 * Results:
 *	Printer closed.
 *
 * -------------------------------------------------------------------------
 */

int PrintClosePrinter(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    TCL_UNUSED(Tcl_Size),
    TCL_UNUSED(Tcl_Obj *const *))
{
    if (printDC == NULL) {
	Tcl_AppendResult(interp, "unable to establish device context", (char *)NULL);
	return TCL_ERROR;
    }

    ClosePrinter(printDC);
    return TCL_OK;
}

/*
 * --------------------------------------------------------------------------
 *
 * PrintOpenDoc--
 *
 *     Opens the document for printing.
 *
 * Results:
 *      Opens the print document.
 *
 * -------------------------------------------------------------------------
 */

int PrintOpenDoc(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    TCL_UNUSED(Tcl_Size),
    TCL_UNUSED(Tcl_Obj *const *))
{
    int output = 0;

    if (printDC == NULL) {
	Tcl_AppendResult(interp, "unable to establish device context", (char *)NULL);
	return TCL_ERROR;
    }

    /*
     * Start printing.
     */
    output = StartDocW(printDC, &di);
    if (output <= 0) {
	Tcl_AppendResult(interp, "unable to start document", (char *)NULL);
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 * --------------------------------------------------------------------------
 *
 * PrintCloseDoc--
 *
 *	Closes the document for printing.
 *
 * Results:
 *	Closes the print document.
 *
 * -------------------------------------------------------------------------
 */

int PrintCloseDoc(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    TCL_UNUSED(Tcl_Size),
    TCL_UNUSED(Tcl_Obj *const *))
{
    if (printDC == NULL) {
	Tcl_AppendResult(interp, "unable to establish device context", (char *)NULL);
	return TCL_ERROR;
    }

    if (EndDoc(printDC) <= 0) {
	Tcl_AppendResult(interp, "unable to establish close document", (char *)NULL);
	return TCL_ERROR;
    }
    DeleteDC(printDC);
    return TCL_OK;
}

/*
 * --------------------------------------------------------------------------
 *
 * PrintOpenPage--
 *
 *    Opens a page for printing.
 *
 * Results:
 *      Opens the print page.
 *
 * -------------------------------------------------------------------------
 */

int PrintOpenPage(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    TCL_UNUSED(Tcl_Size),
    TCL_UNUSED(Tcl_Obj *const *))
{
    if (printDC == NULL) {
	Tcl_AppendResult(interp, "unable to establish device context", (char *)NULL);
	return TCL_ERROR;
    }

    /*Start an individual page.*/
    if (StartPage(printDC) <= 0) {
	Tcl_AppendResult(interp, "unable to start page", (char *)NULL);
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 * --------------------------------------------------------------------------
 *
 * PrintClosePage--
 *
 *	Closes the printed page.
 *
 * Results:
 *	Closes the page.
 *
 * -------------------------------------------------------------------------
 */

int PrintClosePage(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,
    TCL_UNUSED(Tcl_Size),
    TCL_UNUSED(Tcl_Obj *const *))
{
    if (printDC == NULL) {
	Tcl_AppendResult(interp, "unable to establish device context", (char *)NULL);
	return TCL_ERROR;
    }

    if (EndPage(printDC) <= 0) {
	Tcl_AppendResult(interp, "unable to close page", (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */