/* * 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. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include #include #include #include #include #include #include #include "tkWinInt.h" #define DEG2RAD(x) (0.017453292519943295 * (x)) #define ROUND32(x) ((LONG)floor((x) + 0.5)) /* * Create a standard "DrawFunc" to make this more workable.... */ #ifdef _MSC_VER typedef BOOL (WINAPI *DrawFunc) ( HDC, int, int, int, int, int, int, int, int); /* Arc, Chord, Pie. */ #else typedef BOOL WINAPI (*DrawFunc) ( HDC, int, int, int, int, int, int, int, int); /* Arc, Chord, Pie. */ #endif /* Real functions. */ static Tcl_ObjCmdProc2 GdiArc; static Tcl_ObjCmdProc2 GdiBitmap; static Tcl_ObjCmdProc2 GdiCharWidths; static Tcl_ObjCmdProc2 GdiImage; static Tcl_ObjCmdProc2 GdiPhoto; static Tcl_ObjCmdProc2 GdiLine; static Tcl_ObjCmdProc2 GdiOval; static Tcl_ObjCmdProc2 GdiPolygon; static Tcl_ObjCmdProc2 GdiRectangle; static Tcl_ObjCmdProc2 GdiText; static Tcl_ObjCmdProc2 GdiTextPlain; static Tcl_ObjCmdProc2 GdiMap; static Tcl_ObjCmdProc2 GdiCopyBits; /* Local copies of similar routines elsewhere in Tcl/Tk. */ static int GdiGetColor(Tcl_Obj *nameObj, COLORREF *color); /* * Helper functions. */ static int GdiMakeLogFont(Tcl_Interp *interp, Tcl_Obj *specPtr, LOGFONTW *lf, HDC hDC); static int GdiMakePen(Tcl_Interp *interp, double dwidth, int dashstyle, const char *dashstyledata, int capstyle, int joinstyle, int stipplestyle, const char *stippledata, unsigned long color, HDC hDC, HGDIOBJ *oldPen); static int GdiFreePen(Tcl_Interp *interp, HDC hDC, HGDIOBJ oldPen); static int GdiMakeBrush(unsigned long color, long hatch, LOGBRUSH *lb, HDC hDC, HBRUSH *oldBrush); static void GdiFreeBrush(Tcl_Interp *interp, HDC hDC, HGDIOBJ oldBrush); static int GdiGetHdcInfo(HDC hdc, LPPOINT worigin, LPSIZE wextent, LPPOINT vorigin, LPSIZE vextent); /* Helper functions for printing the window client area. */ enum PrintType { PTWindow = 0, PTClient = 1, PTScreen = 2 }; static HANDLE CopyToDIB(HWND wnd, enum PrintType type); static HBITMAP CopyScreenToBitmap(LPRECT lpRect); static HANDLE BitmapToDIB(HBITMAP hb, HPALETTE hp); static HANDLE CopyScreenToDIB(LPRECT lpRect); static int DIBNumColors(LPBITMAPINFOHEADER lpDIB); static int PalEntriesOnDevice(HDC hDC); static HPALETTE GetSystemPalette(void); static void GetDisplaySize(LONG *width, LONG *height); static int GdiParseFontWords(Tcl_Interp *interp, LOGFONTW *lf, Tcl_Obj *const *objv, Tcl_Size argc); static Tcl_ObjCmdProc2 PrintSelectPrinter; static Tcl_ObjCmdProc2 PrintOpenPrinter; static Tcl_ObjCmdProc2 PrintClosePrinter; static Tcl_ObjCmdProc2 PrintOpenDoc; static Tcl_ObjCmdProc2 PrintCloseDoc; static Tcl_ObjCmdProc2 PrintOpenPage; static Tcl_ObjCmdProc2 PrintClosePage; /* * Global state. */ /* static DOCINFOW di; static HDC printDC; static Tcl_DString jobNameW; */ typedef struct WinprintData { DOCINFOW di; HDC printDC; Tcl_DString jobNameW; } WinprintData; /* * To make the "subcommands" follow a standard convention, add them to this * array. The first element is the subcommand name, and the second a standard * Tcl command handler. */ static const struct gdi_command { const char *command_string; Tcl_ObjCmdProc2 *command; } gdi_commands[] = { { "arc", GdiArc }, { "bitmap", GdiBitmap }, { "characters", GdiCharWidths }, { "image", GdiImage }, { "line", GdiLine }, { "map", GdiMap }, { "oval", GdiOval }, { "photo", GdiPhoto }, { "polygon", GdiPolygon }, { "rectangle", GdiRectangle }, { "text", GdiText }, { "textplain", GdiTextPlain }, { "copybits", GdiCopyBits }, }; /* * Helper functions for Tcl_ParseArgsObjv. * These are used in parsing "-option value" pairs in the different GDI * subcommands. */ /* * This structure represents a canvas color, which can be any color * accepted by Tk_GetColor, or the empty string to mean * "don't draw the element" */ typedef struct CanvasColor { COLORREF color; /* Color */ char isempty; /* 1 if color is {}, 0 otherwise */ } CanvasColor; static Tcl_Size ParseColor ( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv, void *dstPtr) { CanvasColor *ccolor = (CanvasColor *)dstPtr; const char *colorname; if (objc == 0) { Tcl_AppendResult(interp, "option \"", Tcl_GetString(objv[-1]), "\" needs an additional argument", (char *)NULL); return -1; } if (GdiGetColor(objv[0], &(ccolor->color))) { ccolor->isempty = 0; return 1; } colorname = Tcl_GetString(objv[0]); if (colorname[0] == '\0') { ccolor->color = 0; ccolor->isempty = 1; return 1; } Tcl_AppendResult(interp, "unknown color name \"", colorname, "\"", (char *)NULL); return -1; } static Tcl_Size ParseDash ( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv, void *dstPtr) { const char *dashspec; Tk_Dash dash; int staticsize = (int) sizeof(char *); if (objc == 0) { Tcl_AppendResult(interp, "option \"-dash\" needs an additional argument", (char *)NULL); return -1; } dashspec = Tcl_GetString(objv[0]); /* Tk_GetDash might potentially call ckfree() on dash.pattern.pt if * dash.number is not initialized to 0 */ dash.number = 0; if (Tk_GetDash(interp, dashspec, &dash) != TCL_OK) { return -1; } if (dash.number == 0) { /* empty string; do nothing */ return 1; } /* free the possibly allocated space */ if (dash.number > staticsize || dash.number < -staticsize) { ckfree(dash.pattern.pt); } *(const char **)dstPtr = dashspec; return 1; } static Tcl_Size ParseAnchor ( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv, void *dstPtr) { Tk_Anchor anchor; if (objc == 0) { Tcl_AppendResult(interp, "option \"-anchor\" needs an additional argument", (char *)NULL); return -1; } if (Tk_GetAnchorFromObj(interp, objv[0], &anchor) != TCL_OK) { return -1; } *(Tk_Anchor *)dstPtr = anchor; return 1; } static Tcl_Size ParseFont ( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv, void *dstPtr) { Tcl_Size fcount; Tcl_Obj **fobjs; const char *fstring; int size; if (objc == 0) { Tcl_AppendResult(interp, "option \"-font\" needs an additional argument", (char *)NULL); return -1; } fstring = Tcl_GetString(objv[0]); /* Accept font description in the same format as provided by * Tk_FontGetDescription(), a list of * family size ?normal|bold? ?roman|italic? ?underline? ?overstrike? */ if (Tcl_ListObjGetElements(NULL, objv[0], &fcount, &fobjs) != TCL_OK || (fcount < 2 || fcount > 6)) { Tcl_AppendResult(interp, "bad font description \"", fstring, "\"", (char *)NULL); return -1; } /* canvas font size should be in pixels (negative) * text font size should be in points (positive) */ if (Tcl_GetIntFromObj(interp, fobjs[1], &size) != TCL_OK) { const char *value = Tcl_GetString(fobjs[1]); Tcl_AppendResult(interp, "bad size \"", value, "\"; should be an integer", (char *)NULL); return -1; } *(Tcl_Obj **)dstPtr = objv[0]; return 1; } static Tcl_Size ParseJoinStyle ( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv, void *dstPtr) { int join; if (objc == 0) { Tcl_AppendResult(interp, "option \"-joinstyle\" needs an additional argument", (char *)NULL); return -1; } if (Tk_GetJoinStyle(interp, Tcl_GetString(objv[0]), &join) != TCL_OK) { return -1; } switch (join) { case JoinBevel: *(int *)dstPtr = PS_JOIN_BEVEL; break; case JoinMiter: *(int *)dstPtr = PS_JOIN_MITER; break; case JoinRound: *(int *)dstPtr = PS_JOIN_ROUND; break; } return 1; } /* *---------------------------------------------------------------------- * * Arc specific: parse "-style" * *---------------------------------------------------------------------- */ static Tcl_Size ParseStyle ( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv, void *dstPtr) { DrawFunc *func = (DrawFunc *)dstPtr; Tcl_Size index; static struct FuncMap { const char *name; DrawFunc gdifunc; } funcmap[] = { {"arc", Arc }, {"chord", Chord}, {"pieslice", Pie }, {NULL, NULL} }; if (objc == 0) { Tcl_AppendResult(interp, "option \"-style\" needs an additional argument", (char *)NULL); return -1; } if (Tcl_GetIndexFromObjStruct(interp, objv[0], funcmap, sizeof(struct FuncMap), "-style option", 0, &index) != TCL_OK) { return -1; } *func = funcmap[index].gdifunc; return 1; } /* *---------------------------------------------------------------------- * * GdiArc -- * * Map canvas arcs to GDI context. * * Results: * Renders arcs. * *---------------------------------------------------------------------- */ static int GdiArc( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } HDC hDC = dataPtr->printDC; double x1, y1, x2, y2; int xr0, yr0, xr1, yr1; /* canvas arc item defaults */ double extent = 90.0; double start = 0.0; double width = 1.0; CanvasColor outline = {0, 0}; CanvasColor fill = {0, 1}; const char *dash = NULL; const char *stipple = NULL; const char *olstipple = NULL; DrawFunc drawfunc = Pie; LOGBRUSH lbrush; HGDIOBJ oldbrush = NULL, oldpen = NULL; /* Verrrrrry simple for now.... */ if (objc < 6) { Tcl_WrongNumArgs(interp, 1, objv, "hdc x1 y1 x2 y2 ?option value ...?"); return TCL_ERROR; } if ((Tcl_GetDoubleFromObj(interp, objv[2], &x1) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[3], &y1) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[4], &x2) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[5], &y2) != TCL_OK)) { return TCL_ERROR; } objv += 6; objc -= 6; if (objc > 0) { Tcl_Size argc = objc + 1; objv--; const Tcl_ArgvInfo arcArgvInfo[] = { {TCL_ARGV_GENFUNC, "-dash", ParseDash, &dash, NULL, NULL}, {TCL_ARGV_FLOAT, "-extent", NULL, &extent, NULL, NULL}, {TCL_ARGV_GENFUNC, "-fill", ParseColor, &fill, NULL, NULL}, {TCL_ARGV_GENFUNC, "-outline", ParseColor, &outline, NULL, NULL}, {TCL_ARGV_STRING, "-outlinestipple", NULL,&olstipple, NULL, NULL}, {TCL_ARGV_FLOAT, "-start", NULL, &start, NULL, NULL}, {TCL_ARGV_STRING, "-stipple", NULL, &stipple, NULL, NULL}, {TCL_ARGV_GENFUNC, "-style", ParseStyle, &drawfunc, NULL, NULL}, {TCL_ARGV_FLOAT, "-width", NULL, &width, NULL, NULL}, TCL_ARGV_TABLE_END }; if (Tcl_ParseArgsObjv(interp, arcArgvInfo, &argc, objv, NULL) != TCL_OK) { return TCL_ERROR; } } /* a simple check: if both -outline and -fill are empty, return */ if (outline.isempty && fill.isempty) { return TCL_OK; } 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 for bringing a * nice example. */ xr0 += (int)(100.0 * (x2 - x1) * cos(DEG2RAD(start))); yr0 -= (int)(100.0 * (y2 - y1) * sin(DEG2RAD(start))); xr1 += (int)(100.0 * (x2 - x1) * cos(DEG2RAD(start+extent))); yr1 -= (int)(100.0 * (y2 - y1) * sin(DEG2RAD(start+extent))); /* * 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 (fill.isempty) { oldbrush = SelectObject(hDC, GetStockObject(HOLLOW_BRUSH)); } else { GdiMakeBrush(fill.color, 0, &lbrush, hDC, (HBRUSH *)&oldbrush); } if (outline.isempty) { oldpen = SelectObject(hDC, GetStockObject(NULL_PEN)); } else { GdiMakePen(interp, width, (dash != NULL), dash, PS_ENDCAP_FLAT, PS_JOIN_BEVEL, 0, 0, outline.color, hDC, &oldpen); } (*drawfunc)(hDC, x1, y1, x2, y2, xr0, yr0, xr1, yr1); GdiFreePen(interp, hDC, oldpen); GdiFreeBrush(interp, hDC, oldbrush); return TCL_OK; } /* *---------------------------------------------------------------------- * * GdiBitmap -- * * Unimplemented for now. Should use the same techniques as * CanvasPsBitmap (tkCanvPs.c). * * Results: * None. * *---------------------------------------------------------------------- */ static int GdiBitmap( TCL_UNUSED(void *), Tcl_Interp *interp, TCL_UNUSED(Tcl_Size), Tcl_Obj *const *objv) { /* * Skip this for now. Should be based on common code with the copybits * command. */ Tcl_WrongNumArgs(interp, 1, objv, "hdc x y " "-anchor [center|n|e|s|w] -background color " "-bitmap bitmap -foreground color\n" "Not implemented yet. Sorry!"); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * GdiImage -- * * Unimplemented for now. Unimplemented for now. Should switch on image * type and call either GdiPhoto or GdiBitmap. This code is similar to * that in tkWinImage.c. * * Results: * None. * *---------------------------------------------------------------------- */ static int GdiImage( TCL_UNUSED(void *), Tcl_Interp *interp, TCL_UNUSED(Tcl_Size), Tcl_Obj *const *objv) { /* Skip this for now..... */ /* Should be based on common code with the copybits command. */ Tcl_WrongNumArgs(interp, 1, objv, "hdc x y -anchor [center|n|e|s|w] -image name\n" "Not implemented yet. Sorry!"); /* Normally, usage results in TCL_ERROR--but wait til' it's implemented. */ return TCL_OK; } /* *---------------------------------------------------------------------- * * GdiPhoto -- * * Contributed by Lukas Rosenthaler * * 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( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } HDC hDC = dataPtr->printDC; int hDC_x = 0, hDC_y = 0, hDC_w = 0, hDC_h = 0; int nx, ny, sll; const char *photoname = NULL; /* 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, k; int retval = TCL_OK; double x, y; Tk_Anchor anchor = TK_ANCHOR_CENTER; /* * Parse the arguments. */ if (objc < 4) { Tcl_WrongNumArgs(interp, 1, objv, "hdc x y ?option value ...?"); return TCL_ERROR; } /* * Next, check to see if 'hDC' can support BitBlt. * If not, raise an error. */ if ((GetDeviceCaps(hDC, 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; } if ((Tcl_GetDoubleFromObj(interp, objv[2], &x) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[3], &y) != TCL_OK)) { return TCL_ERROR; } hDC_x = ROUND32(x); hDC_y = ROUND32(y); objc -= 4; objv += 4; if (objc > 0) { Tcl_Size argc = objc + 1; objv--; const Tcl_ArgvInfo photoArgvInfo[] = { {TCL_ARGV_GENFUNC, "-anchor", ParseAnchor, &anchor, NULL, NULL}, {TCL_ARGV_STRING, "-photo", NULL, &photoname, NULL, NULL}, TCL_ARGV_TABLE_END }; if (Tcl_ParseArgsObjv(interp, photoArgvInfo, &argc, objv, NULL) != TCL_OK) { return TCL_ERROR; } } if (! photoname) { /* No photo provided. */ Tcl_AppendResult(interp, "no photo name provided", (char *)NULL); return TCL_ERROR; } photo_handle = Tk_FindPhoto(interp, photoname); if (! photo_handle) { Tcl_AppendResult(interp, "photo name \"", photoname, "\" can't be located", (char *)NULL); return TCL_ERROR; } Tk_PhotoGetImage(photo_handle, &img_block); hDC_w = nx = img_block.width; hDC_h = 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) { /* 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(hDC, HALFTONE); /* * According to the Win32 Programmer's Manual, we have to set the brush * org, now. */ SetBrushOrgEx(hDC, 0, 0, &pt); /* adjust coords based on the anchor point */ switch (anchor) { case TK_ANCHOR_N: hDC_x -= hDC_w/2; break; case TK_ANCHOR_NE: hDC_x -= hDC_w; break; case TK_ANCHOR_W: hDC_y -= hDC_h/2; break; case TK_ANCHOR_CENTER: hDC_x -= hDC_w/2; hDC_y -= hDC_h/2; break; case TK_ANCHOR_E: hDC_x -= hDC_w; hDC_y -= hDC_h/2; break; case TK_ANCHOR_SW: hDC_y -= hDC_h; break; case TK_ANCHOR_S: hDC_x -= hDC_w/2; hDC_y -= hDC_h; break; case TK_ANCHOR_SE: hDC_x -= hDC_w; hDC_y -= hDC_h; break; default: /* nothing */ break; } if (StretchDIBits(hDC, hDC_x, hDC_y, hDC_w, hDC_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(hDC, oldmode); SetBrushOrgEx(hDC, pt.x, pt.y, &pt); } ckfree(pbuf); if (retval == TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "%d %d %d %d", hDC_x, hDC_y, hDC_w, hDC_h)); } return retval; } /* *---------------------------------------------------------------------- * * Smoothize -- * * Interface to Tk's line smoother, used for lines and pollies. * Provided by Jasper Taylor . * * Results: * Smooths lines. * *---------------------------------------------------------------------- */ #define SMOOTH_NONE 0 #define SMOOTH_BEZIER 1 #define SMOOTH_RAW 2 static int Smoothize( POINT* polypoints, int npoly, int nStep, int smooth, /* either SMOOTH_BEZIER or SMOOTH_RAW */ 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; nprintDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } HDC hDC = dataPtr->printDC; double p1x, p1y, p2x, p2y; POINT *polypoints; int npoly; /* canvas line item defaults */ double width = 1.0; CanvasColor fill = {0, 0}; int smooth = SMOOTH_NONE; int arrow = ARROW_NONE; /* 0=none; 1=end; 2=start; 3=both. */ int arrowshape[3] = {8, 10, 3}; int nStep = 12; const char *dash = NULL; int capstyle = PS_ENDCAP_FLAT; int joinstyle = PS_JOIN_ROUND; /* ignored for now */ const char *stipple = NULL; const char *dashoffset = NULL; LOGBRUSH lbrush; HGDIOBJ oldpen = NULL, oldbrush = NULL; double shapeA = 0, shapeB = 0, shapeC = 0, fracHeight = 0, backup = 0; /* Verrrrrry simple for now.... */ if (objc < 6) { Tcl_WrongNumArgs(interp, 1, objv, "hdc x1 y1... xn yn ?option value ...?"); return TCL_ERROR; } if ((Tcl_GetDoubleFromObj(interp, objv[2], &p1x) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[3], &p1y) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[4], &p2x) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[5], &p2y) != TCL_OK)) { return TCL_ERROR; } polypoints = (POINT *)attemptckalloc((objc - 2)/2 * sizeof(POINT)); if (polypoints == NULL) { Tcl_AppendResult(interp, "Out of memory in GdiLine", (char *)NULL); return TCL_ERROR; } polypoints[0].x = ROUND32(p1x); polypoints[0].y = ROUND32(p1y); polypoints[1].x = ROUND32(p2x); polypoints[1].y = ROUND32(p2y); objc -= 6; objv += 6; npoly = 2; while (objc >= 2 && Tcl_GetDoubleFromObj(NULL, objv[0], &p1x) == TCL_OK && Tcl_GetDoubleFromObj(NULL, objv[1], &p1y) == TCL_OK) { polypoints[npoly].x = ROUND32(p1x); polypoints[npoly].y = ROUND32(p1y); npoly++; objc -= 2; objv += 2; } if (objc > 0) { Tcl_Size argc = objc + 1; objv--; const Tcl_ArgvInfo lineArgvInfo[] = { {TCL_ARGV_GENFUNC, "-arrow", ParseArrow, &arrow, NULL, NULL}, {TCL_ARGV_GENFUNC, "-arrowshape", ParseArrShp, arrowshape, NULL, NULL}, {TCL_ARGV_GENFUNC, "-capstyle", ParseCapStyle, &capstyle, NULL, NULL}, {TCL_ARGV_GENFUNC, "-dash", ParseDash, &dash, NULL, NULL}, {TCL_ARGV_STRING, "-dashoffset", NULL, &dashoffset,NULL, NULL}, {TCL_ARGV_GENFUNC, "-fill", ParseColor, &fill, NULL, NULL}, {TCL_ARGV_GENFUNC, "-joinstyle", ParseJoinStyle,&joinstyle, NULL, NULL}, {TCL_ARGV_GENFUNC, "-smooth", ParseSmooth, &smooth, NULL, NULL}, {TCL_ARGV_INT, "-splinesteps",NULL, &nStep, NULL, NULL}, {TCL_ARGV_STRING, "-stipple", NULL, &stipple, NULL, NULL}, {TCL_ARGV_FLOAT, "-width", NULL, &width, NULL, NULL}, TCL_ARGV_TABLE_END }; if (Tcl_ParseArgsObjv(interp, lineArgvInfo, &argc, objv, NULL ) != TCL_OK) { ckfree(polypoints); return TCL_ERROR; } } if (fill.isempty) { ckfree(polypoints); return TCL_OK; } if (arrow != ARROW_NONE) { /* if -arrow is specified, capstyle is ignored */ capstyle = PS_ENDCAP_FLAT; } if (smooth != SMOOTH_NONE) { /* Use Smoothize. */ int nspoints; POINT *spoints; nspoints = Smoothize(polypoints, npoly, nStep, smooth, &spoints); if (nspoints > 0) { /* replace the old point list with the new one */ ckfree(polypoints); polypoints = spoints; npoly = nspoints; } } if (arrow != ARROW_NONE) { GdiMakeBrush(fill.color, 0, &lbrush, hDC, (HBRUSH *)&oldbrush); GdiMakePen(interp, 1, 0, 0, 0, PS_JOIN_MITER, 0, 0, fill.color, hDC, &oldpen); shapeA = arrowshape[0] + 0.001; shapeB = arrowshape[1] + 0.001; shapeC = arrowshape[2] + width/2.0 + 0.001; fracHeight = (width/2.0)/shapeC; backup = fracHeight*shapeB + shapeA*(1.0 - fracHeight)/2.0; } /* draw the arrowheads, if any. */ if (arrow & ARROW_LAST) { /* 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; 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 - shapeA*cosTheta; vertY = ahead[0].y - shapeC*sinTheta; temp = shapeC*sinTheta; ahead[1].x = ROUND32(ahead[0].x - shapeB*cosTheta + temp); ahead[4].x = ROUND32(ahead[1].x - 2 * temp); temp = shapeC*cosTheta; ahead[1].y = ROUND32(ahead[0].y - shapeB*sinTheta - temp); ahead[4].y = ROUND32(ahead[1].y + 2 * temp); ahead[2].x = ROUND32(ahead[1].x*fracHeight + vertX*(1.0-fracHeight)); ahead[2].y = ROUND32(ahead[1].y*fracHeight + vertY*(1.0-fracHeight)); ahead[3].x = ROUND32(ahead[4].x*fracHeight + vertX*(1.0-fracHeight)); ahead[3].y = ROUND32(ahead[4].y*fracHeight + vertY*(1.0-fracHeight)); Polygon(hDC, ahead, 6); polypoints[npoly-1].x = ROUND32(ahead[0].x - backup*cosTheta); polypoints[npoly-1].y = ROUND32(ahead[0].y - backup*sinTheta); } if (arrow & ARROW_FIRST) { /* Arrowhead at beginning = polypoints[0].x, polypoints[0].y. */ POINT ahead[6]; double dx, dy, length; double sinTheta, cosTheta; double vertX, vertY, temp; 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 - shapeA*cosTheta; vertY = ahead[0].y - shapeA*sinTheta; temp = shapeC*sinTheta; ahead[1].x = ROUND32(ahead[0].x - shapeB*cosTheta + temp); ahead[4].x = ROUND32(ahead[1].x - 2 * temp); temp = shapeC*cosTheta; ahead[1].y = ROUND32(ahead[0].y - shapeB*sinTheta - temp); ahead[4].y = ROUND32(ahead[1].y + 2 * temp); ahead[2].x = ROUND32(ahead[1].x*fracHeight + vertX*(1.0-fracHeight)); ahead[2].y = ROUND32(ahead[1].y*fracHeight + vertY*(1.0-fracHeight)); ahead[3].x = ROUND32(ahead[4].x*fracHeight + vertX*(1.0-fracHeight)); ahead[3].y = ROUND32(ahead[4].y*fracHeight + vertY*(1.0-fracHeight)); Polygon(hDC, ahead, 6); polypoints[0].x = ROUND32(ahead[0].x - backup*cosTheta); polypoints[0].y = ROUND32(ahead[0].y - backup*sinTheta); } /* free arrow's pen and brush (if any) */ if (arrow != ARROW_NONE) { GdiFreePen(interp, hDC, oldpen); GdiFreeBrush(interp, hDC, oldbrush); } /* draw the line */ GdiMakePen(interp, width, (dash != NULL), dash, capstyle, joinstyle, 0, 0, fill.color, hDC, &oldpen); Polyline(hDC, polypoints, npoly); GdiFreePen(interp, hDC, oldpen); ckfree(polypoints); return TCL_OK; } /* *---------------------------------------------------------------------- * * GdiOval -- * * Maps ovals to GDI context. * * Results: * Renders ovals. * *---------------------------------------------------------------------- */ static int GdiOval( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } HDC hDC = dataPtr->printDC; /* canvas oval item defaults */ double width = 1.0; CanvasColor outline = {0, 0}; CanvasColor fill = {0, 1}; const char *dash = NULL; const char *stipple = NULL; LOGBRUSH lbrush; HGDIOBJ oldpen = NULL, oldbrush = NULL; double x1, y1, x2, y2; /* Verrrrrry simple for now.... */ if (objc < 6) { Tcl_WrongNumArgs(interp, 1, objv, "hdc x1 y1 x2 y2 ?option value ...?"); return TCL_ERROR; } if ((Tcl_GetDoubleFromObj(interp, objv[2], &x1) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[3], &y1) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[4], &x2) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[5], &y2) != TCL_OK)) { return TCL_ERROR; } if (x1 > x2) { double x3 = x1; x1 = x2; x2 = x3; } if (y1 > y2) { double y3 = y1; y1 = y2; y2 = y3; } objc -= 6; objv += 6; if (objc > 0) { Tcl_Size argc = objc + 1; objv--; const Tcl_ArgvInfo ovalArgvInfo[] = { {TCL_ARGV_GENFUNC, "-dash", ParseDash, &dash, NULL, NULL}, {TCL_ARGV_GENFUNC, "-fill", ParseColor, &fill, NULL, NULL}, {TCL_ARGV_GENFUNC, "-outline", ParseColor, &outline, NULL, NULL}, {TCL_ARGV_STRING, "-stipple", NULL, &stipple, NULL, NULL}, {TCL_ARGV_FLOAT, "-width", NULL, &width, NULL, NULL}, TCL_ARGV_TABLE_END }; if (Tcl_ParseArgsObjv(interp, ovalArgvInfo, &argc, objv, NULL ) != TCL_OK) { return TCL_ERROR; } } if (outline.isempty && fill.isempty) { return TCL_OK; } if (fill.isempty) { oldbrush = SelectObject(hDC, GetStockObject(NULL_BRUSH)); } else { GdiMakeBrush(fill.color, 0, &lbrush, hDC, (HBRUSH *)&oldbrush); } if (outline.isempty) { oldpen = SelectObject(hDC, GetStockObject(NULL_PEN)); } else { GdiMakePen(interp, width, (dash != NULL), dash, 0, 0, 0, 0, outline.color, hDC, &oldpen); } /* * Per Win32, Ellipse includes lower and right edges--per Tcl8.3.2 and * earlier documentation, canvas oval does not. Thus, add 1 to right * and lower bounds to get appropriate behavior. */ Ellipse(hDC, ROUND32(x1), ROUND32(y1), ROUND32(x2+1), ROUND32(y2+1)); GdiFreePen(interp, hDC, oldpen); GdiFreeBrush(interp, hDC, oldbrush); return TCL_OK; } /* *---------------------------------------------------------------------- * * GdiPolygon -- * * Maps polygons to GDI context. * * Results: * Renders polygons. * *---------------------------------------------------------------------- */ static int GdiPolygon( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } HDC hDC = dataPtr->printDC; POINT *polypoints; int npoly; /* canvas polygon item defaults */ double width = 1.0; CanvasColor outline = {0, 0}; CanvasColor fill = {0, 1}; int joinstyle = PS_JOIN_ROUND; int smooth = SMOOTH_NONE; int nStep = 12; const char *dash = NULL; const char *stipple = NULL; LOGBRUSH lbrush; HGDIOBJ oldpen = NULL, oldbrush = NULL; double p1x, p1y, p2x, p2y; /* Verrrrrry simple for now.... */ if (objc < 6) { Tcl_WrongNumArgs(interp, 1, objv, "hdc x1 y1 ... xn yn ?option value ...?"); return TCL_ERROR; } if ((Tcl_GetDoubleFromObj(interp, objv[2], &p1x) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[3], &p1y) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[4], &p2x) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[5], &p2y) != TCL_OK)) { return TCL_ERROR; } polypoints = (POINT *)attemptckalloc((objc - 2)/2 * sizeof(POINT)); if (polypoints == NULL) { /* TODO: unreachable */ Tcl_AppendResult(interp, "Out of memory in GdiPolygon", (char *)NULL); return TCL_ERROR; } polypoints[0].x = ROUND32(p1x); polypoints[0].y = ROUND32(p1y); polypoints[1].x = ROUND32(p2x); polypoints[1].y = ROUND32(p2y); objc -= 6; objv += 6; npoly = 2; while (objc >= 2 && Tcl_GetDoubleFromObj(NULL, objv[0], &p1x) == TCL_OK && Tcl_GetDoubleFromObj(NULL, objv[1], &p1y) == TCL_OK) { polypoints[npoly].x = ROUND32(p1x); polypoints[npoly].y = ROUND32(p1y); npoly++; objc -= 2; objv += 2; } if (objc > 0) { Tcl_Size argc = objc + 1; objv--; const Tcl_ArgvInfo polyArgvInfo[] = { {TCL_ARGV_GENFUNC, "-dash", ParseDash, &dash, NULL, NULL}, {TCL_ARGV_GENFUNC, "-fill", ParseColor, &fill, NULL, NULL}, {TCL_ARGV_GENFUNC, "-joinstyle", ParseJoinStyle,&joinstyle, NULL, NULL}, {TCL_ARGV_GENFUNC, "-outline", ParseColor, &outline, NULL, NULL}, {TCL_ARGV_GENFUNC, "-smooth", ParseSmooth, &smooth, NULL, NULL}, {TCL_ARGV_INT, "-splinesteps",NULL, &nStep, NULL, NULL}, {TCL_ARGV_STRING, "-stipple", NULL, &stipple, NULL, NULL}, {TCL_ARGV_FLOAT, "-width", NULL, &width, NULL, NULL}, TCL_ARGV_TABLE_END }; if (Tcl_ParseArgsObjv(interp, polyArgvInfo, &argc, objv, NULL ) != TCL_OK) { ckfree(polypoints); return TCL_ERROR; } } if (outline.isempty && fill.isempty) { return TCL_OK; } if (outline.isempty) { oldpen = SelectObject(hDC, GetStockObject(NULL_PEN)); } else { GdiMakePen(interp, width, (dash != NULL), dash, 0, joinstyle, 0, 0, outline.color, hDC, &oldpen); } if (fill.isempty) { oldbrush = SelectObject(hDC, GetStockObject(HOLLOW_BRUSH)); } else { GdiMakeBrush(fill.color, 0, &lbrush, hDC, (HBRUSH *)&oldbrush); } if (smooth) { /* Use Smoothize. */ int nspoints; POINT *spoints; nspoints = Smoothize(polypoints, npoly, nStep, smooth, &spoints); if (nspoints > 0) { /* replace the old point list with the new one */ ckfree(polypoints); polypoints = spoints; npoly = nspoints; } } Polygon(hDC, polypoints, npoly); GdiFreePen(interp, hDC, oldpen); GdiFreeBrush(interp, hDC, oldbrush); ckfree(polypoints); return TCL_OK; } /* *---------------------------------------------------------------------- * * GdiRectangle -- * * Maps rectangles to GDI context. * * Results: * Renders rectangles. * *---------------------------------------------------------------------- */ static int GdiRectangle( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } HDC hDC = dataPtr->printDC; /* canvas rectangle item defaults */ double width = 1.0; CanvasColor outline = {0, 0}; CanvasColor fill = {0, 1}; const char *dash = NULL; const char *stipple = NULL; LOGBRUSH lbrush; HGDIOBJ oldpen = NULL, oldbrush = NULL; double x1, y1, x2, y2; /* Verrrrrry simple for now.... */ if (objc < 6) { Tcl_WrongNumArgs(interp, 1, objv, "hdc x1 y1 x2 y2 ?option value ...?"); return TCL_ERROR; } if ((Tcl_GetDoubleFromObj(interp, objv[2], &x1) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[3], &y1) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[4], &x2) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[5], &y2) != TCL_OK)) { return TCL_ERROR; } if (x1 > x2) { double x3 = x1; x1 = x2; x2 = x3; } if (y1 > y2) { double y3 = y1; y1 = y2; y2 = y3; } objc -= 6; objv += 6; if (objc > 0) { Tcl_Size argc = objc + 1; objv--; const Tcl_ArgvInfo rectArgvInfo[] = { {TCL_ARGV_GENFUNC, "-dash", ParseDash, &dash, NULL, NULL}, {TCL_ARGV_GENFUNC, "-fill", ParseColor, &fill, NULL, NULL}, {TCL_ARGV_GENFUNC, "-outline", ParseColor, &outline, NULL, NULL}, {TCL_ARGV_STRING, "-stipple", NULL, &stipple, NULL, NULL}, {TCL_ARGV_FLOAT, "-width", NULL, &width, NULL, NULL}, TCL_ARGV_TABLE_END }; if (Tcl_ParseArgsObjv(interp, rectArgvInfo, &argc, objv, NULL ) != TCL_OK) { return TCL_ERROR; } } if (outline.isempty && fill.isempty) { return TCL_OK; } if (fill.isempty) { oldbrush = SelectObject(hDC, GetStockObject(NULL_BRUSH)); } else { GdiMakeBrush(fill.color, 0, &lbrush, hDC, (HBRUSH *)&oldbrush); } if (outline.isempty) { oldpen = SelectObject(hDC, GetStockObject(NULL_PEN)); } else { GdiMakePen(interp, width, (dash != NULL), dash, 0, PS_JOIN_MITER, 0, 0, outline.color, hDC, &oldpen); } /* * 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, ROUND32(x1), ROUND32(y1), ROUND32(x2+1), ROUND32(y2+1)); GdiFreePen(interp, hDC, oldpen); GdiFreeBrush(interp, hDC, oldbrush); 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( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv) { /* * 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). */ WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } HDC hDC = dataPtr->printDC; const char *aryvarname = "GdiCharWidths"; Tcl_Obj *fontobj = NULL; LOGFONTW lf; HFONT hfont; HGDIOBJ oldfont = NULL; /* For now, assume 256 characters in the font.... */ int widths[256]; int retval; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "hdc ?opton value ...?"); return TCL_ERROR; } objc -= 2; objv += 2; if (objc > 0) { Tcl_Size argc = objc + 1; objv--; const Tcl_ArgvInfo cwArgvInfo[] = { {TCL_ARGV_STRING, "-array", NULL, &aryvarname, NULL, NULL}, {TCL_ARGV_GENFUNC, "-font", ParseFont, &fontobj, NULL, NULL}, TCL_ARGV_TABLE_END }; if (Tcl_ParseArgsObjv(interp, cwArgvInfo, &argc, objv, NULL ) != TCL_OK) { return TCL_ERROR; } } /* is an error not providing a font */ if (! fontobj) { Tcl_AppendResult(interp, "error: font must be specified", (char *)NULL); return TCL_ERROR; } if (GdiMakeLogFont(interp, fontobj, &lf, hDC)) { if ((hfont = CreateFontIndirectW(&lf)) != NULL) { oldfont = SelectObject(hDC, hfont); } } else { return TCL_ERROR; } /* 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 (oldfont) { 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 (oldfont) { 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. * *---------------------------------------------------------------------- */ typedef struct LayoutChunk { const char *start; /* Pointer to simple string to be displayed. * This is a pointer into the TkTextLayout's * string. */ Tcl_Size numBytes; /* The number of bytes in this chunk. */ Tcl_Size numChars; /* The number of characters in this chunk. */ Tcl_Size numDisplayChars; /* The number of characters to display when * this chunk is displayed. Can be less than * numChars if extra space characters were * absorbed by the end of the chunk. This will * be < 0 if this is a chunk that is holding a * tab or newline. */ int x, y; /* The origin of the first character in this * chunk with respect to the upper-left hand * corner of the TextLayout. */ int totalWidth; /* Width in pixels of this chunk. Used when * hit testing the invisible spaces at the end * of a chunk. */ int displayWidth; /* Width in pixels of the displayable * characters in this chunk. Can be less than * width if extra space characters were * absorbed by the end of the chunk. */ } LayoutChunk; typedef struct TextLayout { Tk_Font tkfont; /* The font used when laying out the text. */ const char *string; /* The string that was layed out. */ int width; /* The maximum width of all lines in the text * layout. */ Tcl_Size numChunks; /* Number of chunks actually used in following * array. */ LayoutChunk chunks[TKFLEXARRAY];/* Array of chunks. The actual size will be * maxChunks. THIS FIELD MUST BE THE LAST IN * THE STRUCTURE. */ } TextLayout; static Tcl_Size ParseJustify ( TCL_UNUSED(void *), Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv, void *dstPtr) { Tk_Justify justify; if (objc == 0) { Tcl_AppendResult(interp, "option \"-justify\" needs an additional argument", (char *)NULL); return -1; } if (Tk_GetJustifyFromObj(interp, objv[0], &justify) != TCL_OK) { return -1; } *(Tk_Justify *)dstPtr = justify; return 1; } static int GdiText( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } HDC hDC = dataPtr->printDC; double x0, y0; Tk_Anchor anchor = TK_ANCHOR_NW; double angle = 0.0; CanvasColor fill = {0, 0}; Tcl_Obj *fontobj = NULL; /* -font shall be provided */ Tk_Justify justify = TK_JUSTIFY_LEFT; const char *string = NULL; int wraplen = 0; const char *stipple = NULL; LOGFONTW lf; HFONT hfont; HGDIOBJ oldfont; int made_font = 0; COLORREF oldtextcolor = 0; int bgmode; if (objc < 4) { Tcl_WrongNumArgs(interp, 1, objv, "hdc x y ?option value ...?"); return TCL_ERROR; } /* Parse the command. */ if ((Tcl_GetDoubleFromObj(interp, objv[2], &x0) != TCL_OK) || (Tcl_GetDoubleFromObj(interp, objv[3], &y0) != TCL_OK)) { return TCL_ERROR; } objc -= 4; objv += 4; if (objc > 0) { Tcl_Size argc = objc + 1; objv--; const Tcl_ArgvInfo textArgvInfo[] = { {TCL_ARGV_GENFUNC, "-anchor", ParseAnchor, &anchor, NULL, NULL}, {TCL_ARGV_FLOAT, "-angle", NULL, &angle, NULL, NULL}, {TCL_ARGV_GENFUNC, "-fill", ParseColor, &fill, NULL, NULL}, {TCL_ARGV_GENFUNC, "-font", ParseFont, &fontobj, NULL, NULL}, {TCL_ARGV_GENFUNC, "-justify", ParseJustify, &justify, NULL, NULL}, {TCL_ARGV_STRING, "-stipple", NULL, &stipple, NULL, NULL}, {TCL_ARGV_STRING, "-text", NULL, &string, NULL, NULL}, {TCL_ARGV_INT, "-width", NULL, &wraplen, NULL, NULL}, TCL_ARGV_TABLE_END }; if (Tcl_ParseArgsObjv(interp, textArgvInfo, &argc, objv, NULL ) != TCL_OK) { return TCL_ERROR; } } /* if we have no text, nothing to do */ if (! string) { return TCL_OK; } /* if we got empty color, nothing to do */ if (fill.isempty) { return TCL_OK; } /* is an error not providing a font */ if (! fontobj) { Tcl_AppendResult(interp, "error: font must be specified", (char *)NULL); return TCL_ERROR; } if (GdiMakeLogFont(interp, fontobj, &lf, hDC)) { lf.lfEscapement = lf.lfOrientation = 10.0 * angle; if ((hfont = CreateFontIndirectW(&lf)) != NULL) { made_font = 1; oldfont = SelectObject(hDC, hfont); } } oldtextcolor = SetTextColor(hDC, fill.color); bgmode = SetBkMode(hDC, TRANSPARENT); /* Recreate the text layout here, so we get the same width and * line breaks. */ Tk_Window tkwin = Tk_MainWindow(interp); Tk_Font tkfont = Tk_AllocFontFromObj(interp, tkwin, fontobj); if (!tkfont) { return TCL_ERROR; } int width, height; TextLayout *layout = (TextLayout *)Tk_ComputeTextLayout(tkfont, string, TCL_INDEX_NONE, wraplen, justify, 0, &width, &height); /* Calculate the anchor position in local coordinates * Origin point is x0, y0 */ int xa = 0; int ya = 0; /* values for the default anchor nw */ switch (anchor) { case TK_ANCHOR_NULL: case TK_ANCHOR_NW: xa = 0; ya = 0; break; case TK_ANCHOR_N: xa = -width / 2; ya = 0; break; case TK_ANCHOR_NE: xa = -width; ya = 0; break; case TK_ANCHOR_W: xa = 0; ya = -height / 2; break; case TK_ANCHOR_CENTER: xa = -width / 2; ya = -height / 2; break; case TK_ANCHOR_E: xa = -width; ya = -height / 2; break; case TK_ANCHOR_SW: xa = 0; ya = -height; break; case TK_ANCHOR_S: xa = -width / 2; ya = -height; break; case TK_ANCHOR_SE: xa = -width; ya = -height; break; } /* Set the align and adjust the x anchor point accordingly */ UINT align = TA_TOP; switch (justify) { case TK_JUSTIFY_NULL: case TK_JUSTIFY_LEFT: align |= TA_LEFT; break; case TK_JUSTIFY_CENTER: align |= TA_CENTER; xa += width / 2; break; case TK_JUSTIFY_RIGHT: align |= TA_RIGHT; xa += width; break; } SetTextAlign(hDC, align); Tk_FontMetrics fm; Tk_GetFontMetrics(tkfont, &fm); /* Our coordinate system has the y axis inverted. * Invert the angle to get the values right. */ const double sinA = sin(DEG2RAD(-angle)); const double cosA = cos(DEG2RAD(-angle)); /* now, print each text chunk adjusting the anchor point */ int retval = 1; int nlseen = 0; for (Tcl_Size i = 0; (i < layout->numChunks) && retval; i++) { WCHAR *wstring; Tcl_DString ds; int xi, yi; if (layout->chunks[i].start[0] == '\n') { if (nlseen) { ya += fm.linespace; } else { nlseen = 1; } continue; } xi = floor(x0 + (xa * cosA - ya * sinA) + 0.5); yi = floor(y0 + (xa * sinA + ya * cosA) + 0.5); Tcl_DStringInit(&ds); wstring = Tcl_UtfToWCharDString(layout->chunks[i].start, layout->chunks[i].numBytes, &ds); retval = TextOutW(hDC, xi, yi, wstring, Tcl_DStringLength(&ds)/2); Tcl_DStringFree(&ds); ya += fm.linespace; nlseen = 0; } /* All done. Cleanup */ Tk_FreeTextLayout((Tk_TextLayout) layout); Tk_FreeFont(tkfont); /* Get the color set back. */ SetTextColor(hDC, oldtextcolor); SetBkMode(hDC, bgmode); if (made_font) { SelectObject(hDC, oldfont); DeleteObject(hfont); } return TCL_OK; } static int GdiTextPlain( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } HDC hDC = dataPtr->printDC; int x0, y0, retval; WCHAR *wstring; const char *string; Tcl_Size strlen; Tcl_DString ds; if (objc != 5) { Tcl_WrongNumArgs(interp, 1, objv, "hdc x y text"); return TCL_ERROR; } if ((Tcl_GetIntFromObj(interp, objv[2], &x0) != TCL_OK) || (Tcl_GetIntFromObj(interp, objv[3], &y0) != TCL_OK)) { return TCL_ERROR; } string = Tcl_GetStringFromObj(objv[4], &strlen); Tcl_DStringInit(&ds); wstring = Tcl_UtfToWCharDString(string, strlen, &ds); retval = TextOutW(hDC, x0, y0, wstring, Tcl_DStringLength(&ds)/2); Tcl_DStringFree(&ds); 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( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } HDC hDC = dataPtr->printDC; static const char usage_message[] = "::tk::print::_gdi map hdc " "[-logical x[y]] [-physical x[y]] " "[-offset {x y} ] [-default] [-mode mode]"; int mapmode; /* Mapping mode. */ SIZE wextent; /* Device extent. */ SIZE vextent; /* Viewport extent. */ POINT worigin; /* Device origin. */ POINT vorigin; /* Viewport origin. */ Tcl_Size argno; /* Keep track of what parts of the function need to be executed. */ int need_usage = 0; int use_logical = 0; int use_physical = 0; int use_offset = 0; int use_default = 0; int use_mode = 0; /* Required parameter: HDC for printer. */ if (objc < 2) { Tcl_AppendResult(interp, usage_message, (char *)NULL); return TCL_ERROR; } if ((mapmode = GdiGetHdcInfo(hDC, &worigin, &wextent, &vorigin, &vextent)) == 0) { /* Failed!. */ Tcl_AppendResult(interp, "Cannot get current HDC info", (char *)NULL); return TCL_ERROR; } /* Parse remaining arguments. */ for (argno = 2; argno < objc; argno++) { if (strcmp(Tcl_GetString(objv[argno]), "-default") == 0) { vextent.cx = vextent.cy = wextent.cx = wextent.cy = 1; vorigin.x = vorigin.y = worigin.x = worigin.y = 0; mapmode = MM_TEXT; use_default = 1; } else if (strcmp(Tcl_GetString(objv[argno]), "-mode") == 0) { if (argno + 1 >= objc) { need_usage = 1; } else { mapmode = GdiNameToMode(Tcl_GetString(objv[argno + 1])); use_mode = 1; argno++; } } else if (strcmp(Tcl_GetString(objv[argno]), "-offset") == 0) { if (argno + 1 >= objc) { need_usage = 1; } else { /* It would be nice if this parsed units as well.... */ if (sscanf(Tcl_GetString(objv[argno + 1]), "%ld%ld", &vorigin.x, &vorigin.y) == 2) { use_offset = 1; } else { need_usage = 1; } argno++; } } else if (strcmp(Tcl_GetString(objv[argno]), "-logical") == 0) { if (argno + 1 >= objc) { need_usage = 1; } else { int count; argno++; /* In "real-life", this should parse units as well.. */ if ((count = sscanf(Tcl_GetString(objv[argno]), "%ld%ld", &wextent.cx, &wextent.cy)) != 2) { if (count == 1) { mapmode = MM_ISOTROPIC; use_logical = 1; wextent.cy = wextent.cx; /* Make them the same. */ } else { need_usage = 1; } } else { mapmode = MM_ANISOTROPIC; use_logical = 2; } } } else if (strcmp(Tcl_GetString(objv[argno]), "-physical") == 0) { if (argno + 1 >= objc) { need_usage = 1; } else { int count; argno++; /* In "real-life", this should parse units as well.. */ if ((count = sscanf(Tcl_GetString(objv[argno]), "%ld%ld", &vextent.cx, &vextent.cy)) != 2) { if (count == 1) { mapmode = MM_ISOTROPIC; use_physical = 1; vextent.cy = vextent.cx; /* Make them the same. */ } else { need_usage = 1; } } else { mapmode = MM_ANISOTROPIC; use_physical = 2; } } } } /* Check for any impossible combinations. */ if (use_logical != use_physical) { need_usage = 1; } if (use_default && (use_logical || use_offset || use_mode)) { need_usage = 1; } if (use_mode && use_logical && (mapmode != MM_ISOTROPIC && mapmode != MM_ANISOTROPIC)) { need_usage = 1; } if (need_usage) { Tcl_AppendResult(interp, usage_message, (char *)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( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } HDC dst = dataPtr->printDC; /* 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; HWND hwnd = 0; HANDLE hDib; /* Handle for device-independent bitmap. */ LPBITMAPINFOHEADER lpDIBHdr; LPSTR lpBits; enum PrintType wintype = PTWindow; int hgt, wid; char *strend; long errcode; Tcl_Size k; /* Variables to remember what we saw in the arguments. */ int do_window = 0; int do_screen = 0; int do_scale = 0; int do_print = 1; /* Variables to remember the values in the arguments. */ const char *window_spec; double scale = 1.0; int src_x = 0, src_y = 0, src_w = 0, src_h = 0; int dst_x = 0, dst_y = 0, dst_w = 0, dst_h = 0; int is_toplevel = 0; /* * The following steps are peculiar to the top level window. * There is likely a clever way to do the mapping of a widget pathname to * the proper window, to support the idea of using a parameter for this * purpose. */ if ((workwin = mainWin = Tk_MainWindow(interp)) == 0) { Tcl_AppendResult(interp, "Can't find main Tk window", (char *)NULL); return TCL_ERROR; } /* * Parse the arguments. */ /* HDC is required. */ if (objc < 2) { Tcl_AppendResult(interp, usage_message, (char *)NULL); return TCL_ERROR; } /* * 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= 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, Tcl_Obj *const *objv, Tcl_Size argc) { Tcl_Size i; int retval = 0; /* Number of words that could not be parsed. */ enum fontStyles { STY_BOLD, STY_ITALIC, STY_NORMAL, STY_OVERSTRIKE, STY_ROMAN, STY_UNDERLINE }; const char *const styleNames[] = { "bold", "italic", "normal", "overstrike", "roman", "underline", NULL }; enum fontStyles sty; for (i = 0; i < argc; i++) { if (Tcl_GetIndexFromObj(NULL, objv[i], styleNames, NULL, 0, &sty) != TCL_OK) { retval++; continue; } switch (sty) { case STY_BOLD: lf->lfWeight = FW_BOLD; break; case STY_ITALIC: lf->lfItalic = TRUE; break; case STY_NORMAL: lf->lfWeight = FW_NORMAL; break; case STY_OVERSTRIKE: lf->lfStrikeOut = TRUE; break; case STY_ROMAN: lf->lfItalic = FALSE; break; case STY_UNDERLINE: lf->lfUnderline = TRUE; } } return retval; } /* *---------------------------------------------------------------------- * * MakeLogFont -- * * Takes the font description Tcl_Obj and converts this into a logical * font spec. * The expected font format is a list of {family size ?style ...?} * * Results: * Sets font weight. * *---------------------------------------------------------------------- */ static int GdiMakeLogFont( Tcl_Interp *interp, Tcl_Obj *specPtr, LOGFONTW *lf, HDC hDC) { Tcl_Obj **listPtr; 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; if (!specPtr || Tcl_ListObjGetElements(interp, specPtr, &count, &listPtr) != TCL_OK) { return 0; } /* Now we have the font structure broken into name, size, weight. */ if (count >= 1) { Tcl_DString ds; const char *str = Tcl_GetString(listPtr[0]); Tcl_DStringInit(&ds); wcsncpy(lf->lfFaceName, Tcl_UtfToWCharDString(str, TCL_INDEX_NONE, &ds), LF_FACESIZE); lf->lfFaceName[LF_FACESIZE-1] = 0; Tcl_DStringFree(&ds); } else { return 0; } if (count >= 2) { int siz; /* * 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 (Tcl_GetIntFromObj(NULL, listPtr[1], &siz) == TCL_OK) { /* 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 { /* what, no size ?? */ GdiParseFontWords(interp, lf, listPtr+1, count-1); } } if (count >= 3) { GdiParseFontWords(interp, lf, listPtr+2, count-2); } return 1; } /* *---------------------------------------------------------------------- * * GdiMakePen -- * * Creates a logical pen based on input parameters and selects it into * the hDC. * * Results: * Sets rendering pen. * *---------------------------------------------------------------------- */ static int GdiMakePen( Tcl_Interp *interp, double dwidth, int dashstyle, const char *dashstyledata, int endStyle, int joinStyle, TCL_UNUSED(int), TCL_UNUSED(const char *), /* Ignored for now. */ unsigned long color, HDC hDC, HGDIOBJ *oldPen) { /* * The LOGPEN structure takes the following dash options: * PS_SOLID: a solid pen * PS_DASH: a dashed pen * PS_DOT: a dotted pen * PS_DASHDOT: a pen with a dash followed by a dot * PS_DASHDOTDOT: a pen with a dash followed by 2 dots * * It seems that converting to ExtCreatePen may be more advantageous, as * it matches the Tk canvas pens much better--but not for Win95, which * does not support PS_USERSTYLE. An explicit test (or storage in a static * after first failure) may suffice for working around this. The * ExtCreatePen is not supported at all under Win32. */ int width = ROUND32(dwidth); HPEN hPen; LOGBRUSH lBrush; DWORD pStyle = PS_SOLID; /* -dash should override*/ DWORD styleCount = 0; DWORD *styleArray = 0; /* * To limit the propagation of allocated memory, the dashes will have a * maximum here. If one wishes to remove the static allocation, please be * sure to update GdiFreePen and ensure that the array is NOT freed if the * LOGPEN option is used. */ static DWORD pStyleData[24]; if (dashstyle != 0 && dashstyledata != 0) { const char *cp; size_t i; char *dup = (char *) ckalloc(strlen(dashstyledata) + 1); strcpy(dup, dashstyledata); /* DEBUG. */ Tcl_SetObjResult(interp, Tcl_ObjPrintf( "DEBUG: Found a dash spec of |%s|\n", dashstyledata)); /* Parse the dash spec. */ if (isdigit(dashstyledata[0])) { cp = strtok(dup, " \t,;"); for (i = 0; cp && i < sizeof(pStyleData) / sizeof(DWORD); i++) { pStyleData[styleCount++] = atoi(cp); cp = strtok(NULL, " \t,;"); } } else { for (i=0; dashstyledata[i] != '\0' && i< sizeof(pStyleData) / sizeof(DWORD); i++) { switch (dashstyledata[i]) { case ' ': pStyleData[styleCount++] = 8; break; case ',': pStyleData[styleCount++] = 4; break; case '_': pStyleData[styleCount++] = 6; break; case '-': pStyleData[styleCount++] = 4; break; case '.': pStyleData[styleCount++] = 2; break; default: break; } } } if (styleCount > 0) { styleArray = pStyleData; } else { dashstyle = 0; } if (dup) { ckfree(dup); } } if (dashstyle != 0) { pStyle = PS_USERSTYLE; } /* -stipple could affect this.... */ lBrush.lbStyle = BS_SOLID; lBrush.lbColor = color; lBrush.lbHatch = 0; /* We only use geometric pens, even for 1-pixel drawing. */ hPen = ExtCreatePen(PS_GEOMETRIC|pStyle|endStyle|joinStyle, width, &lBrush, styleCount, styleArray); if (hPen == 0) { /* Failed for some reason...Fall back on CreatePenIndirect. */ LOGPEN lf; lf.lopnWidth.x = width; lf.lopnWidth.y = 0; /* Unused in LOGPEN. */ if (dashstyle == 0) { lf.lopnStyle = PS_SOLID; /* For now...convert 'style' in the future. */ } else { lf.lopnStyle = PS_DASH; /* REALLLLY simple for now. */ } lf.lopnColor = color; /* Assume we're getting a COLORREF. */ /* Now we have a logical pen. Create the "real" pen and put it in the * hDC. */ hPen = CreatePenIndirect(&lf); } *oldPen = SelectObject(hDC, hPen); return 1; } /* *---------------------------------------------------------------------- * * GdiFreePen -- * * Wraps the protocol to delete a created pen. * * Results: * Deletes pen. * *---------------------------------------------------------------------- */ static int GdiFreePen( TCL_UNUSED(Tcl_Interp *), HDC hDC, HGDIOBJ oldPen) { HGDIOBJ gonePen = SelectObject(hDC, oldPen); DeleteObject(gonePen); return 1; } /* *---------------------------------------------------------------------- * * GdiMakeBrush-- * * Creates a logical brush based on input parameters, and selects it into * the hdc. * * Results: * Creates brush. * *---------------------------------------------------------------------- */ static int GdiMakeBrush( unsigned long color, long hatch, LOGBRUSH *lb, HDC hDC, HBRUSH *oldBrush) { HBRUSH hBrush; lb->lbStyle = BS_SOLID; /* Support other styles later. */ lb->lbColor = color; /* Assume this is a COLORREF. */ lb->lbHatch = hatch; /* Ignored for now, given BS_SOLID in the Style. */ /* Now we have the logical brush. Create the "real" brush and put it in * the hDC. */ hBrush = CreateBrushIndirect(lb); *oldBrush = (HBRUSH)SelectObject(hDC, hBrush); return 1; } /* *---------------------------------------------------------------------- * * GdiFreeBrush -- * * Wraps the protocol to delete a created brush. * * Results: * Deletes brush. * *---------------------------------------------------------------------- */ static void GdiFreeBrush( TCL_UNUSED(Tcl_Interp *), HDC hDC, HGDIOBJ oldBrush) { HGDIOBJ goneBrush; goneBrush = SelectObject(hDC, oldBrush); DeleteObject(goneBrush); } /* * Utility functions from elsewhere in Tcl. * Functions have removed reliance on X and Tk libraries, as well as removing * the need for TkWindows. * GdiGetColor is a copy of a TkpGetColor from tkWinColor.c */ typedef struct { const char *name; int index; } SystemColorEntry; static const SystemColorEntry sysColors[] = { {"3dDarkShadow", COLOR_3DDKSHADOW}, {"3dLight", COLOR_3DLIGHT}, {"ActiveBorder", COLOR_ACTIVEBORDER}, {"ActiveCaption", COLOR_ACTIVECAPTION}, {"AppWorkspace", COLOR_APPWORKSPACE}, {"Background", COLOR_BACKGROUND}, {"ButtonFace", COLOR_BTNFACE}, {"ButtonHighlight", COLOR_BTNHIGHLIGHT}, {"ButtonShadow", COLOR_BTNSHADOW}, {"ButtonText", COLOR_BTNTEXT}, {"CaptionText", COLOR_CAPTIONTEXT}, {"DisabledText", COLOR_GRAYTEXT}, {"GrayText", COLOR_GRAYTEXT}, {"Highlight", COLOR_HIGHLIGHT}, {"HighlightText", COLOR_HIGHLIGHTTEXT}, {"InactiveBorder", COLOR_INACTIVEBORDER}, {"InactiveCaption", COLOR_INACTIVECAPTION}, {"InactiveCaptionText", COLOR_INACTIVECAPTIONTEXT}, {"InfoBackground", COLOR_INFOBK}, {"InfoText", COLOR_INFOTEXT}, {"Menu", COLOR_MENU}, {"MenuText", COLOR_MENUTEXT}, {"Scrollbar", COLOR_SCROLLBAR}, {"Window", COLOR_WINDOW}, {"WindowFrame", COLOR_WINDOWFRAME}, {"WindowText", COLOR_WINDOWTEXT} }; static const size_t numsyscolors = sizeof(sysColors) / sizeof(SystemColorEntry); /* *---------------------------------------------------------------------- * * GdiGetColor -- * * Convert color name to color specification. * * Results: * Color name converted. * *---------------------------------------------------------------------- */ static int GdiGetColor( Tcl_Obj *nameObj, COLORREF *color) { const char *name = Tcl_GetString(nameObj); if (_strnicmp(name, "system", 6) == 0) { size_t i, l, u; int r; l = 0; u = numsyscolors; while (l <= u) { i = (l + u) / 2; if ((r = _strcmpi(name+6, sysColors[i].name)) == 0) { break; } if (r < 0) { u = i - 1; } else { l = i + 1; } } if (l > u) { return 0; } *color = GetSysColor(sysColors[i].index); return 1; } else { int result; XColor xcolor; result = XParseColor(NULL, 0, name, &xcolor); *color = ((xcolor.red & 0xFF00)>>8) | (xcolor.green & 0xFF00) | ((xcolor.blue & 0xFF00)<<8); return result; } } /* * Beginning of functions for screen-to-dib translations. * * Several of these functions are based on those in the WINCAP32 program * provided as a sample by Microsoft on the VC++ 5.0 disk. The copyright on * these functions is retained, even for those with significant changes. */ /* *---------------------------------------------------------------------- * * CopyToDIB -- * * Copy window bits to a DIB. * * Results: * Color specification converted. * *---------------------------------------------------------------------- */ static HANDLE CopyToDIB( HWND hWnd, enum PrintType type) { HANDLE hDIB; HBITMAP hBitmap; HPALETTE hPalette; /* Check for a valid window handle. */ if (!hWnd) { return NULL; } switch (type) { case PTWindow: { /* Copy entire window. */ RECT rectWnd; /* Get the window rectangle. */ GetWindowRect(hWnd, &rectWnd); /* * Get the DIB of the window by calling CopyScreenToDIB and passing it * the window rect. */ hDIB = CopyScreenToDIB(&rectWnd); break; } case PTClient: { /* Copy client area. */ RECT rectClient; POINT pt1, pt2; /* Get the client area dimensions. */ GetClientRect(hWnd, &rectClient); /* Convert client coords to screen coords. */ pt1.x = rectClient.left; pt1.y = rectClient.top; pt2.x = rectClient.right; pt2.y = rectClient.bottom; ClientToScreen(hWnd, &pt1); ClientToScreen(hWnd, &pt2); rectClient.left = pt1.x; rectClient.top = pt1.y; rectClient.right = pt2.x; rectClient.bottom = pt2.y; /* * Get the DIB of the client area by calling CopyScreenToDIB and * passing it the client rect. */ hDIB = CopyScreenToDIB(&rectClient); break; } case PTScreen: { /* Entire screen. */ RECT Rect; /* * Get the device-dependent bitmap in lpRect by calling * CopyScreenToBitmap and passing it the rectangle to grab. */ Rect.top = Rect.left = 0; GetDisplaySize(&Rect.right, &Rect.bottom); hBitmap = CopyScreenToBitmap(&Rect); /* Check for a valid bitmap handle. */ if (!hBitmap) { return NULL; } /* Get the current palette. */ hPalette = GetSystemPalette(); /* Convert the bitmap to a DIB. */ hDIB = BitmapToDIB(hBitmap, hPalette); /* Clean up. */ DeleteObject(hPalette); DeleteObject(hBitmap); /* Return handle to the packed-DIB. */ break; } default: /* Invalid print area. */ return NULL; } /* Return the handle to the DIB. */ return hDIB; } /* *---------------------------------------------------------------------- * * GetDisplaySize-- * * GetDisplaySize does just that. There may be an easier way, but it is * not apparent. * * Results: * Returns display size. * *---------------------------------------------------------------------- */ static void GetDisplaySize( LONG *width, LONG *height) { HDC hDC; hDC = CreateDCW(L"DISPLAY", 0, 0, 0); *width = GetDeviceCaps(hDC, HORZRES); *height = GetDeviceCaps(hDC, VERTRES); DeleteDC(hDC); } /* *---------------------------------------------------------------------- * * CopyScreenToBitmap-- * * Copies screen to bitmap. * * Results: * Screen is copied. * *---------------------------------------------------------------------- */ static HBITMAP CopyScreenToBitmap( LPRECT lpRect) { HDC hScrDC, hMemDC; /* Screen DC and memory DC. */ HGDIOBJ hBitmap, hOldBitmap; /* Handles to deice-dependent bitmaps. */ int nX, nY, nX2, nY2; /* Coordinates of rectangle to grab. */ int nWidth, nHeight; /* DIB width and height */ int xScrn, yScrn; /* Screen resolution. */ /* Check for an empty rectangle. */ if (IsRectEmpty(lpRect)) { return NULL; } /* * Create a DC for the screen and create a memory DC compatible to screen * DC. */ hScrDC = CreateDCW(L"DISPLAY", NULL, NULL, NULL); hMemDC = CreateCompatibleDC(hScrDC); /* Get points of rectangle to grab. */ nX = lpRect->left; nY = lpRect->top; nX2 = lpRect->right; nY2 = lpRect->bottom; /* Get screen resolution. */ xScrn = GetDeviceCaps(hScrDC, HORZRES); yScrn = GetDeviceCaps(hScrDC, VERTRES); /* Make sure bitmap rectangle is visible. */ if (nX < 0) { nX = 0; } if (nY < 0) { nY = 0; } if (nX2 > xScrn) { nX2 = xScrn; } if (nY2 > yScrn) { nY2 = yScrn; } nWidth = nX2 - nX; nHeight = nY2 - nY; /* Create a bitmap compatible with the screen DC. */ hBitmap = CreateCompatibleBitmap(hScrDC, nWidth, nHeight); /* Select new bitmap into memory DC. */ hOldBitmap = SelectObject(hMemDC, hBitmap); /* Bitblt screen DC to memory DC. */ BitBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, nX, nY, SRCCOPY); /* * Select old bitmap back into memory DC and get handle to bitmap of the * screen. */ hBitmap = SelectObject(hMemDC, hOldBitmap); /* Clean up. */ DeleteDC(hScrDC); DeleteDC(hMemDC); /* Return handle to the bitmap. */ return (HBITMAP)hBitmap; } /* *---------------------------------------------------------------------- * * BitmapToDIB-- * * Converts bitmap to DIB. * * Results: * Bitmap converted. * *---------------------------------------------------------------------- */ static HANDLE BitmapToDIB( HBITMAP hBitmap, HPALETTE hPal) { BITMAP bm; BITMAPINFOHEADER bi; LPBITMAPINFOHEADER lpbi; DWORD dwLen; HANDLE hDIB; HANDLE h; HDC hDC; WORD biBits; /* Check if bitmap handle is valid. */ if (!hBitmap) { return NULL; } /* Fill in BITMAP structure, return NULL if it didn't work. */ if (!GetObjectW(hBitmap, sizeof(bm), (LPWSTR)&bm)) { return NULL; } /* Ff no palette is specified, use default palette. */ if (hPal == NULL) { hPal = (HPALETTE)GetStockObject(DEFAULT_PALETTE); } /* Calculate bits per pixel. */ biBits = bm.bmPlanes * bm.bmBitsPixel; /* Make sure bits per pixel is valid. */ if (biBits <= 1) { biBits = 1; } else if (biBits <= 4) { biBits = 4; } else if (biBits <= 8) { biBits = 8; } else { /* If greater than 8-bit, force to 24-bit. */ biBits = 24; } /* Initialize BITMAPINFOHEADER. */ bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = bm.bmWidth; bi.biHeight = bm.bmHeight; bi.biPlanes = 1; bi.biBitCount = biBits; bi.biCompression = BI_RGB; bi.biSizeImage = 0; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrUsed = 0; bi.biClrImportant = 0; /* Calculate size of memory block required to store BITMAPINFO. */ dwLen = bi.biSize + DIBNumColors(&bi) * sizeof(RGBQUAD); /* Get a DC. */ hDC = GetDC(NULL); /* Select and realize our palette. */ hPal = SelectPalette(hDC, hPal, FALSE); RealizePalette(hDC); /* Alloc memory block to store our bitmap. */ hDIB = GlobalAlloc(GHND, dwLen); /* If we couldn't get memory block. */ if (!hDIB) { /* clean up and return NULL. */ SelectPalette(hDC, hPal, TRUE); RealizePalette(hDC); ReleaseDC(NULL, hDC); return NULL; } /* Lock memory and get pointer to it. */ lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB); /* Use our bitmap info. to fill BITMAPINFOHEADER. */ *lpbi = bi; /* Call GetDIBits with a NULL lpBits param, so it will calculate the * biSizeImage field for us */ GetDIBits(hDC, hBitmap, 0, (UINT)bi.biHeight, NULL, (LPBITMAPINFO)lpbi, DIB_RGB_COLORS); /* get the info. returned by GetDIBits and unlock memory block. */ bi = *lpbi; GlobalUnlock(hDIB); /* If the driver did not fill in the biSizeImage field, make one up. */ if (bi.biSizeImage == 0) { bi.biSizeImage = (((((DWORD)bm.bmWidth * biBits) + 31) / 32) * 4) * bm.bmHeight; } /* Realloc the buffer big enough to hold all the bits. */ dwLen = bi.biSize + DIBNumColors(&bi) * sizeof(RGBQUAD) + bi.biSizeImage; if ((h = GlobalReAlloc(hDIB, dwLen, 0)) != 0) { hDIB = h; } else { /* Clean up and return NULL. */ GlobalFree(hDIB); SelectPalette(hDC, hPal, TRUE); RealizePalette(hDC); ReleaseDC(NULL, hDC); return NULL; } /* Lock memory block and get pointer to it. */ lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB); /* Call GetDIBits with a NON-NULL lpBits param, and actualy get the * bits this time. */ if (GetDIBits(hDC, hBitmap, 0, (UINT)bi.biHeight, (LPSTR)lpbi + (WORD)lpbi->biSize + DIBNumColors(lpbi) * sizeof(RGBQUAD), (LPBITMAPINFO)lpbi, DIB_RGB_COLORS) == 0) { /* Clean up and return NULL. */ GlobalUnlock(hDIB); SelectPalette(hDC, hPal, TRUE); RealizePalette(hDC); ReleaseDC(NULL, hDC); return NULL; } bi = *lpbi; /* Clean up. */ GlobalUnlock(hDIB); SelectPalette(hDC, hPal, TRUE); RealizePalette(hDC); ReleaseDC(NULL, hDC); /* Return handle to the DIB. */ return hDIB; } /* *---------------------------------------------------------------------- * * CopyScreenToDIB-- * * Copies screen to DIB. * * Results: * Screen copied. * *---------------------------------------------------------------------- */ static HANDLE CopyScreenToDIB( LPRECT lpRect) { HBITMAP hBitmap; HPALETTE hPalette; HANDLE hDIB; /* * Get the device-dependent bitmap in lpRect by calling CopyScreenToBitmap * and passing it the rectangle to grab. */ hBitmap = CopyScreenToBitmap(lpRect); /* Check for a valid bitmap handle. */ if (!hBitmap) { return NULL; } /* Get the current palette. */ hPalette = GetSystemPalette(); /* convert the bitmap to a DIB. */ hDIB = BitmapToDIB(hBitmap, hPalette); /* Clean up. */ DeleteObject(hPalette); DeleteObject(hBitmap); /* Return handle to the packed-DIB. */ return hDIB; } /* *---------------------------------------------------------------------- * * GetSystemPalette-- * * Obtains the system palette. * * Results: * Returns palette. * *---------------------------------------------------------------------- */ static HPALETTE GetSystemPalette(void) { HDC hDC; /* Handle to a DC. */ static HPALETTE hPal = NULL; /* Handle to a palette. */ HANDLE hLogPal; /* Handle to a logical palette. */ LPLOGPALETTE lpLogPal; /* Pointer to a logical palette. */ int nColors; /* Number of colors. */ /* Find out how many palette entries we want.. */ hDC = GetDC(NULL); if (!hDC) { return NULL; } nColors = PalEntriesOnDevice(hDC); /* Number of palette entries. */ /* Allocate room for the palette and lock it.. */ hLogPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) + nColors * sizeof(PALETTEENTRY)); if (!hLogPal) { /* If we didn't get a logical palette, return NULL. */ return NULL; } /* get a pointer to the logical palette. */ lpLogPal = (LPLOGPALETTE)GlobalLock(hLogPal); /* Set some important fields. */ lpLogPal->palVersion = 0x300; lpLogPal->palNumEntries = nColors; /* Copy the current system palette into our logical palette. */ GetSystemPaletteEntries(hDC, 0, nColors, (LPPALETTEENTRY) lpLogPal->palPalEntry); /* * Go ahead and create the palette. Once it's created, we no longer need * the LOGPALETTE, so free it. */ hPal = CreatePalette(lpLogPal); /* Clean up. */ GlobalUnlock(hLogPal); GlobalFree(hLogPal); ReleaseDC(NULL, hDC); return hPal; } /* *---------------------------------------------------------------------- * * PalEntriesOnDevice-- * * Returns the palettes on the device. * * Results: * Returns palettes. * *---------------------------------------------------------------------- */ static int PalEntriesOnDevice( HDC hDC) { return (1 << (GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES))); } /* * -------------------------------------------------------------------------- * * WinprintDeleted-- * * Free interp's print resources. * * ------------------------------------------------------------------------- */ static void WinprintDeleted( void *clientData, TCL_UNUSED(Tcl_Interp *)) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC != NULL) { DeleteDC(dataPtr->printDC); Tcl_DStringFree(&dataPtr->jobNameW); } ckfree(dataPtr); } /* * -------------------------------------------------------------------------- * * 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); WinprintData *dataPtr = (WinprintData *)ckalloc(sizeof(WinprintData)); /* * Set up the low-level [_gdi] command. */ namespacePtr = Tcl_CreateNamespace(interp, gdiName, NULL, (Tcl_NamespaceDeleteProc *) NULL); for (i=0; iprintDC = NULL; Tcl_CallWhenDeleted(interp, WinprintDeleted, dataPtr); return TCL_OK; } /* Print API functions. */ /*---------------------------------------------------------------------- * * PrintSelectPrinter-- * * Main dialog for selecting printer and initializing data for print job. * * Results: * Printer selected. * *---------------------------------------------------------------------- */ static int PrintSelectPrinter( void *clientData, Tcl_Interp *interp, TCL_UNUSED(Tcl_Size), TCL_UNUSED(Tcl_Obj* const*)) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC != NULL) { Tcl_AppendResult(interp, "device context still in use: call " "_closedoc first", (char *)NULL); return TCL_ERROR; } PRINTDLGW pd; LPCWSTR printerName = NULL; PDEVMODEW devmode = NULL; LPDEVNAMES devnames = NULL; int copies = 0; int paper_width = 0; int paper_height = 0; int dpi_x = 0; int dpi_y = 0; int returnVal = TCL_OK; /* 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 | PD_RETURNDC; if (! PrintDlgW(&pd)) { unsigned int errorcode = CommDlgExtendedError(); /* * The user cancelled, or there was an error * The code on the Tcl side checks if the variable * ::tk::print::printer_name is defined to determine * that a valid selection was made. * So we better unset this here, unconditionally. */ Tcl_UnsetVar(interp, "::tk::print::printer_name", 0); if (errorcode != 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("print failed: error %04x", errorcode)); Tcl_SetErrorCode(interp, "TK", "PRINT", "DIALOG", NULL); return TCL_ERROR; } return TCL_OK; } devmode = (PDEVMODEW) GlobalLock(pd.hDevMode); devnames = (LPDEVNAMES) GlobalLock(pd.hDevNames); if (! devmode) { Tcl_AppendResult(interp, "selected printer doesn't have extended info", (char *)NULL); return TCL_ERROR; } if (! devnames) { Tcl_AppendResult(interp, "can't get device names", (char *)NULL); return TCL_ERROR; } printerName = (LPCWSTR) devnames + devnames->wDeviceOffset; /* Get values from user-set and built-in properties. */ dpi_y = devmode->dmYResolution; dpi_x = devmode->dmPrintQuality; /* Convert height and width to logical points. */ paper_height = (int) devmode->dmPaperLength / 0.254; paper_width = (int) devmode->dmPaperWidth / 0.254; copies = pd.nCopies; /* Set device context here for all GDI printing operations. */ dataPtr->printDC = pd.hDC; /* * Store print properties in variables so they can be accessed from * script level. */ if (printerName != NULL) { Tcl_DString prname; Tcl_DStringInit(&prname); Tcl_WCharToUtfDString((WCHAR *)printerName, TCL_INDEX_NONE, &prname); Tcl_SetVar2Ex(interp, "::tk::print::printer_name", NULL, Tcl_DStringToObj(&prname), 0); Tcl_SetVar2Ex(interp, "::tk::print::copies", NULL, Tcl_NewIntObj(copies), 0); Tcl_SetVar2Ex(interp, "::tk::print::dpi_x", NULL, Tcl_NewIntObj(dpi_x), 0); Tcl_SetVar2Ex(interp, "::tk::print::dpi_y", NULL, Tcl_NewIntObj(dpi_y), 0); Tcl_SetVar2Ex(interp, "::tk::print::paper_width", NULL, Tcl_NewIntObj(paper_width), 0); Tcl_SetVar2Ex(interp, "::tk::print::paper_height", NULL, Tcl_NewIntObj(paper_height), 0); } else { Tcl_UnsetVar(interp, "::tk::print::printer_name", 0); Tcl_AppendResult(interp, "selected printer doesn't have name", (char *)NULL); DeleteDC(dataPtr->printDC); dataPtr->printDC = NULL; returnVal = TCL_ERROR; } GlobalUnlock(devmode); GlobalFree(devmode); GlobalUnlock(devnames); GlobalFree(devnames); return returnVal; } /* * -------------------------------------------------------------------------- * * PrintOpenPrinter-- * * Open the given printer. * * Results: * Opens the selected printer. * * ------------------------------------------------------------------------- */ int PrintOpenPrinter( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[]) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } Tcl_DString ds; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "printer"); return TCL_ERROR; } /*Start an individual page.*/ if (StartPage(dataPtr->printDC) <= 0) { return TCL_ERROR; } const char *printer = Tcl_GetString(objv[1]); Tcl_DStringInit(&ds); if ((OpenPrinterW(Tcl_UtfToWCharDString(printer, -1, &ds), (LPHANDLE)&dataPtr->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( void *clientData, Tcl_Interp *interp, TCL_UNUSED(Tcl_Size), TCL_UNUSED(Tcl_Obj *const *)) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } ClosePrinter(dataPtr->printDC); return TCL_OK; } /* * -------------------------------------------------------------------------- * * PrintOpenDoc-- * * Opens the document for printing. * * Results: * Opens the print document. * * ------------------------------------------------------------------------- */ int PrintOpenDoc( void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const *objv) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } int output = 0; const char *jobname; Tcl_Size len; if (objc < 2 || objc > 3) { Tcl_WrongNumArgs(interp, 1, objv, "jobname ?font?"); return TCL_ERROR; } jobname = Tcl_GetStringFromObj(objv[1], &len); Tcl_DStringInit(&dataPtr->jobNameW); /*Get document info.*/ memset(&dataPtr->di, 0, sizeof(dataPtr->di)); dataPtr->di.cbSize = sizeof(dataPtr->di); dataPtr->di.lpszDocName = Tcl_UtfToWCharDString(jobname, len, &dataPtr->jobNameW); /* * Start printing. */ output = StartDocW(dataPtr->printDC, &dataPtr->di); if (output <= 0) { Tcl_AppendResult(interp, "unable to start document", (char *)NULL); return TCL_ERROR; } /* the optional argument "font" is useful for plain text documents. * we set the font and other defaults here, and return the font width * and height just once */ if (objc == 3) { LOGFONTW lf; HFONT hfont; TEXTMETRICW tmw; if (GdiMakeLogFont(interp, objv[2], &lf, dataPtr->printDC)) { if ((hfont = CreateFontIndirectW(&lf)) != NULL) { SelectObject(dataPtr->printDC, hfont); } } SetTextAlign(dataPtr->printDC, TA_LEFT); SetTextColor(dataPtr->printDC, 0); SetBkMode(dataPtr->printDC, TRANSPARENT); if (GetTextMetricsW(dataPtr->printDC, &tmw) != 0) { Tcl_Obj *ret[2]; ret[0] = Tcl_NewIntObj((int)tmw.tmAveCharWidth); ret[1] = Tcl_NewIntObj((int)tmw.tmHeight); Tcl_SetObjResult(interp, Tcl_NewListObj(2, ret)); } else { Tcl_AppendResult(interp, "_opendoc: can't determine font ", "width and height", (char *)NULL); return TCL_ERROR; } } return TCL_OK; } /* * -------------------------------------------------------------------------- * * PrintCloseDoc-- * * Closes the document for printing. * * Results: * Closes the print document. * * ------------------------------------------------------------------------- */ int PrintCloseDoc( void *clientData, Tcl_Interp *interp, TCL_UNUSED(Tcl_Size), TCL_UNUSED(Tcl_Obj *const *)) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } if (EndDoc(dataPtr->printDC) <= 0) { Tcl_AppendResult(interp, "unable to close document", (char *)NULL); return TCL_ERROR; } /* delete the font object that might be created as default */ DeleteObject(SelectObject (dataPtr->printDC, GetStockObject(DEVICE_DEFAULT_FONT))); DeleteDC(dataPtr->printDC); dataPtr->printDC = NULL; Tcl_DStringFree(&dataPtr->jobNameW); return TCL_OK; } /* * -------------------------------------------------------------------------- * * PrintOpenPage-- * * Opens a page for printing. * * Results: * Opens the print page. * * ------------------------------------------------------------------------- */ int PrintOpenPage( void *clientData, Tcl_Interp *interp, TCL_UNUSED(Tcl_Size), TCL_UNUSED(Tcl_Obj *const *)) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } /*Start an individual page.*/ if (StartPage(dataPtr->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( void *clientData, Tcl_Interp *interp, TCL_UNUSED(Tcl_Size), TCL_UNUSED(Tcl_Obj *const *)) { WinprintData *dataPtr = (WinprintData *)clientData; if (dataPtr->printDC == NULL) { Tcl_AppendResult(interp, "device context not initialized", (char *)NULL); return TCL_ERROR; } if (EndPage(dataPtr->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: */