
/*
 * bltWinPrnt.c --
 *
 *	This module implements Win32 printer access.
 *
 * Copyright 1998 by Bell Labs Innovations for Lucent Technologies.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and warranty
 * disclaimer appear in supporting documentation, and that the names
 * of Lucent Technologies any of their entities not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 *
 * Lucent Technologies disclaims all warranties with regard to this
 * software, including all implied warranties of merchantability and
 * fitness.  In no event shall Lucent Technologies be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether in
 * an action of contract, negligence or other tortuous action, arising
 * out of or in connection with the use or performance of this
 * software.
 *
 */

#include <bltInt.h>
#include <bltHash.h>
#ifndef NO_PRINTER
#include <X11/Xutil.h>
#undef Status
#if defined(_MSC_VER) || defined(__BORLANDC__)
#include <winspool.h>
#endif /* _MSC_VER || __BORLANDC__ */

/*
  set pid [printer open name]
  printer close $pid
  printer write $pid $data
  printer snap $pid .window
  printer names
  printer enum things
  printer getattr $pid
  printer setattr $pid

  set pid [open ]
  blt::printer open {\\alprint\2a211} p1
  p1 getattr varName
  p1 setattr varName
  p1 write $data
  .graph print p1
  p1 snap .window
  p1 close
  blt::printer names
  blt::printer emum things
*/

#define PRINTER_THREAD_KEY	"BLT Printer Data"

typedef struct {
    Blt_HashTable printerTable;	/* Hash table of printer structures keyed by 
				 * the name of the printer. */
    int nextId;
} PrinterInterpData;

typedef struct {
    int type;
    HDC hDC;
} PrintDrawable;

typedef struct {
    Tcl_Interp *interp;
    Tcl_Command cmdToken;	/* Token for vector's Tcl command. */
    char *name;
    char *fileName;
    PrintDrawable drawable;
    HANDLE hPrinter;
    Blt_HashEntry *hashPtr;
    Blt_HashTable *tablePtr;
    char *driverName;
    char *deviceName;
    char *printerName;
    char *docName;
    char *portName;
    DEVMODEA *dmPtr;
    int dmSize;
} PrintQueue;

typedef struct {
    LONG token;
    char *string;
} TokenString;

static TokenString sizeTable[] =
{
 /* Letter 8 1/2 x 11 in */
    { DMPAPER_LETTER, "Letter" },
 /* Letter Small 8 1/2 x 11 in */
    { DMPAPER_LETTERSMALL, "Letter Small" },
 /* Tabloid 11 x 17 in */
    { DMPAPER_TABLOID, "Tabloid" },
 /* Ledger 17 x 11 in */
    { DMPAPER_LEDGER, "Ledger" },
 /* Legal 8 1/2 x 14 in */
    { DMPAPER_LEGAL, "Legal" },
 /* Statement 5 1/2 x 8 1/2 in */
    { DMPAPER_STATEMENT, "Statement" },
 /* Executive 7 1/4 x 10 1/2 in */
    { DMPAPER_EXECUTIVE, "Executive" },
 /* A3 297 x 420 mm */
    { DMPAPER_A3, "A3" },
 /* A4 210 x 297 mm */
    { DMPAPER_A4, "A4" },
 /* A4 Small 210 x 297 mm */
    { DMPAPER_A4SMALL, "A4 Small" },
 /* A5 148 x 210 mm */
    { DMPAPER_A5, "A5" },
 /* B4 (JIS) 250 x 354 */
    { DMPAPER_B4, "B4 (JIS)" },
 /* B5 (JIS) 182 x 257 mm */
    { DMPAPER_B5, "B5 (JIS)" },
 /* Folio 8 1/2 x 13 in */
    { DMPAPER_FOLIO, "Folio" },
 /* Quarto 215 x 275 mm */
    { DMPAPER_QUARTO, "Quarto" },
 /* 10x14 in */
    { DMPAPER_10X14, "10x14" },
 /* 11x17 in */
    { DMPAPER_11X17, "11x17" },
 /* Note 8 1/2 x 11 in */
    { DMPAPER_NOTE, "Note" },
 /* Envelope #9 3 7/8 x 8 7/8 */
    { DMPAPER_ENV_9, "Envelope #9" },
 /* Envelope #10 4 1/8 x 9 1/2 */
    { DMPAPER_ENV_10, "Envelope #10" },
 /* Envelope #11 4 1/2 x 10 3/8 */
    { DMPAPER_ENV_11, "Envelope #11" },
 /* Envelope #12 4 \276 x 11 */
    { DMPAPER_ENV_12, "Envelope #12" },
 /* Envelope #14 5 x 11 1/2 */
    { DMPAPER_ENV_14, "Envelope #14" },
 /* C size sheet */
    { DMPAPER_CSHEET, "C size sheet" },
 /* D size sheet */
    { DMPAPER_DSHEET, "D size sheet" },
 /* E size sheet */
    { DMPAPER_ESHEET, "E size sheet" },
 /* Envelope DL 110 x 220mm */
    { DMPAPER_ENV_DL, "Envelope DL" },
 /* Envelope C5 162 x 229 mm */
    { DMPAPER_ENV_C5, "Envelope C5" },
 /* Envelope C3  324 x 458 mm */
    { DMPAPER_ENV_C3, "Envelope C3" },
 /* Envelope C4  229 x 324 mm */
    { DMPAPER_ENV_C4, "Envelope C4" },
 /* Envelope C6  114 x 162 mm */
    { DMPAPER_ENV_C6, "Envelope C6" },
 /* Envelope C65 114 x 229 mm */
    { DMPAPER_ENV_C65, "Envelope C65" },
 /* Envelope B4  250 x 353 mm */
    { DMPAPER_ENV_B4, "Envelope B4" },
 /* Envelope B5  176 x 250 mm */
    { DMPAPER_ENV_B5, "Envelope B5" },
 /* Envelope B6  176 x 125 mm */
    { DMPAPER_ENV_B6, "Envelope B6" },
 /* Envelope 110 x 230 mm */
    { DMPAPER_ENV_ITALY, "Envelope Italy" },
 /* Env Monarch 3 7/8 x 7 1/2 in */
    { DMPAPER_ENV_MONARCH, "Envelope Monarch" },
 /* 6 3/4 Envelope 3 5/8 x 6 1/2 in */
    { DMPAPER_ENV_PERSONAL, "6 3/4 Envelope" },
 /* US Std Fanfold 14 7/8 x 11 in */
    { DMPAPER_FANFOLD_US, "US Std Fanfold" },
 /* German Std Fanfold 8 1/2 x 12 in */
    { DMPAPER_FANFOLD_STD_GERMAN, "German Std Fanfold" },
 /* German Legal Fanfold 8 1/2 x 13 in */
    { DMPAPER_FANFOLD_LGL_GERMAN, "German Legal Fanfold" },
 /* B4 (ISO) 250 x 353 mm */
    { DMPAPER_ISO_B4, "ISOB4" },
 /* Japanese Postcard 100 x 148 mm */
    { DMPAPER_JAPANESE_POSTCARD, "Postcard (JIS)" },
 /* 9 x 11 in */
    { DMPAPER_9X11, "9x11" },
 /* 10 x 11 in */
    { DMPAPER_10X11, "10x11" },
 /* 15 x 11 in */
    { DMPAPER_15X11, "15x11" },
 /* Envelope Invite 220 x 220 mm */
    { DMPAPER_ENV_INVITE, "Envelope Invite" },
 /* Letter Extra 9 \275 x 12 in */
    { DMPAPER_LETTER_EXTRA, "Letter Extra" },
 /* Legal Extra 9 \275 x 15 in */
    { DMPAPER_LEGAL_EXTRA, "Legal Extra" },
 /* Tabloid Extra 11.69 x 18 in */
    { DMPAPER_TABLOID_EXTRA, "Tabloid Extra" },
 /* A4 Extra 9.27 x 12.69 in */
    { DMPAPER_A4_EXTRA, "A4 Extra" },
 /* Letter Transverse 8 \275 x 11 in */
    { DMPAPER_LETTER_TRANSVERSE, "Letter Transverse" },
 /* A4 Transverse 210 x 297 mm */
    { DMPAPER_A4_TRANSVERSE, "A4 Transverse" },
 /* Letter Extra Transverse 9\275 x 12 in */
    { DMPAPER_LETTER_EXTRA_TRANSVERSE, "Letter Extra Transverse" },
 /* SuperA/SuperA/A4 227 x 356 mm */
    { DMPAPER_A_PLUS, "Super A Plus" },
 /* SuperB/SuperB/A3 305 x 487 mm */
    { DMPAPER_B_PLUS, "Super B Plus" },
 /* Letter Plus 8.5 x 12.69 in */
    { DMPAPER_LETTER_PLUS, "Letter Plus" },
 /* A4 Plus 210 x 330 mm */
    { DMPAPER_A4_PLUS, "A4 Plus" },
 /* A5 Transverse 148 x 210 mm */
    { DMPAPER_A5_TRANSVERSE, "A5 Transverse" },
 /* B5 (JIS) Transverse 182 x 257 mm */
    { DMPAPER_B5_TRANSVERSE, "B5 Transverse" },
 /* A3 Extra 322 x 445 mm */
    { DMPAPER_A3_EXTRA, "A3 Extra" },
 /* A5 Extra 174 x 235 mm */
    { DMPAPER_A5_EXTRA, "A5 Extra" },
 /* B5 (ISO) Extra 201 x 276 mm */
    { DMPAPER_B5_EXTRA, "B5 Extra" },
 /* A2 420 x 594 mm */
    { DMPAPER_A2, "A2" },
 /* A3 Transverse 297 x 420 mm */
    { DMPAPER_A3_TRANSVERSE, "A3 Transverse" },
 /* A3 Extra Transverse 322 x 445 mm   */
    { DMPAPER_A3_EXTRA_TRANSVERSE, "A3 Extra Transverse" },
    { 0, NULL }
};

static TokenString statusTable[] =
{
    { PRINTER_STATUS_BUSY, "Busy" },
    { PRINTER_STATUS_DOOR_OPEN, "Door Open" },
    { PRINTER_STATUS_ERROR, "Error" },
    { PRINTER_STATUS_INITIALIZING, "Initializing" },
    { PRINTER_STATUS_IO_ACTIVE, "IO Active" },
    { PRINTER_STATUS_MANUAL_FEED, "Manual Feed" },
    { PRINTER_STATUS_NOT_AVAILABLE, "Not Available" },
    { PRINTER_STATUS_NO_TONER, "No Toner" },
    { PRINTER_STATUS_OFFLINE, "Offline" },
    { PRINTER_STATUS_OUTPUT_BIN_FULL, "Bin Full" },
    { PRINTER_STATUS_OUT_OF_MEMORY, "Out Of Memory" },
    { PRINTER_STATUS_PAGE_PUNT, "Page Punt" },
    { PRINTER_STATUS_PAPER_JAM, "Paper Jam" },
    { PRINTER_STATUS_PAPER_OUT, "Paper Out" },
    { PRINTER_STATUS_PAPER_PROBLEM, "Paper Problem" },
    { PRINTER_STATUS_PAUSED, "Paused" },
    { PRINTER_STATUS_PENDING_DELETION, "Pending Deletion" },
    { PRINTER_STATUS_POWER_SAVE, "Power Save" },
    { PRINTER_STATUS_PRINTING, "Printing" },
    { PRINTER_STATUS_PROCESSING, "Processing" },
    { PRINTER_STATUS_SERVER_UNKNOWN, "Server Unknown" },
    { PRINTER_STATUS_TONER_LOW, "Toner Low" },
    { PRINTER_STATUS_USER_INTERVENTION, "User Intervention" },
    { PRINTER_STATUS_WAITING, "Waiting" },
    { PRINTER_STATUS_WARMING_UP, "Warming Up" },
    { 0, NULL }
};

static TokenString attributeTable[] =
{
    { PRINTER_ATTRIBUTE_DEFAULT, "Default" },
    { PRINTER_ATTRIBUTE_DIRECT, "Direct" },
    { PRINTER_ATTRIBUTE_DO_COMPLETE_FIRST, "Do Complete First" },
    { PRINTER_ATTRIBUTE_ENABLE_BIDI, "Enable BIDI" },
    { PRINTER_ATTRIBUTE_ENABLE_DEVQ, "Enable Devq" },
    { PRINTER_ATTRIBUTE_HIDDEN, "Hidden" },
    { PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS, "Keep Printed Jobs" },
    { PRINTER_ATTRIBUTE_LOCAL, "Local" },
    { PRINTER_ATTRIBUTE_NETWORK, "Network" },
    { PRINTER_ATTRIBUTE_QUEUED, "Queued" },
    { PRINTER_ATTRIBUTE_RAW_ONLY, "Raw Only" },
    { PRINTER_ATTRIBUTE_SHARED, "Shared" },
    { PRINTER_ATTRIBUTE_WORK_OFFLINE, "Offline" },
    { 0, NULL }
};

static TokenString binTable[] =
{
    { DMBIN_UPPER, "Upper" },
    { DMBIN_LOWER, "Lower" },
    { DMBIN_MIDDLE, "Middle" },
    { DMBIN_MANUAL, "Manual" },
    { DMBIN_ENVELOPE, "Envelope" },
    { DMBIN_ENVMANUAL, "Envelope Manual" },
    { DMBIN_AUTO, "Automatic" },
    { DMBIN_TRACTOR, "Tractor" },
    { DMBIN_SMALLFMT, "Small Format" },
    { DMBIN_LARGEFMT, "Large Format" },
    { DMBIN_LARGECAPACITY, "Large Capacity" },
    { DMBIN_CASSETTE, "Cassette" },
    { DMBIN_FORMSOURCE, "Form Source" },
    { 0, NULL }
};

static TokenString orientationTable[] =
{
    { DMORIENT_PORTRAIT, "Portrait" },
    { DMORIENT_LANDSCAPE, "Landscape" },
    { 0, NULL }
};

static TokenString qualityTable[] =
{
    { DMRES_HIGH, "High" },
    { DMRES_MEDIUM, "Medium" },
    { DMRES_LOW, "Low" },
    { DMRES_DRAFT, "Draft" },
    { 0, NULL }
};

static TokenString colorTable[] =
{
    { DMCOLOR_COLOR, "Color" },
    { DMCOLOR_MONOCHROME, "Monochrome" },
    { 0, NULL }
};

static TokenString duplexTable[] =
{
    { DMDUP_SIMPLEX, "Simplex" },
    { DMDUP_HORIZONTAL, "Horizontal" },
    { DMDUP_VERTICAL, "Vertical" },
    { 0, NULL }
};

static TokenString ttOptionTable[] =
{
    { DMTT_BITMAP, "Bitmap" },
    { DMTT_DOWNLOAD, "Download" },
    { DMTT_SUBDEV, "Substitute Device" },
    { DMTT_DOWNLOAD_OUTLINE, "Download Outline" },
    { 0, NULL }
};

static Tcl_ObjCmdProc PrinterCmd;
static Tcl_InterpDeleteProc PrinterInterpDeleteProc;

void
Blt_GetPrinterScale(HDC printerDC, double *xRatioPtr, double *yRatioPtr)
{
    double xScreen, yScreen;
    double xPrinter, yPrinter;
    HDC screenDC;

    xPrinter = (double)GetDeviceCaps(printerDC, LOGPIXELSX);
    yPrinter = (double)GetDeviceCaps(printerDC, LOGPIXELSY);
    screenDC = GetDC(NULL);
    xScreen = (double)GetDeviceCaps(screenDC, LOGPIXELSX);
    yScreen = (double)GetDeviceCaps(screenDC, LOGPIXELSY);
    ReleaseDC(NULL, screenDC);
    *xRatioPtr = (xPrinter / xScreen);
    *yRatioPtr = (yPrinter / yScreen);
}

static PrinterInterpData *
GetPrinterInterpData(Tcl_Interp *interp)
{
    PrinterInterpData *dataPtr;
    Tcl_InterpDeleteProc *proc;

    dataPtr = (PrinterInterpData *)
	Tcl_GetAssocData(interp, PRINTER_THREAD_KEY, &proc);
    if (dataPtr == NULL) {
	dataPtr = Blt_Malloc(sizeof(PrinterInterpData));
	dataPtr->nextId = 0;
	assert(dataPtr);
	Tcl_SetAssocData(interp, PRINTER_THREAD_KEY, PrinterInterpDeleteProc,
		dataPtr);
	Blt_InitHashTable(&dataPtr->printerTable, BLT_STRING_KEYS);
    }
    return dataPtr;
}

static int
GetQueue(
    Tcl_Interp *interp,
    const char *name,
    PrintQueue **queuePtrPtr)
{
    Blt_HashEntry *hPtr;
    PrinterInterpData *dataPtr;

    dataPtr = GetPrinterInterpData(interp);
    hPtr = Blt_FindHashEntry(&dataPtr->printerTable, name);
    if (hPtr == NULL) {
	Tcl_AppendResult(interp, "can't find printer \"", name, "\"",
	    (char *)NULL);
	return TCL_ERROR;
    }
    *queuePtrPtr = (PrintQueue *)Blt_GetHashValue(hPtr);
    return TCL_OK;
}

static int
GetQueueFromObj(
    Tcl_Interp *interp,
    Tcl_Obj *objPtr,
    PrintQueue **queuePtrPtr)
{
    return GetQueue(interp, Tcl_GetString(objPtr), queuePtrPtr);
}

static void
CloseQueue(
    PrintQueue *queuePtr)
{
    ClosePrinter(queuePtr->hPrinter);
    queuePtr->hPrinter = NULL;
}

static int
OpenQueue(
    Tcl_Interp *interp,
    PrintQueue *queuePtr)
{
    PRINTER_DEFAULTSA pd;
    HANDLE hPrinter;

    ZeroMemory(&pd, sizeof(pd));
    pd.DesiredAccess = PRINTER_ALL_ACCESS;
    if (!OpenPrinterA(queuePtr->printerName, &hPrinter, &pd)) {
        Tcl_AppendResult(interp, "can't open printer \"", 
		queuePtr->printerName, "\": ", Blt_LastError(), (char *)NULL);
	queuePtr->hPrinter = NULL;
	return TCL_ERROR;
    }
    queuePtr->hPrinter = hPrinter;
    return TCL_OK;
}

static HGLOBAL
GetQueueProperties(
    PrintQueue *queuePtr,
    DEVMODEA **dmPtrPtr)
{
    HWND hWnd;
    unsigned int dmSize;
    HGLOBAL hMem;
    DEVMODEA *dmPtr;

    hWnd = GetDesktopWindow();
    dmSize = DocumentPropertiesA(hWnd, queuePtr->hPrinter, 
	queuePtr->printerName, NULL, NULL, 0);
    if (dmSize == 0) {
	Tcl_AppendResult(queuePtr->interp,
		"can't get document properties for \"", 
		queuePtr->printerName,
		"\": ", Blt_LastError(), (char *)NULL);
	return NULL;
    }
    hMem = GlobalAlloc(GHND, dmSize);
    dmPtr = (DEVMODEA *)GlobalLock(hMem);
    if (!DocumentPropertiesA(hWnd, queuePtr->hPrinter, queuePtr->printerName, 
	dmPtr, NULL, DM_OUT_BUFFER)) {
	Tcl_AppendResult(queuePtr->interp,
		"can't allocate document properties for \"",
		queuePtr->printerName, "\": ", Blt_LastError(), 
		(char *)NULL);
	GlobalUnlock(hMem);
	GlobalFree(hMem);
	return NULL;
    }
    *dmPtrPtr = dmPtr;
    queuePtr->dmSize = dmSize;
    return hMem;
}

static int
SetQueueProperties(
    Tcl_Interp *interp, 
    PrintQueue *queuePtr,
    DEVMODEA *dmPtr)
{
    HWND hWnd;
    int result;

    hWnd = GetDesktopWindow();
    result = DocumentPropertiesA(hWnd, queuePtr->hPrinter, 
	queuePtr->printerName, dmPtr, dmPtr, DM_IN_BUFFER | DM_OUT_BUFFER);
    if (result == 0) {
	Tcl_AppendResult(interp, "can't set document properties for \"", 
	    queuePtr->printerName, "\": ", Blt_LastError(), (char *)NULL);
	return TCL_ERROR;
    } 
    if (queuePtr->dmPtr != NULL) {
	Blt_Free(queuePtr->dmPtr);
    }
    queuePtr->dmPtr = Blt_Malloc(queuePtr->dmSize);
    *queuePtr->dmPtr = *dmPtr;
    return TCL_OK;
}

static void
DestroyQueue(PrintQueue *queuePtr)
{
    if (queuePtr->drawable.hDC != NULL) {
	DeleteDC(queuePtr->drawable.hDC);
    }
    if (queuePtr->printerName != NULL) {
	Blt_Free(queuePtr->printerName);
    }
    if (queuePtr->deviceName != NULL) {
	Blt_Free(queuePtr->deviceName);
    }
    if (queuePtr->portName != NULL) {
	Blt_Free(queuePtr->portName);
    }
    if (queuePtr->driverName != NULL) {
	Blt_Free(queuePtr->driverName);
    }
    if (queuePtr->hashPtr != NULL) {
	Blt_DeleteHashEntry(queuePtr->tablePtr, queuePtr->hashPtr);
    }
    if (queuePtr->dmPtr != NULL) {
	Blt_Free(queuePtr->dmPtr);
    }
    Blt_Free(queuePtr);
}

static char *
AttributesToString(DWORD attributes, Tcl_DString * resultPtr)
{
    register TokenString *p;

    Tcl_DStringInit(resultPtr);
    for (p = attributeTable; p->string != NULL; p++) {
	if (attributes & p->token) {
	    Tcl_DStringAppendElement(resultPtr, p->string);
	}
    }
    return Tcl_DStringValue(resultPtr);
}

static char *
StatusToString(DWORD status, Tcl_DString * resultPtr)
{
    register TokenString *p;

    Tcl_DStringInit(resultPtr);
    for (p = statusTable; p->string != NULL; p++) {
	if (status & p->token) {
	    Tcl_DStringAppendElement(resultPtr, p->string);
	}
    }
    return Tcl_DStringValue(resultPtr);
}

static char *
TokenToString(TokenString *table, DWORD token)
{
    register TokenString *p;

    for (p = table; p->string != NULL; p++) {
	if (token == p->token) {
	    return p->string;
	}
    }
    return "???";
}

static DWORD
StringToToken(TokenString * table, char *string)
{
    register TokenString *p;
    int c;

    c = toupper(string[0]);
    for (p = table; p->string != NULL; p++) {
	if ((c == toupper(p->string[0])) &&
	    (strcasecmp(string, p->string) == 0)) {
	    return p->token;
	}
    }
    return 0;
}

static void
GetFormInfo(
    Tcl_Interp *interp,
    FORM_INFO_1A * infoArr,
    int nForms,
    char *varName)
{
    Tcl_DString dString;
    register int i;

    Tcl_DStringInit(&dString);
    for (i = 0; i < nForms; i++) {
	Tcl_DStringAppendElement(&dString, infoArr[i].pName);
    }
    Tcl_SetVar2(interp, varName, "EnumForms", Tcl_DStringValue(&dString),
	TCL_LEAVE_ERR_MSG);
    Tcl_DStringFree(&dString);
}


static int
GetPrinterAttributes(
    Tcl_Interp *interp,		/* Interpreter context. */
    PrintQueue *queuePtr,
    Tcl_Obj *objPtr)		/* Name of array variable to contain
				 * printer device information. */
{	
    char *string;
    Tcl_DString dString;
    DEVMODEA *dmPtr;
    DWORD bytesNeeded;
    HGLOBAL hMem1, hMem2;
    PRINTER_INFO_2A* pi2Ptr;
    LPVOID buffer;
    int result = TCL_ERROR;
    char *varName;

    if (OpenQueue(interp, queuePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    Tcl_DStringInit(&dString);
    hMem2 = NULL;

    GetPrinter(queuePtr->hPrinter, 2, NULL, 0, &bytesNeeded);

    /* Windows 95/98 seems to only want locked memory. Allocating
     * unlocked memory will sometimes crash the printer driver and
     * therefore Windows itself.  */

    hMem1 = GlobalAlloc(GHND, bytesNeeded);
    if (hMem1 == NULL) {
        Tcl_AppendResult(interp, "can't allocate memory for printer \"", 
		queuePtr->name, "\": ", Blt_LastError(), (char *)NULL);
	goto error;
    }
    buffer = (LPVOID)GlobalLock(hMem1);
    if (!GetPrinter(queuePtr->hPrinter, 2, buffer, bytesNeeded, 
	&bytesNeeded)) {
        Tcl_AppendResult(interp, "can't get printer \"", queuePtr->name, "\": ",
	    Blt_LastError(), (char *)NULL);
	goto error;
    }
    hMem2 = GetQueueProperties(queuePtr, &dmPtr);
    if (hMem2 == NULL) {
        Tcl_AppendResult(interp, "can't allocate memory for printer \"", 
		queuePtr->name, "\" properties: ", Blt_LastError(), (char *)NULL);
	goto error;
    }
    pi2Ptr = (PRINTER_INFO_2A *)buffer;
    varName = Tcl_GetString(objPtr);
    Tcl_SetVar2(interp, varName, "ServerName", pi2Ptr->pServerName, 0);
    Tcl_SetVar2(interp, varName, "PrinterName", pi2Ptr->pPrinterName, 0);
    Tcl_SetVar2(interp, varName, "PortName", pi2Ptr->pPortName, 0);
    Tcl_SetVar2(interp, varName, "DriverName", pi2Ptr->pDriverName, 0);
    Tcl_SetVar2(interp, varName, "Comment", pi2Ptr->pComment, 0);
    Tcl_SetVar2(interp, varName, "Location", pi2Ptr->pLocation, 0);
    Tcl_SetVar2(interp, varName, "SepFile", pi2Ptr->pSepFile, 0);
    Tcl_SetVar2(interp, varName, "PrintProcessor", pi2Ptr->pPrintProcessor, 0);
    Tcl_SetVar2(interp, varName, "Datatype", pi2Ptr->pDatatype, 0);
    Tcl_SetVar2(interp, varName, "Parameters", pi2Ptr->pParameters, 0);
    Tcl_SetVar2(interp, varName, "Attributes",
		AttributesToString(pi2Ptr->Attributes, &dString), 0);
    Tcl_SetVar2(interp, varName, "Priority", Blt_Itoa(pi2Ptr->Priority), 0);
    Tcl_SetVar2(interp, varName, "DefaultPriority",
		Blt_Itoa(pi2Ptr->DefaultPriority), 0);
    Tcl_SetVar2(interp, varName, "StartTime", Blt_Itoa(pi2Ptr->StartTime), 0);
    Tcl_SetVar2(interp, varName, "UntilTime", Blt_Itoa(pi2Ptr->UntilTime), 0);
    Tcl_SetVar2(interp, varName, "Status",
		StatusToString(pi2Ptr->Status, &dString), 0);
    Tcl_SetVar2(interp, varName, "Jobs", Blt_Itoa(pi2Ptr->cJobs), 0);
    Tcl_SetVar2(interp, varName, "AveragePPM", Blt_Itoa(pi2Ptr->AveragePPM), 0);

    if (dmPtr->dmFields & DM_ORIENTATION) {
	Tcl_SetVar2(interp, varName, "Orientation",
	    TokenToString(orientationTable, dmPtr->dmOrientation), 0);
    }
    if (dmPtr->dmFields & DM_PAPERSIZE) {
	Tcl_SetVar2(interp, varName, "PaperSize",
	    TokenToString(sizeTable, dmPtr->dmPaperSize), 0);
    }
    if (dmPtr->dmFields & DM_PAPERWIDTH) {
	Tcl_SetVar2(interp, varName, "PaperWidth",
	    Blt_Itoa(dmPtr->dmPaperWidth), 0);
    }
    if (dmPtr->dmFields & DM_PAPERLENGTH) {
	Tcl_SetVar2(interp, varName, "PaperLength",
	    Blt_Itoa(dmPtr->dmPaperLength), 0);
    }
    if (dmPtr->dmFields & DM_SCALE) {
	Tcl_SetVar2(interp, varName, "Scale", Blt_Itoa(dmPtr->dmScale), 0);
    }
    if (dmPtr->dmFields & DM_COPIES) {
	Tcl_SetVar2(interp, varName, "Copies", Blt_Itoa(dmPtr->dmCopies), 0);
    }
    if (dmPtr->dmFields & DM_DEFAULTSOURCE) {
	Tcl_SetVar2(interp, varName, "DefaultSource",
	    TokenToString(binTable, dmPtr->dmDefaultSource), 0);
    }
    if (dmPtr->dmFields & DM_PRINTQUALITY) {
	if (dmPtr->dmPrintQuality < 0) {
	    string = TokenToString(qualityTable, dmPtr->dmPrintQuality);
	} else {
	    string = Blt_Itoa(dmPtr->dmPrintQuality);
	}
	Tcl_SetVar2(interp, varName, "PrintQuality", string, 0);
    }
    if (dmPtr->dmFields & DM_COLOR) {
	Tcl_SetVar2(interp, varName, "Color",
	    TokenToString(colorTable, dmPtr->dmColor), 0);
    }
    if (dmPtr->dmFields & DM_DUPLEX) {
	Tcl_SetVar2(interp, varName, "Duplex",
	    TokenToString(duplexTable, dmPtr->dmDuplex), 0);
    }
    if (dmPtr->dmFields & DM_YRESOLUTION) {
	Tcl_SetVar2(interp, varName, "YResolution",
	    Blt_Itoa(dmPtr->dmYResolution), 0);
    }
    if (dmPtr->dmFields & DM_TTOPTION) {
	Tcl_SetVar2(interp, varName, "TTOption",
	    TokenToString(ttOptionTable, dmPtr->dmTTOption), 0);
    }
    if (dmPtr->dmFields & DM_COLLATE) {
	if (dmPtr->dmCollate == DMCOLLATE_TRUE) {
	    string = "true";
	} else if (dmPtr->dmCollate == DMCOLLATE_FALSE) {
	    string = "false";
	} else {
	    string = "???";
	}
	Tcl_SetVar2(interp, varName, "Collate", string, 0);
    }
    if (dmPtr->dmFields & DM_FORMNAME) {
	Tcl_SetVar2(interp, varName, "FormName", (const char *)dmPtr->dmFormName, 0);
    }
    Tcl_SetVar2(interp, varName, "OutputFile", (const char *)dmPtr->dmDeviceName, 0);
    result = TCL_OK;

 error:
    Tcl_DStringFree(&dString);
    CloseQueue(queuePtr);
    if (hMem1 != NULL) {
	GlobalUnlock(hMem1);
	GlobalFree(hMem1);
    }
    if (hMem2 != NULL) {
	GlobalUnlock(hMem2);
	GlobalFree(hMem2);
    }
    return result;
}

static int
SetQueueAttributes(
    Tcl_Interp *interp,
    PrintQueue *queuePtr,
    Tcl_Obj *objPtr)
{
    char *string;
    DEVMODEA *dmPtr;
    int value;
    HGLOBAL hMem;
    int result;
    char *varName;

    if (OpenQueue(interp, queuePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    hMem = GetQueueProperties(queuePtr, &dmPtr);
    CloseQueue(queuePtr);
    if (hMem == NULL) {
       return TCL_ERROR;
    }
    dmPtr->dmFields = 0;
    varName = Tcl_GetString(objPtr);
    string = (char *)Tcl_GetVar2(interp, varName, "Orientation", 0);
    if (string != NULL) {
	value = StringToToken(orientationTable, string);
	if (value > 0) {
	    dmPtr->dmFields |= DM_ORIENTATION;
	    dmPtr->dmOrientation = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "PaperSize", 0);
    if (string != NULL) {
	value = StringToToken(sizeTable, string);
	if (value > 0) {
	    dmPtr->dmFields |= DM_PAPERSIZE;
	    dmPtr->dmPaperSize = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "PaperWidth", 0);
    if (string != NULL) {
	if (Tcl_GetInt(interp, string, &value) == TCL_OK) {
	    dmPtr->dmFields |= DM_PAPERWIDTH;
	    dmPtr->dmPaperWidth = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "PaperLength", 0);
    if (string != NULL) {
	if (Tcl_GetInt(interp, string, &value) == TCL_OK) {
	    dmPtr->dmFields |= DM_PAPERLENGTH;
	    dmPtr->dmPaperLength = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "Scale", 0);
    if (string != NULL) {
	if (Tcl_GetInt(interp, string, &value) == TCL_OK) {
	    dmPtr->dmFields |= DM_SCALE;
	    dmPtr->dmScale = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "Copies", 0);
    if (string != NULL) {
	if (Tcl_GetInt(interp, string, &value) == TCL_OK) {
	    dmPtr->dmFields |= DM_COPIES;
	    dmPtr->dmCopies = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "DefaultSource", 0);
    if (string != NULL) {
	value = StringToToken(binTable, string);
	if (value > 0) {
	    dmPtr->dmFields |= DM_DEFAULTSOURCE;
	    dmPtr->dmDefaultSource = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "PrintQuality", 0);
    if (string != NULL) {
	value = StringToToken(qualityTable, string);
	if (value > 0) {
	    dmPtr->dmFields |= DM_PRINTQUALITY;
	    dmPtr->dmPrintQuality = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "Color", 0);
    if (string != NULL) {
	value = StringToToken(colorTable, string);
	if (value > 0) {
	    dmPtr->dmFields |= DM_COLOR;
	    dmPtr->dmColor = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "Duplex", 0);
    if (string != NULL) {
	value = StringToToken(duplexTable, string);
	if (value > 0) {
	    dmPtr->dmFields |= DM_DUPLEX;
	    dmPtr->dmDuplex = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "YResolution", 0);
    if (string != NULL) {
	if (Tcl_GetInt(interp, string, &value) == TCL_OK) {
	    dmPtr->dmFields |= DM_YRESOLUTION;
	    dmPtr->dmYResolution = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "TTOption", 0);
    if (string != NULL) {
	value = StringToToken(ttOptionTable, string);
	if (value > 0) {
	    dmPtr->dmFields |= DM_TTOPTION;
	    dmPtr->dmTTOption = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "Collate", 0);
    if (string != NULL) {
	if (Tcl_GetBoolean(interp, string, &value) == TCL_OK) {
	    dmPtr->dmFields |= DM_COLLATE;
	    dmPtr->dmCollate = value;
	}
    }
    string = (char *)Tcl_GetVar2(interp, varName, "OutputFile", 0);
    if (string != NULL) {
	if (queuePtr->fileName != NULL) {
	    Blt_Free(queuePtr->fileName);
	}
	queuePtr->fileName = Blt_Strdup(string);
    }
    if (queuePtr->dmPtr != NULL) {
	Blt_Free(queuePtr->dmPtr);
    }
    string = (char *)Tcl_GetVar2(interp, varName, "DocumentName", 0);
    if (string != NULL) {
	if (queuePtr->docName != NULL) {
	    Blt_Free(queuePtr->docName);
	}
	queuePtr->docName = Blt_Strdup(string);
    }
    result = SetQueueProperties(interp, queuePtr, dmPtr);
    GlobalUnlock(hMem);
    GlobalFree(hMem);
    CloseQueue(queuePtr);
    return result;
}

/*ARGSUSED*/
static int
EnumOp(
    ClientData clientData,	/* Not used. */
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    TokenString *p;
    char c;
    int length;
    char *attr;

    attr = Tcl_GetStringFromObj(objv[2], &length);
    c = attr[0];
    if ((c == 'p') && (strncmp(attr, "paper", length) == 0)) {
	p = sizeTable;
    } else if ((c == 'q') && (strncmp(attr, "quality", length) == 0)) {
	p = qualityTable;
    } else if ((c == 'b') && (strncmp(attr, "bin", length) == 0)) {
	p = binTable;
    } else if ((c == 'o') && (strncmp(attr, "orientation", length) == 0)) {
	p = orientationTable;
    } else if ((c == 'c') && (strncmp(attr, "color", length) == 0)) {
	p = colorTable;
    } else if ((c == 'd') && (strncmp(attr, "duplex", length) == 0)) {
	p = duplexTable;
    } else if ((c == 't') && (strncmp(attr, "ttoption", length) == 0)) {
	p = ttOptionTable;
    } else {
	Tcl_AppendResult(interp, "bad enumeration field \"", attr, 
"\": should be \"paper\", \"quality\", \"bin\", \"orientation\", \"color\", \"duplex\", or \"ttoption\"",
	    (char *)NULL);
	return TCL_ERROR;
    }
    for ( /*empty*/ ; p->string != NULL; p++) {
	Tcl_AppendElement(interp, p->string);
    }
    return TCL_OK;
}

/*ARGSUSED*/
static int
OpenOp(
    ClientData clientData,	/* Interpreter-specific data. */
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    PrinterInterpData *dataPtr = clientData;
    PrintQueue *queuePtr;
    LPVOID buffer;
    PRINTER_INFO_2A* pi2Ptr;
    DWORD bytesNeeded;
    int isNew;
    Blt_HashEntry *hPtr;
    HANDLE hMem;
    char *name;

    name = Tcl_GetString(objv[2]);
    hPtr = Blt_CreateHashEntry(&dataPtr->printerTable, name, &isNew);
    if (isNew) {
	queuePtr = Blt_Calloc(1, sizeof(PrintQueue));
	queuePtr->name = Blt_GetHashKey(&dataPtr->printerTable, hPtr);
	queuePtr->interp = interp;
	Tcl_SetResult(interp, name, TCL_VOLATILE);
	Blt_SetHashValue(hPtr, queuePtr);
	queuePtr->hashPtr = hPtr;
	queuePtr->tablePtr = &dataPtr->printerTable;
	queuePtr->printerName = Blt_Strdup(name);
    } else {
	Tcl_AppendResult(interp, "printer \"", name, "\" is already open",
			 (char *)NULL);
	return TCL_ERROR;
    }
    if (OpenQueue(interp, queuePtr) != TCL_OK) {
	DestroyQueue(queuePtr);
	return TCL_ERROR;
    }
    /* Call the first time to determine the amount of memory needed. */
    GetPrinter(queuePtr->hPrinter, 2, NULL, 0, &bytesNeeded);
    if ((bytesNeeded == 0) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
	Tcl_AppendResult(interp, "can't get size of attribute buffer for \"",
			 name, "\": ", Blt_LastError(), (char *)NULL);
	return TCL_ERROR;
    }
    /* Allocate a buffer to contain all printer information. */
    hMem = GlobalAlloc(GHND, bytesNeeded);
    if (hMem == NULL) {
	return TCL_ERROR;
    }
    buffer = (LPVOID)GlobalLock(hMem);

    /* And call the again to actually get the printer. */
    if (!GetPrinter(queuePtr->hPrinter, 2, buffer, bytesNeeded, 
		    &bytesNeeded)) {
	Tcl_AppendResult(interp, "can't get printer attributes for \"",
	    name, "\": ", Blt_LastError(), (char *)NULL);
        GlobalUnlock(hMem);
        GlobalFree(hMem);
	return TCL_ERROR;
    }
    pi2Ptr = (PRINTER_INFO_2A *)buffer;
    if (pi2Ptr->pDevMode != NULL) {
	queuePtr->deviceName = Blt_Strdup((const char *)pi2Ptr->pDevMode->dmDeviceName);
    }
    queuePtr->driverName = Blt_Strdup(pi2Ptr->pDriverName);
    /*
    queuePtr->printerName = Blt_Strdup(pi2Ptr->pPrinterName);
    */
    queuePtr->portName = Blt_Strdup(pi2Ptr->pPortName);
    GlobalUnlock(hMem);
    GlobalFree(hMem);
    return TCL_OK;
}

/*ARGSUSED*/
static int
NamesOp(
    ClientData clientData,	/* Interpreter-specific data. */
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)	/* Not used. */
{
    DWORD nPrinters, bytesNeeded;
    int elemSize, level;
    char *buffer;
    int result, flags;
    HANDLE hMem;

    if (Blt_GetPlatformId() == VER_PLATFORM_WIN32_NT) {
	level = 4;
	elemSize = sizeof(PRINTER_INFO_4);
	flags = PRINTER_ENUM_NAME;
    } else {
	level = 5;
	elemSize = sizeof(PRINTER_INFO_5);
	flags = PRINTER_ENUM_LOCAL;
    }
    result = EnumPrinters(
	flags,			/* Flags */
	NULL,			/* Printer name */
	level,			/* Information level: 1, 2, 4, or 5 */
	NULL,			/* Array of returned information */
	0,			/* Size of array */
	&bytesNeeded,		/* Size needed for array */
	&nPrinters);		/* Number of structures returned */

    if ((!result) && (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
	Tcl_AppendResult(interp, "can't enumerate printers (memory alloc): ",
	    Blt_LastError(), (char *)NULL);
	return TCL_ERROR;
    }
    hMem = GlobalAlloc(GHND, bytesNeeded);
    buffer = (char *)GlobalLock(hMem);

    result = EnumPrinters(
	flags,			/* Flags */
	NULL,			/* Printer name */
	level,			/* Information level: 1, 2, 4, or 5 */
	(LPBYTE)buffer,			/* Array of returned information */
	bytesNeeded,		/* Size of array */
	&bytesNeeded,		/* Size needed for array */
	&nPrinters);		/* Number of structures returned */

    if (!result) {
	Tcl_AppendResult(interp, "can't enumerate printers: ",
	    Blt_LastError(), (char *)NULL);
	return TCL_ERROR;
    }
    if (objc > 2) {	
	register unsigned int i;
	char *pattern;
	char *p;

	p = buffer;
        pattern = Tcl_GetString(objv[2]);
	for (i = 0; i < nPrinters; i++) {
	    if (Tcl_StringMatch(p, pattern)) {
		Tcl_AppendElement(interp, *(char **)p);
	    }
	    p += elemSize;
	}
    } else {
	register unsigned int i;
	char *p;

	p = buffer;
	for (i = 0; i < nPrinters; i++) {
	    Tcl_AppendElement(interp, *(char **)p);
	    p += elemSize;
	}
    }
    GlobalUnlock(hMem);
    GlobalFree(hMem);
    return TCL_OK;
}

/*ARGSUSED*/
static int
CloseOp(
    ClientData clientData,	/* Interpreter-specific data. */
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    PrintQueue *queuePtr;

    if (GetQueueFromObj(interp, objv[2], &queuePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    DestroyQueue(queuePtr);
    return TCL_OK;
}

/*ARGSUSED*/
static int
GetAttrOp(
    ClientData clientData,	/* Interpreter-specific data. */
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    PrintQueue *queuePtr;

    if (GetQueueFromObj(interp, objv[2], &queuePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    return GetPrinterAttributes(interp, queuePtr, objv[3]);
}

/*ARGSUSED*/
static int
SetAttrOp(
    ClientData clientData,	/* Interpreter-specific data. */
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    PrintQueue *queuePtr;

    if (GetQueueFromObj(interp, objv[2], &queuePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    return SetQueueAttributes(interp, queuePtr, objv[3]);
}

/*
 * --------------------------------------------------------------------------
 *
 * SnapOp --
 *
 *	Prints a snapshot of a Tk_Window to the designated printer.
 *
 * Results:
 *	Returns a standard Tcl result.  If an error occurred
 *	TCL_ERROR is returned and interp->result will contain an
 *	error message.
 *
 * -------------------------------------------------------------------------
 */
static int
SnapOp(
    ClientData clientData,	/* Interpreter-specific data. */
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    BITMAPINFO bi;
    DIBSECTION ds;
    HBITMAP hBitmap;
    HPALETTE hPalette;
    HDC hDC, printDC, memDC;
    void *data;
    Tk_Window tkwin;
    TkWinDCState state;
    int result;
    PrintQueue *queuePtr;
    DOCINFOA di;
    double pageWidth, pageHeight;
    int jobId;
    char *driverName;
    DEVMODEA *dmPtr;
    HGLOBAL hMem;
    Tcl_DString dString;
    char *path;

    Tcl_DStringInit(&dString);
    if (GetQueueFromObj(interp, objv[2], &queuePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    path = Tcl_GetString(objv[3]);
    tkwin = Tk_NameToWindow(interp, path, Tk_MainWindow(interp));
    if (tkwin == NULL) {
	return TCL_ERROR;
    }
    if (Tk_WindowId(tkwin) == None) {
	Tk_MakeWindowExist(tkwin);
    }
    
    result = TCL_ERROR;
    hDC = TkWinGetDrawableDC(Tk_Display(tkwin), Tk_WindowId(tkwin), &state);

    ZeroMemory(&bi, sizeof(bi));
    bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bi.bmiHeader.biWidth = Tk_Width(tkwin);
    bi.bmiHeader.biHeight = Tk_Height(tkwin);
    bi.bmiHeader.biPlanes = 1;
    bi.bmiHeader.biBitCount = 32;
    bi.bmiHeader.biCompression = BI_RGB;
    hBitmap = CreateDIBSection(hDC, &bi, DIB_RGB_COLORS, &data, NULL, 0);
    memDC = CreateCompatibleDC(hDC);
    SelectBitmap(memDC, hBitmap);
    hPalette = Blt_GetSystemPalette();
    if (hPalette != NULL) {
	SelectPalette(hDC, hPalette, FALSE);
	RealizePalette(hDC);
	SelectPalette(memDC, hPalette, FALSE);
	RealizePalette(memDC);
    }
    /* Copy the window contents to the memory surface. */
    if (!BitBlt(memDC, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), hDC, 0, 0,
		SRCCOPY)) {
	Tcl_AppendResult(interp, "can't blit \"", Tk_PathName(tkwin), "\": ",
			 Blt_LastError(), (char *)NULL);
	goto done;
    }
    /* Now that the DIB contains the image of the window, get the
     * databits and write them to the printer device, stretching the
     * image to the fit the printer's resolution.  */
    if (GetObject(hBitmap, sizeof(DIBSECTION), &ds) == 0) {
	Tcl_AppendResult(interp, "can't get DIB object: ", Blt_LastError(), 
			 (char *)NULL);
	goto done;
    } 
    driverName = NULL;
    if (Blt_GetPlatformId() == VER_PLATFORM_WIN32_NT) {
	driverName = queuePtr->driverName;
    }
    if (OpenQueue(interp, queuePtr) != TCL_OK) {
	goto done;
    }
    hMem = GetQueueProperties(queuePtr, &dmPtr);
    if (hMem == NULL) {
	goto done;
    }
    printDC = CreateDCA(driverName, queuePtr->deviceName, NULL, dmPtr);
    GlobalUnlock(hMem);
    GlobalFree(hMem);
    if (printDC == NULL) {
	Tcl_AppendResult(interp, "can't allocate printer DC for \"",
		queuePtr->name, "\": ", Blt_LastError(), (char *)NULL);
	goto done;
    }
    {
	double scale, sx, sy;

	/* Get the resolution of the printer device. */
	sx = (double)GetDeviceCaps(printDC, HORZRES)/(double)Tk_Width(tkwin);
	sy = (double)GetDeviceCaps(printDC, VERTRES)/(double)Tk_Height(tkwin);
	scale = MIN(sx, sy);
	pageWidth = scale * Tk_Width(tkwin);
	pageHeight = scale * Tk_Height(tkwin);
    }
    ZeroMemory(&di, sizeof(di));
    di.cbSize = sizeof(di);
    Tcl_DStringAppend(&dString, "Snapshot of \"", -1);
    Tcl_DStringAppend(&dString, Tk_PathName(tkwin), -1);
    Tcl_DStringAppend(&dString, "\"", -1);
    di.lpszDocName = Tcl_DStringValue(&dString);
    jobId = StartDocA(printDC, &di);
    if (jobId <= 0) {
	Tcl_AppendResult(interp, "can't start document: ", Blt_LastError(), 
		(char *)NULL);
	goto done;
    }
    if (StartPage(printDC) <= 0) {
	Tcl_AppendResult(interp, "error starting page: ", Blt_LastError(), 
		(char *)NULL);
	goto done;
    }
    StretchDIBits(printDC, 0, 0, ROUND(pageWidth), ROUND(pageHeight), 0, 0, 
	Tk_Width(tkwin), Tk_Height(tkwin), ds.dsBm.bmBits, 
	(LPBITMAPINFO)&ds.dsBmih, DIB_RGB_COLORS, SRCCOPY);
    EndPage(printDC);
    EndDoc(printDC);
    DeleteDC(printDC);
    Tcl_SetResult(interp, Blt_Itoa(jobId), TCL_VOLATILE);
    result = TCL_OK;

  done:
    Tcl_DStringFree(&dString);
    if (queuePtr->hPrinter != NULL) {
	CloseQueue(queuePtr);
    }    
    DeleteBitmap(hBitmap);
    DeleteDC(memDC);
    TkWinReleaseDrawableDC(Tk_WindowId(tkwin), hDC, &state);
    if (hPalette != NULL) {
	DeletePalette(hPalette);
    }
    return result;
}

/*ARGSUSED*/
static int
WriteOp(
    ClientData clientData,	/* Interpreter-specific data. */
    Tcl_Interp *interp,
    int objc,			/* Not used. */
    Tcl_Obj *CONST *objv)
{
    DWORD nBytes;
	int bytesLeft;
    DOC_INFO_1A di1;
    DWORD jobId;
    char *title;
    register char *data;
    static int nextJob = 0;
    char string[200];
    PrintQueue *queuePtr;
    int result;

    if (GetQueueFromObj(interp, objv[2], &queuePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (OpenQueue(interp, queuePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (objc == 5) {
	title = Tcl_GetString(objv[3]);
	data = Tcl_GetStringFromObj(objv[4], &bytesLeft);
    } else {
	sprintf(string, "Print Job #%d", nextJob++);
	title = string;
	data = Tcl_GetStringFromObj(objv[3], &bytesLeft);
    }
    ZeroMemory(&di1, sizeof(DOC_INFO_1A));
    di1.pDocName = title;
    if (queuePtr->fileName != NULL) {
	di1.pOutputFile = queuePtr->fileName;
    } else {
	di1.pOutputFile = NULL;
    }
    di1.pDatatype = "RAW";

    result = TCL_ERROR;
    /* Start new document */
    jobId = StartDocPrinter(queuePtr->hPrinter, 1, (unsigned char *)&di1);
    if (jobId == 0) {
	Tcl_AppendResult(interp, "error starting document on \"", 
	 queuePtr->printerName, "\": ", Blt_LastError(), (char *)NULL);
	goto error;
    }
    /* Start new page */
    if (!StartPagePrinter(queuePtr->hPrinter)) {
	Tcl_AppendResult(interp, "error starting page on \"", 
	 queuePtr->printerName, "\": ", Blt_LastError(), (char *)NULL);
	goto error;
    }
    do {
	if (!WritePrinter(queuePtr->hPrinter, data, bytesLeft, &nBytes)) {
	    Tcl_AppendResult(interp, "can't write data to \"", 
		queuePtr->printerName, "\": ", Blt_LastError(), (char *)NULL);
	    EndDocPrinter(queuePtr->hPrinter);
	    goto error;
	}
	data += nBytes;
	bytesLeft -= nBytes;
    } while (bytesLeft > 0);
    /* End last page */
    if (!EndPagePrinter(queuePtr->hPrinter)) {
	Tcl_AppendResult(interp, "error ending page on \"", 
		queuePtr->printerName, "\": ", Blt_LastError(), (char *)NULL);
	goto error;
    }
    /* End document */
    if (!EndDocPrinter(queuePtr->hPrinter)) {
	Tcl_AppendResult(interp, "error ending document on \"", 
		queuePtr->printerName, "\": ", Blt_LastError(), (char *)NULL);
	goto error;
    }
    result = TCL_OK;
 error:
    CloseQueue(queuePtr);
    return result;
}

static Blt_OpSpec printerOps[] =
{
    {"close", 1, (Blt_Op)CloseOp, 3, 3, "pid",},
    {"enum", 1, (Blt_Op)EnumOp, 3, 3, "attribute",},
    {"getattrs", 1, (Blt_Op)GetAttrOp, 4, 4, "pid varName",},
    {"names", 1, (Blt_Op)NamesOp, 2, 3, "?pattern?",},
    {"open", 1, (Blt_Op)OpenOp, 3, 3, "printerName",},
    {"setattrs", 1, (Blt_Op)SetAttrOp, 4, 4, "pid varName",},
    {"snap", 1, (Blt_Op)SnapOp, 4, 4, "pid window",},
    {"write", 1, (Blt_Op)WriteOp, 4, 5, "pid ?title? string",},
};
static int nPrinterOps = sizeof(printerOps) / sizeof(Blt_OpSpec);

/* ARGSUSED */
static int
PrinterCmd(
    ClientData clientData,	/* Not used. */
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *CONST *objv)
{
    Blt_Op proc;
    int result;

    proc = Blt_GetOpFromObj(interp, nPrinterOps, printerOps, BLT_OP_ARG1, 
		    objc, objv, 0);
    if (proc == NULL) {
	return TCL_ERROR;
    }
    result = (*proc) (clientData, interp, objc, objv);
    return result;
}

/*
 * -----------------------------------------------------------------------
 *
 * PrinterInterpDeleteProc --
 *
 *	This is called when the interpreter hosting one or more printer 
 *	commands is destroyed.  
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Closes and removes all open printers.
 *
 * ------------------------------------------------------------------------
 */
/* ARGSUSED */
static void
PrinterInterpDeleteProc(clientData, interp)
    ClientData clientData;	/* Interpreter-specific data. */
    Tcl_Interp *interp;
{
    PrinterInterpData *dataPtr = clientData;
    Blt_HashEntry *hPtr;
    Blt_HashSearch cursor;
    PrintQueue *queuePtr;

    for (hPtr = Blt_FirstHashEntry(&dataPtr->printerTable, &cursor);
	 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
	queuePtr = (PrintQueue *)Blt_GetHashValue(hPtr);
	queuePtr->hashPtr = NULL;
	DestroyQueue(queuePtr);
    }
    Blt_DeleteHashTable(&dataPtr->printerTable);
    Tcl_DeleteAssocData(interp, PRINTER_THREAD_KEY);
    Blt_Free(dataPtr);
}


int
Blt_PrinterInit(Tcl_Interp *interp)
{
    static Blt_ObjCmdSpec cmdSpec = {
	"printer", PrinterCmd
    };
    PrinterInterpData *dataPtr;

    dataPtr = GetPrinterInterpData(interp);
    cmdSpec.clientData = dataPtr;
    if (Blt_InitObjCmd(interp, "blt", &cmdSpec) == NULL) {
	return TCL_ERROR;
    }
    return TCL_OK;
}


/* Public routines */
int
Blt_GetOpenPrinter(
    Tcl_Interp *interp,
    const char *name,
    Drawable *drawablePtr)
{
    PrintQueue *queuePtr;
    
    if (GetQueue(interp, name, &queuePtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (queuePtr->drawable.hDC == NULL) {
	char *driverName;
	HGLOBAL hMem;
	DEVMODEA *dmPtr;
	HDC hDC;

	driverName = NULL;
	if (Blt_GetPlatformId() == VER_PLATFORM_WIN32_NT) {
	    driverName = queuePtr->driverName;
	}
	if (OpenQueue(interp, queuePtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	hMem = GetQueueProperties(queuePtr, &dmPtr);
	if (hMem == NULL) {
	    CloseQueue(queuePtr);
	    return TCL_ERROR;
	}
	if (queuePtr->dmPtr != NULL) {
	    *dmPtr = *queuePtr->dmPtr;
	}
	hDC = CreateDCA(driverName, queuePtr->deviceName, NULL, dmPtr);
	GlobalUnlock(hMem);
	GlobalFree(hMem);
	CloseQueue(queuePtr);
	if (hDC == NULL) {
	    Tcl_AppendResult(interp, "can't allocate printer DC for \"",
		queuePtr->name, "\": ", Blt_LastError(), (char *)NULL);
	    return TCL_ERROR;
	}
	queuePtr->drawable.hDC = hDC;
	queuePtr->drawable.type = TWD_WINDC;
    }
    *drawablePtr = (Drawable)(&queuePtr->drawable);
    return TCL_OK;
}

#include <commdlg.h>

int
Blt_PrintDialog(
    Tcl_Interp *interp,
    Drawable *drawablePtr)
{
    PRINTDLG dlg;
    static PrintDrawable drawable;
    int mode, result;
    
    ZeroMemory(&dlg, sizeof(PRINTDLG));
    dlg.lStructSize = sizeof(PRINTDLG);
    dlg.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION;
    mode = Tcl_SetServiceMode(TCL_SERVICE_NONE);
    result = PrintDlg(&dlg);
    Tcl_SetServiceMode(mode);
    if (!result) {
	if (!CommDlgExtendedError()) {
	    return TCL_RETURN;	/* User canceled. */
	}
	Tcl_AppendResult(interp, "can't access printer:", Blt_LastError(), 
			 (char *)NULL);
	return TCL_ERROR;
    } 
    *drawablePtr = (Drawable)&drawable;
    drawable.type = TWD_WINDC;
    drawable.hDC = dlg.hDC;
    return TCL_OK;
}

int
Blt_StartPrintJob(
    Tcl_Interp *interp,
    Drawable drawable)
{
    DOCINFOA di;
    PrintDrawable *drawPtr = (PrintDrawable *)drawable;
    int jobId;

    ZeroMemory((char *)&di, sizeof(DOCINFO));
    di.cbSize = sizeof(DOCINFO);
    di.lpszDocName = "Unknown";
    jobId = StartDocA(drawPtr->hDC, &di);
    if (jobId == 0) {
	Tcl_AppendResult(interp, "error starting document: ",
 			 Blt_LastError(), (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

int
Blt_EndPrintJob(
    Tcl_Interp *interp,
    Drawable drawable)
{
    PrintDrawable *drawPtr = (PrintDrawable *)drawable;

    EndPage(drawPtr->hDC);
    EndDoc(drawPtr->hDC);
    return TCL_OK;
}

#endif /*NO_PRINTER*/
