OSDN Git Service

Please enter the commit message for your changes. Lines starting
[eos/base.git] / util / src / TclTk / blt2.5 / generic / bltWinPrnt.c
diff --git a/util/src/TclTk/blt2.5/generic/bltWinPrnt.c b/util/src/TclTk/blt2.5/generic/bltWinPrnt.c
new file mode 100644 (file)
index 0000000..b48bc42
--- /dev/null
@@ -0,0 +1,1587 @@
+
+/*
+ * 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;
+    DEVMODE *dmPtr;
+    int dmSize;
+} PrintQueue;
+
+typedef struct {
+    DWORD 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_DEFAULTS pd;
+    HANDLE hPrinter;
+
+    ZeroMemory(&pd, sizeof(pd));
+    pd.DesiredAccess = PRINTER_ALL_ACCESS;
+    if (!OpenPrinter(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,
+    DEVMODE **dmPtrPtr)
+{
+    HWND hWnd;
+    unsigned int dmSize;
+    HGLOBAL hMem;
+    DEVMODE *dmPtr;
+
+    hWnd = GetDesktopWindow();
+    dmSize = DocumentProperties(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 = (DEVMODE *)GlobalLock(hMem);
+    if (!DocumentProperties(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,
+    DEVMODE *dmPtr)
+{
+    HWND hWnd;
+    int result;
+
+    hWnd = GetDesktopWindow();
+    result = DocumentProperties(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;
+    char 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_1 * 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;
+    DEVMODE *dmPtr;
+    DWORD bytesNeeded;
+    HGLOBAL hMem1, hMem2;
+    PRINTER_INFO_2* 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_2 *)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", dmPtr->dmFormName, 0);
+    }
+    Tcl_SetVar2(interp, varName, "OutputFile", 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;
+    DEVMODE *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;
+    unsigned 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_2* 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_2 *)buffer;
+    if (pi2Ptr->pDevMode != NULL) {
+       queuePtr->deviceName = Blt_Strdup(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;
+    unsigned 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 = (unsigned char *)GlobalLock(hMem);
+
+    result = EnumPrinters(
+       flags,                  /* Flags */
+       NULL,                   /* Printer name */
+       level,                  /* Information level: 1, 2, 4, or 5 */
+       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;
+    DOCINFO di;
+    double pageWidth, pageHeight;
+    int jobId;
+    char *driverName;
+    DEVMODE *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 = CreateDC(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 = StartDoc(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 bytesLeft, nBytes;
+    DOC_INFO_1 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_1));
+    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;
+}
+\f
+
+/* 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;
+       DEVMODE *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 = CreateDC(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)
+{
+    DOCINFO di;
+    PrintDrawable *drawPtr = (PrintDrawable *)drawable;
+    int jobId;
+
+    ZeroMemory((char *)&di, sizeof(DOCINFO));
+    di.cbSize = sizeof(DOCINFO);
+    di.lpszDocName = "Unknown";
+    jobId = StartDoc(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*/