blob: c026f4fc521bed809ba96afb695f6d7441a785cf [file] [log] [blame]
/*
* R : A Computer Language for Statistical Data Analysis
* Copyright (C) 1998--2004 Guido Masarotto and Brian Ripley
* Copyright (C) 2005-8 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 <winbase.h>
#ifndef W64
WINGDIAPI BOOL WINAPI AlphaBlend(HDC,int,int,int,int,HDC,int,int,int,int,BLENDFUNCTION);
#endif
#include <wchar.h>
#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")))
lf.lfCharSet = SYMBOL_CHARSET;
else
lf.lfCharSet = DEFAULT_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);
}