diff options
Diffstat (limited to 'win/tkWinGDI.c')
-rw-r--r-- | win/tkWinGDI.c | 3886 |
1 files changed, 3886 insertions, 0 deletions
diff --git a/win/tkWinGDI.c b/win/tkWinGDI.c new file mode 100644 index 0000000..7611880 --- /dev/null +++ b/win/tkWinGDI.c @@ -0,0 +1,3886 @@ +/* + * tkWinGDI.c -- + * + * This module implements access to the Win32 GDI API. + * + * Copyright © 1991-2018 Microsoft Corp. + * Copyright © 2009, Michael I. Schwartz. + * Copyright © 1998-2019 Harald Oehlmann, Elmicron GmbH + * Copyright © 2021 Kevin Walzer/WordTech Communications LLC. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + + +#include <windows.h> +#include <math.h> +#include <wtypes.h> +#include <winspool.h> +#include <commdlg.h> +#include <wingdi.h> + +#include <tcl.h> + +#include "tkWinInt.h" + +/* + * Create a standard "DrawFunc" to make this more workable.... + */ +#ifdef _MSC_VER +typedef BOOL (WINAPI *DrawFunc) ( + HDC, int, int, int, int, int, int, int, int); /* Arc, Chord, Pie. */ +#else +typedef BOOL WINAPI (*DrawFunc) ( + HDC, int, int, int, int, int, int, int, int); /* Arc, Chord, Pie. */ +#endif + +/* Real functions. */ +static Tcl_ObjCmdProc GdiArc; +static Tcl_ObjCmdProc GdiBitmap; +static Tcl_ObjCmdProc GdiCharWidths; +static Tcl_ObjCmdProc GdiImage; +static Tcl_ObjCmdProc GdiPhoto; +static Tcl_ObjCmdProc GdiLine; +static Tcl_ObjCmdProc GdiOval; +static Tcl_ObjCmdProc GdiPolygon; +static Tcl_ObjCmdProc GdiRectangle; +static Tcl_ObjCmdProc GdiText; +static Tcl_ObjCmdProc GdiMap; +static Tcl_ObjCmdProc GdiCopyBits; + +/* Local copies of similar routines elsewhere in Tcl/Tk. */ +static int GdiGetColor(Tcl_Obj *nameObj, COLORREF *color); + +/* + * Helper functions. + */ +static int GdiMakeLogFont(Tcl_Interp *interp, const char *str, + LOGFONTW *lf, HDC hDC); +static int GdiMakePen(Tcl_Interp *interp, int width, + int dashstyle, const char *dashstyledata, + int capstyle, int joinstyle, + int stipplestyle, const char *stippledata, + unsigned long color, HDC hDC, HGDIOBJ *oldPen); +static int GdiFreePen(Tcl_Interp *interp, HDC hDC, HGDIOBJ oldPen); +static int GdiMakeBrush(unsigned long color, long hatch, + LOGBRUSH *lb, HDC hDC, HBRUSH *oldBrush); +static void GdiFreeBrush(Tcl_Interp *interp, HDC hDC, + HGDIOBJ oldBrush); +static int GdiGetHdcInfo(HDC hdc, + LPPOINT worigin, LPSIZE wextent, + LPPOINT vorigin, LPSIZE vextent); + +/* Helper functions for printing the window client area. */ +enum PrintType { PTWindow = 0, PTClient = 1, PTScreen = 2 }; + +static HANDLE CopyToDIB(HWND wnd, enum PrintType type); +static HBITMAP CopyScreenToBitmap(LPRECT lpRect); +static HANDLE BitmapToDIB(HBITMAP hb, HPALETTE hp); +static HANDLE CopyScreenToDIB(LPRECT lpRect); +static int DIBNumColors(LPBITMAPINFOHEADER lpDIB); +static int PalEntriesOnDevice(HDC hDC); +static HPALETTE GetSystemPalette(void); +static void GetDisplaySize(LONG *width, LONG *height); +static int GdiWordToWeight(const char *str); +static int GdiParseFontWords(Tcl_Interp *interp, LOGFONTW *lf, + const char *str[], int numargs); +static Tcl_ObjCmdProc PrintSelectPrinter; +static Tcl_ObjCmdProc PrintOpenPrinter; +static Tcl_ObjCmdProc PrintClosePrinter; +static Tcl_ObjCmdProc PrintOpenDoc; +static Tcl_ObjCmdProc PrintCloseDoc; +static Tcl_ObjCmdProc PrintOpenPage; +static Tcl_ObjCmdProc PrintClosePage; + +/* + * Global state. + */ + +static PRINTDLGW pd; +static DOCINFOW di; +static WCHAR *localPrinterName = NULL; +static int copies, paper_width, paper_height, dpi_x, dpi_y; +static LPDEVNAMES devnames; +static HDC printDC; + +/* + * To make the "subcommands" follow a standard convention, add them to this + * array. The first element is the subcommand name, and the second a standard + * Tcl command handler. + */ + +static const struct gdi_command { + const char *command_string; + Tcl_ObjCmdProc *command; +} gdi_commands[] = { + { "arc", GdiArc }, + { "bitmap", GdiBitmap }, + { "characters", GdiCharWidths }, + { "image", GdiImage }, + { "line", GdiLine }, + { "map", GdiMap }, + { "oval", GdiOval }, + { "photo", GdiPhoto }, + { "polygon", GdiPolygon }, + { "rectangle", GdiRectangle }, + { "text", GdiText }, + { "copybits", GdiCopyBits }, +}; + +/* + *---------------------------------------------------------------------- + * + * GdiArc -- + * + * Map canvas arcs to GDI context. + * + * Results: + * Renders arcs. + * + *---------------------------------------------------------------------- + */ + +static int GdiArc( + TCL_UNUSED(void *), + Tcl_Interp *interp, + int argc, + Tcl_Obj *const *objv) +{ + static const char usage_message[] = + "::tk::print::_gdi arc hdc x1 y1 x2 y2 " + "-extent angle -start angle -style arcstyle " + "-fill color -outline color " + "-width dimension -dash dashrule " + "-outlinestipple ignored -stipple ignored\n" ; + int x1, y1, x2, y2; + int xr0, yr0, xr1, yr1; + HDC hDC; + double extent = 0.0, start = 0.0; + DrawFunc drawfunc; + int width = 0; + HPEN hPen; + COLORREF linecolor = 0, fillcolor = BS_NULL; + int dolinecolor = 0, dofillcolor = 0; + HBRUSH hBrush = NULL; + LOGBRUSH lbrush; + HGDIOBJ oldobj = NULL; + int dodash = 0; + const char *dashdata = 0; + + drawfunc = Pie; + + /* Verrrrrry simple for now.... */ + if (argc < 6) { + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + + hDC = printDC; + + if ((Tcl_GetIntFromObj(interp, objv[2], &x1) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[3], &y1) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[4], &x2) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[5], &y2) != TCL_OK)) { + return TCL_ERROR; + } + + argc -= 6; + objv += 6; + while (argc >= 2) { + if (strcmp(Tcl_GetString(objv[0]), "-extent") == 0) { + extent = atof(Tcl_GetString(objv[1])); + } else if (strcmp(Tcl_GetString(objv[0]), "-start") == 0) { + start = atof(Tcl_GetString(objv[1])); + } else if (strcmp(Tcl_GetString(objv[0]), "-style") == 0) { + if (strcmp(Tcl_GetString(objv[1]), "pieslice") == 0) { + drawfunc = Pie; + } else if (strcmp(Tcl_GetString(objv[1]), "arc") == 0) { + drawfunc = Arc; + } else if (strcmp(Tcl_GetString(objv[1]), "chord") == 0) { + drawfunc = Chord; + } + } else if (strcmp(Tcl_GetString(objv[0]), "-fill") == 0) { + /* Handle all args, even if we don't use them yet. */ + if (GdiGetColor(objv[1], &fillcolor)) { + dofillcolor = 1; + } + } else if (strcmp(Tcl_GetString(objv[0]), "-outline") == 0) { + if (GdiGetColor(objv[1], &linecolor)) { + dolinecolor = 1; + } + } else if (strcmp(Tcl_GetString(objv[0]), "-outlinestipple") == 0) { + /* ignored */ + } else if (strcmp(Tcl_GetString(objv[0]), "-stipple") == 0) { + /* ignored */ + } else if (strcmp(Tcl_GetString(objv[0]), "-width") == 0) { + if (Tcl_GetIntFromObj(interp, objv[1], &width)) { + return TCL_ERROR; + } + } else if (strcmp(Tcl_GetString(objv[0]), "-dash") == 0) { + if (Tcl_GetString(objv[1])) { + dodash = 1; + dashdata = Tcl_GetString(objv[1]); + } + } else { + /* Don't know that option! */ + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + argc -= 2; + objv += 2; + } + xr0 = xr1 = (x1 + x2) / 2; + yr0 = yr1 = (y1 + y2) / 2; + + /* + * The angle used by the arc must be "warped" by the eccentricity of the + * ellipse. Thanks to Nigel Dodd <nigel.dodd@avellino.com> for bringing a + * nice example. + */ + + xr0 += (int)(100.0 * (x2 - x1) * cos((start * 2.0 * 3.14159265) / 360.0)); + yr0 -= (int)(100.0 * (y2 - y1) * sin((start * 2.0 * 3.14159265) / 360.0)); + xr1 += (int)(100.0 * (x2 - x1) * cos(((start+extent) * 2.0 * 3.14159265) / 360.0)); + yr1 -= (int)(100.0 * (y2 - y1) * sin(((start+extent) * 2.0 * 3.14159265) / 360.0)); + + /* + * Under Win95, SetArcDirection isn't implemented--so we have to assume + * that arcs are drawn counterclockwise (e.g., positive extent) So if it's + * negative, switch the coordinates! + */ + + if (extent < 0) { + int xr2 = xr0; + int yr2 = yr0; + + xr0 = xr1; + xr1 = xr2; + yr0 = yr1; + yr1 = yr2; + } + + if (dofillcolor) { + GdiMakeBrush(fillcolor, 0, &lbrush, hDC, &hBrush); + } else { + oldobj = SelectObject(hDC, GetStockObject(HOLLOW_BRUSH)); + } + + if (width || dolinecolor) { + GdiMakePen(interp, width, dodash, dashdata, + 0, 0, 0, 0, linecolor, hDC, (HGDIOBJ *)&hPen); + } + + (*drawfunc)(hDC, x1, y1, x2, y2, xr0, yr0, xr1, yr1); + + if (width || dolinecolor) { + GdiFreePen(interp, hDC, hPen); + } + if (hBrush) { + GdiFreeBrush(interp, hDC, hBrush); + } else { + SelectObject(hDC, oldobj); + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GdiBitmap -- + * + * Unimplemented for now. Should use the same techniques as + * CanvasPsBitmap (tkCanvPs.c). + * + * Results: + * None. + * + *---------------------------------------------------------------------- + */ + +static int GdiBitmap( + TCL_UNUSED(void *), + Tcl_Interp *interp, + TCL_UNUSED(int), + Tcl_Obj *const *objv) +{ + /* + * Skip this for now. Should be based on common code with the copybits + * command. + */ + + Tcl_WrongNumArgs(interp, 1, objv, "hdc x y " + "-anchor [center|n|e|s|w] -background color " + "-bitmap bitmap -foreground color\n" + "Not implemented yet. Sorry!"); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * GdiImage -- + * + * Unimplemented for now. Unimplemented for now. Should switch on image + * type and call either GdiPhoto or GdiBitmap. This code is similar to + * that in tkWinImage.c. + * + * Results: + * None. + * + *---------------------------------------------------------------------- + */ + +static int GdiImage( + TCL_UNUSED(void *), + Tcl_Interp *interp, + TCL_UNUSED(int), + Tcl_Obj *const *objv) +{ + /* Skip this for now..... */ + /* Should be based on common code with the copybits command. */ + + Tcl_WrongNumArgs(interp, 1, objv, "hdc x y -anchor [center|n|e|s|w] -image name\n" + "Not implemented yet. Sorry!"); + /* Normally, usage results in TCL_ERROR--but wait til' it's implemented. */ + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GdiPhoto -- + * + * Contributed by Lukas Rosenthaler <lukas.rosenthaler@balcab.ch> + * + * Note: The canvas doesn't directly support photos (only as images), so + * this is the first ::tk::print::_gdi command without an equivalent + * canvas command. This code may be modified to support photo images on + * the canvas. + * + * Results: + * Renders a photo. + * + *---------------------------------------------------------------------- + */ + +static int GdiPhoto( + TCL_UNUSED(void *), + Tcl_Interp *interp, + int argc, + Tcl_Obj *const *objv) +{ + static const char usage_message[] = + "::tk::print::_gdi photo hdc [-destination x y [w [h]]] -photo name\n"; + HDC dst; + int dst_x = 0, dst_y = 0, dst_w = 0, dst_h = 0; + int nx, ny, sll; + const char *photoname = 0; /* For some reason Tk_FindPhoto takes a char *. */ + Tk_PhotoHandle photo_handle; + Tk_PhotoImageBlock img_block; + BITMAPINFO bitmapinfo; /* Since we don't need the bmiColors table, + * there is no need for dynamic allocation. */ + int oldmode; /* For saving the old stretch mode. */ + POINT pt; /* For saving the brush org. */ + char *pbuf = NULL; + int i, j, k; + int retval = TCL_OK; + + /* + * Parse the arguments. + */ + + /* HDC is required. */ + if (argc < 2) { + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + + dst = printDC; + + /* + * Next, check to see if 'dst' can support BitBlt. + * If not, raise an error. + */ + + if ((GetDeviceCaps(dst, RASTERCAPS) & RC_STRETCHDIB) == 0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "::tk::print::_gdi photo not supported on device context (0x%s)", + Tcl_GetString(objv[1]))); + return TCL_ERROR; + } + + /* Parse the command line arguments. */ + for (j = 2; j < argc; j++) { + if (strcmp(Tcl_GetString(objv[j]), "-destination") == 0) { + double x, y, w, h; + int count = 0; + char dummy; + + if (j < argc) { + count = sscanf(Tcl_GetString(objv[++j]), "%lf%lf%lf%lf%c", + &x, &y, &w, &h, &dummy); + } + + if (count < 2 || count > 4) { + /* Destination must provide at least 2 arguments. */ + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "-destination requires a list of 2 to 4 numbers\n%s", + usage_message)); + return TCL_ERROR; + } + + dst_x = (int) x; + dst_y = (int) y; + if (count == 3) { + dst_w = (int) w; + dst_h = -1; + } else if (count == 4) { + dst_w = (int) w; + dst_h = (int) h; + } + } else if (strcmp(Tcl_GetString(objv[j]), "-photo") == 0) { + photoname = Tcl_GetString(objv[++j]); + } + } + + if (photoname == 0) { /* No photo provided. */ + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "No photo name provided to ::tk::print::_gdi photo\n%s", + usage_message)); + return TCL_ERROR; + } + + photo_handle = Tk_FindPhoto(interp, photoname); + if (photo_handle == 0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "::tk::print::_gdi photo: Photo name %s can't be located\n%s", + photoname, usage_message)); + return TCL_ERROR; + } + Tk_PhotoGetImage(photo_handle, &img_block); + + nx = img_block.width; + ny = img_block.height; + sll = ((3*nx + 3) / 4)*4; /* Must be multiple of 4. */ + + /* + * Buffer is potentially large enough that failure to allocate might be + * recoverable. + */ + + pbuf = (char *)attemptckalloc(sll * ny * sizeof(char)); + if (pbuf == 0) { /* Memory allocation failure. */ + Tcl_AppendResult(interp, + "::tk::print::_gdi photo failed--out of memory", (char *)NULL); + return TCL_ERROR; + } + + /* After this, all returns must go through retval. */ + + /* BITMAP expects BGR; photo provides RGB. */ + for (k = 0; k < ny; k++) { + for (i = 0; i < nx; i++) { + pbuf[k*sll + 3*i] = img_block.pixelPtr[ + k*img_block.pitch + i*img_block.pixelSize + img_block.offset[2]]; + pbuf[k*sll + 3*i + 1] = img_block.pixelPtr[ + k*img_block.pitch + i*img_block.pixelSize + img_block.offset[1]]; + pbuf[k*sll + 3*i + 2] = img_block.pixelPtr[ + k*img_block.pitch + i*img_block.pixelSize + img_block.offset[0]]; + } + } + + memset(&bitmapinfo, 0L, sizeof(BITMAPINFO)); + + bitmapinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bitmapinfo.bmiHeader.biWidth = nx; + bitmapinfo.bmiHeader.biHeight = -ny; + bitmapinfo.bmiHeader.biPlanes = 1; + bitmapinfo.bmiHeader.biBitCount = 24; + bitmapinfo.bmiHeader.biCompression = BI_RGB; + bitmapinfo.bmiHeader.biSizeImage = 0; /* sll*ny;. */ + bitmapinfo.bmiHeader.biXPelsPerMeter = 0; + bitmapinfo.bmiHeader.biYPelsPerMeter = 0; + bitmapinfo.bmiHeader.biClrUsed = 0; + bitmapinfo.bmiHeader.biClrImportant = 0; + + oldmode = SetStretchBltMode(dst, HALFTONE); + /* + * According to the Win32 Programmer's Manual, we have to set the brush + * org, now. + */ + SetBrushOrgEx(dst, 0, 0, &pt); + + if (dst_w <= 0) { + dst_w = nx; + dst_h = ny; + } else if (dst_h <= 0) { + dst_h = ny*dst_w / nx; + } + + if (StretchDIBits(dst, dst_x, dst_y, dst_w, dst_h, 0, 0, nx, ny, + pbuf, &bitmapinfo, DIB_RGB_COLORS, SRCCOPY) == (int)GDI_ERROR) { + int errcode = GetLastError(); + + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "::tk::print::_gdi photo internal failure: " + "StretchDIBits error code %d", errcode)); + retval = TCL_ERROR; + } + + /* Clean up the hDC. */ + if (oldmode != 0) { + SetStretchBltMode(dst, oldmode); + SetBrushOrgEx(dst, pt.x, pt.y, &pt); + } + + ckfree(pbuf); + + if (retval == TCL_OK) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "%d %d %d %d", dst_x, dst_y, dst_w, dst_h)); + } + + return retval; +} + +/* + *---------------------------------------------------------------------- + * + * Bezierize -- + * + * Interface to Tk's line smoother, used for lines and pollies. + * Provided by Jasper Taylor <jasper.taylor@ed.ac.uk>. + * + * Results: + * Smooths lines. + * + *---------------------------------------------------------------------- + */ + +static int Bezierize( + POINT* polypoints, + int npoly, + int nStep, + POINT* bpointptr) +{ + /* First, translate my points into a list of doubles. */ + double *inPointList, *outPointList; + int n; + int nbpoints = 0; + POINT* bpoints; + + inPointList = (double *)attemptckalloc(2 * sizeof(double) * npoly); + if (inPointList == 0) { + /* TODO: unreachable */ + return nbpoints; /* 0. */ + } + + for (n=0; n<npoly; n++) { + inPointList[2*n] = polypoints[n].x; + inPointList[2*n + 1] = polypoints[n].y; + } + + nbpoints = 1 + npoly * nStep; /* this is the upper limit. */ + outPointList = (double *)attemptckalloc(2 * sizeof(double) * nbpoints); + if (outPointList == 0) { + /* TODO: unreachable */ + ckfree(inPointList); + return 0; + } + + nbpoints = TkMakeBezierCurve(NULL, inPointList, npoly, nStep, + NULL, outPointList); + + ckfree(inPointList); + bpoints = (POINT *)attemptckalloc(sizeof(POINT)*nbpoints); + if (bpoints == 0) { + /* TODO: unreachable */ + ckfree(outPointList); + return 0; + } + + for (n=0; n<nbpoints; n++) { + bpoints[n].x = (long)outPointList[2*n]; + bpoints[n].y = (long)outPointList[2*n + 1]; + } + ckfree(outPointList); + *bpointptr = *bpoints; + return nbpoints; +} + +/* + *---------------------------------------------------------------------- + * + * GdiLine -- + * + * Maps lines to GDI context. + * + * Results: + * Renders lines. + * + *---------------------------------------------------------------------- + */ + +static int GdiLine( + TCL_UNUSED(void *), + Tcl_Interp *interp, + int argc, + Tcl_Obj *const *objv) +{ + static const char usage_message[] = + "::tk::print::_gdi line hdc x1 y1 ... xn yn " + "-arrow [first|last|both|none] -arrowshape {d1 d2 d3} " + "-dash dashlist " + "-capstyle [butt|projecting|round] -fill color " + "-joinstyle [bevel|miter|round] -smooth [true|false|bezier] " + "-splinesteps number -stipple bitmap -width linewid"; + char *strend; + POINT *polypoints; + int npoly; + int x, y; + HDC hDC; + HPEN hPen; + + LOGBRUSH lbrush; + HBRUSH hBrush = NULL; + + int width = 0; + COLORREF linecolor = 0; + int dolinecolor = 0; + int dosmooth = 0; + int doarrow = 0; /* 0=none; 1=end; 2=start; 3=both. */ + int arrowshape[3]; + + int nStep = 12; + + int dodash = 0; + const char *dashdata = 0; + + arrowshape[0] = 8; + arrowshape[1] = 10; + arrowshape[2] = 3; + + /* Verrrrrry simple for now.... */ + if (argc < 6) { + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + + hDC = printDC; + + polypoints = (POINT *)attemptckalloc((argc - 1) * sizeof(POINT)); + if (polypoints == 0) { + Tcl_AppendResult(interp, "Out of memory in GdiLine", (char *)NULL); + return TCL_ERROR; + } + if ((Tcl_GetIntFromObj(interp, objv[2], (int *)&polypoints[0].x) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[3], (int *)&polypoints[0].y) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[4], (int *)&polypoints[1].x) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[5], (int *)&polypoints[1].y) != TCL_OK) + ) { + return TCL_ERROR; + } + argc -= 6; + objv += 6; + npoly = 2; + + while (argc >= 2) { + /* Check for a number. */ + x = strtoul(Tcl_GetString(objv[0]), &strend, 0); + if (strend > Tcl_GetString(objv[0])) { + /* One number.... */ + y = strtoul(Tcl_GetString(objv[1]), &strend, 0); + if (strend > Tcl_GetString(objv[1])) { + /* TWO numbers!. */ + polypoints[npoly].x = x; + polypoints[npoly].y = y; + npoly++; + argc -= 2; + objv += 2; + } else { + /* Only one number... Assume a usage error. */ + ckfree(polypoints); + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + } else { + if (strcmp(Tcl_GetString(*objv), "-arrow") == 0) { + if (strcmp(Tcl_GetString(objv[1]), "none") == 0) { + doarrow = 0; + } else if (strcmp(Tcl_GetString(objv[1]), "both") == 0) { + doarrow = 3; + } else if (strcmp(Tcl_GetString(objv[1]), "first") == 0) { + doarrow = 2; + } else if (strcmp(Tcl_GetString(objv[1]), "last") == 0) { + doarrow = 1; + } + objv += 2; + argc -= 2; + } else if (strcmp(Tcl_GetString(*objv), "-arrowshape") == 0) { + /* List of 3 numbers--set arrowshape array. */ + int a1, a2, a3; + char dummy; + + if (sscanf(Tcl_GetString(objv[1]), "%d%d%d%c", &a1, &a2, &a3, &dummy) == 3 + && a1 > 0 && a2 > 0 && a3 > 0) { + arrowshape[0] = a1; + arrowshape[1] = a2; + arrowshape[2] = a3; + } + /* Else the argument was bad. */ + + objv += 2; + argc -= 2; + } else if (strcmp(Tcl_GetString(*objv), "-capstyle") == 0) { + objv += 2; + argc -= 2; + } else if (strcmp(Tcl_GetString(*objv), "-fill") == 0) { + if (GdiGetColor(objv[1], &linecolor)) { + dolinecolor = 1; + } + objv += 2; + argc -= 2; + } else if (strcmp(Tcl_GetString(*objv), "-joinstyle") == 0) { + objv += 2; + argc -= 2; + } else if (strcmp(Tcl_GetString(*objv), "-smooth") == 0) { + /* Argument is true/false or 1/0 or bezier. */ + if (Tcl_GetString(objv[1])) { + switch (Tcl_GetString(objv[1])[0]) { + case 't': case 'T': + case '1': + case 'b': case 'B': /* bezier. */ + dosmooth = 1; + break; + default: + dosmooth = 0; + break; + } + objv += 2; + argc -= 2; + } + } else if (strcmp(Tcl_GetString(*objv), "-splinesteps") == 0) { + if (Tcl_GetIntFromObj(interp, objv[1], &nStep) != TCL_OK) { + return TCL_ERROR; + } + objv += 2; + argc -= 2; + } else if (strcmp(Tcl_GetString(*objv), "-dash") == 0) { + if (Tcl_GetString(objv[1])) { + dodash = 1; + dashdata = Tcl_GetString(objv[1]); + } + objv += 2; + argc -= 2; + } else if (strcmp(Tcl_GetString(*objv), "-dashoffset") == 0) { + objv += 2; + argc -= 2; + } else if (strcmp(Tcl_GetString(*objv), "-stipple") == 0) { + objv += 2; + argc -= 2; + } else if (strcmp(Tcl_GetString(*objv), "-width") == 0) { + if (Tcl_GetIntFromObj(interp, objv[1], &width) != TCL_OK) { + return TCL_ERROR; + } + objv += 2; + argc -= 2; + } else { /* It's an unknown argument!. */ + argc--; + objv++; + } + /* Check for arguments + * Most of the arguments affect the "Pen" + */ + } + } + + if (width || dolinecolor || dodash) { + GdiMakePen(interp, width, dodash, dashdata, + 0, 0, 0, 0, linecolor, hDC, (HGDIOBJ *)&hPen); + } + if (doarrow != 0) { + GdiMakeBrush(linecolor, 0, &lbrush, hDC, &hBrush); + } + + if (dosmooth) { /* Use PolyBezier. */ + int nbpoints; + POINT *bpoints = 0; + + nbpoints = Bezierize(polypoints,npoly,nStep,bpoints); + if (nbpoints > 0) { + Polyline(hDC, bpoints, nbpoints); + } else { + Polyline(hDC, polypoints, npoly); /* Out of memory? Just draw a regular line. */ + } + if (bpoints != 0) { + ckfree(bpoints); + } + } else { + Polyline(hDC, polypoints, npoly); + } + + if (dodash && doarrow) { /* Don't use dashed or thick pen for the arrows! */ + GdiFreePen(interp, hDC, hPen); + GdiMakePen(interp, width, 0, 0, 0, 0, 0, 0, + linecolor, hDC, (HGDIOBJ *)&hPen); + } + + /* Now the arrowheads, if any. */ + if (doarrow & 1) { + /* Arrowhead at end = polypoints[npoly-1].x, polypoints[npoly-1].y. */ + POINT ahead[6]; + double dx, dy, length; + double sinTheta, cosTheta; + double vertX, vertY, temp; + double fracHeight; + + fracHeight = 2.0 / arrowshape[2]; + + ahead[0].x = ahead[5].x = polypoints[npoly-1].x; + ahead[0].y = ahead[5].y = polypoints[npoly-1].y; + dx = ahead[0].x - polypoints[npoly-2].x; + dy = ahead[0].y - polypoints[npoly-2].y; + if ((length = hypot(dx, dy)) == 0) { + sinTheta = cosTheta = 0.0; + } else { + sinTheta = dy / length; + cosTheta = dx / length; + } + vertX = ahead[0].x - arrowshape[0]*cosTheta; + vertY = ahead[0].y - arrowshape[0]*sinTheta; + temp = arrowshape[2]*sinTheta; + ahead[1].x = (long)(ahead[0].x - arrowshape[1]*cosTheta + temp); + ahead[4].x = (long)(ahead[1].x - 2 * temp); + temp = arrowshape[2]*cosTheta; + ahead[1].y = (long)(ahead[0].y - arrowshape[1]*sinTheta - temp); + ahead[4].y = (long)(ahead[1].y + 2 * temp); + ahead[2].x = (long)(ahead[1].x*fracHeight + vertX*(1.0-fracHeight)); + ahead[2].y = (long)(ahead[1].y*fracHeight + vertY*(1.0-fracHeight)); + ahead[3].x = (long)(ahead[4].x*fracHeight + vertX*(1.0-fracHeight)); + ahead[3].y = (long)(ahead[4].y*fracHeight + vertY*(1.0-fracHeight)); + + Polygon(hDC, ahead, 6); + } + + if (doarrow & 2) { + /* Arrowhead at end = polypoints[0].x, polypoints[0].y. */ + POINT ahead[6]; + double dx, dy, length; + double sinTheta, cosTheta; + double vertX, vertY, temp; + double fracHeight; + + fracHeight = 2.0 / arrowshape[2]; + + ahead[0].x = ahead[5].x = polypoints[0].x; + ahead[0].y = ahead[5].y = polypoints[0].y; + dx = ahead[0].x - polypoints[1].x; + dy = ahead[0].y - polypoints[1].y; + if ((length = hypot(dx, dy)) == 0) { + sinTheta = cosTheta = 0.0; + } else { + sinTheta = dy / length; + cosTheta = dx / length; + } + vertX = ahead[0].x - arrowshape[0]*cosTheta; + vertY = ahead[0].y - arrowshape[0]*sinTheta; + temp = arrowshape[2]*sinTheta; + ahead[1].x = (long)(ahead[0].x - arrowshape[1]*cosTheta + temp); + ahead[4].x = (long)(ahead[1].x - 2 * temp); + temp = arrowshape[2]*cosTheta; + ahead[1].y = (long)(ahead[0].y - arrowshape[1]*sinTheta - temp); + ahead[4].y = (long)(ahead[1].y + 2 * temp); + ahead[2].x = (long)(ahead[1].x*fracHeight + vertX*(1.0-fracHeight)); + ahead[2].y = (long)(ahead[1].y*fracHeight + vertY*(1.0-fracHeight)); + ahead[3].x = (long)(ahead[4].x*fracHeight + vertX*(1.0-fracHeight)); + ahead[3].y = (long)(ahead[4].y*fracHeight + vertY*(1.0-fracHeight)); + + Polygon(hDC, ahead, 6); + } + + if (width || dolinecolor || dodash) { + GdiFreePen(interp, hDC, hPen); + } + if (hBrush) { + GdiFreeBrush(interp, hDC, hBrush); + } + + ckfree(polypoints); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GdiOval -- + * + * Maps ovals to GDI context. + * + * Results: + * Renders ovals. + * + *---------------------------------------------------------------------- + */ + +static int GdiOval( + TCL_UNUSED(void *), + Tcl_Interp *interp, + int argc, + Tcl_Obj *const *objv) +{ + static const char usage_message[] = + "::tk::print::_gdi oval hdc x1 y1 x2 y2 -fill color -outline color " + "-stipple bitmap -width linewid"; + int x1, y1, x2, y2; + HDC hDC; + HPEN hPen; + int width = 0; + COLORREF linecolor = 0, fillcolor = 0; + int dolinecolor = 0, dofillcolor = 0; + HBRUSH hBrush = NULL; + LOGBRUSH lbrush; + HGDIOBJ oldobj = NULL; + + int dodash = 0; + const char *dashdata = 0; + + /* Verrrrrry simple for now.... */ + if (argc < 6) { + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + + hDC = printDC; + + if ((Tcl_GetIntFromObj(interp, objv[2], &x1) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[2], &y1) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[3], &x2) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[4], &y2) != TCL_OK)) { + return TCL_ERROR; + } + if (x1 > x2) { + int x3 = x1; + x1 = x2; + x2 = x3; + } + if (y1 > y2) { + int y3 = y1; + y1 = y2; + y2 = y3; + } + argc -= 6; + objv += 6; + + while (argc > 0) { + /* Now handle any other arguments that occur. */ + if (strcmp(Tcl_GetString(objv[0]), "-fill") == 0) { + if (Tcl_GetString(objv[1]) && GdiGetColor(objv[1], &fillcolor)) { + dofillcolor = 1; + } + } else if (strcmp(Tcl_GetString(objv[0]), "-outline") == 0) { + if (Tcl_GetString(objv[1]) && GdiGetColor(objv[1], &linecolor)) { + dolinecolor = 1; + } + } else if (strcmp(Tcl_GetString(objv[0]), "-stipple") == 0) { + /* Not actually implemented */ + } else if (strcmp(Tcl_GetString(objv[0]), "-width") == 0) { + if (Tcl_GetString(objv[1])) { + if (Tcl_GetIntFromObj(interp, objv[1], &width) != TCL_OK) { + return TCL_ERROR; + } + } + } else if (strcmp(Tcl_GetString(objv[0]), "-dash") == 0) { + if (Tcl_GetString(objv[1])) { + dodash = 1; + dashdata = Tcl_GetString(objv[1]); + } + } + objv += 2; + argc -= 2; + } + + if (dofillcolor) { + GdiMakeBrush(fillcolor, 0, &lbrush, hDC, &hBrush); + } else { + oldobj = SelectObject(hDC, GetStockObject(HOLLOW_BRUSH)); + } + + if (width || dolinecolor) { + GdiMakePen(interp, width, dodash, dashdata, + 0, 0, 0, 0, linecolor, hDC, (HGDIOBJ *)&hPen); + } + /* + * Per Win32, Rectangle includes lower and right edges--per Tcl8.3.2 and + * earlier documentation, canvas rectangle does not. Thus, add 1 to right + * and lower bounds to get appropriate behavior. + */ + Ellipse(hDC, x1, y1, x2+1, y2+1); + + if (width || dolinecolor) { + GdiFreePen(interp, hDC, hPen); + } + if (hBrush) { + GdiFreeBrush(interp, hDC, hBrush); + } else { + SelectObject(hDC, oldobj); + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GdiPolygon -- + * + * Maps polygons to GDI context. + * + * Results: + * Renders polygons. + * + *---------------------------------------------------------------------- + */ + +static int GdiPolygon( + TCL_UNUSED(void *), + Tcl_Interp *interp, + int argc, + Tcl_Obj *const *objv) +{ + static const char usage_message[] = + "::tk::print::_gdi polygon hdc x1 y1 ... xn yn " + "-fill color -outline color -smooth [true|false|bezier] " + "-splinesteps number -stipple bitmap -width linewid"; + + char *strend; + POINT *polypoints; + int npoly; + int dosmooth = 0; + int nStep = 12; + int x, y; + HDC hDC; + HPEN hPen; + int width = 0; + COLORREF linecolor = 0, fillcolor = BS_NULL; + int dolinecolor = 0, dofillcolor = 0; + LOGBRUSH lbrush; + HBRUSH hBrush = NULL; + HGDIOBJ oldobj = NULL; + + int dodash = 0; + const char *dashdata = 0; + + /* Verrrrrry simple for now.... */ + if (argc < 6) { + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + + hDC = printDC; + + polypoints = (POINT *)attemptckalloc((argc - 1) * sizeof(POINT)); + if (polypoints == 0) { + /* TODO: unreachable */ + Tcl_AppendResult(interp, "Out of memory in GdiLine", (char *)NULL); + return TCL_ERROR; + } + if ((Tcl_GetIntFromObj(interp, objv[2], (int *)&polypoints[0].x) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[3], (int *)&polypoints[0].y) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[4], (int *)&polypoints[1].x) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[5], (int *)&polypoints[1].y) != TCL_OK)) { + return TCL_ERROR; + } + argc -= 6; + objv += 6; + npoly = 2; + + while (argc >= 2) { + /* Check for a number */ + x = strtoul(Tcl_GetString(objv[0]), &strend, 0); + if (strend > Tcl_GetString(objv[0])) { + /* One number.... */ + y = strtoul(Tcl_GetString(objv[1]), &strend, 0); + if (strend > Tcl_GetString(objv[1])) { + /* TWO numbers!. */ + polypoints[npoly].x = x; + polypoints[npoly].y = y; + npoly++; + argc -= 2; + objv += 2; + } else { + /* Only one number... Assume a usage error. */ + ckfree(polypoints); + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + } else { + /* + * Check for arguments. + * Most of the arguments affect the "Pen" and "Brush". + */ + if (strcmp(Tcl_GetString(objv[0]), "-fill") == 0) { + if (Tcl_GetString(objv[1]) && GdiGetColor(objv[1], &fillcolor)) { + dofillcolor = 1; + } + } else if (strcmp(Tcl_GetString(objv[0]), "-outline") == 0) { + if (GdiGetColor(objv[1], &linecolor)) { + dolinecolor = 0; + } + } else if (strcmp(Tcl_GetString(objv[0]), "-smooth") == 0) { + if (Tcl_GetString(objv[1])) { + switch (Tcl_GetString(objv[1])[0]) { + case 't': case 'T': + case '1': + case 'b': case 'B': /* bezier. */ + dosmooth = 1; + break; + default: + dosmooth = 0; + break; + } + } + } else if (strcmp(Tcl_GetString(objv[0]), "-splinesteps") == 0) { + if (Tcl_GetString(objv[1])) { + if (Tcl_GetIntFromObj(interp, objv[1], &nStep) != TCL_OK) { + return TCL_ERROR; + } + } + } else if (strcmp(Tcl_GetString(objv[0]), "-stipple") == 0) { + /* Not supported */ + } else if (strcmp(Tcl_GetString(objv[0]), "-width") == 0) { + if (Tcl_GetString(objv[1])) { + if (Tcl_GetIntFromObj(interp, objv[1], &width) != TCL_OK) { + return TCL_ERROR; + } + } + } else if (strcmp(Tcl_GetString(objv[0]), "-dash") == 0) { + if (Tcl_GetString(objv[1])) { + dodash = 1; + dashdata = Tcl_GetString(objv[1]); + } + } + argc -= 2; + objv += 2; + } + } + + if (dofillcolor) { + GdiMakeBrush(fillcolor, 0, &lbrush, hDC, &hBrush); + } else { + oldobj = SelectObject(hDC, GetStockObject(HOLLOW_BRUSH)); + } + + if (width || dolinecolor) { + GdiMakePen(interp, width, dodash, dashdata, 0, 0, 0, 0, + linecolor, hDC, (HGDIOBJ *)&hPen); + } + + if (dosmooth) { + int nbpoints; + POINT *bpoints = 0; + nbpoints = Bezierize(polypoints,npoly,nStep,bpoints); + if (nbpoints > 0) { + Polygon(hDC, bpoints, nbpoints); + } else { + Polygon(hDC, polypoints, npoly); + } + if (bpoints != 0) { + ckfree(bpoints); + } + } else { + Polygon(hDC, polypoints, npoly); + } + + if (width || dolinecolor) { + GdiFreePen(interp, hDC, hPen); + } + if (hBrush) { + GdiFreeBrush(interp, hDC, hBrush); + } else { + SelectObject(hDC, oldobj); + } + + ckfree(polypoints); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GdiRectangle -- + * + * Maps rectangles to GDI context. + * + * Results: + * Renders rectangles. + * + *---------------------------------------------------------------------- + */ + +static int GdiRectangle( + TCL_UNUSED(void *), + Tcl_Interp *interp, + int argc, + Tcl_Obj *const *objv) +{ + static const char usage_message[] = + "::tk::print::_gdi rectangle hdc x1 y1 x2 y2 " + "-fill color -outline color " + "-stipple bitmap -width linewid"; + + int x1, y1, x2, y2; + HDC hDC; + HPEN hPen; + int width = 0; + COLORREF linecolor = 0, fillcolor = BS_NULL; + int dolinecolor = 0, dofillcolor = 0; + LOGBRUSH lbrush; + HBRUSH hBrush = NULL; + HGDIOBJ oldobj = NULL; + + int dodash = 0; + const char *dashdata = 0; + + /* Verrrrrry simple for now.... */ + if (argc < 6) { + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + + hDC = printDC; + + if ((Tcl_GetIntFromObj(interp, objv[2], &x1) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[3], &y1) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[4], &x2) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[5], &y2) != TCL_OK)) { + return TCL_ERROR; + } + if (x1 > x2) { + int x3 = x1; + x1 = x2; + x2 = x3; + } + if (y1 > y2) { + int y3 = y1; + y1 = y2; + y2 = y3; + } + argc -= 6; + objv += 6; + + /* Now handle any other arguments that occur. */ + while (argc > 1) { + if (strcmp(Tcl_GetString(objv[0]), "-fill") == 0) { + if (Tcl_GetString(objv[1]) && GdiGetColor(objv[1], &fillcolor)) { + dofillcolor = 1; + } + } else if (strcmp(Tcl_GetString(objv[0]), "-outline") == 0) { + if (Tcl_GetString(objv[1]) && GdiGetColor(objv[1], &linecolor)) { + dolinecolor = 1; + } + } else if (strcmp(Tcl_GetString(objv[0]), "-stipple") == 0) { + /* Not supported; ignored */ + } else if (strcmp(Tcl_GetString(objv[0]), "-width") == 0) { + if (Tcl_GetString(objv[1])) { + if (Tcl_GetIntFromObj(interp, objv[1], &width) != TCL_OK) { + return TCL_ERROR; + } + } + } else if (strcmp(Tcl_GetString(objv[0]), "-dash") == 0) { + if (Tcl_GetString(objv[1])) { + dodash = 1; + dashdata = Tcl_GetString(objv[1]); + } + } + + argc -= 2; + objv += 2; + } + + /* + * Note: If any fill is specified, the function must create a brush and + * put the coordinates in a RECTANGLE structure, and call FillRect. + * FillRect requires a BRUSH / color. + * If not, the function Rectangle must be called. + */ + if (dofillcolor) { + GdiMakeBrush(fillcolor, 0, &lbrush, hDC, &hBrush); + } else { + oldobj = SelectObject(hDC, GetStockObject(HOLLOW_BRUSH)); + } + + if (width || dolinecolor) { + GdiMakePen(interp, width, dodash, dashdata, + 0, 0, 0, 0, linecolor, hDC, (HGDIOBJ *)&hPen); + } + /* + * Per Win32, Rectangle includes lower and right edges--per Tcl8.3.2 and + * earlier documentation, canvas rectangle does not. Thus, add 1 to + * right and lower bounds to get appropriate behavior. + */ + Rectangle(hDC, x1, y1, x2+1, y2+1); + + if (width || dolinecolor) { + GdiFreePen(interp, hDC, hPen); + } + if (hBrush) { + GdiFreeBrush(interp, hDC, hBrush); + } else { + SelectObject(hDC, oldobj); + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GdiCharWidths -- + * + * Computes /character widths. This is completely inadequate for + * typesetting, but should work for simple text manipulation. + * + * Results: + * Returns character width. + * + *---------------------------------------------------------------------- + */ + + +static int GdiCharWidths( + TCL_UNUSED(void *), + Tcl_Interp *interp, + int argc, + Tcl_Obj *const *objv) +{ + static const char usage_message[] = + "::tk::print::_gdi characters hdc [-font fontname] [-array ary]"; + /* + * Returns widths of characters from font in an associative array. + * Font is currently selected font for HDC if not specified. + * Array name is GdiCharWidths if not specified. + * Widths should be in the same measures as all other values (1/1000 inch). + */ + + HDC hDC; + LOGFONTW lf; + HFONT hfont, oldfont; + int made_font = 0; + const char *aryvarname = "GdiCharWidths"; + /* For now, assume 256 characters in the font.... */ + int widths[256]; + int retval; + + if (argc < 2) { + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + + hDC = printDC; + + argc -= 2; + objv += 2; + + while (argc > 0) { + if (strcmp(Tcl_GetString(objv[0]), "-font") == 0) { + argc--; + objv++; + if (GdiMakeLogFont(interp, Tcl_GetString(objv[0]), &lf, hDC)) { + if ((hfont = CreateFontIndirectW(&lf)) != NULL) { + made_font = 1; + oldfont = SelectObject(hDC, hfont); + } + } + /* Else leave the font alone!. */ + } else if (strcmp(Tcl_GetString(objv[0]), "-array") == 0) { + objv++; + argc--; + if (argc > 0) { + aryvarname = Tcl_GetString(objv[0]); + } + } + objv++; + argc--; + } + + /* Now, get the widths using the correct function for font type. */ + if ((retval = GetCharWidth32W(hDC, 0, 255, widths)) == FALSE) { + retval = GetCharWidthW(hDC, 0, 255, widths); + } + + /* + * Retval should be 1 (TRUE) if the function succeeded. If the function + * fails, get the "extended" error code and return. Be sure to deallocate + * the font if necessary. + */ + if (retval == FALSE) { + DWORD val = GetLastError(); + + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "::tk::print::_gdi character failed with code %ld", val)); + if (made_font) { + SelectObject(hDC, oldfont); + DeleteObject(hfont); + } + return TCL_ERROR; + } + + { + int i; + char ind[2]; + ind[1] = '\0'; + + for (i = 0; i < 255; i++) { + /* TODO: use a bytearray for the index name so NUL works */ + ind[0] = i; + Tcl_SetVar2Ex(interp, aryvarname, ind, Tcl_NewIntObj(widths[i]), + TCL_GLOBAL_ONLY); + } + } + /* Now, remove the font if we created it only for this function. */ + if (made_font) { + SelectObject(hDC, oldfont); + DeleteObject(hfont); + } + + /* The return value should be the array name(?). */ + Tcl_AppendResult(interp, aryvarname, (char *)NULL); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GdiText -- + * + * Maps text to GDI context. + * + * Results: + * Renders text. + * + *---------------------------------------------------------------------- + */ + +int GdiText( + TCL_UNUSED(void *), + Tcl_Interp *interp, + int argc, + Tcl_Obj *const *objv) +{ + static const char usage_message[] = + "::tk::print::_gdi text hdc x y -anchor [center|n|e|s|w] " + "-fill color -font fontname " + "-justify [left|right|center] " + "-stipple bitmap -text string -width linelen " + "-single -backfill"; + + HDC hDC; + int x, y; + const char *string = 0; + RECT sizerect; + UINT format_flags = DT_EXPANDTABS|DT_NOPREFIX; /* Like the canvas. */ + Tk_Anchor anchor = 0; + LOGFONTW lf; + HFONT hfont, oldfont; + int made_font = 0; + int retval; + int dotextcolor = 0; + int dobgmode = 0; + int bgmode; + COLORREF textcolor = 0; + int usesingle = 0; + WCHAR *wstring; + Tcl_DString tds; + + if (argc < 4) { + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + + /* Parse the command. */ + + hDC = printDC; + + if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK) + || (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) { + return TCL_ERROR; + } + argc -= 4; + objv += 4; + + sizerect.left = sizerect.right = x; + sizerect.top = sizerect.bottom = y; + + while (argc > 0) { + if (strcmp(Tcl_GetString(objv[0]), "-anchor") == 0) { + argc--; + objv++; + if (argc > 0) { + Tk_GetAnchor(interp, Tcl_GetString(objv[0]), &anchor); + } + } else if (strcmp(Tcl_GetString(objv[0]), "-justify") == 0) { + argc--; + objv++; + if (argc > 0) { + if (strcmp(Tcl_GetString(objv[0]), "left") == 0) { + format_flags |= DT_LEFT; + } else if (strcmp(Tcl_GetString(objv[0]), "center") == 0) { + format_flags |= DT_CENTER; + } else if (strcmp(Tcl_GetString(objv[0]), "right") == 0) { + format_flags |= DT_RIGHT; + } + } + } else if (strcmp(Tcl_GetString(objv[0]), "-text") == 0) { + argc--; + objv++; + if (argc > 0) { + string = Tcl_GetString(objv[0]); + } + } else if (strcmp(Tcl_GetString(objv[0]), "-font") == 0) { + argc--; + objv++; + if (GdiMakeLogFont(interp, Tcl_GetString(objv[0]), &lf, hDC)) { + if ((hfont = CreateFontIndirectW(&lf)) != NULL) { + made_font = 1; + oldfont = SelectObject(hDC, hfont); + } + } + /* Else leave the font alone! */ + } else if (strcmp(Tcl_GetString(objv[0]), "-stipple") == 0) { + argc--; + objv++; + /* Not implemented yet. */ + } else if (strcmp(Tcl_GetString(objv[0]), "-fill") == 0) { + argc--; + objv++; + /* Get text color. */ + if (GdiGetColor(objv[0], &textcolor)) { + dotextcolor = 1; + } + } else if (strcmp(Tcl_GetString(objv[0]), "-width") == 0) { + argc--; + objv++; + if (argc > 0) { + int value; + if (Tcl_GetIntFromObj(interp, objv[0], &value) != TCL_OK) { + return TCL_ERROR; + } + sizerect.right += value; + } + /* If a width is specified, break at words. */ + format_flags |= DT_WORDBREAK; + } else if (strcmp(Tcl_GetString(objv[0]), "-single") == 0) { + usesingle = 1; + } else if (strcmp(Tcl_GetString(objv[0]), "-backfill") == 0) { + dobgmode = 1; + } + + argc--; + objv++; + } + + if (string == 0) { + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + + /* Set the format flags for -single: Overrides -width. */ + if (usesingle == 1) { + format_flags |= DT_SINGLELINE; + format_flags |= DT_NOCLIP; + format_flags &= ~DT_WORDBREAK; + } + + Tcl_DStringInit(&tds); + /* Just for fun, let's try translating string to Unicode. */ + wstring = Tcl_UtfToWCharDString(string, TCL_INDEX_NONE, &tds); + DrawTextW(hDC, wstring, Tcl_DStringLength(&tds)/2, &sizerect, + format_flags | DT_CALCRECT); + + /* Adjust the rectangle according to the anchor. */ + x = y = 0; + switch (anchor) { + case TK_ANCHOR_N: + x = (sizerect.right - sizerect.left) / 2; + break; + case TK_ANCHOR_S: + x = (sizerect.right - sizerect.left) / 2; + y = (sizerect.bottom - sizerect.top); + break; + case TK_ANCHOR_E: + x = (sizerect.right - sizerect.left); + y = (sizerect.bottom - sizerect.top) / 2; + break; + case TK_ANCHOR_W: + y = (sizerect.bottom - sizerect.top) / 2; + break; + case TK_ANCHOR_NE: + x = (sizerect.right - sizerect.left); + break; + case TK_ANCHOR_NW: + break; + case TK_ANCHOR_SE: + x = (sizerect.right - sizerect.left); + y = (sizerect.bottom - sizerect.top); + break; + case TK_ANCHOR_SW: + y = (sizerect.bottom - sizerect.top); + break; + default: + x = (sizerect.right - sizerect.left) / 2; + y = (sizerect.bottom - sizerect.top) / 2; + break; + } + sizerect.right -= x; + sizerect.left -= x; + sizerect.top -= y; + sizerect.bottom -= y; + + /* Get the color right. */ + if (dotextcolor) { + textcolor = SetTextColor(hDC, textcolor); + } + + if (dobgmode) { + bgmode = SetBkMode(hDC, OPAQUE); + } else { + bgmode = SetBkMode(hDC, TRANSPARENT); + } + + /* Print the text. */ + retval = DrawTextW(hDC, wstring, + Tcl_DStringLength(&tds)/2, &sizerect, format_flags); + Tcl_DStringFree(&tds); + + /* Get the color set back. */ + if (dotextcolor) { + textcolor = SetTextColor(hDC, textcolor); + } + SetBkMode(hDC, bgmode); + if (made_font) { + SelectObject(hDC, oldfont); + DeleteObject(hfont); + } + + /* In this case, the return value is the height of the text. */ + Tcl_SetObjResult(interp, Tcl_NewIntObj(retval)); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GdiGetHdcInfo -- + * + * Gets salient characteristics of the CTM. + * + * Results: + * The return value is 0 if any failure occurs--in which case none of the + * other values are meaningful. Otherwise the return value is the + * current mapping mode. + * + *---------------------------------------------------------------------- + */ + +static int GdiGetHdcInfo( + HDC hdc, + LPPOINT worigin, + LPSIZE wextent, + LPPOINT vorigin, + LPSIZE vextent) +{ + int mapmode; + int retval; + + memset(worigin, 0, sizeof(POINT)); + memset(vorigin, 0, sizeof(POINT)); + memset(wextent, 0, sizeof(SIZE)); + memset(vextent, 0, sizeof(SIZE)); + + if ((mapmode = GetMapMode(hdc)) == 0) { + /* Failed! */ + retval = 0; + } else { + retval = mapmode; + } + + if (GetWindowExtEx(hdc, wextent) == FALSE) { + /* Failed! */ + retval = 0; + } + if (GetViewportExtEx(hdc, vextent) == FALSE) { + /* Failed! */ + retval = 0; + } + if (GetWindowOrgEx(hdc, worigin) == FALSE) { + /* Failed! */ + retval = 0; + } + if (GetViewportOrgEx(hdc, vorigin) == FALSE) { + /* Failed! */ + retval = 0; + } + + return retval; +} + +/* + *---------------------------------------------------------------------- + * + * GdiNameToMode -- + * + * Converts Windows mapping mode names. + * + * Results: + * Mapping modes are delineated. + * + *---------------------------------------------------------------------- + */ + +static int GdiNameToMode( + const char *name) +{ + static const struct gdimodes { + int mode; + const char *name; + } modes[] = { + { MM_ANISOTROPIC, "MM_ANISOTROPIC" }, + { MM_HIENGLISH, "MM_HIENGLISH" }, + { MM_HIMETRIC, "MM_HIMETRIC" }, + { MM_ISOTROPIC, "MM_ISOTROPIC" }, + { MM_LOENGLISH, "MM_LOENGLISH" }, + { MM_LOMETRIC, "MM_LOMETRIC" }, + { MM_TEXT, "MM_TEXT" }, + { MM_TWIPS, "MM_TWIPS" } + }; + + size_t i; + for (i=0; i < sizeof(modes) / sizeof(struct gdimodes); i++) { + if (strcmp(modes[i].name, name) == 0) { + return modes[i].mode; + } + } + return atoi(name); +} + +/* + *---------------------------------------------------------------------- + * + * GdiModeToName -- + * + * Converts the mode number to a printable form. + * + * Results: + * Mapping numbers are delineated. + * + *---------------------------------------------------------------------- + */ + +static const char *GdiModeToName( + int mode) +{ + static const struct gdi_modes { + int mode; + const char *name; + } modes[] = { + { MM_ANISOTROPIC, "Anisotropic" }, + { MM_HIENGLISH, "1/1000 inch" }, + { MM_HIMETRIC, "1/100 mm" }, + { MM_ISOTROPIC, "Isotropic" }, + { MM_LOENGLISH, "1/100 inch" }, + { MM_LOMETRIC, "1/10 mm" }, + { MM_TEXT, "1 to 1" }, + { MM_TWIPS, "1/1440 inch" } + }; + + size_t i; + for (i=0; i < sizeof(modes) / sizeof(struct gdi_modes); i++) { + if (modes[i].mode == mode) { + return modes[i].name; + } + } + return "Unknown"; +} + +/* + *---------------------------------------------------------------------- + * + * GdiMap -- + * + * Sets mapping mode between logical and physical device space. + * + * Results: + * Bridges map modes. + * + *---------------------------------------------------------------------- + */ + +static int GdiMap( + TCL_UNUSED(void *), + Tcl_Interp *interp, + int argc, + Tcl_Obj *const *objv) +{ + static const char usage_message[] = + "::tk::print::_gdi map hdc " + "[-logical x[y]] [-physical x[y]] " + "[-offset {x y} ] [-default] [-mode mode]"; + HDC hdc; + int mapmode; /* Mapping mode. */ + SIZE wextent; /* Device extent. */ + SIZE vextent; /* Viewport extent. */ + POINT worigin; /* Device origin. */ + POINT vorigin; /* Viewport origin. */ + int argno; + + /* Keep track of what parts of the function need to be executed. */ + int need_usage = 0; + int use_logical = 0; + int use_physical = 0; + int use_offset = 0; + int use_default = 0; + int use_mode = 0; + + /* Required parameter: HDC for printer. */ + if (argc < 2) { + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + + hdc = printDC; + + if ((mapmode = GdiGetHdcInfo(hdc, &worigin, &wextent, &vorigin, &vextent)) == 0) { + /* Failed!. */ + Tcl_AppendResult(interp, "Cannot get current HDC info", (char *)NULL); + return TCL_ERROR; + } + + /* Parse remaining arguments. */ + for (argno = 2; argno < argc; argno++) { + if (strcmp(Tcl_GetString(objv[argno]), "-default") == 0) { + vextent.cx = vextent.cy = wextent.cx = wextent.cy = 1; + vorigin.x = vorigin.y = worigin.x = worigin.y = 0; + mapmode = MM_TEXT; + use_default = 1; + } else if (strcmp(Tcl_GetString(objv[argno]), "-mode") == 0) { + if (argno + 1 >= argc) { + need_usage = 1; + } else { + mapmode = GdiNameToMode(Tcl_GetString(objv[argno + 1])); + use_mode = 1; + argno++; + } + } else if (strcmp(Tcl_GetString(objv[argno]), "-offset") == 0) { + if (argno + 1 >= argc) { + need_usage = 1; + } else { + /* It would be nice if this parsed units as well.... */ + if (sscanf(Tcl_GetString(objv[argno + 1]), "%ld%ld", + &vorigin.x, &vorigin.y) == 2) { + use_offset = 1; + } else { + need_usage = 1; + } + argno++; + } + } else if (strcmp(Tcl_GetString(objv[argno]), "-logical") == 0) { + if (argno + 1 >= argc) { + need_usage = 1; + } else { + int count; + + argno++; + /* In "real-life", this should parse units as well.. */ + if ((count = sscanf(Tcl_GetString(objv[argno]), "%ld%ld", + &wextent.cx, &wextent.cy)) != 2) { + if (count == 1) { + mapmode = MM_ISOTROPIC; + use_logical = 1; + wextent.cy = wextent.cx; /* Make them the same. */ + } else { + need_usage = 1; + } + } else { + mapmode = MM_ANISOTROPIC; + use_logical = 2; + } + } + } else if (strcmp(Tcl_GetString(objv[argno]), "-physical") == 0) { + if (argno + 1 >= argc) { + need_usage = 1; + } else { + int count; + + argno++; + /* In "real-life", this should parse units as well.. */ + if ((count = sscanf(Tcl_GetString(objv[argno]), "%ld%ld", + &vextent.cx, &vextent.cy)) != 2) { + if (count == 1) { + mapmode = MM_ISOTROPIC; + use_physical = 1; + vextent.cy = vextent.cx; /* Make them the same. */ + } else { + need_usage = 1; + } + } else { + mapmode = MM_ANISOTROPIC; + use_physical = 2; + } + } + } + } + + /* Check for any impossible combinations. */ + if (use_logical != use_physical) { + need_usage = 1; + } + if (use_default && (use_logical || use_offset || use_mode)) { + need_usage = 1; + } + if (use_mode && use_logical && + (mapmode != MM_ISOTROPIC && mapmode != MM_ANISOTROPIC)) { + need_usage = 1; + } + + if (need_usage) { + Tcl_AppendResult(interp, usage_message, NULL); + return TCL_ERROR; + } + + /* Call Windows CTM functions. */ + if (use_logical || use_default || use_mode) { /* Don't call for offset only. */ + SetMapMode(hdc, mapmode); + } + + if (use_offset || use_default) { + POINT oldorg; + SetViewportOrgEx(hdc, vorigin.x, vorigin.y, &oldorg); + SetWindowOrgEx(hdc, worigin.x, worigin.y, &oldorg); + } + + if (use_logical) { /* Same as use_physical. */ + SIZE oldsiz; + SetWindowExtEx(hdc, wextent.cx, wextent.cy, &oldsiz); + SetViewportExtEx(hdc, vextent.cx, vextent.cy, &oldsiz); + } + + /* + * Since we may not have set up every parameter, get them again for the + * report. + */ + mapmode = GdiGetHdcInfo(hdc, &worigin, &wextent, &vorigin, &vextent); + + /* + * Output current CTM info. + * Note: This should really be in terms that can be used in a + * ::tk::print::_gdi map command! + */ + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "Transform: \"(%ld, %ld) -> (%ld, %ld)\" " + "Origin: \"(%ld, %ld)\" " + "MappingMode: \"%s\"", + vextent.cx, vextent.cy, wextent.cx, wextent.cy, + vorigin.x, vorigin.y, + GdiModeToName(mapmode))); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * GdiCopyBits -- + * + * Copies window bits from source to destination. + * + * Results: + * Copies window bits. + * + *---------------------------------------------------------------------- + */ + +static int GdiCopyBits( + TCL_UNUSED(void *), + Tcl_Interp *interp, + int argc, + Tcl_Obj *const *objv) +{ + /* Goal: get the Tk_Window from the top-level + * convert it to an HWND + * get the HDC + * Do a bitblt to the given hdc + * Use an optional parameter to point to an arbitrary window instead of + * the main + * Use optional parameters to map to the width and height required for the + * dest. + */ + static const char usage_message[] = + "::tk::print::_gdi copybits hdc [-window w|-screen] [-client] " + "[-source \"a b c d\"] " + "[-destination \"a b c d\"] [-scale number] [-calc]"; + + Tk_Window mainWin; + Tk_Window workwin; + Window wnd; + HDC src; + HDC dst; + HWND hwnd = 0; + + HANDLE hDib; /* Handle for device-independent bitmap. */ + LPBITMAPINFOHEADER lpDIBHdr; + LPSTR lpBits; + enum PrintType wintype = PTWindow; + + int hgt, wid; + char *strend; + long errcode; + int k; + + /* Variables to remember what we saw in the arguments. */ + int do_window = 0; + int do_screen = 0; + int do_scale = 0; + int do_print = 1; + + /* Variables to remember the values in the arguments. */ + const char *window_spec; + double scale = 1.0; + int src_x = 0, src_y = 0, src_w = 0, src_h = 0; + int dst_x = 0, dst_y = 0, dst_w = 0, dst_h = 0; + int is_toplevel = 0; + + /* + * The following steps are peculiar to the top level window. + * There is likely a clever way to do the mapping of a widget pathname to + * the proper window, to support the idea of using a parameter for this + * purpose. + */ + if ((workwin = mainWin = Tk_MainWindow(interp)) == 0) { + Tcl_AppendResult(interp, "Can't find main Tk window", (char *)NULL); + return TCL_ERROR; + } + + /* + * Parse the arguments. + */ + /* HDC is required. */ + if (argc < 2) { + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + + dst = printDC; + + /* + * Next, check to see if 'dst' can support BitBlt. If not, raise an + * error. + */ + if ((GetDeviceCaps(dst, RASTERCAPS) & RC_BITBLT) == 0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "Can't do bitmap operations on device context\n")); + return TCL_ERROR; + } + + /* Loop through the remaining arguments. */ + for (k=2; k<argc; k++) { + if (strcmp(Tcl_GetString(objv[k]), "-window") == 0) { + if (Tcl_GetString(objv[k+1]) && Tcl_GetString(objv[k+1])[0] == '.') { + do_window = 1; + workwin = Tk_NameToWindow(interp, window_spec = Tcl_GetString(objv[++k]), mainWin); + if (workwin == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "Can't find window %s in this application", + window_spec)); + return TCL_ERROR; + } + } else { + /* Use strtoul() so octal or hex representations will be + * parsed. */ + hwnd = (HWND) INT2PTR(strtoul(Tcl_GetString(objv[++k]), &strend, 0)); + if (strend == 0 || strend == Tcl_GetString(objv[k])) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "Can't understand window id %s", Tcl_GetString(objv[k]))); + return TCL_ERROR; + } + } + } else if (strcmp(Tcl_GetString(objv[k]), "-screen") == 0) { + do_screen = 1; + wintype = PTScreen; + } else if (strcmp(Tcl_GetString(objv[k]), "-client") == 0) { + wintype = PTClient; + } else if (strcmp(Tcl_GetString(objv[k]), "-source") == 0) { + float a, b, c, d; + int count = sscanf(Tcl_GetString(objv[++k]), "%f%f%f%f", &a, &b, &c, &d); + + if (count < 2) { /* Can't make heads or tails of it.... */ + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + src_x = (int)a; + src_y = (int)b; + if (count == 4) { + src_w = (int)c; + src_h = (int)d; + } + } else if (strcmp(Tcl_GetString(objv[k]), "-destination") == 0) { + float a, b, c, d; + int count; + + count = sscanf(Tcl_GetString(objv[++k]), "%f%f%f%f", &a, &b, &c, &d); + if (count < 2) { /* Can't make heads or tails of it.... */ + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + dst_x = (int)a; + dst_y = (int)b; + if (count == 3) { + dst_w = (int)c; + dst_h = -1; + } else if (count == 4) { + dst_w = (int)c; + dst_h = (int)d; + } + } else if (strcmp(Tcl_GetString(objv[k]), "-scale") == 0) { + if (Tcl_GetString(objv[++k])) { + if (Tcl_GetDouble(interp, Tcl_GetString(objv[k]), &scale) != TCL_OK) { + return TCL_ERROR; + } + if (scale <= 0.01 || scale >= 100.0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "Unreasonable scale specification %s", Tcl_GetString(objv[k]))); + return TCL_ERROR; + } + do_scale = 1; + } + } else if (strcmp(Tcl_GetString(objv[k]), "-noprint") == 0 + || strncmp(Tcl_GetString(objv[k]), "-calc", 5) == 0) { + /* This option suggested by Pascal Bouvier to get sizes without + * printing. */ + do_print = 0; + } + } + + /* + * Check to ensure no incompatible arguments were used. + */ + if (do_window && do_screen) { + Tcl_AppendResult(interp, usage_message, (char *)NULL); + return TCL_ERROR; + } + + /* + * Get the MS Window we want to copy. Given the HDC, we can get the + * "Window". + */ + if (hwnd == 0) { + if (Tk_IsTopLevel(workwin)) { + is_toplevel = 1; + } + + if ((wnd = Tk_WindowId(workwin)) == 0) { + Tcl_AppendResult(interp, "Can't get id for Tk window", (char *)NULL); + return TCL_ERROR; + } + + /* Given the "Window" we can get a Microsoft Windows HWND. */ + + if ((hwnd = Tk_GetHWND(wnd)) == 0) { + Tcl_AppendResult(interp, "Can't get Windows handle for Tk window", + (char *)NULL); + return TCL_ERROR; + } + + /* + * If it's a toplevel, give it special treatment: Get the top-level + * window instead. If the user only wanted the client, the -client + * flag will take care of it. This uses "windows" tricks rather than + * Tk since the obvious method of getting the wrapper window didn't + * seem to work. + */ + if (is_toplevel) { + HWND tmpWnd = hwnd; + while ((tmpWnd = GetParent(tmpWnd)) != 0) { + hwnd = tmpWnd; + } + } + } + + /* Given the HWND, we can get the window's device context. */ + if ((src = GetWindowDC(hwnd)) == 0) { + Tcl_AppendResult(interp, "Can't get device context for Tk window", (char *)NULL); + return TCL_ERROR; + } + + if (do_screen) { + LONG w, h; + GetDisplaySize(&w, &h); + wid = w; + hgt = h; + } else if (is_toplevel) { + RECT tl; + GetWindowRect(hwnd, &tl); + wid = tl.right - tl.left; + hgt = tl.bottom - tl.top; + } else { + if ((hgt = Tk_Height(workwin)) <= 0) { + Tcl_AppendResult(interp, "Can't get height of Tk window", (char *)NULL); + ReleaseDC(hwnd,src); + return TCL_ERROR; + } + + if ((wid = Tk_Width(workwin)) <= 0) { + Tcl_AppendResult(interp, "Can't get width of Tk window", (char *)NULL); + ReleaseDC(hwnd,src); + return TCL_ERROR; + } + } + + /* + * Ensure all the widths and heights are set up right + * A: No dimensions are negative + * B: No dimensions exceed the maximums + * C: The dimensions don't lead to a 0 width or height image. + */ + if (src_x < 0) { + src_x = 0; + } + if (src_y < 0) { + src_y = 0; + } + if (dst_x < 0) { + dst_x = 0; + } + if (dst_y < 0) { + dst_y = 0; + } + + if (src_w > wid || src_w <= 0) { + src_w = wid; + } + + if (src_h > hgt || src_h <= 0) { + src_h = hgt; + } + + if (do_scale && dst_w == 0) { + /* Calculate destination width and height based on scale. */ + dst_w = (int)(scale * src_w); + dst_h = (int)(scale * src_h); + } + + if (dst_h == -1) { + dst_h = (int) (((long)src_h * dst_w) / (src_w + 1)) + 1; + } + + if (dst_h == 0 || dst_w == 0) { + dst_h = src_h; + dst_w = src_w; + } + + if (do_print) { + /* + * Based on notes from Heiko Schock and Arndt Roger Schneider, create + * this as a DIBitmap, to allow output to a greater range of devices. + * This approach will also allow selection of + * a) Whole screen + * b) Whole window + * c) Client window only + * for the "grab" + */ + hDib = CopyToDIB(hwnd, wintype); + + /* GdiFlush();. */ + + if (!hDib) { + Tcl_AppendResult(interp, "Can't create DIB", (char *)NULL); + ReleaseDC(hwnd,src); + return TCL_ERROR; + } + + lpDIBHdr = (LPBITMAPINFOHEADER) GlobalLock(hDib); + if (!lpDIBHdr) { + Tcl_AppendResult(interp, "Can't get DIB header", (char *)NULL); + ReleaseDC(hwnd,src); + return TCL_ERROR; + } + + lpBits = (LPSTR) lpDIBHdr + lpDIBHdr->biSize + DIBNumColors(lpDIBHdr) * sizeof(RGBQUAD); + + /* stretch the DIBbitmap directly in the target device. */ + + if (StretchDIBits(dst, + dst_x, dst_y, dst_w, dst_h, + src_x, src_y, src_w, src_h, + lpBits, (LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, + SRCCOPY) == (int)GDI_ERROR) { + errcode = GetLastError(); + GlobalUnlock(hDib); + GlobalFree(hDib); + ReleaseDC(hwnd,src); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "StretchDIBits failed with code %ld", errcode)); + return TCL_ERROR; + } + + /* free allocated memory. */ + GlobalUnlock(hDib); + GlobalFree(hDib); + } + + ReleaseDC(hwnd,src); + + /* + * The return value should relate to the size in the destination space. + * At least the height should be returned (for page layout purposes). + */ + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "%d %d %d %d", dst_x, dst_y, dst_w, dst_h)); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * DIBNumColors -- + * + * Computes the number of colors required for a DIB palette. + * + * Results: + * Returns number of colors. + + * + *---------------------------------------------------------------------- + */ + +static int DIBNumColors( + LPBITMAPINFOHEADER lpDIB) +{ + WORD wBitCount; /* DIB bit count. */ + DWORD dwClrUsed; + + /* + * If this is a Windows-style DIB, the number of colors in the color table + * can be less than the number of bits per pixel allows for (i.e. + * lpbi->biClrUsed can be set to some value). If this is the case, return + * the appropriate value.. + */ + + dwClrUsed = lpDIB->biClrUsed; + if (dwClrUsed) { + return (WORD) dwClrUsed; + } + + /* + * Calculate the number of colors in the color table based on. + * The number of bits per pixel for the DIB. + */ + + wBitCount = lpDIB->biBitCount; + + /* Return number of colors based on bits per pixel. */ + + switch (wBitCount) { + case 1: + return 2; + case 4: + return 16; + case 8: + return 256; + default: + return 0; + } +} + +/* + * Helper functions + */ + +/* + * ParseFontWords converts various keywords to modifyers of a + * font specification. + * For all words, later occurrences override earlier occurrences. + * Overstrike and underline cannot be "undone" by other words + */ + +/* + *---------------------------------------------------------------------- + * + * GdiParseFontWords -- + * + * Converts various keywords to modifiers of a font specification. For + * all words, later occurrences override earlier occurrences. Overstrike + * and underline cannot be "undone" by other words + * + * Results: + * Keywords converted to modifiers. + * + *---------------------------------------------------------------------- + */ + +static int GdiParseFontWords( + TCL_UNUSED(Tcl_Interp *), + LOGFONTW *lf, + const char *str[], + int numargs) +{ + int i; + int retval = 0; /* Number of words that could not be parsed. */ + + for (i=0; i<numargs; i++) { + if (str[i]) { + int wt; + if ((wt = GdiWordToWeight(str[i])) != -1) { + lf->lfWeight = wt; + } else if (strcmp(str[i], "roman") == 0) { + lf->lfItalic = FALSE; + } else if (strcmp(str[i], "italic") == 0) { + lf->lfItalic = TRUE; + } else if (strcmp(str[i], "underline") == 0) { + lf->lfUnderline = TRUE; + } else if (strcmp(str[i], "overstrike") == 0) { + lf->lfStrikeOut = TRUE; + } else { + retval++; + } + } + } + return retval; +} + +/* + *---------------------------------------------------------------------- + * + * GdiWordToWeight -- + * + * Converts keywords to font weights. + * + * Results: + * Helps set the proper font for GDI rendering. + * + *---------------------------------------------------------------------- + */ + +static int GdiWordToWeight( + const char *str) +{ + int retval = -1; + size_t i; + static const struct font_weight { + const char *name; + int weight; + } font_weights[] = { + { "thin", FW_THIN }, + { "extralight", FW_EXTRALIGHT }, + { "ultralight", FW_EXTRALIGHT }, + { "light", FW_LIGHT }, + { "normal", FW_NORMAL }, + { "regular", FW_NORMAL }, + { "medium", FW_MEDIUM }, + { "semibold", FW_SEMIBOLD }, + { "demibold", FW_SEMIBOLD }, + { "bold", FW_BOLD }, + { "extrabold", FW_EXTRABOLD }, + { "ultrabold", FW_EXTRABOLD }, + { "heavy", FW_HEAVY }, + { "black", FW_HEAVY }, + }; + + if (str == 0) { + return -1; + } + + for (i=0; i<sizeof(font_weights) / sizeof(struct font_weight); i++) { + if (strcmp(str, font_weights[i].name) == 0) { + retval = font_weights[i].weight; + break; + } + } + + return retval; +} + +/* + *---------------------------------------------------------------------- + * + * MakeLogFont -- + * + * Takes the font description string and converts this into a logical + * font spec. + * + * Results: + * Sets font weight. + * + *---------------------------------------------------------------------- + */ + +static int GdiMakeLogFont( + Tcl_Interp *interp, + const char *str, + LOGFONTW *lf, + HDC hDC) +{ + const char **list; + Tcl_Size count; + + /* Set up defaults for logical font. */ + memset(lf, 0, sizeof(*lf)); + lf->lfWeight = FW_NORMAL; + lf->lfCharSet = DEFAULT_CHARSET; + lf->lfOutPrecision = OUT_DEFAULT_PRECIS; + lf->lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf->lfQuality = DEFAULT_QUALITY; + lf->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; + + /* The cast to (char *) is silly, based on prototype of Tcl_SplitList. */ + if (Tcl_SplitList(interp, str, &count, &list) != TCL_OK) { + return 0; + } + + /* Now we have the font structure broken into name, size, weight. */ + if (count >= 1) { + Tcl_DString ds; + + Tcl_DStringInit(&ds); + wcsncpy(lf->lfFaceName, Tcl_UtfToWCharDString(list[0], TCL_INDEX_NONE, &ds), + LF_FACESIZE-1); + Tcl_DStringFree(&ds); + lf->lfFaceName[LF_FACESIZE-1] = 0; + } else { + return 0; + } + + if (count >= 2) { + int siz; + char *strend; + siz = strtol(list[1], &strend, 0); + + /* + * Assumptions: + * 1) Like canvas, if a positive number is specified, it's in points. + * 2) Like canvas, if a negative number is specified, it's in pixels. + */ + if (strend > list[1]) { /* If it looks like a number, it is a number.... */ + if (siz > 0) { /* Size is in points. */ + SIZE wextent, vextent; + POINT worigin, vorigin; + double factor; + + switch (GdiGetHdcInfo(hDC, &worigin, &wextent, &vorigin, &vextent)) { + case MM_ISOTROPIC: + if (vextent.cy < -1 || vextent.cy > 1) { + factor = (double)wextent.cy / vextent.cy; + if (factor < 0.0) { + factor = -factor; + } + lf->lfHeight = (int)(-siz * GetDeviceCaps(hDC, LOGPIXELSY) * factor / 72.0); + } else if (vextent.cx < -1 || vextent.cx > 1) { + factor = (double)wextent.cx / vextent.cx; + if (factor < 0.0) { + factor = -factor; + } + lf->lfHeight = (int)(-siz * GetDeviceCaps(hDC, LOGPIXELSY) * factor / 72.0); + } else { + lf->lfHeight = -siz; /* This is bad news.... */ + } + break; + case MM_ANISOTROPIC: + if (vextent.cy != 0) { + factor = (double)wextent.cy / vextent.cy; + if (factor < 0.0) { + factor = -factor; + } + lf->lfHeight = (int)(-siz * GetDeviceCaps(hDC, LOGPIXELSY) * factor / 72.0); + } else { + lf->lfHeight = -siz; /* This is bad news.... */ + } + break; + case MM_TEXT: + default: + /* If mapping mode is MM_TEXT, use the documented + * formula. */ + lf->lfHeight = -MulDiv(siz, GetDeviceCaps(hDC, LOGPIXELSY), 72); + break; + case MM_HIENGLISH: + lf->lfHeight = -MulDiv(siz, 1000, 72); + break; + case MM_LOENGLISH: + lf->lfHeight = -MulDiv(siz, 100, 72); + break; + case MM_HIMETRIC: + lf->lfHeight = -MulDiv(siz, (int)(1000*2.54), 72); + break; + case MM_LOMETRIC: + lf->lfHeight = -MulDiv(siz, (int)(100*2.54), 72); + break; + case MM_TWIPS: + lf->lfHeight = -MulDiv(siz, 1440, 72); + break; + } + } else if (siz == 0) { /* Use default size of 12 points. */ + lf->lfHeight = -MulDiv(12, GetDeviceCaps(hDC, LOGPIXELSY), 72); + } else { /* Use pixel size. */ + lf->lfHeight = siz; /* Leave this negative. */ + } + } else { + GdiParseFontWords(interp, lf, list+1, count-1); + } + } + + if (count >= 3) { + GdiParseFontWords(interp, lf, list+2, count-2); + } + + ckfree(list); + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * GdiMakePen -- + * + * Creates a logical pen based on input parameters and selects it into + * the hDC. + * + * Results: + * Sets rendering pen. + * + *---------------------------------------------------------------------- + */ + +static int GdiMakePen( + Tcl_Interp *interp, + int width, + int dashstyle, + const char *dashstyledata, + TCL_UNUSED(int), /* Ignored for now. */ + TCL_UNUSED(int), /* Ignored for now. */ + TCL_UNUSED(int), + TCL_UNUSED(const char *), /* Ignored for now. */ + unsigned long color, + HDC hDC, + HGDIOBJ *oldPen) +{ + /* + * The LOGPEN structure takes the following dash options: + * PS_SOLID: a solid pen + * PS_DASH: a dashed pen + * PS_DOT: a dotted pen + * PS_DASHDOT: a pen with a dash followed by a dot + * PS_DASHDOTDOT: a pen with a dash followed by 2 dots + * + * It seems that converting to ExtCreatePen may be more advantageous, as + * it matches the Tk canvas pens much better--but not for Win95, which + * does not support PS_USERSTYLE. An explicit test (or storage in a static + * after first failure) may suffice for working around this. The + * ExtCreatePen is not supported at all under Win32. + */ + + HPEN hPen; + LOGBRUSH lBrush; + DWORD pStyle = PS_SOLID; /* -dash should override*/ + DWORD endStyle = PS_ENDCAP_ROUND; /* -capstyle should override. */ + DWORD joinStyle = PS_JOIN_ROUND; /* -joinstyle should override. */ + DWORD styleCount = 0; + DWORD *styleArray = 0; + + /* + * To limit the propagation of allocated memory, the dashes will have a + * maximum here. If one wishes to remove the static allocation, please be + * sure to update GdiFreePen and ensure that the array is NOT freed if the + * LOGPEN option is used. + */ + static DWORD pStyleData[24]; + if (dashstyle != 0 && dashstyledata != 0) { + const char *cp; + size_t i; + char *dup = (char *) ckalloc(strlen(dashstyledata) + 1); + strcpy(dup, dashstyledata); + /* DEBUG. */ + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "DEBUG: Found a dash spec of |%s|\n", + dashstyledata)); + + /* Parse the dash spec. */ + if (isdigit(dashstyledata[0])) { + cp = strtok(dup, " \t,;"); + for (i = 0; cp && i < sizeof(pStyleData) / sizeof(DWORD); i++) { + pStyleData[styleCount++] = atoi(cp); + cp = strtok(NULL, " \t,;"); + } + } else { + for (i=0; dashstyledata[i] != '\0' && i< sizeof(pStyleData) / sizeof(DWORD); i++) { + switch (dashstyledata[i]) { + case ' ': + pStyleData[styleCount++] = 8; + break; + case ',': + pStyleData[styleCount++] = 4; + break; + case '_': + pStyleData[styleCount++] = 6; + break; + case '-': + pStyleData[styleCount++] = 4; + break; + case '.': + pStyleData[styleCount++] = 2; + break; + default: + break; + } + } + } + if (styleCount > 0) { + styleArray = pStyleData; + } else { + dashstyle = 0; + } + if (dup) { + ckfree(dup); + } + } + + if (dashstyle != 0) { + pStyle = PS_USERSTYLE; + } + + /* -stipple could affect this.... */ + lBrush.lbStyle = BS_SOLID; + lBrush.lbColor = color; + lBrush.lbHatch = 0; + + /* We only use geometric pens, even for 1-pixel drawing. */ + hPen = ExtCreatePen(PS_GEOMETRIC|pStyle|endStyle|joinStyle, + width, &lBrush, styleCount, styleArray); + + if (hPen == 0) { /* Failed for some reason...Fall back on CreatePenIndirect. */ + LOGPEN lf; + lf.lopnWidth.x = width; + lf.lopnWidth.y = 0; /* Unused in LOGPEN. */ + if (dashstyle == 0) { + lf.lopnStyle = PS_SOLID; /* For now...convert 'style' in the future. */ + } else { + lf.lopnStyle = PS_DASH; /* REALLLLY simple for now. */ + } + lf.lopnColor = color; /* Assume we're getting a COLORREF. */ + /* Now we have a logical pen. Create the "real" pen and put it in the + * hDC. */ + hPen = CreatePenIndirect(&lf); + } + + *oldPen = SelectObject(hDC, hPen); + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * GdiFreePen -- + * + * Wraps the protocol to delete a created pen. + * + * Results: + * Deletes pen. + * + *---------------------------------------------------------------------- + */ + +static int GdiFreePen( + TCL_UNUSED(Tcl_Interp *), + HDC hDC, + HGDIOBJ oldPen) +{ + HGDIOBJ gonePen = SelectObject(hDC, oldPen); + + DeleteObject(gonePen); + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * GdiMakeBrush-- + * + * Creates a logical brush based on input parameters, and selects it into + * the hdc. + * + * Results: + * Creates brush. + * + *---------------------------------------------------------------------- + */ + +static int GdiMakeBrush( + unsigned long color, + long hatch, + LOGBRUSH *lb, + HDC hDC, + HBRUSH *oldBrush) +{ + HBRUSH hBrush; + lb->lbStyle = BS_SOLID; /* Support other styles later. */ + lb->lbColor = color; /* Assume this is a COLORREF. */ + lb->lbHatch = hatch; /* Ignored for now, given BS_SOLID in the Style. */ + + /* Now we have the logical brush. Create the "real" brush and put it in + * the hDC. */ + hBrush = CreateBrushIndirect(lb); + *oldBrush = (HBRUSH)SelectObject(hDC, hBrush); + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * GdiFreeBrush -- + * + * Wraps the protocol to delete a created brush. + * + * Results: + * Deletes brush. + * + *---------------------------------------------------------------------- + */ +static void GdiFreeBrush( + TCL_UNUSED(Tcl_Interp *), + HDC hDC, + HGDIOBJ oldBrush) +{ + HGDIOBJ goneBrush; + + goneBrush = SelectObject(hDC, oldBrush); + DeleteObject(goneBrush); +} + +/* + * Utility functions from elsewhere in Tcl. + * Functions have removed reliance on X and Tk libraries, as well as removing + * the need for TkWindows. + * GdiGetColor is a copy of a TkpGetColor from tkWinColor.c + */ +typedef struct { + const char *name; + int index; +} SystemColorEntry; + +static const SystemColorEntry sysColors[] = { + {"3dDarkShadow", COLOR_3DDKSHADOW}, + {"3dLight", COLOR_3DLIGHT}, + {"ActiveBorder", COLOR_ACTIVEBORDER}, + {"ActiveCaption", COLOR_ACTIVECAPTION}, + {"AppWorkspace", COLOR_APPWORKSPACE}, + {"Background", COLOR_BACKGROUND}, + {"ButtonFace", COLOR_BTNFACE}, + {"ButtonHighlight", COLOR_BTNHIGHLIGHT}, + {"ButtonShadow", COLOR_BTNSHADOW}, + {"ButtonText", COLOR_BTNTEXT}, + {"CaptionText", COLOR_CAPTIONTEXT}, + {"DisabledText", COLOR_GRAYTEXT}, + {"GrayText", COLOR_GRAYTEXT}, + {"Highlight", COLOR_HIGHLIGHT}, + {"HighlightText", COLOR_HIGHLIGHTTEXT}, + {"InactiveBorder", COLOR_INACTIVEBORDER}, + {"InactiveCaption", COLOR_INACTIVECAPTION}, + {"InactiveCaptionText", COLOR_INACTIVECAPTIONTEXT}, + {"InfoBackground", COLOR_INFOBK}, + {"InfoText", COLOR_INFOTEXT}, + {"Menu", COLOR_MENU}, + {"MenuText", COLOR_MENUTEXT}, + {"Scrollbar", COLOR_SCROLLBAR}, + {"Window", COLOR_WINDOW}, + {"WindowFrame", COLOR_WINDOWFRAME}, + {"WindowText", COLOR_WINDOWTEXT} +}; + +static const size_t numsyscolors = sizeof(sysColors) / sizeof(SystemColorEntry); + +/* + *---------------------------------------------------------------------- + * + * GdiGetColor -- + * + * Convert color name to color specification. + * + * Results: + * Color name converted. + * + *---------------------------------------------------------------------- + */ + +static int GdiGetColor( + Tcl_Obj *nameObj, + COLORREF *color) +{ + const char *name = Tcl_GetString(nameObj); + + if (_strnicmp(name, "system", 6) == 0) { + size_t i, l, u; + int r; + + l = 0; + u = numsyscolors; + while (l <= u) { + i = (l + u) / 2; + if ((r = _strcmpi(name+6, sysColors[i].name)) == 0) { + break; + } + if (r < 0) { + u = i - 1; + } else { + l = i + 1; + } + } + if (l > u) { + return 0; + } + *color = GetSysColor(sysColors[i].index); + return 1; + } else { + int result; + XColor xcolor; + result = XParseColor(NULL, 0, name, &xcolor); + *color = ((xcolor.red & 0xFF00)>>8) | (xcolor.green & 0xFF00) + | ((xcolor.blue & 0xFF00)<<8); + return result; + } +} + +/* + * Beginning of functions for screen-to-dib translations. + * + * Several of these functions are based on those in the WINCAP32 program + * provided as a sample by Microsoft on the VC++ 5.0 disk. The copyright on + * these functions is retained, even for those with significant changes. + */ + +/* + *---------------------------------------------------------------------- + * + * CopyToDIB -- + * + * Copy window bits to a DIB. + * + * Results: + * Color specification converted. + * + *---------------------------------------------------------------------- + */ + +static HANDLE CopyToDIB( + HWND hWnd, + enum PrintType type) +{ + HANDLE hDIB; + HBITMAP hBitmap; + HPALETTE hPalette; + + /* Check for a valid window handle. */ + + if (!hWnd) { + return NULL; + } + + switch (type) { + case PTWindow: { /* Copy entire window. */ + RECT rectWnd; + + /* Get the window rectangle. */ + + GetWindowRect(hWnd, &rectWnd); + + /* + * Get the DIB of the window by calling CopyScreenToDIB and passing it + * the window rect. + */ + + hDIB = CopyScreenToDIB(&rectWnd); + break; + } + + case PTClient: { /* Copy client area. */ + RECT rectClient; + POINT pt1, pt2; + + /* Get the client area dimensions. */ + + GetClientRect(hWnd, &rectClient); + + /* Convert client coords to screen coords. */ + + pt1.x = rectClient.left; + pt1.y = rectClient.top; + pt2.x = rectClient.right; + pt2.y = rectClient.bottom; + ClientToScreen(hWnd, &pt1); + ClientToScreen(hWnd, &pt2); + rectClient.left = pt1.x; + rectClient.top = pt1.y; + rectClient.right = pt2.x; + rectClient.bottom = pt2.y; + + /* + * Get the DIB of the client area by calling CopyScreenToDIB and + * passing it the client rect. + */ + + hDIB = CopyScreenToDIB(&rectClient); + break; + } + + case PTScreen: { /* Entire screen. */ + RECT Rect; + + /* + * Get the device-dependent bitmap in lpRect by calling + * CopyScreenToBitmap and passing it the rectangle to grab. + */ + Rect.top = Rect.left = 0; + GetDisplaySize(&Rect.right, &Rect.bottom); + + hBitmap = CopyScreenToBitmap(&Rect); + + /* Check for a valid bitmap handle. */ + + if (!hBitmap) { + return NULL; + } + + /* Get the current palette. */ + + hPalette = GetSystemPalette(); + + /* Convert the bitmap to a DIB. */ + + hDIB = BitmapToDIB(hBitmap, hPalette); + + /* Clean up. */ + + DeleteObject(hPalette); + DeleteObject(hBitmap); + + /* Return handle to the packed-DIB. */ + break; + } + default: /* Invalid print area. */ + return NULL; + } + + /* Return the handle to the DIB. */ + return hDIB; +} + +/* + *---------------------------------------------------------------------- + * + * GetDisplaySize-- + * + * GetDisplaySize does just that. There may be an easier way, but it is + * not apparent. + * + * Results: + * Returns display size. + * + *---------------------------------------------------------------------- + */ + +static void GetDisplaySize( + LONG *width, + LONG *height) +{ + HDC hDC; + + hDC = CreateDCW(L"DISPLAY", 0, 0, 0); + *width = GetDeviceCaps(hDC, HORZRES); + *height = GetDeviceCaps(hDC, VERTRES); + DeleteDC(hDC); +} + +/* + *---------------------------------------------------------------------- + * + * CopyScreenToBitmap-- + * + * Copies screen to bitmap. + * + * Results: + * Screen is copied. + * + *---------------------------------------------------------------------- + */ + +static HBITMAP CopyScreenToBitmap( + LPRECT lpRect) +{ + HDC hScrDC, hMemDC; /* Screen DC and memory DC. */ + HBITMAP hBitmap, hOldBitmap; /* Handles to deice-dependent bitmaps. */ + int nX, nY, nX2, nY2; /* Coordinates of rectangle to grab. */ + int nWidth, nHeight; /* DIB width and height */ + int xScrn, yScrn; /* Screen resolution. */ + + /* Check for an empty rectangle. */ + + if (IsRectEmpty(lpRect)) { + return NULL; + } + + /* + * Create a DC for the screen and create a memory DC compatible to screen + * DC. + */ + + hScrDC = CreateDCW(L"DISPLAY", NULL, NULL, NULL); + hMemDC = CreateCompatibleDC(hScrDC); + + /* Get points of rectangle to grab. */ + + nX = lpRect->left; + nY = lpRect->top; + nX2 = lpRect->right; + nY2 = lpRect->bottom; + + /* Get screen resolution. */ + + xScrn = GetDeviceCaps(hScrDC, HORZRES); + yScrn = GetDeviceCaps(hScrDC, VERTRES); + + /* Make sure bitmap rectangle is visible. */ + + if (nX < 0) { + nX = 0; + } + if (nY < 0) { + nY = 0; + } + if (nX2 > xScrn) { + nX2 = xScrn; + } + if (nY2 > yScrn) { + nY2 = yScrn; + } + + nWidth = nX2 - nX; + nHeight = nY2 - nY; + + /* Create a bitmap compatible with the screen DC. */ + hBitmap = CreateCompatibleBitmap(hScrDC, nWidth, nHeight); + + /* Select new bitmap into memory DC. */ + hOldBitmap = SelectObject(hMemDC, hBitmap); + + /* Bitblt screen DC to memory DC. */ + BitBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, nX, nY, SRCCOPY); + + /* + * Select old bitmap back into memory DC and get handle to bitmap of the + * screen. + */ + + hBitmap = SelectObject(hMemDC, hOldBitmap); + + /* Clean up. */ + + DeleteDC(hScrDC); + DeleteDC(hMemDC); + + /* Return handle to the bitmap. */ + + return hBitmap; +} + +/* + *---------------------------------------------------------------------- + * + * BitmapToDIB-- + * + * Converts bitmap to DIB. + * + * Results: + * Bitmap converted. + * + *---------------------------------------------------------------------- + */ + +static HANDLE BitmapToDIB( + HBITMAP hBitmap, + HPALETTE hPal) +{ + BITMAP bm; + BITMAPINFOHEADER bi; + LPBITMAPINFOHEADER lpbi; + DWORD dwLen; + HANDLE hDIB; + HANDLE h; + HDC hDC; + WORD biBits; + + /* Check if bitmap handle is valid. */ + + if (!hBitmap) { + return NULL; + } + + /* Fill in BITMAP structure, return NULL if it didn't work. */ + + if (!GetObjectW(hBitmap, sizeof(bm), (LPWSTR)&bm)) { + return NULL; + } + + /* Ff no palette is specified, use default palette. */ + + if (hPal == NULL) { + hPal = GetStockObject(DEFAULT_PALETTE); + } + + /* Calculate bits per pixel. */ + + biBits = bm.bmPlanes * bm.bmBitsPixel; + + /* Make sure bits per pixel is valid. */ + + if (biBits <= 1) { + biBits = 1; + } else if (biBits <= 4) { + biBits = 4; + } else if (biBits <= 8) { + biBits = 8; + } else { /* If greater than 8-bit, force to 24-bit. */ + biBits = 24; + } + + /* Initialize BITMAPINFOHEADER. */ + + bi.biSize = sizeof(BITMAPINFOHEADER); + bi.biWidth = bm.bmWidth; + bi.biHeight = bm.bmHeight; + bi.biPlanes = 1; + bi.biBitCount = biBits; + bi.biCompression = BI_RGB; + bi.biSizeImage = 0; + bi.biXPelsPerMeter = 0; + bi.biYPelsPerMeter = 0; + bi.biClrUsed = 0; + bi.biClrImportant = 0; + + /* Calculate size of memory block required to store BITMAPINFO. */ + + dwLen = bi.biSize + DIBNumColors(&bi) * sizeof(RGBQUAD); + + /* Get a DC. */ + + hDC = GetDC(NULL); + + /* Select and realize our palette. */ + + hPal = SelectPalette(hDC, hPal, FALSE); + RealizePalette(hDC); + + /* Alloc memory block to store our bitmap. */ + + hDIB = GlobalAlloc(GHND, dwLen); + + /* If we couldn't get memory block. */ + + if (!hDIB) { + /* clean up and return NULL. */ + + SelectPalette(hDC, hPal, TRUE); + RealizePalette(hDC); + ReleaseDC(NULL, hDC); + return NULL; + } + + /* Lock memory and get pointer to it. */ + + lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB); + + /* Use our bitmap info. to fill BITMAPINFOHEADER. */ + + *lpbi = bi; + + /* Call GetDIBits with a NULL lpBits param, so it will calculate the + * biSizeImage field for us + */ + + GetDIBits(hDC, hBitmap, 0, (UINT)bi.biHeight, NULL, (LPBITMAPINFO)lpbi, + DIB_RGB_COLORS); + + /* get the info. returned by GetDIBits and unlock memory block. */ + + bi = *lpbi; + GlobalUnlock(hDIB); + + /* If the driver did not fill in the biSizeImage field, make one up. */ + if (bi.biSizeImage == 0) { + bi.biSizeImage = (((((DWORD)bm.bmWidth * biBits) + 31) / 32) * 4) + * bm.bmHeight; + } + + /* Realloc the buffer big enough to hold all the bits. */ + + dwLen = bi.biSize + DIBNumColors(&bi) * sizeof(RGBQUAD) + bi.biSizeImage; + + if ((h = GlobalReAlloc(hDIB, dwLen, 0)) != 0) { + hDIB = h; + } else { + /* Clean up and return NULL. */ + + GlobalFree(hDIB); + SelectPalette(hDC, hPal, TRUE); + RealizePalette(hDC); + ReleaseDC(NULL, hDC); + return NULL; + } + + /* Lock memory block and get pointer to it. */ + + lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB); + + /* Call GetDIBits with a NON-NULL lpBits param, and actualy get the + * bits this time. + */ + + if (GetDIBits(hDC, hBitmap, 0, (UINT)bi.biHeight, (LPSTR)lpbi + + (WORD)lpbi->biSize + DIBNumColors(lpbi) * sizeof(RGBQUAD), + (LPBITMAPINFO)lpbi, DIB_RGB_COLORS) == 0) { + /* Clean up and return NULL. */ + + GlobalUnlock(hDIB); + SelectPalette(hDC, hPal, TRUE); + RealizePalette(hDC); + ReleaseDC(NULL, hDC); + return NULL; + } + + bi = *lpbi; + + /* Clean up. */ + GlobalUnlock(hDIB); + SelectPalette(hDC, hPal, TRUE); + RealizePalette(hDC); + ReleaseDC(NULL, hDC); + + /* Return handle to the DIB. */ + return hDIB; +} + +/* + *---------------------------------------------------------------------- + * + * CopyScreenToDIB-- + * + * Copies screen to DIB. + * + * Results: + * Screen copied. + * + *---------------------------------------------------------------------- + */ + +static HANDLE CopyScreenToDIB( + LPRECT lpRect) +{ + HBITMAP hBitmap; + HPALETTE hPalette; + HANDLE hDIB; + + /* + * Get the device-dependent bitmap in lpRect by calling CopyScreenToBitmap + * and passing it the rectangle to grab. + */ + + hBitmap = CopyScreenToBitmap(lpRect); + + /* Check for a valid bitmap handle. */ + + if (!hBitmap) { + return NULL; + } + + /* Get the current palette. */ + + hPalette = GetSystemPalette(); + + /* convert the bitmap to a DIB. */ + + hDIB = BitmapToDIB(hBitmap, hPalette); + + /* Clean up. */ + + DeleteObject(hPalette); + DeleteObject(hBitmap); + + /* Return handle to the packed-DIB. */ + return hDIB; +} + +/* + *---------------------------------------------------------------------- + * + * GetSystemPalette-- + * + * Obtains the system palette. + * + * Results: + * Returns palette. + * + *---------------------------------------------------------------------- + */ + +static HPALETTE GetSystemPalette(void) +{ + HDC hDC; /* Handle to a DC. */ + static HPALETTE hPal = NULL; /* Handle to a palette. */ + HANDLE hLogPal; /* Handle to a logical palette. */ + LPLOGPALETTE lpLogPal; /* Pointer to a logical palette. */ + int nColors; /* Number of colors. */ + + /* Find out how many palette entries we want.. */ + + hDC = GetDC(NULL); + if (!hDC) { + return NULL; + } + + nColors = PalEntriesOnDevice(hDC); /* Number of palette entries. */ + + /* Allocate room for the palette and lock it.. */ + + hLogPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) + nColors * + sizeof(PALETTEENTRY)); + if (!hLogPal) { + /* If we didn't get a logical palette, return NULL. */ + + return NULL; + } + + /* get a pointer to the logical palette. */ + + lpLogPal = (LPLOGPALETTE)GlobalLock(hLogPal); + + /* Set some important fields. */ + + lpLogPal->palVersion = 0x300; + lpLogPal->palNumEntries = nColors; + + /* Copy the current system palette into our logical palette. */ + + GetSystemPaletteEntries(hDC, 0, nColors, + (LPPALETTEENTRY) lpLogPal->palPalEntry); + + /* + * Go ahead and create the palette. Once it's created, we no longer need + * the LOGPALETTE, so free it. + */ + + hPal = CreatePalette(lpLogPal); + + /* Clean up. */ + + GlobalUnlock(hLogPal); + GlobalFree(hLogPal); + ReleaseDC(NULL, hDC); + + return hPal; +} + +/* + *---------------------------------------------------------------------- + * + * PalEntriesOnDevice-- + * + * Returns the palettes on the device. + * + * Results: + * Returns palettes. + * + *---------------------------------------------------------------------- + */ + +static int PalEntriesOnDevice( + HDC hDC) +{ + return (1 << (GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES))); +} + +/* + * -------------------------------------------------------------------------- + * + * Winprint_Init-- + * + * Initializes printing module on Windows. + * + * Results: + * Module initialized. + * + * ------------------------------------------------------------------------- + */ + +int Winprint_Init( + Tcl_Interp * interp) +{ + size_t i; + Tcl_Namespace *namespacePtr; + static const char *gdiName = "::tk::print::_gdi"; + static const size_t numCommands = + sizeof(gdi_commands) / sizeof(struct gdi_command); + + /* + * Set up the low-level [_gdi] command. + */ + + namespacePtr = Tcl_CreateNamespace(interp, gdiName, + NULL, (Tcl_NamespaceDeleteProc *) NULL); + for (i=0; i<numCommands; i++) { + char buffer[100]; + + snprintf(buffer, sizeof(buffer), "%s::%s", gdiName, gdi_commands[i].command_string); + Tcl_CreateObjCommand(interp, buffer, gdi_commands[i].command, + NULL, (Tcl_CmdDeleteProc *) 0); + Tcl_Export(interp, namespacePtr, gdi_commands[i].command_string, 0); + } + Tcl_CreateEnsemble(interp, gdiName, namespacePtr, 0); + + /* + * The other printing-related commands. + */ + + Tcl_CreateObjCommand(interp, "::tk::print::_selectprinter", + PrintSelectPrinter, NULL, NULL); + Tcl_CreateObjCommand(interp, "::tk::print::_openprinter", + PrintOpenPrinter, NULL, NULL); + Tcl_CreateObjCommand(interp, "::tk::print::_closeprinter", + PrintClosePrinter, NULL, NULL); + Tcl_CreateObjCommand(interp, "::tk::print::_opendoc", + PrintOpenDoc, NULL, NULL); + Tcl_CreateObjCommand(interp, "::tk::print::_closedoc", + PrintCloseDoc, NULL, NULL); + Tcl_CreateObjCommand(interp, "::tk::print::_openpage", + PrintOpenPage, NULL, NULL); + Tcl_CreateObjCommand(interp, "::tk::print::_closepage", + PrintClosePage, NULL, NULL); + return TCL_OK; +} + +/* Print API functions. */ + +/*---------------------------------------------------------------------- + * + * PrintSelectPrinter-- + * + * Main dialog for selecting printer and initializing data for print job. + * + * Results: + * Printer selected. + * + *---------------------------------------------------------------------- + */ + +static int PrintSelectPrinter( + TCL_UNUSED(void *), + Tcl_Interp *interp, + TCL_UNUSED(int), + TCL_UNUSED(Tcl_Obj* const*)) +{ + LPCWSTR printerName = NULL; + PDEVMODEW returnedDevmode = NULL; + PDEVMODEW localDevmode = NULL; + + copies = 0; + paper_width = 0; + paper_height = 0; + dpi_x = 0; + dpi_y = 0; + + /* Set up print dialog and initalize property structure. */ + + memset(&pd, 0, sizeof(pd)); + pd.lStructSize = sizeof(pd); + pd.hwndOwner = GetDesktopWindow(); + pd.Flags = PD_HIDEPRINTTOFILE | PD_DISABLEPRINTTOFILE | PD_NOSELECTION; + + if (PrintDlgW(&pd) == TRUE) { + + /*Get document info.*/ + memset(&di, 0, sizeof(di)); + di.cbSize = sizeof(di); + di.lpszDocName = L"Tk Print Output"; + + /* Copy print attributes to local structure. */ + returnedDevmode = (PDEVMODEW) GlobalLock(pd.hDevMode); + devnames = (LPDEVNAMES) GlobalLock(pd.hDevNames); + printerName = (LPCWSTR) devnames + devnames->wDeviceOffset; + localDevmode = (LPDEVMODEW) HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, + returnedDevmode->dmSize); + + if (localDevmode != NULL) { + memcpy((LPVOID)localDevmode, (LPVOID)returnedDevmode, + returnedDevmode->dmSize); + + /* Get values from user-set and built-in properties. */ + localPrinterName = localDevmode->dmDeviceName; + dpi_y = localDevmode->dmYResolution; + dpi_x = localDevmode->dmPrintQuality; + /* Convert height and width to logical points. */ + paper_height = (int) localDevmode->dmPaperLength / 0.254; + paper_width = (int) localDevmode->dmPaperWidth / 0.254; + copies = pd.nCopies; + /* Set device context here for all GDI printing operations. */ + printDC = CreateDCW(L"WINSPOOL", printerName, NULL, localDevmode); + } else { + localDevmode = NULL; + } + } + + if (pd.hDevMode != NULL) { + GlobalFree(pd.hDevMode); + } + + /* + * Store print properties and link variables so they can be accessed from + * script level. + */ + if (localPrinterName != NULL) { + char* varlink1 = (char*)ckalloc(100 * sizeof(char)); + char** varlink2 = (char**)ckalloc(sizeof(char*)); + *varlink2 = varlink1; + WideCharToMultiByte(CP_UTF8, 0, localPrinterName, -1, varlink1, 0, NULL, NULL); + + Tcl_LinkVar(interp, "::tk::print::printer_name", (char*)varlink2, + TCL_LINK_STRING | TCL_LINK_READ_ONLY); + Tcl_LinkVar(interp, "::tk::print::copies", (char*)&copies, + TCL_LINK_INT | TCL_LINK_READ_ONLY); + Tcl_LinkVar(interp, "::tk::print::dpi_x", (char*)&dpi_x, + TCL_LINK_INT | TCL_LINK_READ_ONLY); + Tcl_LinkVar(interp, "::tk::print::dpi_y", (char*)&dpi_y, + TCL_LINK_INT | TCL_LINK_READ_ONLY); + Tcl_LinkVar(interp, "::tk::print::paper_width", (char*)&paper_width, + TCL_LINK_INT | TCL_LINK_READ_ONLY); + Tcl_LinkVar(interp, "::tk::print::paper_height", (char*)&paper_height, + TCL_LINK_INT | TCL_LINK_READ_ONLY); + } + + return TCL_OK; +} + +/* + * -------------------------------------------------------------------------- + * + * PrintOpenPrinter-- + * + * Open the given printer. + * + * Results: + * Opens the selected printer. + * + * ------------------------------------------------------------------------- + */ + +int PrintOpenPrinter( + TCL_UNUSED(void *), + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + Tcl_DString ds; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "printer"); + return TCL_ERROR; + } + + /*Start an individual page.*/ + if (StartPage(printDC) <= 0) { + return TCL_ERROR; + } + + const char *printer = Tcl_GetString(objv[1]); + + if (printDC == NULL) { + Tcl_AppendResult(interp, "unable to establish device context", (char *)NULL); + return TCL_ERROR; + } + + Tcl_DStringInit(&ds); + if ((OpenPrinterW(Tcl_UtfToWCharDString(printer, -1, &ds), + (LPHANDLE)&printDC, NULL)) == FALSE) { + Tcl_AppendResult(interp, "unable to open printer", (char *)NULL); + Tcl_DStringFree(&ds); + return TCL_ERROR; + } + + Tcl_DStringFree(&ds); + return TCL_OK; +} + +/* + * -------------------------------------------------------------------------- + * + * PrintClosePrinter-- + * + * Closes the given printer. + * + * Results: + * Printer closed. + * + * ------------------------------------------------------------------------- + */ + +int PrintClosePrinter( + TCL_UNUSED(void *), + Tcl_Interp *interp, + TCL_UNUSED(int), + TCL_UNUSED(Tcl_Obj *const *)) +{ + if (printDC == NULL) { + Tcl_AppendResult(interp, "unable to establish device context", (char *)NULL); + return TCL_ERROR; + } + + ClosePrinter(printDC); + return TCL_OK; +} + +/* + * -------------------------------------------------------------------------- + * + * PrintOpenDoc-- + * + * Opens the document for printing. + * + * Results: + * Opens the print document. + * + * ------------------------------------------------------------------------- + */ + +int PrintOpenDoc( + TCL_UNUSED(void *), + Tcl_Interp *interp, + TCL_UNUSED(int), + TCL_UNUSED(Tcl_Obj *const *)) +{ + int output = 0; + + if (printDC == NULL) { + Tcl_AppendResult(interp, "unable to establish device context", (char *)NULL); + return TCL_ERROR; + } + + /* + * Start printing. + */ + output = StartDocW(printDC, &di); + if (output <= 0) { + Tcl_AppendResult(interp, "unable to start document", (char *)NULL); + return TCL_ERROR; + } + + return TCL_OK; +} + +/* + * -------------------------------------------------------------------------- + * + * PrintCloseDoc-- + * + * Closes the document for printing. + * + * Results: + * Closes the print document. + * + * ------------------------------------------------------------------------- + */ + +int PrintCloseDoc( + TCL_UNUSED(void *), + Tcl_Interp *interp, + TCL_UNUSED(int), + TCL_UNUSED(Tcl_Obj *const *)) +{ + if (printDC == NULL) { + Tcl_AppendResult(interp, "unable to establish device context", (char *)NULL); + return TCL_ERROR; + } + + if (EndDoc(printDC) <= 0) { + Tcl_AppendResult(interp, "unable to establish close document", (char *)NULL); + return TCL_ERROR; + } + DeleteDC(printDC); + return TCL_OK; +} + +/* + * -------------------------------------------------------------------------- + * + * PrintOpenPage-- + * + * Opens a page for printing. + * + * Results: + * Opens the print page. + * + * ------------------------------------------------------------------------- + */ + +int PrintOpenPage( + TCL_UNUSED(void *), + Tcl_Interp *interp, + TCL_UNUSED(int), + TCL_UNUSED(Tcl_Obj *const *)) +{ + if (printDC == NULL) { + Tcl_AppendResult(interp, "unable to establish device context", (char *)NULL); + return TCL_ERROR; + } + + /*Start an individual page.*/ + if (StartPage(printDC) <= 0) { + Tcl_AppendResult(interp, "unable to start page", (char *)NULL); + return TCL_ERROR; + } + + return TCL_OK; +} + +/* + * -------------------------------------------------------------------------- + * + * PrintClosePage-- + * + * Closes the printed page. + * + * Results: + * Closes the page. + * + * ------------------------------------------------------------------------- + */ + +int PrintClosePage( + TCL_UNUSED(void *), + Tcl_Interp *interp, + TCL_UNUSED(int), + TCL_UNUSED(Tcl_Obj *const *)) +{ + if (printDC == NULL) { + Tcl_AppendResult(interp, "unable to establish device context", (char *)NULL); + return TCL_ERROR; + } + + if (EndPage(printDC) <= 0) { + Tcl_AppendResult(interp, "unable to close page", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |