The R Project SVN R

Rev

Rev 78769 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

/*
 *  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 <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"))
        || (! 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);

}