/* * R : A Computer Language for Statistical Data Analysis * Copyright (C) 1998--2004 Guido Masarotto and Brian Ripley * Copyright (C) 2005-22 The R Core Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, a copy is available at * https://www.R-project.org/Licenses/ */ /* A version of drawing.c without current/state. More safe in case of multiple redrawing */ #include "internal.h" extern unsigned int TopmostDialogs; /* from dialogs.c */ #include #ifndef W64 WINGDIAPI BOOL WINAPI AlphaBlend(HDC,int,int,int,int,HDC,int,int,int,int,BLENDFUNCTION); #endif #include #ifdef __GNUC__ # define alloca(x) __builtin_alloca((x)) #else # error need appropriate declaration for alloca #endif /* from extra.c */ extern size_t Rf_utf8towcs(wchar_t *wc, const char *s, size_t n); static HDC GETHDC(drawing d) { if (!d) { DebugBreak(); return (HDC) 0; /* We should never get here, but we do? */ } if ( (d->kind == PrinterObject) || (d->kind == MetafileObject)) { HDC dc = (HDC) d->handle ; SelectObject(dc, GetStockObject(NULL_PEN)); SelectObject(dc, GetStockObject(NULL_BRUSH)); return dc ; } else return get_context(d); } /* * Some clipping functions. */ rect ggetcliprect(drawing d) { RECT R; rect r; HDC dc = GETHDC(d); GetClipBox(dc, &R); r.x = R.left; r.y = R.top; r.width = R.right - R.left; r.height = R.bottom - R.top; return r; } void gsetcliprect(drawing d, rect r) { HRGN rgn; HDC dc = GETHDC(d); rgn = CreateRectRgn(r.x, r.y, r.x + r.width, r.y + r.height); SelectClipRgn(dc, rgn); DeleteObject(rgn); } void gbitblt(drawing db, drawing sb, point p, rect r) { HDC src; HDC dst; dst = GETHDC(db); src = GETHDC(sb); BitBlt(dst, p.x, p.y, r.width, r.height, src, r.x, r.y, SRCCOPY); } /* dp gives the amount to scroll; r the full rectangle to scroll */ void gscroll(drawing d, point dp, rect r) { HDC dc = GETHDC(d); RECT rr ; rr.left = r.x; rr.top = r.y; rr.right = r.x + r.width; rr.bottom = r.y + r.height; ScrollDC(dc, dp.x , dp.y , &rr, &rr, 0, NULL); } void ginvert(drawing d, rect r) { HDC dc = GETHDC(d); PatBlt(dc, r.x, r.y, r.width, r.height, DSTINVERT); } rgb ggetpixel(drawing d, point p) { rgb c; HDC dc = GETHDC(d); c = GetPixel(dc, p.x, p.y); c = ((c&0x000000FFL)<<16) | (c&0x0000FF00L) | ((c&0x00FF0000L)>>16); return c; } static COLORREF getwinrgb(drawing d, rgb c) { int r, g, b; #ifdef UNUSED long luminance; int depth; #endif r = (int) ((c >> 16) & 0x000000FF); g = (int) ((c >> 8) & 0x000000FF); b = (int) ((c >> 0) & 0x000000FF); #ifdef UNUSED depth = getdepth(d); /* note: next is unused! */ if (depth <= 2) /* map to black or white, or grey if c == Grey */ { luminance = (r*3 + g*5 + b) / 9; if (luminance > 0x0087) r = g = b = 0x00FF; else if (luminance <= 0x0077) r = g = b = 0x0000; else r = g = b = 0x0080; c = rgb(r, g, b); } #endif return RGB(r, g, b); } void gsetpixel(drawing d, point p, rgb c) { HDC dc = GETHDC(d); HBRUSH br = CreateSolidBrush(getwinrgb(d, c)); fix_brush(dc, d, br); SelectObject(dc, br); PatBlt(dc, p.x, p.y, 1, 1, PATCOPY); SelectObject(dc, GetStockObject(NULL_BRUSH)); DeleteObject(br); } typedef struct { HDC dc; int len2; /* squared length of current dash */ int curseg, on; /* current dash (0-7), on/off flag */ int style, width; int curx, cury; /* start of current dash */ } DashStruct; static int npieces; static void CALLBACK gLineHelper(int x, int y, LPARAM aa) { DashStruct *a = (DashStruct *) aa; int distx, disty; npieces++; distx = x - (a->curx); disty = y - (a->cury); if (distx*distx + disty*disty >= (a->len2)) { if (a->on) LineTo(a->dc, x, y); else MoveToEx(a->dc, x, y, NULL); a->curx = x; a->cury = y; a->len2 = 0; while (!a->len2) { a->curseg = (a->curseg + 4) % 32; a->len2 = (((a->style) >> (a->curseg)) & 15) * (a->width); a->len2 = (a->len2) * (a->len2); a->on = (a->on) ? 0 : 1; } } } void gdrawline(drawing d, int width, int style, rgb c, point p1, point p2, int fast, int lend, int ljoin, float lmitre) { point p[2]; p[0] = p1; p[1] = p2; gdrawpolyline( d, width, style, c, p, 2, 0, fast, lend, ljoin, lmitre); } void gdrawpolyline(drawing d, int width, int style, rgb c, point p[], int n, int closepath, int fast, int lend, int ljoin, float lmitre) { int tmpx, tmpy, tmp; HDC dc = GETHDC(d); COLORREF winrgb = getwinrgb(d, c); LOGBRUSH lb; HPEN gpen; int i; float oldmitre; if (n < 2) return; lb.lbStyle = BS_SOLID; lb.lbColor = winrgb; lb.lbHatch = 0; SetMiterLimit(dc, lmitre, &oldmitre); if (!style) { if (fast) gpen = CreatePen(PS_INSIDEFRAME, width, winrgb); else gpen = ExtCreatePen(PS_GEOMETRIC|PS_SOLID|lend|ljoin, width, &lb, 0, NULL); SelectObject(dc, gpen); SetROP2(dc, R2_COPYPEN); npieces = 0; BeginPath(dc); MoveToEx(dc, p[0].x, p[0].y, NULL); for (i = 1; i < n ; i++) { LineTo(dc, p[i].x, p[i].y); npieces++; if (npieces > 1000) { EndPath(dc); StrokePath(dc); npieces = 0; BeginPath(dc); } } if (closepath) LineTo(dc, p[0].x, p[0].y); EndPath(dc); StrokePath(dc); SelectObject(dc, GetStockObject(NULL_PEN)); DeleteObject(gpen); } else { DashStruct a; gpen = ExtCreatePen(PS_GEOMETRIC|PS_SOLID|lend|ljoin, width, &lb, 0, NULL); SelectObject(dc, gpen); SetROP2(dc, R2_COPYPEN); a.on = 1; a.dc = dc; a.len2 = (style & 15) * width; a.len2 = (a.len2) * (a.len2); a.curseg = 0; a.style = style; a.width = width; a.curx = p[0].x; a.cury = p[0].y; MoveToEx(dc, p[0].x, p[0].y, NULL); npieces = 0; BeginPath(dc); for (i = 1; i < n; i++) { LineDDA(p[i-1].x, p[i-1].y, p[i].x, p[i].y, gLineHelper, (LPARAM) &a); if ((p[i].x != a.curx) || (p[i].y != a.cury)) { if (a.on) LineTo(dc, p[i].x, p[i].y); else MoveToEx(dc, p[i].x, p[i].y, NULL); tmpx = (a.curx-p[i].x); tmpy = (a.cury-p[i].y); tmp = tmpx*tmpx + tmpy*tmpy; a.len2 = a.len2 + tmp - 2*sqrt((double)(tmp*a.len2)); a.curx = p[i].x; a.cury = p[i].y; } npieces++; if (npieces > 1000) { EndPath(dc); StrokePath(dc); npieces = 0; BeginPath(dc); } } if (closepath) { LineDDA(p[n-1].x, p[n-1].y, p[0].x, p[0].y, gLineHelper, (LPARAM) &a); if (a.on) LineTo(dc,p[0].x,p[0].y); } EndPath(dc); StrokePath(dc); SelectObject(dc, GetStockObject(NULL_PEN)); DeleteObject(gpen); } } void gdrawrect(drawing d, int width, int style, rgb c, rect r, int fast, int lend, int ljoin, float lmitre) { int x0 = r.x; int y0 = r.y; int x1 = r.x + r.width; int y1 = r.y + r.height; point p[4]; p[0] = pt(x0,y0); p[1] = pt(x1,y0); p[2] = pt(x1,y1); p[3] = pt(x0,y1); gdrawpolyline(d, width, style, c, p, 4, 1, fast, lend, ljoin, lmitre); } void gfillrect(drawing d, rgb fill, rect r) { HDC dc = GETHDC(d); HBRUSH br = CreateSolidBrush(getwinrgb(d, fill)); fix_brush(dc, d, br); SelectObject(dc, br); PatBlt(dc, r.x, r.y, r.width, r.height, PATCOPY); SelectObject(dc, GetStockObject(NULL_BRUSH)); DeleteObject(br); } void gcopy(drawing d, drawing d2, rect r) { HDC dc = GETHDC(d), sdc = GETHDC(d2); BitBlt(dc, r.x, r.y, r.width, r.height, sdc, r.x, r.y, SRCCOPY); } void gcopyalpha(drawing d, drawing d2, rect r, int alpha) { if(alpha <= 0) return; { BLENDFUNCTION bl; bl.BlendOp = AC_SRC_OVER; bl.BlendFlags = 0; bl.SourceConstantAlpha = alpha; bl.AlphaFormat = 0; AlphaBlend(GETHDC(d), r.x, r.y, r.width, r.height, GETHDC(d2), r.x, r.y, r.width, r.height, bl); } } void gcopyalpha2(drawing d, image src, rect r) { BLENDFUNCTION bl; bl.BlendOp = AC_SRC_OVER; bl.BlendFlags = 0; bl.SourceConstantAlpha = 255; /* per-pixel alpha only */ bl.AlphaFormat = AC_SRC_ALPHA; bitmap bm = imagetobitmap(src); AlphaBlend(GETHDC(d), r.x, r.y, r.width, r.height, GETHDC(bm), 0, 0, r.width, r.height, bl); del(bm); } void gdrawellipse(drawing d, int width, rgb border, rect r, int fast, int lend, int ljoin, float lmitre) { HDC dc = GETHDC(d); LOGBRUSH lb; HPEN gpen; float oldmitre; if (fast) gpen = CreatePen(PS_INSIDEFRAME, width, getwinrgb(d, border)); else { SetMiterLimit(dc, lmitre, &oldmitre); lb.lbStyle = BS_SOLID; lb.lbColor = getwinrgb(d, border); lb.lbHatch = 0; gpen = ExtCreatePen(PS_GEOMETRIC|PS_SOLID|lend|ljoin, width, &lb, 0, NULL); } SelectObject(dc, gpen); SetROP2(dc, R2_COPYPEN); Ellipse(dc, r.x, r.y, r.x+r.width, r.y+r.height); SelectObject(dc, GetStockObject(NULL_PEN)); DeleteObject(gpen); } void goldfillellipse(drawing d, rgb fill, rect r) { HDC dc = GETHDC(d); HBRUSH br = CreateSolidBrush(getwinrgb(d, fill)); fix_brush(dc, d, br); SelectObject(dc, br); Ellipse(dc, r.x, r.y, r.x+r.width, r.y+r.height); SelectObject(dc, GetStockObject(NULL_BRUSH)); DeleteObject(br); } #ifndef fastfillrect #define fastfillrect(x, y, w, h) PatBlt(dc, (x), (y), (w), (h), mode) #endif void gfillellipse(drawing d, rgb fill, rect r) { /* e(x,y) = b*b*x*x + a*a*y*y - a*a*b*b */ register long mode = PATCOPY; int w_odd = (r.width & 0x0001); int h_odd = (r.height & 0x0001); int a = r.width >> 1; int b = r.height >> 1; point c = pt(r.x+a,r.y+b); int x = 0; int y = b; long a2 = a*a; long b2 = b*b; long xcrit = ((a2+a2+a2) >> 2) + 1; long ycrit = ((b2+b2+b2) >> 2) + 1; long t = b2 + a2 - (a2+a2)*b; /* t = e(x+1,y-1) */ long dxt = b2*(3+x+x); long dyt = a2*(3-y-y); int d2xt = b2+b2; int d2yt = a2+a2; int stored = 0; int sx = 0, sy = 0, sh = 0; /* stored values of x, y, height */ HDC dc; HBRUSH br; if ((r.width > 31) && (r.height > 31)) { goldfillellipse(d, fill, r); return; } if ((r.width < 3) || (r.height < 3)) { gfillrect(d, fill, r); return; } dc = GETHDC(d); br = CreateSolidBrush(getwinrgb(d, fill)); fix_brush(dc, d, br); SelectObject(dc, br); if (w_odd == 0) { fastfillrect(c.x-1,c.y-b,2,r.height); } while (y > 0) { if (stored) { if (sx != x) { /* output stored rect */ fastfillrect(c.x-sx,c.y-sy, sx+sx+w_odd,sh); fastfillrect(c.x-sx,c.y+sy+h_odd-sh, sx+sx+w_odd,sh); stored = 0; } else /* increment height of stored rect */ sh++; } if (t + a2*y < xcrit) { /* e(x+1,y-1/2) <= 0 */ /* move left and right to encounter edge */ x += 1; t += dxt; dxt += d2xt; } else if (t - b2*x >= ycrit) { /* e(x+1/2,y-1) > 0 */ /* drop down one line */ if (!stored) { sx = x; sy = y; sh = 1; stored = 1; } y -= 1; t += dyt; dyt += d2yt; } else { /* drop diagonally down and out */ if (!stored) { sx = x; sy = y; sh = 1; stored = 1; } x += 1; y -= 1; t += dxt + dyt; dxt += d2xt; dyt += d2yt; } } if (stored) { /* output stored rectangle */ fastfillrect(c.x-sx, c.y-sy, sx+sx+w_odd, sh); fastfillrect(c.x-sx, c.y+sy+h_odd-sh, sx+sx+w_odd, sh); stored = 0; } if (x <= a){ fastfillrect(c.x-a, c.y-y, a+a+w_odd, 1); fastfillrect(c.x-a, c.y+y-1+h_odd, a+a+w_odd, 1); } SelectObject(dc, GetStockObject(NULL_BRUSH)); DeleteObject(br); } void gsetpolyfillmode(drawing d, int oddeven) { HDC dc = GETHDC(d); SetPolyFillMode(dc, oddeven ? ALTERNATE : WINDING); } void gfillpolygon(drawing d, rgb fill, point *p, int n) { HDC dc = GETHDC(d); HBRUSH br = CreateSolidBrush(getwinrgb(d,fill)); fix_brush(dc, d, br); SelectObject(dc, br); Polygon(dc, (POINT FAR *) p, n); SelectObject(dc, GetStockObject(NULL_BRUSH)); DeleteObject(br); } void gfillpolypolygon(drawing d, rgb fill, point *p, int npoly, int *nper) { HDC dc = GETHDC(d); HBRUSH br = CreateSolidBrush(getwinrgb(d,fill)); fix_brush(dc, d, br); SelectObject(dc, br); PolyPolygon(dc, (POINT FAR *) p, nper, npoly); SelectObject(dc, GetStockObject(NULL_BRUSH)); DeleteObject(br); } /* Assumes all pixels in image are opaque */ void gdrawimage(drawing d, image img, rect dr, rect sr) { HDC dc = GETHDC(d); HDC bc; bitmap b; image i = img; if (! img) return; dr = rcanon(dr); if ((dr.width != img->width) || (dr.height != img->height)) { i = scaleimage(img, rect(0, 0, dr.width, dr.height), sr); } b = imagetobitmap(i); /* The next line assumes that the context returned is a NEW context, but that should be ok because the object 'b' has just been created in the line above, which means that get_context() should create a new context. */ bc = get_context(b); BitBlt(dc, dr.x, dr.y, dr.width, dr.height, bc, sr.x, sr.y, SRCCOPY); /* DO NOT rely on the del() mechanism to (eventually) clean up the context 'bc' (via deletion_traversal() in objects.c). That leads to running out of contexts (see MAX_CONTEXTS in contexts.c). Instead, explicitly dispose of the context here */ del_context(b); if (i != img) del(i); del(b); } /* Use this to draw an image containing fully transparent pixels * by using a mask based on the transparent pixels * (you need to create the mask) */ void gmaskimage(drawing d, image img, rect dr, rect sr, image mask) { HDC dc = GETHDC(d); HDC bc, mbc, mbwc; bitmap b, mb, mbw; image i = img; image m = mask; if (! img || ! mask) return; dr = rcanon(dr); if ((dr.width != img->width) || (dr.height != img->height)) { i = scaleimage(img, rect(0, 0, dr.width, dr.height), sr); m = scaleimage(mask, rect(0, 0, dr.width, dr.height), sr); } b = imagetobitmap(i); mb = imagetobitmap(m); mbw = newbitmap(dr.width, dr.height, 1); bc = get_context(b); mbc = get_context(mb); mbwc = get_context(mbw); BitBlt(mbwc, sr.x, sr.y, sr.width, sr.height, mbc, sr.x, sr.y, SRCCOPY); MaskBlt(dc, dr.x, dr.y, dr.width, dr.height, bc, sr.x, sr.y, (HBITMAP) mbw->handle, 0, 0, MAKEROP4(SRCCOPY, SRCAND)); del_context(b); del_context(mb); del_context(mbw); if (i != img) del(i); if (m != mask) del(m); del(b); del(mb); del(mbw); } /* For ordinary text, e.g. in console */ int gdrawstr(drawing d, font f, rgb c, point p, const char *s) { POINT curr_pos; int width; HFONT old; HDC dc = GETHDC(d); SetTextColor(dc, getwinrgb(d,c)); old = SelectObject(dc, f->handle); MoveToEx(dc, p.x, p.y, NULL); SetBkMode(dc, TRANSPARENT); SetTextAlign(dc, TA_TOP | TA_LEFT | TA_UPDATECP); if (localeCP > 0 && (localeCP != GetACP())) { /* This allows us to change locales and output in the new locale */ wchar_t *wc; int n = strlen(s), cnt; wc = alloca((n+1) * sizeof(wchar_t)); cnt = mbstowcs(wc, s, n); TextOutW(dc, p.x, p.y, wc, cnt); } else TextOut(dc, p.x, p.y, s, strlen(s)); GetCurrentPositionEx(dc, &curr_pos); width = curr_pos.x - p.x; SelectObject(dc, old); return width; } int gdrawwcs(drawing d, font f, rgb c, point p, const wchar_t *s) { POINT curr_pos; int width; HFONT old; HDC dc = GETHDC(d); SetTextColor(dc, getwinrgb(d,c)); old = SelectObject(dc, f->handle); MoveToEx(dc, p.x, p.y, NULL); SetBkMode(dc, TRANSPARENT); SetTextAlign(dc, TA_TOP | TA_LEFT | TA_UPDATECP); TextOutW(dc, p.x, p.y, s, wcslen(s)); GetCurrentPositionEx(dc, &curr_pos); width = curr_pos.x - p.x; SelectObject(dc, old); return width; } #define CE_NATIVE 0 #define CE_UTF8 1 /* This version aligns on baseline, and allows hadj = 0, 0.5, 1 */ void gdrawstr1(drawing d, font f, rgb c, point p, const char *s, double hadj) { HFONT old; HDC dc = GETHDC(d); UINT flags = TA_BASELINE | TA_UPDATECP; SetTextColor(dc, getwinrgb(d,c)); old = SelectObject(dc, f->handle); MoveToEx(dc, p.x, p.y, NULL); SetBkMode(dc, TRANSPARENT); if (hadj < 0.25) flags |= TA_LEFT; else if (hadj < 0.75) flags |= TA_CENTER; else flags |= TA_RIGHT; SetTextAlign(dc, flags); TextOut(dc, p.x, p.y, s, strlen(s)); SelectObject(dc, old); } /* widechar version */ void gwdrawstr1(drawing d, font f, rgb c, point p, const wchar_t *wc, int cnt, double hadj) { HFONT old; HDC dc = GETHDC(d); UINT flags = TA_BASELINE | TA_UPDATECP; SetTextColor(dc, getwinrgb(d,c)); old = SelectObject(dc, f->handle); MoveToEx(dc, p.x, p.y, NULL); SetBkMode(dc, TRANSPARENT); if (hadj < 0.25) flags |= TA_LEFT; else if (hadj < 0.75) flags |= TA_CENTER; else flags |= TA_RIGHT; SetTextAlign(dc, flags); TextOutW(dc, p.x, p.y, wc, cnt); SelectObject(dc, old); } rect gstrrect(drawing d, font f, const char *s) { SIZE size; HFONT old; HDC dc; if (! f) f = SystemFont; if (d) dc = GETHDC(d); else dc = GetDC(0); old = SelectObject(dc, f->handle); GetTextExtentPoint32(dc, (LPSTR)s, strlen(s), &size); SelectObject(dc, old); if (!d) ReleaseDC(0,dc); return rect(0, 0, size.cx, size.cy); } point gstrsize(drawing d, font f, const char *s) { rect r = gstrrect(d, f, s); return pt(r.width, r.height); } int gstrwidth(drawing d, font f, const char *s) { rect r = gstrrect(d, f, s); return r.width; } static rect gwcsrect(drawing d, font f, const wchar_t *s) { SIZE size; HFONT old; HDC dc; if (! f) f = SystemFont; if (d) dc = GETHDC(d); else dc = GetDC(0); old = SelectObject(dc, f->handle); GetTextExtentPoint32W(dc, (LPWSTR)s, wcslen(s), &size); SelectObject(dc, old); if (!d) ReleaseDC(0,dc); return rect(0, 0, size.cx, size.cy); } int gwcswidth(drawing d, font f, const wchar_t *s) { rect r = gwcsrect(d, f ,s); return r.width; } int gstrwidth1(drawing d, font f, const char *s, int enc) { rect r; if (enc == CE_UTF8) { wchar_t *wc; int n = strlen(s); wc = alloca((n+1) * sizeof(wchar_t)); Rf_utf8towcs(wc, s, n+1); r = gwcsrect(d, f, wc); } else r = gstrrect(d, f, s); return r.width; } int ghasfixedwidth(font f) { TEXTMETRIC tm; HFONT old; HDC dc = GetDC(0); old = SelectObject(dc, (HFONT)f->handle); GetTextMetrics(dc, &tm); SelectObject(dc, old); ReleaseDC(0,dc); return !(tm.tmPitchAndFamily & TMPF_FIXED_PITCH); } void gcharmetric(drawing d, font f, int c, int *ascent, int *descent, int *width) { int first, last, extra; TEXTMETRIC tm; HFONT old; HDC dc = GETHDC(d); old = SelectObject(dc, (HFONT)f->handle); GetTextMetrics(dc, &tm); first = tm.tmFirstChar; last = tm.tmLastChar; extra = tm.tmExternalLeading + tm.tmInternalLeading - 1; if (c < 0) { /* used for setting cra */ SIZE size; char *cc = "M"; GetTextExtentPoint32(dc,(LPSTR) cc, 1, &size); *descent = tm.tmDescent ; *ascent = size.cy - *descent; *width = size.cx; if (*width > size.cy) *width = size.cy; } else if (c == 0) { *descent = tm.tmDescent ; *ascent = tm.tmHeight - *descent - extra ; *width = tm.tmMaxCharWidth ; } else if ((first <= c) && (c <= last)) { SIZE size; GetTextExtentPoint32(dc, (LPSTR) &c, 1, &size); *descent = tm.tmDescent ; *ascent = size.cy - *descent - extra ; *width = size.cx; /* Under NT, ' ' gives 0 ascent and descent, which seems correct but this : (i) makes R engine to center in random way; (ii) doesn't correspond to what 98 and X do (' ' is there high as the full font) */ if ((c != ' ') && (tm.tmPitchAndFamily & TMPF_TRUETYPE)) { GLYPHMETRICS gm; MAT2 m2; m2.eM11.value = m2.eM22.value = (WORD) 1 ; m2.eM21.value = m2.eM12.value = (WORD) 0 ; m2.eM11.fract = m2.eM12.fract = m2.eM21.fract = m2.eM22.fract = (short) 0 ; if (GetGlyphOutline(dc, c, GGO_METRICS, &gm, 0, NULL, &m2) != GDI_ERROR) { *descent = gm.gmBlackBoxY - gm.gmptGlyphOrigin.y ; *ascent = gm.gmptGlyphOrigin.y + 1; } } } else { *ascent = 0; *descent = 0; *width = 0; } SelectObject(dc, old); } void gwcharmetric(drawing d, font f, int c, int *ascent, int *descent, int *width) { int first, last, extra; TEXTMETRICW tm; HFONT old; HDC dc = GETHDC(d); old = SelectObject(dc, (HFONT)f->handle); GetTextMetricsW(dc, &tm); first = tm.tmFirstChar; last = tm.tmLastChar; extra = tm.tmExternalLeading + tm.tmInternalLeading - 1; if (c < 0) { /* used for setting cra */ SIZE size; char *cc = "M"; GetTextExtentPoint32(dc,(LPSTR) cc, 1, &size); *descent = tm.tmDescent ; *ascent = size.cy - *descent; *width = size.cx; if (*width > size.cy) *width = size.cy; } else if (c == 0) { *descent = tm.tmDescent ; *ascent = tm.tmHeight - *descent - extra ; *width = tm.tmMaxCharWidth ; } else if ((first <= c) && (c <= last)) { SIZE size; wchar_t wc = c; GetTextExtentPoint32W(dc, &wc, 1, &size); *descent = tm.tmDescent ; *ascent = size.cy - *descent - extra ; *width = size.cx; /* Under NT, ' ' gives 0 ascent and descent, which seems correct but this : (i) makes R engine to center in random way; (ii) doesn't correspond to what 98 and X do (' ' is there high as the full font) */ if ((c!=' ') && (tm.tmPitchAndFamily & TMPF_TRUETYPE)) { GLYPHMETRICS gm; MAT2 m2; m2.eM11.value = m2.eM22.value = (WORD) 1 ; m2.eM21.value = m2.eM12.value = (WORD) 0 ; m2.eM11.fract = m2.eM12.fract = m2.eM21.fract = m2.eM22.fract = (short) 0 ; if (GetGlyphOutlineW(dc, c, GGO_METRICS, &gm, 0, NULL, &m2) != GDI_ERROR) { *descent = gm.gmBlackBoxY - gm.gmptGlyphOrigin.y ; *ascent = gm.gmptGlyphOrigin.y + 1; } } } else { /* Unicode char out of range */ *ascent = 0; *descent = 0; *width = 0; } SelectObject(dc, old); } font gnewfont2(drawing d, const char *face, int style, int size, double rot, int usePoints, int quality) { font obj; HFONT hf; LOGFONT lf; if (usePoints) { if ((rot <= 45.0) || ((rot > 135) && (rot <= 225)) || (rot > 315)) lf.lfHeight = -MulDiv(size, devicepixelsy(d), 72); else lf.lfHeight = -MulDiv(size, devicepixelsx(d), 72); } else lf.lfHeight = -size; lf.lfWidth = 0 ; lf.lfEscapement = lf.lfOrientation = (int) 10*rot; lf.lfWeight = FW_NORMAL; lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = 0; if ((! strcmp(face, "Symbol")) || (! strcmp(face, "Wingdings")) || (! strcmp(face, "TT Symbol")) || (! strcmp(face, "TT Wingdings"))) lf.lfCharSet = SYMBOL_CHARSET; else lf.lfCharSet = default_font_charset(); lf.lfClipPrecision = CLIP_DEFAULT_PRECIS; lf.lfQuality = quality; lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; if ((strlen(face) > 1) && (face[0] == 'T') && (face[1] == 'T')) { const char *pf; lf.lfOutPrecision = OUT_TT_ONLY_PRECIS; for (pf = &face[2]; isspace(*pf) ; pf++); strncpy(lf.lfFaceName, pf, LF_FACESIZE-1); } else { lf.lfOutPrecision = OUT_DEFAULT_PRECIS; strncpy(lf.lfFaceName, face, LF_FACESIZE-1); } if (style & Italic) lf.lfItalic = 1; if (style & Bold) lf.lfWeight = FW_BOLD; if (style & FixedWidth) lf.lfPitchAndFamily |= FIXED_PITCH; if (style & SansSerif) lf.lfPitchAndFamily |= FF_SWISS; if ((hf = CreateFontIndirect(&lf)) == 0) return NULL; obj = new_font_object(hf); if (obj) obj->text = new_string(face); if (d && ((d->kind == PrinterObject) || (d->kind == MetafileObject))) { TEXTMETRIC tm; HFONT old = SelectObject((HDC)d->handle, hf); GetTextMetrics((HDC)d->handle, &tm); obj->rect.width = tm.tmAveCharWidth; obj->rect.height = tm.tmHeight; obj->rect.x = tm.tmAscent - tm.tmInternalLeading; obj->rect.y = tm.tmDescent; SelectObject((HDC)d->handle, old); } return (font) obj; } font gnewfont(drawing d, const char *face, int style, int size, double rot, int usePoints) { return gnewfont2(d, face, style, size, rot, usePoints, DEFAULT_QUALITY); } static int measuredev(drawing dev, int what) { HDC hDC; int n; if (dev) hDC = GETHDC(dev); else hDC = GetDC(NULL); n = GetDeviceCaps(hDC, what); if (!dev) ReleaseDC(NULL, hDC); return n; } #define MEASUREDEV(a) {return measuredev(dev,a);} int devicewidth(drawing dev) MEASUREDEV(HORZRES) int deviceheight(drawing dev) MEASUREDEV(VERTRES) int devicewidthmm(drawing dev) MEASUREDEV(HORZSIZE) int deviceheightmm(drawing dev) MEASUREDEV(VERTSIZE) int devicepixelsx(drawing dev) MEASUREDEV(LOGPIXELSX) int devicepixelsy(drawing dev) MEASUREDEV(LOGPIXELSY) int isTopmost(window c) { return GetWindowLong(c->handle, GWL_EXSTYLE) & WS_EX_TOPMOST; } static void setMessageBoxTopmost(window obj) { if ((obj->kind == WindowObject) && (isTopmost(obj))) TopmostDialogs |= MB_TOPMOST; } void * getHandle(window c) { return (void *) c->handle; } void BringToTop(window c, int stay) /* stay=0 for regular, 1 for topmost, 2 for toggle */ { SetForegroundWindow(c->handle); /* needed in Rterm */ if (ismdi()) BringWindowToTop(hwndFrame); BringWindowToTop(c->handle); if (stay == 2) stay = !isTopmost(c); if (stay) SetWindowPos(c->handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); else SetWindowPos(c->handle, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); TopmostDialogs &= !MB_TOPMOST; apply_to_list(c->parent->child, setMessageBoxTopmost); } /* type = 1 minimize 2 restore 3 maximize 4 hide */ void GA_msgWindow(window c, int type) { int state = -1; switch(type){ case 1: state = SW_MINIMIZE; break; case 2: state = SW_SHOWNOACTIVATE; break; case 3: state = SW_MAXIMIZE; break; case 4: state = SW_HIDE; break; default: break; } if (state >= 0) ShowWindow(c->handle, state); }