| /* | |
| * R : A Computer Language for Statistical Data Analysis | |
| * Copyright (C) 1995, 1996 Robert Gentleman and Ross Ihaka | |
| * Copyright (C) 1997--2015 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/ | |
| */ | |
| /* The version for R 2.1.0 is partly based on patches by | |
| Ei-ji Nakama for use in Japanese. | |
| <MBCS> all the strings manipulated here like display and fonts specs | |
| are probably ASCII, or at least start with ASCII in the part searched. | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| # include <config.h> | |
| #endif | |
| #include <Defn.h> | |
| /* rint is C99 */ | |
| #ifdef HAVE_RINT | |
| #define R_rint(x) rint(x) | |
| #else | |
| #define R_rint(x) ((int) x + 0.5) | |
| #endif | |
| /* needed on Solaris */ | |
| #define XK_MISCELLANY | |
| #include <stdio.h> | |
| #include <X11/X.h> | |
| #include <X11/Xlib.h> | |
| #include <X11/Xatom.h> | |
| #include <X11/Xutil.h> | |
| #include <X11/cursorfont.h> | |
| #include <X11/Intrinsic.h> /*-> Xlib.h Xutil.h Xresource.h .. */ | |
| #ifdef HAVE_X11_Xmu | |
| # include <X11/Xmu/Atoms.h> | |
| #endif | |
| #include <X11/keysymdef.h> | |
| #define R_USE_PROTOTYPES 1 | |
| #include <R_ext/GraphicsEngine.h> | |
| #include "Fileio.h" /* R_fopen */ | |
| #include "rotated.h" /* 'Public' routines from here */ | |
| /* For the input handlers of the event loop mechanism: */ | |
| #include <R_ext/eventloop.h> | |
| #include <R_ext/Memory.h> /* vmaxget */ | |
| /* In theory we should do this, but it works less well | |
| # ifdef X_HAVE_UTF8_STRING | |
| # define HAVE_XUTF8TEXTESCAPEMENT 1 | |
| # define HAVE_XUTF8TEXTEXTENTS 1 | |
| # endif */ | |
| typedef int (*X11IOhandler)(Display *); | |
| #include "devX11.h" | |
| #include "rlogo_icon.h" /* hard-coded ARGB icon */ | |
| #include <Rmodules/RX11.h> | |
| static Cursor watch_cursor = (Cursor) 0 ; | |
| static Cursor arrow_cursor = (Cursor) 0 ; | |
| static Cursor cross_cursor = (Cursor) 0 ; | |
| #define MM_PER_INCH 25.4 /* mm -> inch conversion */ | |
| #define X_BELL_VOLUME 0 /* integer between -100 and 100 for the volume | |
| of the bell in locator. */ | |
| /* Note: This is in relation to | |
| the general bell level. Was 50, but if > 0 | |
| then "xset b off" will not disable the | |
| locator bell - pd 2002-3-11 */ | |
| /* a colour used to represent the background on png if transparent | |
| NB: must be grey as used as RGB and BGR | |
| */ | |
| #define PNG_TRANS 0xfefefe | |
| /********************************************************/ | |
| /* If there are resources that are shared by all devices*/ | |
| /* of this type, you may wish to make them globals */ | |
| /* rather than including them in the device-specific */ | |
| /* parameters structure (especially if they are large !)*/ | |
| /********************************************************/ | |
| /* X11 Driver Specific parameters | |
| * with only one copy for all x11 devices */ | |
| static Display *display; /* Display */ | |
| static char dspname[101]=""; | |
| static int screen; /* Screen */ | |
| static Window rootwin; /* Root Window */ | |
| static Visual *visual; /* Visual */ | |
| static int depth; /* Pixmap depth */ | |
| static int Vclass; /* Visual class */ | |
| static X_COLORTYPE model; /* User color model */ | |
| static int maxcubesize; /* Max colorcube size */ | |
| static XSetWindowAttributes attributes; /* Window attributes */ | |
| static Colormap colormap; /* Default color map */ | |
| static int whitepixel; /* bg overlaying canvas */ | |
| static XContext devPtrContext; | |
| static Atom _XA_WM_PROTOCOLS, protocol; | |
| static Rboolean displayOpen = FALSE; | |
| static Rboolean inclose = FALSE; | |
| static int numX11Devices = 0; | |
| /********************************************************/ | |
| /* There must be an entry point for the device driver */ | |
| /* which will create device-specific resources, */ | |
| /* initialise the device-specific parameters structure */ | |
| /* and return whether the setup succeeded */ | |
| /* This is called by the graphics engine when the user */ | |
| /* creates a new device of this type */ | |
| /********************************************************/ | |
| /********************************************************/ | |
| /* There are a number of actions that every device */ | |
| /* driver is expected to perform (even if, in some */ | |
| /* cases it does nothing - just so long as it doesn't */ | |
| /* crash !). this is how the graphics engine interacts */ | |
| /* with each device. ecah action will be documented */ | |
| /* individually. */ | |
| /* hooks for these actions must be set up when the */ | |
| /* device is first created */ | |
| /********************************************************/ | |
| /* Device Driver Actions */ | |
| static void X11_Activate(pDevDesc dd); | |
| static void X11_Circle(double x, double y, double r, | |
| const pGEcontext gc, pDevDesc dd); | |
| static void X11_Clip(double x0, double x1, double y0, double y1, | |
| pDevDesc dd); | |
| static void X11_Close(pDevDesc dd); | |
| static void X11_Deactivate(pDevDesc dd); | |
| static Rboolean X11_Locator(double *x, double *y, pDevDesc dd); | |
| static void X11_Line(double x1, double y1, double x2, double y2, | |
| const pGEcontext gc, pDevDesc dd); | |
| static void X11_MetricInfo(int c, const pGEcontext gc, | |
| double* ascent, double* descent, | |
| double* width, pDevDesc dd); | |
| static void X11_Mode(int mode, pDevDesc dd); | |
| static void X11_NewPage(const pGEcontext gc, pDevDesc dd); | |
| static void X11_Polygon(int n, double *x, double *y, | |
| const pGEcontext gc, pDevDesc dd); | |
| static void X11_Polyline(int n, double *x, double *y, | |
| const pGEcontext gc, pDevDesc dd); | |
| static void X11_Rect(double x0, double y0, double x1, double y1, | |
| const pGEcontext gc, pDevDesc dd); | |
| static void X11_Raster(unsigned int *raster, int w, int h, | |
| double x, double y, double width, double height, | |
| double rot, Rboolean interpolate, | |
| const pGEcontext gc, pDevDesc dd); | |
| static SEXP X11_Cap(pDevDesc dd); | |
| static void X11_Size(double *left, double *right, | |
| double *bottom, double *top, | |
| pDevDesc dd); | |
| static double X11_StrWidth(const char *str, const pGEcontext gc, pDevDesc dd); | |
| static void X11_Text(double x, double y, const char *str, | |
| double rot, double hadj, | |
| const pGEcontext gc, pDevDesc dd); | |
| static void X11_eventHelper(pDevDesc dd, int code); | |
| /*************************************************/ | |
| /* End of list of required device driver actions */ | |
| /*************************************************/ | |
| /* Support Routines */ | |
| static void *RLoadFont(pX11Desc, char*, int, int); | |
| static double pixelHeight(void); | |
| static double pixelWidth(void); | |
| static void SetColor(unsigned int, pX11Desc); | |
| static void SetFont(const pGEcontext, pX11Desc); | |
| static void SetLinetype(const pGEcontext, pX11Desc); | |
| static void X11_Close_bitmap(pX11Desc xd); | |
| static char* translateFontFamily(char* family, pX11Desc xd); | |
| static double RedGamma = 1.0; | |
| static double GreenGamma = 1.0; | |
| static double BlueGamma = 1.0; | |
| #ifdef HAVE_WORKING_CAIRO | |
| # include "cairoFns.c" | |
| /************************/ | |
| /* Buffering */ | |
| /************************/ | |
| /* | |
| Buffering is only implemented for the cairo-based devices. | |
| The original (Feb 2008) version had two types: | |
| - "nbcairo". This wrote directly to a cairo_xlib_surface, xd->cs. | |
| - "cairo". This wrote to a cairo_image_surface xd->cs, and copied that to | |
| the cairo_xlib_surface (xd->xcs) at mode(0) calls. | |
| Further types were introduced (experimentally) in May 2011. We kept: | |
| - "dbcairo". Similar to cairo, but the copying is only done when needed | |
| based on a timer. | |
| Timing requires a medium-res timer. The current method is to update | |
| ca 100ms after the last activity (using the event loop) or at a | |
| mode(0) call if it is 500ms after the last update. | |
| */ | |
| #if (defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_REALTIME)) || defined(HAVE_GETTIMEOFDAY) | |
| /* We need to avoid this in the rare case that it is only in seconds */ | |
| extern double currentTime(void); /* from datetime.c */ | |
| #else | |
| /* Alternatively, use times() which R >= 2.14.0 requires. This could | |
| conceivably wrap around, but on the sort of system where this might | |
| be used, clock_t is 32-bit (it is typically long or unsigned long) | |
| and CLK_TCK is 60-100, so it happens after many months of uptime. | |
| */ | |
| # include <sys/times.h> | |
| # ifndef CLK_TCK | |
| # define CLK_TCK 60 | |
| # endif | |
| static double currentTime(void) | |
| { | |
| struct tms ti; | |
| return ((double) times(&ti))/CLK_TCK; | |
| } | |
| #endif | |
| static void Cairo_update(pX11Desc xd) | |
| { | |
| if(inclose || !xd || !xd->buffered || xd->holdlevel > 0) return; | |
| cairo_paint(xd->xcc); | |
| /* workaround for bug in cairo 1.12.x (PR#15168) */ | |
| cairo_surface_flush(xd->xcs); | |
| if (xd->type == WINDOW) XDefineCursor(display, xd->window, arrow_cursor); | |
| XSync(display, 0); | |
| xd->last = currentTime(); | |
| } | |
| /* | |
| We record a linked list of devices which are open and double-buffered. | |
| The head of the list is a dummy entry to make removals the same for | |
| any element. | |
| */ | |
| struct xd_list { | |
| pX11Desc this; | |
| struct xd_list *next; | |
| }; | |
| typedef struct xd_list *Xdl; | |
| static struct xd_list xdl0; | |
| static Xdl xdl = &xdl0; | |
| static void CairoHandler(void) | |
| { | |
| static int buffer_lock = 0; /* reentrancy guard */ | |
| if (!buffer_lock && xdl->next) { | |
| double current = currentTime(); | |
| buffer_lock = 1; | |
| for(Xdl z = xdl->next; z; z = z->next) { | |
| pX11Desc xd = z->this; | |
| if(xd->last > xd->last_activity) continue; | |
| if((current - xd->last) < xd->update_interval) continue; | |
| Cairo_update(xd); | |
| } | |
| buffer_lock = 0; | |
| } | |
| } | |
| /* private hooks in sys-std.c */ | |
| extern void (* Rg_PolledEvents)(void); | |
| extern int Rg_wait_usec; | |
| /* | |
| check for updates every 50ms: | |
| by default the updater is only run >= 100ms after last update. | |
| */ | |
| #define WAIT 50000 | |
| static int timingInstalled = 0; | |
| static void addBuffering(pX11Desc xd) | |
| { | |
| Xdl xdln = (Xdl) malloc(sizeof(struct xd_list)); | |
| xdln->this = xd; | |
| xdln->next = xdl->next; | |
| xdl->next = xdln; | |
| if(timingInstalled) return; | |
| timingInstalled = 1; | |
| Rg_PolledEvents = CairoHandler; | |
| Rg_wait_usec = WAIT; | |
| } | |
| static void removeBuffering(pX11Desc xd) | |
| { | |
| for(Xdl z = xdl; z->next; z = z->next) | |
| if (z->next->this == xd) { | |
| Xdl old = z->next; | |
| z->next = z->next->next; | |
| free(old); | |
| break; | |
| } | |
| if(xdl->next == NULL) { | |
| Rg_wait_usec = 0; | |
| timingInstalled = 0; | |
| } | |
| } | |
| static void Cairo_NewPage(const pGEcontext gc, pDevDesc dd) | |
| { | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| cairo_reset_clip(xd->cc); | |
| xd->fill = R_OPAQUE(gc->fill) ? gc->fill: xd->canvas; | |
| CairoColor(xd->fill, xd); | |
| cairo_new_path(xd->cc); | |
| cairo_paint(xd->cc); | |
| if(xd->buffered) Cairo_update(xd); | |
| else XSync(display, 0); | |
| } | |
| static int Cairo_holdflush(pDevDesc dd, int level) | |
| { | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| int old = xd->holdlevel; | |
| if(!xd->buffered) return old; | |
| xd->holdlevel += level; | |
| if(xd->holdlevel <= 0) xd->holdlevel = 0; | |
| // printf("holdlevel = %d\n", xd->holdlevel); | |
| /* flush if at level zero - also changes cursor */ | |
| if(xd->holdlevel == 0) { | |
| if(xd->buffered) Cairo_update(xd); | |
| else { | |
| if (xd->type == WINDOW) XDefineCursor(display, xd->window, arrow_cursor); | |
| XSync(display, 0); | |
| } | |
| } else if (old == 0) { | |
| /* May need to flush before holding */ | |
| if(xd->buffered > 1 && xd->last_activity > xd->last) { | |
| xd->holdlevel = old; | |
| Cairo_update(xd); | |
| xd->holdlevel = level; | |
| } | |
| if (xd->type == WINDOW) XDefineCursor(display, xd->window, watch_cursor); | |
| XSync(display, 0); | |
| } | |
| return xd->holdlevel; | |
| } | |
| #endif /* HAVE_WORKING_CAIRO */ | |
| /************************/ | |
| /* X11 Color Management */ | |
| /************************/ | |
| /* Variables Used To Store Colormap Information */ | |
| static struct { int red; int green; int blue; } RPalette[512]; | |
| static XColor XPalette[512]; | |
| static int PaletteSize; | |
| /* Monochome Displays : Compute pixel values by converting */ | |
| /* RGB values to luminance and then thresholding. */ | |
| /* See: Foley & van Damm. */ | |
| static void SetupMonochrome(void) | |
| { | |
| depth = 1; | |
| } | |
| static unsigned GetMonochromePixel(int r, int g, int b) | |
| { | |
| if ((int)(0.299 * r + 0.587 * g + 0.114 * b) > 127) | |
| return (unsigned) WhitePixel(display, screen); | |
| else | |
| return (unsigned) BlackPixel(display, screen); | |
| } | |
| /* Grayscale Displays : Compute pixel values by converting */ | |
| /* RGB values to luminance. See: Foley & van Damm. */ | |
| static unsigned GetGrayScalePixel(int r, int g, int b) | |
| { | |
| unsigned int d, dmin = 0xFFFFFFFF; | |
| unsigned int dr; | |
| int i; | |
| unsigned int pixel = 0; /* -Wall */ | |
| int gray = (int)((0.299 * r + 0.587 * g + 0.114 * b) + 0.0001); | |
| for (i = 0; i < PaletteSize; i++) { | |
| dr = (RPalette[i].red - gray); | |
| d = dr * dr; | |
| if (d < dmin) { | |
| pixel = (unsigned) XPalette[i].pixel; | |
| dmin = d; | |
| } | |
| } | |
| return pixel; | |
| } | |
| static Rboolean GetGrayPalette(Display *displ, Colormap cmap, int n) | |
| { | |
| int status, i, m; | |
| m = 0; | |
| i = 0; | |
| for (i = 0; i < n; i++) { | |
| RPalette[i].red = (unsigned short) ((i * 0xff) / (n - 1)); | |
| RPalette[i].green = RPalette[i].red; | |
| RPalette[i].blue = RPalette[i].red; | |
| /* Gamma correct here */ | |
| XPalette[i].red = (unsigned short)((i * 0xffff) / (n - 1)); | |
| XPalette[i].green = XPalette[i].red; | |
| XPalette[i].blue = XPalette[i].red; | |
| status = XAllocColor(displ, cmap, &XPalette[i]); | |
| if (status == 0) { | |
| XPalette[i].flags = 0; | |
| m++; | |
| } | |
| else | |
| XPalette[i].flags = DoRed|DoGreen|DoBlue; | |
| } | |
| PaletteSize = n; | |
| if (m > 0) { | |
| for (i = 0; i < PaletteSize; i++) { | |
| if (XPalette[i].flags != 0) | |
| XFreeColors(displ, cmap, &(XPalette[i].pixel), 1, 0); | |
| } | |
| PaletteSize = 0; | |
| return FALSE; | |
| } | |
| else return TRUE; | |
| } | |
| static void SetupGrayScale(void) | |
| { | |
| int res = 0, d; | |
| PaletteSize = 0; | |
| /* try for 128 grays on an 8-bit display */ | |
| if (depth > 8) d = depth = 8; else d = depth - 1; | |
| /* try (256), 128, 64, 32, 16 grays */ | |
| while (d >= 4 && !(res = GetGrayPalette(display, colormap, 1 << d))) | |
| d--; | |
| if (!res) { | |
| /* Can't find a sensible grayscale, so revert to monochrome */ | |
| warning(_("cannot set grayscale: reverting to monochrome")); | |
| model = MONOCHROME; | |
| SetupMonochrome(); | |
| } | |
| } | |
| /* PseudoColor Displays : There are two strategies here. */ | |
| /* 1) allocate a standard color cube and match colors */ | |
| /* within that based on (weighted) distances in RGB space. */ | |
| /* 2) allocate colors exactly as they are requested until */ | |
| /* all color cells are used. Fail with an error message */ | |
| /* when this happens. */ | |
| static int RGBlevels[][3] = { /* PseudoColor Palettes */ | |
| { 8, 8, 4 }, | |
| { 6, 7, 6 }, | |
| { 6, 6, 6 }, | |
| { 6, 6, 5 }, | |
| { 6, 6, 4 }, | |
| { 5, 5, 5 }, | |
| { 5, 5, 4 }, | |
| { 4, 4, 4 }, | |
| { 4, 4, 3 }, | |
| { 3, 3, 3 }, | |
| { 2, 2, 2 } | |
| }; | |
| static int NRGBlevels = sizeof(RGBlevels) / (3 * sizeof(int)); | |
| static int GetColorPalette(Display *dpy, Colormap cmap, int nr, int ng, int nb) | |
| { | |
| int status, i, m, r, g, b; | |
| m = 0; | |
| i = 0; | |
| for (r = 0; r < nr; r++) { | |
| for (g = 0; g < ng; g++) { | |
| for (b = 0; b < nb; b++) { | |
| RPalette[i].red = (r * 0xff) / (nr - 1); | |
| RPalette[i].green = (g * 0xff) / (ng - 1); | |
| RPalette[i].blue = (b * 0xff) / (nb - 1); | |
| /* Perform Gamma Correction Here */ | |
| XPalette[i].red = | |
| (unsigned short)(pow(r / (nr - 1.0), RedGamma) * 0xffff); | |
| XPalette[i].green = | |
| (unsigned short)(pow(g / (ng - 1.0), GreenGamma) * 0xffff); | |
| XPalette[i].blue = | |
| (unsigned short)(pow(b / (nb - 1.0), BlueGamma) * 0xffff); | |
| /* End Gamma Correction */ | |
| status = XAllocColor(dpy, cmap, &XPalette[i]); | |
| if (status == 0) { | |
| XPalette[i].flags = 0; | |
| m++; | |
| } | |
| else | |
| XPalette[i].flags = DoRed|DoGreen|DoBlue; | |
| i++; | |
| } | |
| } | |
| } | |
| PaletteSize = nr * ng * nb; | |
| if (m > 0) { | |
| for (i = 0; i < PaletteSize; i++) { | |
| if (XPalette[i].flags != 0) | |
| XFreeColors(dpy, cmap, &(XPalette[i].pixel), 1, 0); | |
| } | |
| PaletteSize = 0; | |
| return 0; | |
| } | |
| else | |
| return 1; | |
| } | |
| static void SetupPseudoColor(void) | |
| { | |
| int i, size; | |
| PaletteSize = 0; | |
| if (model == PSEUDOCOLOR1) { | |
| for (i = 0; i < NRGBlevels; i++) { | |
| size = RGBlevels[i][0] * RGBlevels[i][1] * RGBlevels[i][2]; | |
| if (size < maxcubesize && GetColorPalette(display, colormap, | |
| RGBlevels[i][0], | |
| RGBlevels[i][1], | |
| RGBlevels[i][2])) | |
| break; | |
| } | |
| if (PaletteSize == 0) { | |
| warning(_("X11 driver unable to obtain color cube\n reverting to monochrome")); | |
| model = MONOCHROME; | |
| SetupMonochrome(); | |
| } | |
| } | |
| else { | |
| PaletteSize = 0; | |
| } | |
| } | |
| static unsigned int GetPseudoColor1Pixel(int r, int g, int b) | |
| { | |
| unsigned int d, dmin = 0xFFFFFFFF; | |
| unsigned int dr, dg, db; | |
| unsigned int pixel; | |
| int i; | |
| pixel = 0; /* -Wall */ | |
| for (i = 0; i < PaletteSize; i++) { | |
| dr = (RPalette[i].red - r); | |
| dg = (RPalette[i].green - g); | |
| db = (RPalette[i].blue - b); | |
| d = dr * dr + dg * dg + db * db; | |
| if (d < dmin) { | |
| pixel = (unsigned int) XPalette[i].pixel; | |
| dmin = d; | |
| } | |
| } | |
| return pixel; | |
| } | |
| static unsigned int GetPseudoColor2Pixel(int r, int g, int b) | |
| { | |
| int i; | |
| /* Search for previously allocated color */ | |
| for (i = 0; i < PaletteSize ; i++) { | |
| if (r == RPalette[i].red && | |
| g == RPalette[i].green && | |
| b == RPalette[i].blue) return (unsigned int) XPalette[i].pixel; | |
| } | |
| /* Attempt to allocate a new color */ | |
| XPalette[PaletteSize].red = | |
| (unsigned short)(pow(r / 255.0, RedGamma) * 0xffff); | |
| XPalette[PaletteSize].green = | |
| (unsigned short)(pow(g / 255.0, GreenGamma) * 0xffff); | |
| XPalette[PaletteSize].blue = | |
| (unsigned short)(pow(b / 255.0, BlueGamma) * 0xffff); | |
| if (PaletteSize == 256 || | |
| XAllocColor(display, colormap, &XPalette[PaletteSize]) == 0) { | |
| error(_("Error: X11 cannot allocate additional graphics colors.\n\ | |
| Consider using X11 with colortype=\"pseudo.cube\" or \"gray\".")); | |
| } | |
| RPalette[PaletteSize].red = r; | |
| RPalette[PaletteSize].green = g; | |
| RPalette[PaletteSize].blue = b; | |
| PaletteSize++; | |
| return (unsigned int)XPalette[PaletteSize - 1].pixel; | |
| } | |
| static unsigned int GetPseudoColorPixel(int r, int g, int b) | |
| { | |
| if (model == PSEUDOCOLOR1) | |
| return GetPseudoColor1Pixel(r, g, b); | |
| else | |
| return GetPseudoColor2Pixel(r, g, b); | |
| } | |
| /* Truecolor Displays : Allocate the colors as they are requested */ | |
| static unsigned int RMask, RShift; | |
| static unsigned int GMask, GShift; | |
| static unsigned int BMask, BShift; | |
| static void SetupTrueColor(void) | |
| { | |
| RMask = (unsigned int)visual->red_mask; | |
| GMask = (unsigned int)visual->green_mask; | |
| BMask = (unsigned int)visual->blue_mask; | |
| RShift = 0; while ((RMask & 1) == 0) { RShift++; RMask >>= 1; } | |
| GShift = 0; while ((GMask & 1) == 0) { GShift++; GMask >>= 1; } | |
| BShift = 0; while ((BMask & 1) == 0) { BShift++; BMask >>= 1; } | |
| } | |
| static unsigned GetTrueColorPixel(int r, int g, int b) | |
| { | |
| r = (int)(pow((r / 255.0), RedGamma) * 255); | |
| g = (int)(pow((g / 255.0), GreenGamma) * 255); | |
| b = (int)(pow((b / 255.0), BlueGamma) * 255); | |
| return | |
| (((r * RMask) / 255) << RShift) | | |
| (((g * GMask) / 255) << GShift) | | |
| (((b * BMask) / 255) << BShift); | |
| } | |
| /* Interface for General Visual */ | |
| static unsigned int GetX11Pixel(int r, int g, int b) | |
| { | |
| switch(model) { | |
| case MONOCHROME: | |
| return GetMonochromePixel(r, g, b); | |
| case GRAYSCALE: | |
| return GetGrayScalePixel(r, g, b); | |
| case PSEUDOCOLOR1: | |
| case PSEUDOCOLOR2: | |
| return GetPseudoColorPixel(r, g, b); | |
| case TRUECOLOR: | |
| return GetTrueColorPixel(r, g, b); | |
| default: | |
| printf("Unknown Visual\n"); | |
| } | |
| return 0; | |
| } | |
| static void FreeX11Colors(void) | |
| { | |
| int i; | |
| if (model == PSEUDOCOLOR2) { | |
| for (i = 0; i < PaletteSize; i++) | |
| XFreeColors(display, colormap, &(XPalette[i].pixel), 1, 0); | |
| PaletteSize = 0; | |
| } | |
| } | |
| static Rboolean SetupX11Color(void) | |
| { | |
| if (depth <= 1) { | |
| /* On monchome displays we must use black/white */ | |
| model = MONOCHROME; | |
| SetupMonochrome(); | |
| } | |
| else if (Vclass == StaticGray || Vclass == GrayScale) { | |
| if (model == MONOCHROME) | |
| SetupMonochrome(); | |
| else { | |
| model = GRAYSCALE; | |
| SetupGrayScale(); | |
| } | |
| } | |
| else if (Vclass == StaticColor) { | |
| /* FIXME : Currently revert to mono. */ | |
| /* Should do the real thing. */ | |
| model = MONOCHROME; | |
| SetupMonochrome(); | |
| } | |
| else if (Vclass == PseudoColor) { | |
| if (model == MONOCHROME) | |
| SetupMonochrome(); | |
| else if (model == GRAYSCALE) | |
| SetupGrayScale(); | |
| else { | |
| if (model == TRUECOLOR) | |
| model = PSEUDOCOLOR2; | |
| SetupPseudoColor(); | |
| } | |
| } | |
| else if (Vclass == TrueColor) { | |
| if (model == MONOCHROME) | |
| SetupMonochrome(); | |
| else if (model == GRAYSCALE) | |
| SetupGrayScale(); | |
| else if (model == PSEUDOCOLOR1 || model == PSEUDOCOLOR2) | |
| SetupPseudoColor(); | |
| else | |
| SetupTrueColor(); | |
| } | |
| else if (Vclass == DirectColor) { | |
| /* FIXME : Currently revert to mono. */ | |
| /* Should do the real thing. */ | |
| model = MONOCHROME; | |
| SetupMonochrome(); | |
| } | |
| else { | |
| printf("Unknown Visual\n"); | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| /* Pixel Dimensions (Inches) */ | |
| static double pixelWidth(void) | |
| { | |
| double width, widthMM; | |
| width = DisplayWidth(display, screen); | |
| widthMM = DisplayWidthMM(display, screen); | |
| return ((double)widthMM / (double)width) / MM_PER_INCH; | |
| } | |
| static double pixelHeight(void) | |
| { | |
| double height, heightMM; | |
| height = DisplayHeight(display, screen); | |
| heightMM = DisplayHeightMM(display, screen); | |
| return ((double)heightMM / (double)height) / MM_PER_INCH; | |
| } | |
| static void handleEvent(XEvent event) | |
| { | |
| if (event.xany.type == Expose) { | |
| /* ----- window repaint ------ */ | |
| while (XCheckTypedWindowEvent(display, event.xexpose.window, Expose, &event)); | |
| if (inclose) return; | |
| if (event.xexpose.count != 0) return; | |
| caddr_t temp; | |
| XFindContext(display, event.xexpose.window, devPtrContext, &temp); | |
| pDevDesc dd = (pDevDesc) temp; | |
| pGEDevDesc gdd = desc2GEDesc(dd); | |
| if(gdd->dirty) { | |
| #ifdef HAVE_WORKING_CAIRO | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| /* We can use the buffered copy where we have it */ | |
| if(xd->buffered == 1) { | |
| cairo_paint(xd->xcc); | |
| /* workaround for bug in cairo 1.12.x (PR#15168) */ | |
| cairo_surface_flush(xd->xcs); | |
| } else if (xd->buffered > 1) | |
| /* rely on timer to repaint eventually */ | |
| xd->last_activity = currentTime(); | |
| else | |
| #endif | |
| GEplayDisplayList(gdd); | |
| XSync(display, 0); | |
| } | |
| } else if (event.type == ConfigureNotify) { | |
| while (XCheckTypedEvent(display, ConfigureNotify, &event)) ; | |
| if (inclose) return; | |
| caddr_t temp; | |
| XFindContext(display, event.xconfigure.window, devPtrContext, &temp); | |
| pDevDesc dd = (pDevDesc) temp; | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| if (xd->windowWidth != event.xconfigure.width || | |
| xd->windowHeight != event.xconfigure.height) { | |
| /* ----- window resize ------ */ | |
| xd->windowWidth = event.xconfigure.width; | |
| xd->windowHeight = event.xconfigure.height; | |
| #if defined HAVE_WORKING_CAIRO | |
| if(xd->useCairo) { | |
| if(xd->buffered) { | |
| cairo_surface_destroy(xd->cs); xd->cs = NULL; | |
| cairo_destroy(xd->cc); xd->cc = NULL; | |
| cairo_xlib_surface_set_size(xd->xcs, xd->windowWidth, | |
| xd->windowHeight); | |
| xd->cs = | |
| cairo_image_surface_create(CAIRO_FORMAT_RGB24, | |
| xd->windowWidth, | |
| xd->windowHeight); | |
| cairo_status_t res = cairo_surface_status(xd->cs); | |
| if (res != CAIRO_STATUS_SUCCESS) { | |
| warning("cairo error '%s'", | |
| cairo_status_to_string(res)); | |
| error("fatal error on resize: please shut down the device"); | |
| } | |
| xd->cc = cairo_create(xd->cs); | |
| cairo_set_antialias(xd->cc, xd->antialias); | |
| cairo_set_source_surface (xd->xcc, xd->cs, 0, 0); | |
| } else { /* not buffered */ | |
| cairo_xlib_surface_set_size(xd->cs, xd->windowWidth, | |
| xd->windowHeight); | |
| cairo_reset_clip(xd->cc); | |
| } | |
| } | |
| #endif | |
| dd->size(&(dd->left), &(dd->right), &(dd->bottom), &(dd->top), dd); | |
| /* gobble Expose events; we'll redraw anyway */ | |
| while (XCheckTypedWindowEvent(display, event.xexpose.window, Expose, &event)); | |
| pGEDevDesc gdd = desc2GEDesc(dd); | |
| if(gdd->dirty) { | |
| GEplayDisplayList(gdd); | |
| XSync(display, 0); | |
| } | |
| } | |
| } else if ((event.type == ClientMessage) && | |
| (event.xclient.message_type == _XA_WM_PROTOCOLS)) { | |
| if (!inclose && event.xclient.data.l[0] == protocol) { | |
| caddr_t temp; | |
| XFindContext(display, event.xclient.window, devPtrContext, &temp); | |
| killDevice(ndevNumber((pDevDesc) temp)); | |
| } | |
| } | |
| } | |
| static void R_ProcessX11Events(void *data) | |
| { | |
| XEvent event; | |
| while (!R_isForkedChild && displayOpen && XPending(display)) { | |
| XNextEvent(display, &event); | |
| /* printf("%i\n",event.type); */ | |
| handleEvent(event); | |
| } | |
| } | |
| /************************/ | |
| /* X11 Font Management */ | |
| /************************/ | |
| static char *fontname = "-adobe-helvetica-%s-%s-*-*-%d-*-*-*-*-*-*-*"; | |
| static char *symbolname = "-adobe-symbol-medium-r-*-*-%d-*-*-*-*-*-*-*"; | |
| static char *slant[] = {"r", "o"}; | |
| static char *weight[] = {"medium", "bold"}; | |
| /* Bitmap of the Adobe design sizes */ | |
| static unsigned int adobe_sizes = 0x0403165D; | |
| #define MAXFONTS 64 | |
| #define CLRFONTS 16 /* Number to free when cache runs full */ | |
| typedef struct { | |
| char family[500]; | |
| int face, size; | |
| R_XFont *font; | |
| } cacheentry; | |
| static cacheentry fontcache[MAXFONTS]; | |
| static int nfonts = 0; | |
| static int force_nonscalable = 0; /* for testing */ | |
| #define ADOBE_SIZE(I) ((I) > 7 && (I) < 35 && (adobe_sizes & (1<<((I)-8)))) | |
| #define SMALLEST 2 | |
| static R_XFont *R_XLoadQueryFont(Display *display, char *name) | |
| { | |
| R_XFont *tmp; | |
| tmp = (R_XFont *) malloc(sizeof(R_XFont)); | |
| tmp->type = One_Font; | |
| tmp->font = XLoadQueryFont(display, name); | |
| if(tmp->font) | |
| return tmp; | |
| else { | |
| free(tmp); | |
| return NULL; | |
| } | |
| } | |
| static void R_XFreeFont(Display *display, R_XFont *font) | |
| { | |
| if(font->type == Font_Set) XFreeFontSet(display, font->fontset); | |
| else XFreeFont(display, font->font); | |
| free(font); | |
| } | |
| /* | |
| * Can't load Symbolfont to XFontSet!! | |
| */ | |
| static R_XFont *R_XLoadQueryFontSet(Display *display, | |
| const char *fontset_name) | |
| { | |
| R_XFont *tmp = (R_XFont *) malloc(sizeof(R_XFont)); | |
| XFontSet fontset; | |
| int /*i,*/ missing_charset_count; | |
| char **missing_charset_list, *def_string; | |
| #ifdef DEBUG_X11 | |
| printf("loading fontset %s\n", fontset_name); | |
| #endif | |
| fontset = XCreateFontSet(display, fontset_name, &missing_charset_list, | |
| &missing_charset_count, &def_string); | |
| if(!fontset) { | |
| free(tmp); | |
| return NULL; | |
| } | |
| if (missing_charset_count) { | |
| #ifdef DEBUG_X11 | |
| int i; | |
| for(i = 0; i < missing_charset_count; i++) | |
| warning("font for charset %s is lacking.", missing_charset_list[i]); | |
| XFreeStringList(missing_charset_list); | |
| #endif | |
| } | |
| tmp->type = Font_Set; | |
| tmp->fontset = fontset; | |
| return tmp; | |
| } | |
| static void *RLoadFont(pX11Desc xd, char* family, int face, int size) | |
| { | |
| /* size is in points here */ | |
| int pixelsize, i, dpi; | |
| cacheentry *f; | |
| char buf[BUFSIZ]; | |
| char buf1[BUFSIZ]; | |
| R_XFont *tmp = NULL; | |
| #ifdef DEBUG_X11 | |
| printf("trying face %d size %d\n", face, size); | |
| #endif | |
| if (size < SMALLEST) size = SMALLEST; | |
| face--; | |
| if(xd->type == PNG || xd->type == JPEG || | |
| xd->type == TIFF || xd->type == BMP) { | |
| dpi = (xd->res_dpi > 0) ? (int)(xd->res_dpi + 0.5) : 72; | |
| } else { | |
| dpi = (int)(1./pixelHeight() + 0.5); | |
| } | |
| if(abs(dpi - 75) < 5) { | |
| /* use pointsize as pixel size */ | |
| } else if(abs(dpi - 100) < 5) { | |
| /* Here's a 1st class fudge: make sure that the Adobe design sizes | |
| 8, 10, 11, 12, 14, 17, 18, 20, 24, 25, 34 can be obtained via | |
| an integer "size" at 100 dpi, namely 6, 7, 8, 9, 10, 12, 13, | |
| 14, 17, 18, 24 points. It's almost y = x * 100/72, but not | |
| quite. The constants were found using lm(). --pd */ | |
| size = (int) R_rint(size * 1.43 - 0.4); | |
| } else size = (int) R_rint(size * dpi/72); | |
| /* search fontcache */ | |
| for ( i = nfonts ; i-- ; ) { | |
| f = &fontcache[i]; | |
| if ( strcmp(f->family, family) == 0 && | |
| f->face == face && | |
| f->size == size ) | |
| return f->font; | |
| } | |
| /* 'size' is the requested size, 'pixelsize' the size of the | |
| actually allocated font*/ | |
| pixelsize = size; | |
| /* | |
| * The symbol font face is a historical oddity | |
| * Always use a standard font for font face 5 | |
| */ | |
| if (face == SYMBOL_FONTFACE - 1) /* NB: face-- above */ | |
| sprintf(buf, xd->symbolfamily, pixelsize); | |
| else | |
| if (mbcslocale && *slant[(face & 2) >> 1] == 'o') { | |
| sprintf(buf, family, weight[face & 1], slant[(face & 2) >> 1], | |
| pixelsize); | |
| sprintf(buf1, family, weight[face & 1], "i", pixelsize); | |
| strcat(buf,","); | |
| strcat(buf,buf1); | |
| } else | |
| sprintf(buf, family, weight[face & 1], slant[(face & 2) >> 1], | |
| pixelsize); | |
| #ifdef DEBUG_X11 | |
| Rprintf("loading:\n%s\n",buf); | |
| #endif | |
| if (!mbcslocale || face == SYMBOL_FONTFACE - 1) | |
| tmp = R_XLoadQueryFont(display, buf); | |
| else | |
| tmp = R_XLoadQueryFontSet(display, buf); | |
| #ifdef DEBUG_X11 | |
| if (tmp) Rprintf("success\n"); else Rprintf("failure\n"); | |
| #endif | |
| /* | |
| * IF can't find the font specified then | |
| * go to great lengths to find something else to use. | |
| */ | |
| if (!tmp || (force_nonscalable && !ADOBE_SIZE(size)) ){ | |
| static int near[]= | |
| {14,14,14,17,17,18,20,20,20,20,24,24,24,25,25,25,25}; | |
| /* 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 */ | |
| /* If ADOBE_SIZE(pixelsize) is true at this point then | |
| the user's system does not have the standard ADOBE font set | |
| so we just have to use a "fixed" font. | |
| If we can't find a "fixed" font then something is seriously | |
| wrong */ | |
| if ( ADOBE_SIZE(pixelsize) ) { | |
| if(tmp) | |
| R_XFreeFont(display, tmp); | |
| if(mbcslocale) | |
| tmp = (void*) R_XLoadQueryFontSet(display, | |
| "-*-fixed-medium-r-*--13-*-*-*-*-*-*-*"); | |
| else | |
| tmp = (void*) R_XLoadQueryFont(display, "fixed"); | |
| if (tmp) | |
| return tmp; | |
| else | |
| error(_("could not find any X11 fonts\nCheck that the Font Path is correct.")); | |
| } | |
| if ( pixelsize < 8 ) | |
| pixelsize = 8; | |
| else if (pixelsize == 9) | |
| pixelsize = 8; | |
| else if (pixelsize < 30) /* must be at least 13 */ | |
| pixelsize = near[size-13]; | |
| else | |
| pixelsize = 34; | |
| if (face == SYMBOL_FONTFACE - 1) | |
| sprintf(buf, symbolname, pixelsize); | |
| else | |
| sprintf(buf, fontname, | |
| weight[face & 1], | |
| slant[(face & 2) >> 1 ], pixelsize); | |
| #ifdef DEBUG_X11 | |
| Rprintf("loading:\n%s\n",buf); | |
| #endif | |
| if (!mbcslocale || face == SYMBOL_FONTFACE - 1) | |
| tmp = R_XLoadQueryFont(display, buf); | |
| else | |
| tmp = R_XLoadQueryFontSet(display, buf); | |
| #ifdef DEBUG_X11 | |
| if (tmp) Rprintf("success\n"); else Rprintf("failure\n"); | |
| #endif | |
| } | |
| if(!tmp && size > 24) { | |
| /* final try, size 24 */ | |
| pixelsize = 24; | |
| if (face == 4) | |
| sprintf(buf, symbolname, 24); | |
| else | |
| sprintf(buf, fontname, | |
| weight[face & 1], | |
| slant[(face & 2) >> 1 ], 24); | |
| #ifdef DEBUG_X11 | |
| Rprintf("loading:\n%s\n",buf); | |
| #endif | |
| if (!mbcslocale || face == SYMBOL_FONTFACE - 1) | |
| tmp = R_XLoadQueryFont(display, buf); | |
| else | |
| tmp = R_XLoadQueryFontSet(display, buf); | |
| #ifdef DEBUG_X11 | |
| if (tmp) Rprintf("success\n"); else Rprintf("failure\n"); | |
| #endif | |
| } | |
| if (tmp){ | |
| f = &fontcache[nfonts++]; | |
| strcpy(f->family, family); | |
| f->face = face; | |
| f->size = size; | |
| f->font = tmp; | |
| if (fabs( (pixelsize - size)/(double)size ) > 0.1) | |
| warning(_("X11 used font size %d when %d was requested"), | |
| pixelsize, size); | |
| } | |
| if (nfonts == MAXFONTS) /* make room in the font cache */ | |
| { | |
| for (i = 0 ; i < CLRFONTS ; i++) | |
| R_XFreeFont(display, fontcache[i].font); | |
| for (i = CLRFONTS ; i < MAXFONTS ; i++) | |
| fontcache[i - CLRFONTS] = fontcache[i]; | |
| nfonts -= CLRFONTS; | |
| } | |
| return tmp; | |
| } | |
| static void SetFont(const pGEcontext gc, pX11Desc xd) | |
| { | |
| R_XFont *tmp; | |
| char *family = translateFontFamily(gc->fontfamily, xd); | |
| /* size is in points here */ | |
| int size = (int)(gc->cex * gc->ps + 0.5), face = gc->fontface; | |
| if (face < 1 || face > 5) face = 1; | |
| if (size != xd->fontsize || face != xd->fontface || | |
| strcmp(family, xd->fontfamily) != 0) { | |
| tmp = RLoadFont(xd, family, face, size); | |
| if(tmp) { | |
| xd->font = tmp; | |
| strcpy(xd->fontfamily, family); | |
| xd->fontface = face; | |
| xd->fontsize = size; | |
| } else | |
| error(_("X11 font %s, face %d at size %d could not be loaded"), | |
| family, face, size); | |
| } | |
| } | |
| static void CheckAlpha(int color, pX11Desc xd) | |
| { | |
| unsigned int alpha = R_ALPHA(color); | |
| if (alpha > 0 && alpha < 255 && !xd->warn_trans) { | |
| warning(_("semi-transparency is not supported on this device: reported only once per page")); | |
| xd->warn_trans = TRUE; | |
| } | |
| } | |
| static void SetColor(unsigned int color, pX11Desc xd) | |
| { | |
| if (color != xd->col) { | |
| int col = GetX11Pixel(R_RED(color), R_GREEN(color), R_BLUE(color)); | |
| xd->col = color; | |
| XSetState(display, xd->wgc, col, whitepixel, GXcopy, AllPlanes); | |
| } | |
| } | |
| static int gcToX11lend(R_GE_lineend lend) { | |
| int newend = CapRound; /* -Wall */ | |
| switch (lend) { | |
| case GE_ROUND_CAP: | |
| newend = CapRound; | |
| break; | |
| case GE_BUTT_CAP: | |
| newend = CapButt; | |
| break; | |
| case GE_SQUARE_CAP: | |
| newend = CapProjecting; | |
| break; | |
| default: | |
| error(_("invalid line end")); | |
| } | |
| return newend; | |
| } | |
| static int gcToX11ljoin(R_GE_linejoin ljoin) { | |
| int newjoin = JoinRound; /* -Wall */ | |
| switch (ljoin) { | |
| case GE_ROUND_JOIN: | |
| newjoin = JoinRound; | |
| break; | |
| case GE_MITRE_JOIN: | |
| newjoin = JoinMiter; | |
| break; | |
| case GE_BEVEL_JOIN: | |
| newjoin = JoinBevel; | |
| break; | |
| default: | |
| error(_("invalid line join")); | |
| } | |
| return newjoin; | |
| } | |
| /* --> See "Notes on Line Textures" in GraphicsEngine.h | |
| * | |
| * 27/5/98 Paul - change to allow lty and lwd to interact: | |
| * the line texture is now scaled by the line width so that, | |
| * for example, a wide (lwd=2) dotted line (lty=2) has bigger | |
| * dots which are more widely spaced. Previously, such a line | |
| * would have "dots" which were wide, but not long, nor widely | |
| * spaced. | |
| */ | |
| /* Not at all clear the optimization here is worth it */ | |
| static void SetLinetype(const pGEcontext gc, pX11Desc xd) | |
| { | |
| int i, newlty, newlend, newljoin; | |
| double newlwd; | |
| newlty = gc->lty; | |
| newlwd = gc->lwd; | |
| if (newlwd < 1)/* not less than 1 pixel */ | |
| newlwd = 1; | |
| if (newlty != xd->lty || newlwd != xd->lwd || | |
| gc->lend != xd->lend || gc->ljoin != xd->ljoin) { | |
| xd->lty = newlty; | |
| xd->lwd = newlwd; | |
| xd->lend = gc->lend; | |
| xd->ljoin = gc->ljoin; | |
| newlend = gcToX11lend(gc->lend); | |
| newljoin = gcToX11ljoin(gc->ljoin); | |
| if (newlty == 0 || newlty == NA_INTEGER) {/* special hack for lty = 0 -- only for X11 */ | |
| XSetLineAttributes(display, xd->wgc, | |
| (int)(newlwd*xd->lwdscale+0.5), | |
| LineSolid, newlend, newljoin); | |
| } else { | |
| static char dashlist[8]; | |
| for(i = 0 ; i < 8 && (newlty != 0); i++) { | |
| int j = newlty & 15; | |
| if (j == 0) j = 1; /* Or we die with an X Error */ | |
| /* scale line texture for line width */ | |
| j = (int)(j*newlwd*xd->lwdscale+0.5); | |
| /* make sure that scaled line texture */ | |
| /* does not exceed X11 storage limits */ | |
| if (j > 255) j = 255; | |
| dashlist[i] = (char) j; | |
| newlty >>= 4; | |
| } | |
| /* NB if i is odd the pattern will be interpreted as | |
| the original pattern concatenated with itself */ | |
| XSetDashes(display, xd->wgc, 0, dashlist, i); | |
| XSetLineAttributes(display, xd->wgc, | |
| (int)(newlwd*xd->lwdscale+0.5), | |
| LineOnOffDash, newlend, newljoin); | |
| } | |
| } | |
| } | |
| /* Error handling. FIXME: This is rather sloppy; we ought to respect | |
| any 3rd party handlers by checking whether dsp is "our" display and | |
| calling the previous handler otherwise. */ | |
| static int R_X11Err(Display *dsp, XErrorEvent *event) | |
| { | |
| char buff[1000]; | |
| /* for tcl/tk */ | |
| if (event->error_code == BadWindow) return 0; | |
| XGetErrorText(dsp, event->error_code, buff, 1000); | |
| warning(_("X11 protocol error: %s"), buff); | |
| return 0; | |
| } | |
| static int NORET R_X11IOErrSimple(Display *dsp) | |
| { | |
| char *dn = XDisplayName(dspname); | |
| strcpy(dspname, ""); | |
| error(_("X11 I/O error while opening X11 connection to '%s'"), dn); | |
| } | |
| static int NORET R_X11IOErr(Display *dsp) | |
| { | |
| int fd = ConnectionNumber(display); | |
| /* | |
| while (nfonts--) R_XFreeFont(display, fontcache[nfonts].font); | |
| nfonts = 0; | |
| */ | |
| removeInputHandler(&R_InputHandlers, | |
| getInputHandler(R_InputHandlers,fd)); | |
| /* | |
| XCloseDisplay(display); | |
| displayOpen = FALSE; | |
| strcpy(dspname, ""); | |
| */ | |
| error(_("X11 fatal IO error: please save work and shut down R")); | |
| } | |
| #define USE_Xt 1 | |
| #ifdef USE_Xt | |
| #include <X11/StringDefs.h> | |
| #include <X11/Shell.h> | |
| typedef struct gx_device_X_s { | |
| Pixel background, foreground, borderColor; | |
| Dimension borderWidth; | |
| String geometry; | |
| } gx_device_X; | |
| /* (String) casts are here to suppress warnings about discarding `const' */ | |
| #define RINIT(a,b,t,s,o,it,n)\ | |
| {(String)(a), (String)(b), (String)t, sizeof(s),\ | |
| XtOffsetOf(gx_device_X, o), (String)it, (n)} | |
| #define rpix(a,b,o,n)\ | |
| RINIT(a,b,XtRPixel,Pixel,o,XtRString,(XtPointer)(n)) | |
| #define rdim(a,b,o,n)\ | |
| RINIT(a,b,XtRDimension,Dimension,o,XtRImmediate,(XtPointer)(n)) | |
| #define rstr(a,b,o,n)\ | |
| RINIT(a,b,XtRString,String,o,XtRString,(char*)(n)) | |
| static XtResource x_resources[] = { | |
| rpix(XtNbackground, XtCBackground, background, "XtDefaultBackground"), | |
| rstr(XtNgeometry, XtCGeometry, geometry, NULL), | |
| }; | |
| static const int x_resource_count = XtNumber(x_resources); | |
| static String x_fallback_resources[] = { | |
| (String) "R_x11*Background: white", | |
| NULL | |
| }; | |
| #endif | |
| Rboolean | |
| X11_Open(pDevDesc dd, pX11Desc xd, const char *dsp, | |
| double w, double h, double gamma_fac, X_COLORTYPE colormodel, | |
| int maxcube, int bgcolor, int canvascolor, int res, | |
| int xpos, int ypos) | |
| { | |
| /* if we have to bail out with "error", then must free(dd) and free(xd) */ | |
| /* That means the *caller*: the X11DeviceDriver code frees xd, for example */ | |
| XEvent event; | |
| int iw, ih, blackpixel; | |
| X_GTYPE type; | |
| const char *p = dsp; | |
| XGCValues gcv; | |
| XSizeHints *hint; | |
| if (!XSupportsLocale ()) | |
| warning(_("locale not supported by Xlib: some X ops will operate in C locale")); | |
| if (!XSetLocaleModifiers ("")) warning(_("X cannot set locale modifiers")); | |
| if (!strncmp(dsp, "png::", 5)) { | |
| #ifndef HAVE_PNG | |
| warning(_("no png support in this version of R")); | |
| return FALSE; | |
| #else | |
| char buf[PATH_MAX]; /* allow for pageno formats */ | |
| FILE *fp; | |
| if(strlen(dsp+5) >= PATH_MAX) | |
| error(_("filename too long in png() call")); | |
| strcpy(xd->filename, dsp+5); | |
| snprintf(buf, PATH_MAX, dsp+5, 1); /* page 1 to start */ | |
| if (!(fp = R_fopen(R_ExpandFileName(buf), "w"))) { | |
| warning(_("could not open PNG file '%s'"), buf); | |
| return FALSE; | |
| } | |
| xd->fp = fp; | |
| type = PNG; | |
| p = ""; | |
| xd->res_dpi = res; /* place holder */ | |
| dd->displayListOn = FALSE; | |
| #endif | |
| } | |
| else if (!strncmp(dsp, "jpeg::", 6)) { | |
| #ifndef HAVE_JPEG | |
| warning(_("no jpeg support in this version of R")); | |
| return FALSE; | |
| #else | |
| char buf[PATH_MAX]; /* allow for pageno formats */ | |
| char tmp[PATH_MAX], *pp; | |
| FILE *fp; | |
| strcpy(tmp, dsp+6); | |
| pp = strchr(tmp, ':'); *pp='\0'; | |
| xd->quality = atoi(dsp+6); | |
| if(strlen(pp+1) >= PATH_MAX) | |
| error(_("filename too long in jpeg() call")); | |
| strcpy(xd->filename, pp+1); | |
| snprintf(buf, PATH_MAX, pp+1, 1); /* page 1 to start */ | |
| if (!(fp = R_fopen(R_ExpandFileName(buf), "w"))) { | |
| warning(_("could not open JPEG file '%s'"), buf); | |
| return FALSE; | |
| } | |
| xd->fp = fp; | |
| type = JPEG; | |
| p = ""; | |
| xd->res_dpi = res; /* place holder */ | |
| dd->displayListOn = FALSE; | |
| #endif | |
| } | |
| else if (!strncmp(dsp, "tiff::", 5)) { | |
| #ifndef HAVE_TIFF | |
| warning(_("no tiff support in this version of R")); | |
| return FALSE; | |
| #else | |
| char tmp[PATH_MAX], *pp; | |
| strcpy(tmp, dsp+6); | |
| pp = strchr(tmp, ':'); *pp='\0'; | |
| xd->quality = atoi(dsp+6); | |
| if(strlen(pp+1) >= PATH_MAX) | |
| error(_("filename too long in tiff() call")); | |
| strcpy(xd->filename, pp+1); | |
| xd->fp = NULL; | |
| type = TIFF; | |
| p = ""; | |
| xd->res_dpi = res; /* place holder */ | |
| dd->displayListOn = FALSE; | |
| #endif | |
| } else if (!strncmp(dsp, "bmp::", 5)) { | |
| char buf[PATH_MAX]; /* allow for pageno formats */ | |
| FILE *fp; | |
| if(strlen(dsp+5) >= PATH_MAX) | |
| error(_("filename too long in bmp() call")); | |
| strcpy(xd->filename, dsp+5); | |
| snprintf(buf, PATH_MAX, dsp+5, 1); /* page 1 to start */ | |
| if (!(fp = R_fopen(R_ExpandFileName(buf), "w"))) { | |
| warning(_("could not open BMP file '%s'"), buf); | |
| return FALSE; | |
| } | |
| xd->fp = fp; | |
| type = BMP; | |
| p = ""; | |
| xd->res_dpi = res; /* place holder */ | |
| dd->displayListOn = FALSE; | |
| } else if (!strcmp(dsp, "XImage")) { | |
| type = XIMAGE; | |
| xd->fp = NULL; | |
| p = ""; | |
| } | |
| else type = WINDOW; | |
| xd->type = type; | |
| /* If there is no server connection, establish one and */ | |
| /* initialize the X11 device driver data structures. */ | |
| if (!displayOpen) { | |
| /* Bill Dunlap sees an error when tunneling to a non-existent | |
| X11 connection that BDR cannot reproduce. We leave a handler set | |
| if we get an error, but that is rare. | |
| */ | |
| X11IOhandler old; | |
| strncpy(dspname, p, 101); | |
| dspname[100] = '\0'; | |
| old = XSetIOErrorHandler(R_X11IOErrSimple); | |
| if ((display = XOpenDisplay(p)) == NULL) { | |
| XSetIOErrorHandler(old); | |
| warning(_("unable to open connection to X11 display '%s'"), p); | |
| return FALSE; | |
| } | |
| XSetIOErrorHandler(old); | |
| Rf_setX11Display(display, gamma_fac, colormodel, maxcube, TRUE); | |
| displayOpen = TRUE; | |
| if(xd->handleOwnEvents == FALSE) | |
| addInputHandler(R_InputHandlers, ConnectionNumber(display), | |
| R_ProcessX11Events, XActivity); | |
| } else if(strcmp(p, dspname)) | |
| warning(_("ignoring 'display' argument as an X11 device is already open")); | |
| whitepixel = GetX11Pixel(R_RED(canvascolor), R_GREEN(canvascolor), | |
| R_BLUE(canvascolor)); | |
| blackpixel = GetX11Pixel(0, 0, 0); | |
| #ifdef HAVE_WORKING_CAIRO | |
| if(xd->useCairo && Vclass != TrueColor) { | |
| warning(_("cairo-based types may only work correctly on TrueColor visuals")); | |
| } | |
| #endif | |
| /* Foreground and Background Colors */ | |
| xd->fill = bgcolor; /* was transparent */ | |
| xd->col = R_RGB(0, 0, 0); | |
| xd->canvas = canvascolor; | |
| if(type == JPEG && !R_OPAQUE(xd->canvas)) { | |
| warning(_("jpeg() does not support transparency: using white bg")); | |
| xd->canvas = 0xffffff; | |
| } | |
| if(type > WINDOW) xd->fill = xd->canvas; | |
| /* Try to create a simple window. */ | |
| /* We want to know about exposures */ | |
| /* and window-resizes and locations. */ | |
| /* | |
| * <MBCS-FIXED>: R on gnome window manager task-bar button see? | |
| * I try it. | |
| * A button such as, maximization disappears | |
| * unless I give Hint for clear statement in | |
| * gnome window manager. | |
| */ | |
| memset(&attributes, 0, sizeof(attributes)); | |
| attributes.background_pixel = whitepixel; | |
| attributes.border_pixel = blackpixel; | |
| attributes.backing_store = NotUseful; | |
| attributes.event_mask = ButtonPressMask | |
| | PointerMotionMask | |
| | PointerMotionHintMask | |
| | ButtonReleaseMask | |
| | ExposureMask | |
| | StructureNotifyMask | |
| | KeyPressMask; | |
| if (type == WINDOW) { | |
| int alreadyCreated = (xd->window != (Window)NULL); | |
| if(alreadyCreated == 0) { | |
| xd->windowWidth = iw = (int)((ISNA(w)?7:w)/pixelWidth()); | |
| xd->windowHeight = ih = (int)((ISNA(h)?7:h)/pixelHeight()); | |
| hint = XAllocSizeHints(); | |
| if(xpos == NA_INTEGER) | |
| hint->x = numX11Devices*20 % | |
| ( DisplayWidth(display, screen) - iw - 10 ); | |
| else hint->x = (xpos >= 0) ? xpos : | |
| DisplayWidth(display, screen) - iw + xpos; | |
| if(ypos == NA_INTEGER) | |
| hint->y = numX11Devices*20 % | |
| ( DisplayHeight(display, screen) + ih - 10 ); | |
| else hint->y = (ypos >= 0)? ypos : | |
| DisplayHeight(display, screen) - iw - ypos; | |
| hint->width = iw; | |
| hint->height = ih; | |
| hint->flags = PPosition | PSize; | |
| #ifdef USE_Xt | |
| { | |
| XtAppContext app_con; | |
| Widget toplevel; | |
| Display *xtdpy; | |
| int zero = 0; | |
| gx_device_X xdev; | |
| XtToolkitInitialize(); | |
| app_con = XtCreateApplicationContext(); | |
| XtAppSetFallbackResources(app_con, x_fallback_resources); | |
| xtdpy = XtOpenDisplay(app_con, dspname, "r_x11", "R_x11", | |
| NULL, 0, &zero, NULL); | |
| if(xtdpy) { | |
| toplevel = XtAppCreateShell(NULL, "R_x11", | |
| applicationShellWidgetClass, | |
| xtdpy, NULL, 0); | |
| XtGetApplicationResources(toplevel, (XtPointer) &xdev, | |
| x_resources, | |
| x_resource_count, | |
| NULL, 0); | |
| if (xdev.geometry != NULL) { | |
| char gstr[40]; | |
| int bitmask; | |
| sprintf(gstr, "%dx%d+%d+%d", hint->width, | |
| hint->height, hint->x, hint->y); | |
| bitmask = XWMGeometry(display, DefaultScreen(display), | |
| xdev.geometry, gstr, | |
| 1, | |
| hint, | |
| &hint->x, &hint->y, | |
| &hint->width, &hint->height, | |
| &hint->win_gravity); | |
| if (bitmask & (XValue | YValue)) | |
| hint->flags |= USPosition; | |
| if (bitmask & (WidthValue | HeightValue)) | |
| hint->flags |= USSize; | |
| /* Restore user-specified settings */ | |
| if(xpos != NA_INTEGER) | |
| hint->x = (xpos >= 0) ? xpos : | |
| DisplayWidth(display, screen) - iw + xpos; | |
| if(ypos != NA_INTEGER) | |
| hint->y = (ypos >= 0)? ypos : | |
| DisplayHeight(display, screen) - iw - ypos; | |
| if(!ISNA(w)) hint->width = iw; | |
| if(!ISNA(h)) hint->height = ih; | |
| } | |
| XtDestroyWidget(toplevel); | |
| XtCloseDisplay(xtdpy); | |
| } else { | |
| warning(_("unable to obtain information on display '%s'"), | |
| dsp); | |
| } | |
| XtDestroyApplicationContext(app_con); | |
| } | |
| #endif | |
| xd->windowWidth = hint->width; | |
| xd->windowHeight = hint->height; | |
| /*printf("x = %d, y = %d\n", hint->x, hint->y);*/ | |
| xd->window = XCreateSimpleWindow(display, | |
| rootwin, | |
| hint->x,hint->y, | |
| hint->width, hint->height, | |
| 1, | |
| blackpixel, | |
| whitepixel); | |
| if (xd->window == 0 ) { | |
| XFree(hint); | |
| warning(_("unable to create X11 window")); | |
| return FALSE; | |
| } | |
| XSetWMNormalHints(display, xd->window, hint); | |
| XFree(hint); | |
| XChangeWindowAttributes(display, xd->window, | |
| CWEventMask | CWBackPixel | | |
| CWBorderPixel | CWBackingStore, | |
| &attributes); | |
| XStoreName(display, xd->window, xd->title); | |
| /* See (PR#14588) */ | |
| XClassHint *chint; | |
| chint = XAllocClassHint(); | |
| if (chint) { | |
| chint->res_name = "r_x11"; | |
| chint->res_class = "R_x11"; | |
| XSetClassHint(display, xd->window, chint); | |
| XFree(chint); | |
| } | |
| /* set window icon */ | |
| XChangeProperty(display, xd->window, | |
| XInternAtom(display, "_NET_WM_ICON", False), | |
| XInternAtom(display, "CARDINAL", False), 32, | |
| PropModeReplace, | |
| (const unsigned char*) rlogo_icon, 2 + 99*77); | |
| /* set up protocols so that window manager sends */ | |
| /* me an event when user "destroys" window */ | |
| _XA_WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0); | |
| protocol = XInternAtom(display, "WM_DELETE_WINDOW", 0); | |
| XSetWMProtocols(display, xd->window, &protocol, 1); | |
| if(!arrow_cursor) | |
| arrow_cursor = XCreateFontCursor(display, XC_left_ptr) ; | |
| if(!cross_cursor) | |
| cross_cursor = XCreateFontCursor(display, XC_crosshair); | |
| if(!watch_cursor) | |
| watch_cursor = XCreateFontCursor(display, XC_watch) ; | |
| if(xd->type==WINDOW) XDefineCursor(display, xd->window, arrow_cursor); | |
| #ifdef HAVE_WORKING_CAIRO | |
| if(xd->useCairo) { | |
| cairo_status_t res; | |
| if(xd->buffered) { | |
| xd->xcs = | |
| cairo_xlib_surface_create(display, xd->window, | |
| visual, | |
| xd->windowWidth, | |
| xd->windowHeight); | |
| res = cairo_surface_status(xd->xcs); | |
| if (res != CAIRO_STATUS_SUCCESS) { | |
| warning("cairo error '%s'", | |
| cairo_status_to_string(res)); | |
| /* bail out */ | |
| return FALSE; | |
| } | |
| xd->xcc = cairo_create(xd->xcs); | |
| res = cairo_status(xd->xcc); | |
| if (res != CAIRO_STATUS_SUCCESS) { | |
| warning("cairo error '%s'", | |
| cairo_status_to_string(res)); | |
| cairo_surface_destroy(xd->xcs); | |
| /* bail out */ | |
| return FALSE; | |
| } | |
| xd->cs = | |
| cairo_image_surface_create(CAIRO_FORMAT_RGB24, | |
| xd->windowWidth, | |
| xd->windowHeight); | |
| cairo_set_source_surface (xd->xcc, xd->cs, 0, 0); | |
| if(xd->buffered > 1) addBuffering(xd); | |
| } else /* non-buffered */ | |
| xd->cs = | |
| cairo_xlib_surface_create(display, xd->window, | |
| visual, | |
| xd->windowWidth, | |
| xd->windowHeight); | |
| res = cairo_surface_status(xd->cs); | |
| if (res != CAIRO_STATUS_SUCCESS) { | |
| warning("cairo error '%s'", cairo_status_to_string(res)); | |
| /* bail out */ | |
| if(xd->xcs) cairo_surface_destroy(xd->xcs); | |
| if(xd->xcc) cairo_destroy(xd->xcc); | |
| return FALSE; | |
| } | |
| xd->cc = cairo_create(xd->cs); | |
| res = cairo_status(xd->cc); | |
| if (res != CAIRO_STATUS_SUCCESS) { | |
| warning("cairo error '%s'", cairo_status_to_string(res)); | |
| cairo_surface_destroy(xd->cs); | |
| /* bail out */ | |
| if(xd->xcs) cairo_surface_destroy(xd->xcs); | |
| if(xd->xcc) cairo_destroy(xd->xcc); | |
| return FALSE; | |
| } | |
| cairo_set_operator(xd->cc, CAIRO_OPERATOR_OVER); | |
| cairo_set_antialias(xd->cc, xd->antialias); | |
| CairoColor(xd->canvas, xd); | |
| cairo_new_path(xd->cc); | |
| cairo_paint(xd->cc); | |
| } | |
| #endif | |
| } | |
| /* Save the pDevDesc with the window for event dispatching */ | |
| XSaveContext(display, xd->window, devPtrContext, (caddr_t) dd); | |
| /* Map the window */ | |
| if(alreadyCreated == 0) { | |
| XSelectInput(display, xd->window, | |
| ExposureMask | ButtonPressMask | StructureNotifyMask | |
| | ButtonReleaseMask | PointerMotionMask | |
| | PointerMotionHintMask | KeyPressMask); | |
| XMapWindow(display, xd->window); | |
| XSync(display, 0); | |
| /* Gobble MapNotify events */ | |
| while ( XPeekEvent(display, &event), | |
| !XCheckTypedEvent(display, MapNotify, &event)) | |
| ; | |
| /* XNextEvent(display, &event); | |
| if (event.xany.type == Expose) { | |
| while (event.xexpose.count) | |
| XNextEvent(display, &event); | |
| } | |
| */ | |
| } | |
| } else { /* PIXMAP */ | |
| xd->windowWidth = iw = (int) w; | |
| xd->windowHeight = ih = (int) h; | |
| if (iw < 20 && ih < 20) | |
| warning(_("'width=%d, height=%d' are unlikely values in pixels"), | |
| iw, ih); | |
| if ((xd->window = XCreatePixmap( | |
| display, rootwin, | |
| iw, ih, DefaultDepth(display, screen))) == 0) { | |
| warning(_("unable to create pixmap")); | |
| return FALSE; | |
| } | |
| /* Save the pDevDesc with the window for event dispatching */ | |
| /* Is this needed? */ | |
| XSaveContext(display, xd->window, devPtrContext, (caddr_t) dd); | |
| xd->npages = 0; | |
| } | |
| /* Set the graphics context */ | |
| gcv.arc_mode = ArcChord; | |
| xd->wgc = XCreateGC(display, xd->window, GCArcMode, &gcv); | |
| XSetState(display, xd->wgc, blackpixel, whitepixel, GXcopy, AllPlanes); | |
| /* ensure that line drawing is set up at the first graphics call */ | |
| xd->lty = -1; | |
| xd->lwd = -1; | |
| xd->lend = 0; | |
| xd->ljoin = 0; | |
| numX11Devices++; | |
| return TRUE; | |
| } | |
| /* Return a non-relocatable copy of a string */ | |
| static char *SaveFontSpec(SEXP sxp, int offset) | |
| { | |
| char *s; | |
| if(!isString(sxp) || length(sxp) <= offset) | |
| error(_("invalid font specification")); | |
| s = R_alloc(strlen(CHAR(STRING_ELT(sxp, offset)))+1, sizeof(char)); | |
| strcpy(s, CHAR(STRING_ELT(sxp, offset))); | |
| return s; | |
| } | |
| /* | |
| * Take the fontfamily from a gcontext (which is device-independent) | |
| * and convert it into an X11-specific font description using | |
| * the X11 font database (see src/library/graphics/R/unix/x11.R) | |
| * | |
| * IF gcontext fontfamily is empty ("") | |
| * OR IF can't find gcontext fontfamily in font database | |
| * THEN return xd->basefontfamily (the family set up when the | |
| * device was created) | |
| */ | |
| static char* translateFontFamily(char* family, pX11Desc xd) | |
| { | |
| SEXP graphicsNS, x11env, fontdb, fontnames; | |
| int i, nfonts; | |
| char* result = xd->basefontfamily; | |
| PROTECT_INDEX xpi; | |
| PROTECT(graphicsNS = R_FindNamespace(ScalarString(mkChar("grDevices")))); | |
| PROTECT_WITH_INDEX(x11env = findVar(install(".X11env"), graphicsNS), &xpi); | |
| if(TYPEOF(x11env) == PROMSXP) | |
| REPROTECT(x11env = eval(x11env, graphicsNS), xpi); | |
| PROTECT(fontdb = findVar(install(".X11.Fonts"), x11env)); | |
| PROTECT(fontnames = getAttrib(fontdb, R_NamesSymbol)); | |
| nfonts = LENGTH(fontdb); | |
| if (family[0]) { | |
| Rboolean found = FALSE; | |
| for (i = 0; i < nfonts && !found; i++) { | |
| const char* fontFamily = CHAR(STRING_ELT(fontnames, i)); | |
| if (strcmp(family, fontFamily) == 0) { | |
| found = TRUE; | |
| result = SaveFontSpec(VECTOR_ELT(fontdb, i), 0); | |
| } | |
| } | |
| if (!found) | |
| warning(_("font family not found in X11 font database")); | |
| } | |
| UNPROTECT(4); | |
| return result; | |
| } | |
| static double X11_StrWidth(const char *str, const pGEcontext gc, pDevDesc dd) | |
| { | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| SetFont(gc, xd); | |
| if (xd->font->type == One_Font) | |
| return (double) XTextWidth(xd->font->font, str, (int)strlen(str)); | |
| else { | |
| #ifdef HAVE_XUTF8TEXTESCAPEMENT | |
| if(utf8locale) | |
| return (double) Xutf8TextEscapement(xd->font->fontset, | |
| str, (int)strlen(str)); | |
| else | |
| #endif | |
| return (double) XmbTextEscapement(xd->font->fontset, | |
| str, (int)strlen(str)); | |
| } | |
| } | |
| /* Character Metric Information */ | |
| /* Passing c == 0 gets font information */ | |
| static void X11_MetricInfo(int c, const pGEcontext gc, | |
| double* ascent, double* descent, | |
| double* width, pDevDesc dd) | |
| { | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| int first = 0, last = 0; | |
| XFontStruct *f = NULL; | |
| if (c < 0) | |
| error(_("invalid use of %d < 0 in '%s'"), c, "X11_MetricInfo"); | |
| SetFont(gc, xd); | |
| *ascent = 0; *descent = 0; *width = 0; /* fallback position */ | |
| if (xd->font) { | |
| if (xd->font->type != One_Font) { | |
| char **ml; XFontStruct **fs_list; | |
| #ifdef DEBUG_X11 | |
| int i, cnt = XFontsOfFontSet(xd->font->fontset, &fs_list, &ml); | |
| for (i = 0; i < cnt; i++) printf("%s\n", ml[i]); | |
| printf("--- end of fontlist ---\n\n"); | |
| #else | |
| XFontsOfFontSet(xd->font->fontset, &fs_list, &ml); | |
| #endif | |
| f = fs_list[0]; | |
| } else f = xd->font->font; | |
| first = f->min_char_or_byte2; | |
| last = f->max_char_or_byte2; | |
| } else return; | |
| if (c == 0) { | |
| *ascent = f->ascent; | |
| *descent = f->descent; | |
| *width = f->max_bounds.width; | |
| return; | |
| } | |
| if (xd->font->type != One_Font) { /* so an MBCS */ | |
| XRectangle ink, log; | |
| char buf[16]; | |
| ucstomb(buf, (unsigned int) c); | |
| #ifdef HAVE_XUTF8TEXTEXTENTS | |
| if(utf8locale) | |
| Xutf8TextExtents(xd->font->fontset, buf, (int)strlen(buf), &ink, &log); | |
| else | |
| #endif | |
| XmbTextExtents(xd->font->fontset, buf, (int)strlen(buf), &ink, &log); | |
| /* Rprintf("%d %d %d %d\n", ink.x, ink.y, ink.width, ink.height); | |
| Rprintf("%d %d %d %d\n", log.x, log.y, log.width, log.height); */ | |
| *ascent = -ink.y; | |
| *descent = ink.y + ink.height; | |
| /* <FIXME> why logical and not ink width? */ | |
| *width = log.width; | |
| /* Rprintf("%d %lc w=%f a=%f d=%f\n", c, wc[0], | |
| *width, *ascent, *descent);*/ | |
| } else { /* symbol font */ | |
| if(first <= c && c <= last) { | |
| /* | |
| * <MBCS-FIXED>: try demo(lm.glm,package="stats") | |
| * per_char is NULL case. | |
| */ | |
| if(f->per_char) { | |
| *ascent = f->per_char[c-first].ascent; | |
| *descent = f->per_char[c-first].descent; | |
| *width = f->per_char[c-first].width; | |
| } else { | |
| *ascent = f->max_bounds.ascent; | |
| *descent = f->max_bounds.descent; | |
| *width = f->max_bounds.width; | |
| } | |
| } | |
| } | |
| } | |
| static void X11_Clip(double x0, double x1, double y0, double y1, | |
| pDevDesc dd) | |
| { | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| if (x0 < x1) { | |
| xd->clip.x = (unsigned short) x0 ; | |
| xd->clip.width = (unsigned short) x1 - (unsigned short) x0 + 1; | |
| } | |
| else { | |
| xd->clip.x = (unsigned short) x1; | |
| xd->clip.width = (unsigned short) x0 - (unsigned short) x1 + 1; | |
| } | |
| if (y0 < y1) { | |
| xd->clip.y = (unsigned short) y0; | |
| xd->clip.height = (unsigned short) y1 - (unsigned short) y0 + 1; | |
| } | |
| else { | |
| xd->clip.y = (unsigned short) y1; | |
| xd->clip.height = (unsigned short) y0 - (unsigned short) y1 + 1; | |
| } | |
| XSetClipRectangles(display, xd->wgc, 0, 0, &(xd->clip), 1, Unsorted); | |
| } | |
| static void X11_Size(double *left, double *right, | |
| double *bottom, double *top, | |
| pDevDesc dd) | |
| { | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| *left = 0.0; | |
| *right = xd->windowWidth; | |
| *bottom = xd->windowHeight; | |
| *top = 0.0; | |
| } | |
| static void X11_NewPage(const pGEcontext gc, pDevDesc dd) | |
| { | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| xd->warn_trans = FALSE; | |
| if (xd->type > WINDOW) { | |
| if (xd->npages++) { | |
| /* try to preserve the page we do have */ | |
| if (xd->type != XIMAGE) X11_Close_bitmap(xd); | |
| if (xd->type != XIMAGE && xd->fp != NULL) fclose(xd->fp); | |
| if (xd->type == PNG || xd->type == JPEG || xd->type == BMP) { | |
| char buf[PATH_MAX]; | |
| snprintf(buf, PATH_MAX, xd->filename, xd->npages); | |
| xd->fp = R_fopen(R_ExpandFileName(buf), "w"); | |
| if (!xd->fp) | |
| error(_("could not open file '%s'"), buf); | |
| } | |
| } | |
| CheckAlpha(gc->fill, xd); | |
| xd->fill = R_OPAQUE(gc->fill) ? gc->fill: PNG_TRANS; | |
| SetColor(xd->fill, xd); | |
| xd->clip.x = 0; xd->clip.width = (unsigned short)xd->windowWidth; | |
| xd->clip.y = 0; xd->clip.height = (unsigned short)xd->windowHeight; | |
| XSetClipRectangles(display, xd->wgc, 0, 0, &(xd->clip), 1, Unsorted); | |
| XFillRectangle(display, xd->window, xd->wgc, 0, 0, | |
| xd->windowWidth, xd->windowHeight); | |
| return; | |
| } | |
| FreeX11Colors(); | |
| if ( (model == PSEUDOCOLOR2) || (xd->fill != gc->fill)) { | |
| xd->fill = R_OPAQUE(gc->fill) ? gc->fill : xd->canvas; | |
| whitepixel = GetX11Pixel(R_RED(xd->fill),R_GREEN(xd->fill),R_BLUE(xd->fill)); | |
| XSetWindowBackground(display, xd->window, whitepixel); | |
| } | |
| XClearWindow(display, xd->window); | |
| XSync(display, 0); | |
| } | |
| #include "bitmap.h" | |
| static int knowncols[512]; | |
| static unsigned int bitgp(void *xi, int x, int y) | |
| { | |
| int i, r, g, b; | |
| XColor xcol; | |
| /* returns the colour of the (x,y) pixel stored as RGB */ | |
| i = (int) XGetPixel((XImage *) xi, y, x); | |
| switch(model) { | |
| case MONOCHROME: | |
| return i == 0 ? 0xFFFFFFFF : 0; | |
| case GRAYSCALE: | |
| case PSEUDOCOLOR1: | |
| case PSEUDOCOLOR2: | |
| if (i < 512) { | |
| if (knowncols[i] < 0) { | |
| xcol.pixel = i; | |
| XQueryColor(display, colormap, &xcol); | |
| knowncols[i] = ((xcol.red>>8)<<16) | ((xcol.green>>8)<<8) | |
| | (xcol.blue>>8); | |
| } | |
| return knowncols[i] | 0xFF000000; | |
| } else { | |
| xcol.pixel = i; | |
| XQueryColor(display, colormap, &xcol); | |
| return ((xcol.red>>8)<<16) | ((xcol.green>>8)<<8) | (xcol.blue>>8); | |
| } | |
| case TRUECOLOR: | |
| r = ((i>>RShift)&RMask) * 255 /(RMask); | |
| g = ((i>>GShift)&GMask) * 255 /(GMask); | |
| b = ((i>>BShift)&BMask) * 255 /(BMask); | |
| return (r<<16) | (g<<8) | b | 0xFF000000; | |
| default: | |
| return 0; | |
| } | |
| /* return 0; not reached, needed for some compilers */ | |
| } | |
| static void X11_Close_bitmap(pX11Desc xd) | |
| { | |
| int i; | |
| XImage *xi; | |
| for (i = 0; i < 512; i++) knowncols[i] = -1; | |
| xi = XGetImage(display, xd->window, 0, 0, | |
| xd->windowWidth, xd->windowHeight, | |
| AllPlanes, ZPixmap); | |
| if (xd->type == PNG) { | |
| unsigned int pngtrans = PNG_TRANS; | |
| if(model == TRUECOLOR) { | |
| int i, r, g, b; | |
| /* some 'truecolor' displays distort colours */ | |
| i = GetX11Pixel(R_RED(PNG_TRANS), | |
| R_GREEN(PNG_TRANS), | |
| R_BLUE(PNG_TRANS)); | |
| r = ((i>>RShift)&RMask) * 255 /(RMask); | |
| g = ((i>>GShift)&GMask) * 255 /(GMask); | |
| b = ((i>>BShift)&BMask) * 255 /(BMask); | |
| pngtrans = (r<<16) | (g<<8) | b | 0xFF000000; | |
| } | |
| R_SaveAsPng(xi, xd->windowWidth, xd->windowHeight, | |
| bitgp, 0, xd->fp, | |
| (xd->fill != PNG_TRANS) ? 0 : pngtrans, xd->res_dpi); | |
| } else if (xd->type == JPEG) | |
| R_SaveAsJpeg(xi, xd->windowWidth, xd->windowHeight, | |
| bitgp, 0, xd->quality, xd->fp, xd->res_dpi); | |
| else if (xd->type == BMP) | |
| R_SaveAsBmp(xi, xd->windowWidth, xd->windowHeight, | |
| bitgp, 0, xd->fp, xd->res_dpi); | |
| else if (xd->type == TIFF) { | |
| char buf[PATH_MAX]; | |
| snprintf(buf, PATH_MAX, xd->filename, xd->npages); | |
| R_SaveAsTIFF(xi, xd->windowWidth, xd->windowHeight, | |
| bitgp, 0, R_ExpandFileName(buf), xd->res_dpi, | |
| xd->quality); | |
| } | |
| XDestroyImage(xi); | |
| } | |
| static void X11_Close(pDevDesc dd) | |
| { | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| if (xd->type == WINDOW) { | |
| #ifdef HAVE_WORKING_CAIRO | |
| if(xd->buffered > 1) removeBuffering(xd); | |
| #endif | |
| /* process pending events */ | |
| /* set block on destroy events */ | |
| inclose = TRUE; | |
| R_ProcessX11Events((void*) NULL); | |
| #ifdef HAVE_WORKING_CAIRO | |
| if(xd->useCairo) { | |
| if(xd->cs) cairo_surface_destroy(xd->cs); | |
| if(xd->cc) cairo_destroy(xd->cc); | |
| if(xd->xcs) cairo_surface_destroy(xd->xcs); | |
| if(xd->xcc) cairo_destroy(xd->xcc); | |
| } | |
| #endif | |
| XFreeGC(display, xd->wgc); | |
| XDestroyWindow(display, xd->window); | |
| XSync(display, 0); | |
| } else { | |
| if (xd->npages && xd->type != XIMAGE) X11_Close_bitmap(xd); | |
| XFreeGC(display, xd->wgc); | |
| XFreePixmap(display, xd->window); | |
| if (xd->type != XIMAGE && xd->fp != NULL) fclose(xd->fp); | |
| } | |
| numX11Devices--; | |
| if (numX11Devices == 0) { | |
| int fd = ConnectionNumber(display); | |
| /* Free Resources Here */ | |
| while (nfonts--) | |
| R_XFreeFont(display, fontcache[nfonts].font); | |
| nfonts = 0; | |
| if(xd->handleOwnEvents == FALSE) | |
| removeInputHandler(&R_InputHandlers, | |
| getInputHandler(R_InputHandlers,fd)); | |
| if(arrow_cursor) XFreeCursor(display, arrow_cursor); | |
| if(cross_cursor) XFreeCursor(display, cross_cursor); | |
| if(watch_cursor) XFreeCursor(display, watch_cursor); | |
| arrow_cursor = cross_cursor = watch_cursor = (Cursor) 0; | |
| XCloseDisplay(display); | |
| displayOpen = FALSE; | |
| } | |
| free(xd); | |
| inclose = FALSE; | |
| } | |
| static void X11_Activate(pDevDesc dd) | |
| { | |
| char t[150]; | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| if (xd->type > WINDOW) return; | |
| if(xd->title[0]) { | |
| snprintf(t, 140, xd->title, ndevNumber(dd) + 1); | |
| t[139] = '\0'; | |
| } else { | |
| sprintf(t, "R Graphics: Device %d", ndevNumber(dd) + 1); | |
| } | |
| strcat(t, " (ACTIVE)"); | |
| XStoreName(display, xd->window, t); | |
| XSync(display, 0); | |
| } | |
| static void X11_Deactivate(pDevDesc dd) | |
| { | |
| char t[150]; | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| if (xd->type > WINDOW) return; | |
| if(xd->title[0]) { | |
| snprintf(t, 140, xd->title, ndevNumber(dd) + 1); | |
| t[139] = '\0'; | |
| } else { | |
| sprintf(t, "R Graphics: Device %d", ndevNumber(dd) + 1); | |
| } | |
| strcat(t, " (inactive)"); | |
| XStoreName(display, xd->window, t); | |
| XSync(display, 0); | |
| } | |
| static void X11_Rect(double x0, double y0, double x1, double y1, | |
| const pGEcontext gc, pDevDesc dd) | |
| { | |
| double tmp; | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| if (x0 > x1) { | |
| tmp = x0; | |
| x0 = x1; | |
| x1 = tmp; | |
| } | |
| if (y0 > y1) { | |
| tmp = y0; | |
| y0 = y1; | |
| y1 = tmp; | |
| } | |
| CheckAlpha(gc->fill, xd); | |
| if (R_OPAQUE(gc->fill)) { | |
| SetColor(gc->fill, xd); | |
| XFillRectangle(display, xd->window, xd->wgc, (int)x0, (int)y0, | |
| (int)x1 - (int)x0, (int)y1 - (int)y0); | |
| } | |
| CheckAlpha(gc->col, xd); | |
| if (R_OPAQUE(gc->col)) { | |
| SetColor(gc->col, xd); | |
| SetLinetype(gc, xd); | |
| XDrawRectangle(display, xd->window, xd->wgc, (int)x0, (int)y0, | |
| (int)x1 - (int)x0, (int)y1 - (int)y0); | |
| } | |
| } | |
| static void X11_Path(double *x, double *y, | |
| int npoly, int *nper, | |
| Rboolean winding, | |
| const pGEcontext gc, pDevDesc dd) | |
| { | |
| warning(_("%s not available for this device"), "Path drawing"); | |
| } | |
| static unsigned int makeX11Pixel(unsigned int * rasterImage, int pixel) { | |
| return GetX11Pixel(R_RED(rasterImage[pixel]), | |
| R_GREEN(rasterImage[pixel]), | |
| R_BLUE(rasterImage[pixel])); | |
| } | |
| static void flipRaster(unsigned int *rasterImage, | |
| int imageWidth, int imageHeight, | |
| int invertX, int invertY, | |
| unsigned int *flippedRaster) { | |
| int i, j; | |
| int rowInc, rowOff, colInc, colOff; | |
| if (invertX) { | |
| colInc = -1; | |
| colOff = imageWidth - 1; | |
| } else { | |
| colInc = 1; | |
| colOff = 0; | |
| } | |
| if (invertY) { | |
| rowInc = -1; | |
| rowOff = imageHeight - 1; | |
| } else { | |
| rowInc = 1; | |
| rowOff = 0; | |
| } | |
| for (i = 0; i < imageHeight ;i++) { | |
| for (j = 0; j < imageWidth; j++) { | |
| int row = (rowInc*i + rowOff); | |
| int col = (colInc*j + colOff); | |
| flippedRaster[i*imageWidth + j] = | |
| rasterImage[row*imageWidth + col]; | |
| } | |
| } | |
| } | |
| static void X11_Raster(unsigned int *raster, int w, int h, | |
| double x, double y, | |
| double width, double height, | |
| double rot, | |
| Rboolean interpolate, | |
| const pGEcontext gc, pDevDesc dd) | |
| { | |
| int i, j, pixel; | |
| int imageWidth; | |
| int imageHeight; | |
| int invertX = 0; | |
| int invertY = 0; | |
| double angle = rot*M_PI/180; | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| XImage *image; | |
| unsigned int *rasterImage; | |
| const void *vmax = vmaxget(); | |
| if (height < 0) { | |
| imageHeight = (int) -(height - .5); | |
| /* convert (x, y) from bottom-left to top-left */ | |
| y = y - imageHeight*cos(angle); | |
| if (angle != 0) { | |
| x = x - imageHeight*sin(angle); | |
| } | |
| } else { | |
| imageHeight = (int) (height + .5); | |
| invertY = 1; | |
| } | |
| if (width < 0) { | |
| imageWidth = (int) -(width - .5); | |
| x = x - imageWidth*cos(angle); | |
| if (angle != 0) | |
| y = y + imageWidth*sin(angle); | |
| invertX = 1; | |
| } else { | |
| imageWidth = (int) (width + .5); | |
| } | |
| rasterImage = (unsigned int *) R_alloc(imageWidth * imageHeight, | |
| sizeof(unsigned int)); | |
| if (interpolate) { | |
| R_GE_rasterInterpolate(raster, w, h, | |
| rasterImage, imageWidth, imageHeight); | |
| } else { | |
| R_GE_rasterScale(raster, w, h, | |
| rasterImage, imageWidth, imageHeight); | |
| } | |
| if (invertX || invertY) { | |
| unsigned int *flippedRaster; | |
| flippedRaster = (unsigned int *) R_alloc(imageWidth * imageHeight, | |
| sizeof(unsigned int)); | |
| flipRaster(rasterImage, imageWidth, imageHeight, | |
| invertX, invertY, flippedRaster); | |
| rasterImage = flippedRaster; | |
| } | |
| if (rot != 0) { | |
| int newW, newH; | |
| double xoff, yoff; | |
| unsigned int *resizedRaster, *rotatedRaster; | |
| R_GE_rasterRotatedSize(imageWidth, imageHeight, angle, &newW, &newH); | |
| R_GE_rasterRotatedOffset(imageWidth, imageHeight, angle, 0, | |
| &xoff, &yoff); | |
| resizedRaster = (unsigned int *) R_alloc(newW * newH, | |
| sizeof(unsigned int)); | |
| R_GE_rasterResizeForRotation(rasterImage, imageWidth, imageHeight, | |
| resizedRaster, newW, newH, gc); | |
| rotatedRaster = (unsigned int *) R_alloc(newW * newH, | |
| sizeof(unsigned int)); | |
| R_GE_rasterRotate(resizedRaster, newW, newH, angle, rotatedRaster, gc, | |
| FALSE); | |
| /* | |
| * Adjust (x, y) for resized and rotated image | |
| */ | |
| x = x - (newW - imageWidth)/2 - xoff; | |
| y = y - (newH - imageHeight)/2 + yoff; | |
| rasterImage = rotatedRaster; | |
| imageWidth = newW; | |
| imageHeight = newH; | |
| } | |
| image = XCreateImage(display, visual, depth, | |
| ZPixmap, | |
| 0, /* offset */ | |
| /* This is just provides (at least enough) | |
| * allocated memory for the image data; | |
| * each pixel is set separately below | |
| */ | |
| (char *) rasterImage, | |
| imageWidth, imageHeight, | |
| depth >= 24 ? 32 : 16, /* bitmap_pad */ | |
| 0); /* bytes_per_line: 0 means auto-calculate*/ | |
| if (image == NULL || XInitImage(image) == 0) | |
| error(_("Unable to create XImage")); | |
| for (i = 0; i < imageHeight ;i++) { | |
| for (j = 0; j < imageWidth; j++) { | |
| pixel = i*imageWidth + j; | |
| XPutPixel(image, j, i, makeX11Pixel(rasterImage, pixel)); | |
| } | |
| } | |
| XPutImage(display, xd->window, xd->wgc, | |
| image, 0, 0, | |
| (int) x, (int) y, imageWidth, imageHeight); | |
| /* XFree() rather than XDestroyImage() because the latter | |
| * tries to free the image 'data' as well | |
| */ | |
| XFree(image); | |
| vmaxset(vmax); | |
| } | |
| static SEXP X11_Cap(pDevDesc dd) | |
| { | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| XImage *image = XGetImage(display, xd->window, 0, 0, | |
| xd->windowWidth, xd->windowHeight, | |
| AllPlanes, ZPixmap); | |
| SEXP raster = R_NilValue; | |
| if (image) { | |
| int i, j; | |
| SEXP dim; | |
| int size = xd->windowWidth * xd->windowHeight; | |
| const void *vmax = vmaxget(); | |
| unsigned int *rint; | |
| PROTECT(raster = allocVector(INTSXP, size)); | |
| /* Copy each byte of screen to an R matrix. | |
| * The ARGB32 needs to be converted to an R ABGR32 */ | |
| rint = (unsigned int *) INTEGER(raster); | |
| for (i = 0; i < xd->windowHeight; i++) { | |
| for (j = 0; j < xd->windowWidth; j++) { | |
| /* | |
| * Convert each pixel in image to an R colour | |
| */ | |
| rint[i*xd->windowWidth + j] = bitgp((void *) image, i, j); | |
| } | |
| } | |
| PROTECT(dim = allocVector(INTSXP, 2)); | |
| INTEGER(dim)[0] = xd->windowHeight; | |
| INTEGER(dim)[1] = xd->windowWidth; | |
| setAttrib(raster, R_DimSymbol, dim); | |
| UNPROTECT(2); | |
| XDestroyImage(image); | |
| vmaxset(vmax); | |
| } | |
| return raster; | |
| } | |
| static void X11_Circle(double x, double y, double r, | |
| const pGEcontext gc, pDevDesc dd) | |
| { | |
| int ir, ix, iy; | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| ir = (int)floor(r + 0.5); | |
| ix = (int)x; | |
| iy = (int)y; | |
| CheckAlpha(gc->fill, xd); | |
| if (R_OPAQUE(gc->fill)) { | |
| SetColor(gc->fill, xd); | |
| XFillArc(display, xd->window, xd->wgc, | |
| ix-ir, iy-ir, 2*ir, 2*ir, 0, 23040); | |
| } | |
| CheckAlpha(gc->col, xd); | |
| if (R_OPAQUE(gc->col)) { | |
| SetLinetype(gc, xd); | |
| SetColor(gc->col, xd); | |
| XDrawArc(display, xd->window, xd->wgc, | |
| ix-ir, iy-ir, 2*ir, 2*ir, 0, 23040); | |
| } | |
| } | |
| static void X11_Line(double x1, double y1, double x2, double y2, | |
| const pGEcontext gc, pDevDesc dd) | |
| { | |
| int xx1, yy1, xx2, yy2; | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| /* In-place conversion ok */ | |
| xx1 = (int) x1; | |
| yy1 = (int) y1; | |
| xx2 = (int) x2; | |
| yy2 = (int) y2; | |
| CheckAlpha(gc->col, xd); | |
| if (R_OPAQUE(gc->col)) { | |
| SetColor(gc->col, xd); | |
| SetLinetype(gc, xd); | |
| XDrawLine(display, xd->window, xd->wgc, xx1, yy1, xx2, yy2); | |
| } | |
| } | |
| static void X11_Polyline(int n, double *x, double *y, | |
| const pGEcontext gc, pDevDesc dd) | |
| { | |
| const void *vmax = vmaxget(); | |
| XPoint *points; | |
| int i, j; | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| points = (XPoint *) R_alloc(n, sizeof(XPoint)); | |
| for(i = 0 ; i < n ; i++) { | |
| points[i].x = (short)(x[i]); | |
| points[i].y = (short)(y[i]); | |
| } | |
| CheckAlpha(gc->col, xd); | |
| if (R_OPAQUE(gc->col)) { | |
| SetColor(gc->col, xd); | |
| SetLinetype(gc, xd); | |
| /* Some X servers need npoints < 64K */ | |
| for(i = 0; i < n; i+= 10000-1) { | |
| j = n - i; | |
| j = (j <= 10000) ? j: 10000; /* allow for overlap */ | |
| XDrawLines(display, xd->window, xd->wgc, points+i, j, | |
| CoordModeOrigin); | |
| } | |
| } | |
| vmaxset(vmax); | |
| } | |
| static void X11_Polygon(int n, double *x, double *y, | |
| const pGEcontext gc, pDevDesc dd) | |
| { | |
| const void *vmax = vmaxget(); | |
| XPoint *points; | |
| int i; | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| points = (XPoint *) R_alloc(n+1, sizeof(XPoint)); | |
| for (i = 0 ; i < n ; i++) { | |
| points[i].x = (short)(x[i]); | |
| points[i].y = (short)(y[i]); | |
| } | |
| points[n].x = (short)(x[0]); | |
| points[n].y = (short)(y[0]); | |
| CheckAlpha(gc->fill, xd); | |
| if (R_OPAQUE(gc->fill)) { | |
| SetColor(gc->fill, xd); | |
| XFillPolygon(display, xd->window, xd->wgc, points, n, | |
| Complex, CoordModeOrigin); | |
| } | |
| CheckAlpha(gc->col, xd); | |
| if (R_OPAQUE(gc->col)) { | |
| SetColor(gc->col, xd); | |
| SetLinetype(gc, xd); | |
| XDrawLines(display, xd->window, xd->wgc, points, n+1, CoordModeOrigin); | |
| } | |
| vmaxset(vmax); | |
| } | |
| static void X11_Text(double x, double y, | |
| const char *str, double rot, double hadj, | |
| const pGEcontext gc, pDevDesc dd) | |
| { | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| SetFont(gc, xd); | |
| CheckAlpha(gc->col, xd); | |
| if (R_OPAQUE(gc->col)) { | |
| SetColor(gc->col, xd); | |
| XRfRotDrawString(display, xd->font, rot, xd->window, | |
| xd->wgc, (int)x, (int)y, str); | |
| } | |
| } | |
| static Rboolean X11_Locator(double *x, double *y, pDevDesc dd) | |
| { | |
| XEvent event; | |
| pDevDesc ddEvent; | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| caddr_t temp; | |
| int done = 0; | |
| if (xd->type > WINDOW) return 0; | |
| #ifdef HAVE_WORKING_CAIRO | |
| if (xd->holdlevel > 0) | |
| error(_("attempt to use the locator after dev.hold()")); | |
| if (xd->buffered) Cairo_update(xd); | |
| #endif | |
| R_ProcessX11Events((void*)NULL); /* discard pending events */ | |
| if(xd->type==WINDOW) XDefineCursor(display, xd->window, cross_cursor); | |
| XSync(display, 1); | |
| /* handle X events as normal until get a button */ | |
| /* click in the desired device */ | |
| while (!done && displayOpen) { | |
| XNextEvent(display, &event); | |
| /* possibly later R_CheckUserInterrupt(); */ | |
| if (event.type == ButtonPress) { | |
| XFindContext(display, event.xbutton.window, | |
| devPtrContext, &temp); | |
| ddEvent = (pDevDesc) temp; | |
| if (ddEvent == dd) { | |
| if (event.xbutton.button == Button1) { | |
| int useBeep = asLogical(GetOption1(install("locatorBell"))); | |
| *x = event.xbutton.x; | |
| *y = event.xbutton.y; | |
| /* Make a beep! Was print "\07", but that | |
| messes up some terminals. */ | |
| if(useBeep) XBell(display, X_BELL_VOLUME); | |
| XSync(display, 0); | |
| done = 1; | |
| } | |
| else | |
| done = 2; | |
| } | |
| } | |
| else | |
| handleEvent(event); | |
| } | |
| /* In case it got closed asynchronously, PR#14872 */ | |
| if (!displayOpen) return 0; | |
| /* if it was a Button1 succeed, otherwise fail */ | |
| if(xd->type==WINDOW) XDefineCursor(display, xd->window, arrow_cursor); | |
| XSync(display, 0); | |
| return (done == 1); | |
| } | |
| static int translate_key(KeySym keysym) | |
| { | |
| if ((keysym >= XK_F1) && (keysym <= XK_F12)) | |
| return knF1 + (int)keysym - XK_F1; | |
| else { | |
| switch(keysym) { | |
| case XK_Left: return knLEFT; | |
| case XK_Up: return knUP; | |
| case XK_Right:return knRIGHT; | |
| case XK_Down: return knDOWN; | |
| case XK_Page_Up: return knPGUP; | |
| case XK_Page_Down: return knPGDN; | |
| case XK_End: return knEND; | |
| case XK_Begin:return knHOME; | |
| case XK_Insert: return knINS; | |
| } | |
| } | |
| return knUNKNOWN; | |
| } | |
| static void X11_eventHelper(pDevDesc dd, int code) | |
| { | |
| XEvent event; | |
| pDevDesc ddEvent; | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| caddr_t temp; | |
| int done = 0; | |
| if (xd->type > WINDOW) return; | |
| if (code == 1) { | |
| R_ProcessX11Events((void*)NULL); /* discard pending events */ | |
| if (isEnvironment(dd->eventEnv)) { | |
| SEXP prompt = findVar(install("prompt"), dd->eventEnv); | |
| if (isString(prompt) && length(prompt) == 1) { | |
| PROTECT(prompt); | |
| XStoreName(display, xd->window, CHAR(asChar(prompt))); | |
| UNPROTECT(1); | |
| } else | |
| XStoreName(display, xd->window, ""); | |
| } | |
| XSync(display, 1); | |
| } else if (code == 2) { | |
| if (doesIdle(dd) && (XPending(display) == 0)) { | |
| // The device descriptor event environment has an idle | |
| // handler, and there are no pending events | |
| doIdle(dd); | |
| return; | |
| } | |
| XNextEvent(display, &event); | |
| if (event.type == ButtonRelease || event.type == ButtonPress || event.type == MotionNotify) { | |
| int RButtons; | |
| XFindContext(display, event.xbutton.window, | |
| devPtrContext, &temp); | |
| ddEvent = (pDevDesc) temp; | |
| if (ddEvent == dd && dd->gettingEvent) { | |
| if (event.type == MotionNotify) { /* Because of PointerMotionHintMask, need to update */ | |
| Window root, child; | |
| int rootX, rootY, winX, winY; | |
| unsigned int mask; | |
| if (!XQueryPointer(display, event.xbutton.window, | |
| &root, &child, &rootX, &rootY, | |
| &winX, &winY, &mask)) { | |
| done = 1; | |
| } else { | |
| event.xbutton.x = winX; | |
| event.xbutton.y = winY; | |
| } | |
| RButtons = mask >> 8; /* See PR#16700 */ | |
| } else | |
| RButtons = 1 << (event.xbutton.button - 1); | |
| if (!done) { | |
| doMouseEvent(dd, event.type == ButtonRelease ? meMouseUp : | |
| event.type == ButtonPress ? meMouseDown : meMouseMove, | |
| RButtons, event.xbutton.x, event.xbutton.y); | |
| XSync(display, 0); | |
| done = 1; | |
| } | |
| } | |
| } else if (event.type == KeyPress) { | |
| char keybuffer[13] = ""; | |
| char *keystart=keybuffer; | |
| XComposeStatus compose; | |
| KeySym keysym; | |
| int keycode; | |
| if (event.xkey.state & ControlMask) { | |
| keystart += 5; | |
| sprintf(keybuffer, "ctrl-"); /* report control keys using labels like "ctrl-A" */ | |
| event.xkey.state &= ~ControlMask; | |
| event.xkey.state |= ShiftMask; | |
| } | |
| XLookupString(&event.xkey, keystart, | |
| sizeof(keybuffer)-(int)(keystart-keybuffer), | |
| &keysym, &compose); | |
| /* Rprintf("keysym=%x\n", keysym); */ | |
| if ((keycode = translate_key(keysym)) > knUNKNOWN) | |
| doKeybd(dd, keycode, NULL); | |
| else if (*keystart) | |
| doKeybd(dd, knUNKNOWN, keybuffer); | |
| done = 1; | |
| } | |
| if (!done) | |
| handleEvent(event); | |
| } else if (code == 0) { | |
| /* Restore the default title */ | |
| if (ndevNumber(dd) == curDevice()) | |
| X11_Activate(dd); | |
| else | |
| X11_Deactivate(dd); | |
| } | |
| return; | |
| } | |
| /********************************************************/ | |
| /* device_Mode is called whenever the graphics engine */ | |
| /* starts drawing (mode=1) or stops drawing (mode=0) */ | |
| /* the device is not required to do anything */ | |
| /********************************************************/ | |
| static void X11_Mode(int mode, pDevDesc dd) | |
| { | |
| pX11Desc xd = (pX11Desc) dd->deviceSpecific; | |
| if(xd->holdlevel > 0) { | |
| #ifdef HAVE_WORKING_CAIRO | |
| if(mode == 0 && xd->buffered > 1) | |
| xd->last_activity = currentTime(); | |
| #endif | |
| return; | |
| } | |
| if(mode == 1) { | |
| if(xd->type==WINDOW) XDefineCursor(display, xd->window, watch_cursor); | |
| XSync(display, 0); | |
| } | |
| if(mode == 0) { | |
| #ifdef HAVE_WORKING_CAIRO | |
| if(xd->buffered > 1) { | |
| xd->last_activity = currentTime(); | |
| if((currentTime() - xd->last) > 0.5 /* 5*xd->update_interval */) | |
| Cairo_update(xd); | |
| return; | |
| } | |
| if(xd->buffered) { | |
| cairo_paint(xd->xcc); | |
| cairo_surface_flush(xd->xcs); | |
| } | |
| #endif | |
| if(xd->type==WINDOW) XDefineCursor(display, xd->window, arrow_cursor); | |
| XSync(display, 0); | |
| } | |
| } | |
| /* X11 Device Driver Arguments : */ | |
| /* 1) display name */ | |
| /* 2) width (inches) */ | |
| /* 3) height (inches) */ | |
| /* 4) base pointsize */ | |
| /* 5) gamma correction factor */ | |
| /* 6) colormodel, */ | |
| /* see X_COLORTYPE at top of file */ | |
| /* 7) maxcube */ | |
| Rboolean X11DeviceDriver(pDevDesc dd, | |
| const char *disp_name, | |
| double width, | |
| double height, | |
| double pointsize, | |
| double gamma_fac, | |
| X_COLORTYPE colormodel, | |
| int maxcube, | |
| int bgcolor, | |
| int canvascolor, | |
| SEXP sfonts, | |
| int res, | |
| int xpos, int ypos, | |
| const char *title, | |
| int useCairo, | |
| int antialias, | |
| const char *family) | |
| { | |
| pX11Desc xd; | |
| const char *fn; | |
| xd = Rf_allocX11DeviceDesc(pointsize); | |
| if(!xd) return FALSE; | |
| xd->bg = bgcolor; | |
| #ifdef HAVE_WORKING_CAIRO | |
| xd->useCairo = useCairo != 0; | |
| xd->buffered = 0; | |
| switch(useCairo) { | |
| case 0: break; /* Xlib */ | |
| case 1: xd->buffered = 1; break; /* cairo */ | |
| case 2: xd->buffered = 0; break; /* nbcairo */ | |
| case 3: xd->buffered = 2; break; /* dbcairo */ | |
| default: | |
| warning("that type is not supported on this platform - using \"nbcairo\""); | |
| xd->buffered = 0; | |
| } | |
| if(useCairo) { | |
| switch(antialias){ | |
| case 1: xd->antialias = CAIRO_ANTIALIAS_DEFAULT; break; | |
| case 2: xd->antialias = CAIRO_ANTIALIAS_NONE; break; | |
| case 3: xd->antialias = CAIRO_ANTIALIAS_GRAY; break; | |
| case 4: xd->antialias = CAIRO_ANTIALIAS_SUBPIXEL; break; | |
| } | |
| } | |
| #else | |
| /* Currently this gets caught at R level */ | |
| if(useCairo) { | |
| warning("cairo-based types are not supported on this build - using \"Xlib\""); | |
| useCairo = FALSE; | |
| } | |
| #endif | |
| if(!useCairo) { | |
| if(strlen(fn = CHAR(STRING_ELT(sfonts, 0))) > 499) { | |
| strcpy(xd->basefontfamily, fontname); | |
| strcpy(xd->fontfamily, fontname); | |
| } else { | |
| strcpy(xd->basefontfamily, fn); | |
| strcpy(xd->fontfamily, fn); | |
| } | |
| if(strlen(fn = CHAR(STRING_ELT(sfonts, 1))) > 499) | |
| strcpy(xd->symbolfamily, symbolname); | |
| else strcpy(xd->symbolfamily, fn); | |
| } else strcpy(xd->basefontfamily, family); | |
| /* Start the Device Driver and Hardcopy. */ | |
| strncpy(xd->title, title, 100); | |
| xd->title[100] = '\0'; | |
| #ifdef HAVE_WORKING_CAIRO | |
| { | |
| SEXP timeouts = GetOption1(install("X11updates")); | |
| double tm = asReal(timeouts); | |
| xd->update_interval = (ISNAN(tm) || tm < 0) ? 0.10 : tm; | |
| } | |
| #endif | |
| if (!X11_Open(dd, xd, disp_name, width, height, | |
| gamma_fac, colormodel, maxcube, bgcolor, | |
| canvascolor, res, xpos, ypos)) { | |
| free(xd); | |
| return FALSE; | |
| } | |
| Rf_setX11DeviceData(dd, gamma_fac, xd); | |
| xd->fill = 0xffffffff; /* this is needed to ensure that the | |
| first newpage does set whitecolor | |
| if par("bg") is not transparent */ | |
| #if BUG | |
| R_ProcessX11Events((void*) NULL); | |
| #endif | |
| return TRUE; | |
| } | |
| /** | |
| This fills the general device structure (dd) with the X-specific | |
| methods/functions. It also specifies the current values of the | |
| dimensions of the device, and establishes the fonts, line styles, etc. | |
| */ | |
| int | |
| Rf_setX11DeviceData(pDevDesc dd, double gamma_fac, pX11Desc xd) | |
| { | |
| double ps = xd->pointsize; | |
| int res0 = (xd->res_dpi > 0) ? xd->res_dpi : 72; | |
| /* Set up Data Structures. */ | |
| #ifdef HAVE_WORKING_CAIRO | |
| if(xd->useCairo) { | |
| dd->newPage = Cairo_NewPage; | |
| dd->clip = Cairo_Clip; | |
| dd->rect = Cairo_Rect; | |
| dd->circle = Cairo_Circle; | |
| dd->line = Cairo_Line; | |
| dd->polyline = Cairo_Polyline; | |
| dd->polygon = Cairo_Polygon; | |
| dd->path = Cairo_Path; | |
| dd->raster = Cairo_Raster; | |
| dd->cap = Cairo_Cap; | |
| dd->hasTextUTF8 = TRUE; | |
| dd->wantSymbolUTF8 = TRUE; | |
| #ifdef HAVE_PANGOCAIRO | |
| dd->metricInfo = PangoCairo_MetricInfo; | |
| dd->strWidth = dd->strWidthUTF8 = PangoCairo_StrWidth; | |
| dd->text = dd->textUTF8 = PangoCairo_Text; | |
| #else | |
| dd->metricInfo = Cairo_MetricInfo; | |
| dd->strWidth = dd->strWidthUTF8 = Cairo_StrWidth; | |
| dd->text = dd->textUTF8 = Cairo_Text; | |
| #endif | |
| dd->holdflush = Cairo_holdflush; | |
| dd->haveTransparency = 2; | |
| dd->haveTransparentBg = 3; | |
| dd->haveRaster = 2; | |
| dd->haveCapture = (xd->type > WINDOW) ? 1 : 2; | |
| dd->haveLocator = (xd->type > WINDOW) ? 1 : 2; | |
| } else | |
| #endif | |
| { | |
| dd->newPage = X11_NewPage; | |
| dd->clip = X11_Clip; | |
| dd->strWidth = X11_StrWidth; | |
| dd->text = X11_Text; | |
| dd->rect = X11_Rect; | |
| dd->path = X11_Path; | |
| dd->raster = X11_Raster; | |
| dd->cap = X11_Cap; | |
| dd->circle = X11_Circle; | |
| dd->line = X11_Line; | |
| dd->polyline = X11_Polyline; | |
| dd->polygon = X11_Polygon; | |
| dd->metricInfo = X11_MetricInfo; | |
| dd->hasTextUTF8 = FALSE; | |
| dd->haveTransparency = 1; | |
| dd->haveTransparentBg = 2; | |
| dd->haveRaster = 3; | |
| dd->haveCapture = (xd->type > WINDOW) ? 1 : 2; | |
| dd->haveLocator = (xd->type > WINDOW) ? 1 : 2; | |
| } | |
| dd->eventHelper = X11_eventHelper; | |
| dd->canGenMouseDown = TRUE; | |
| dd->canGenMouseUp = TRUE; | |
| dd->canGenMouseMove = TRUE; | |
| dd->canGenKeybd = TRUE; | |
| dd->canGenIdle = TRUE; | |
| dd->activate = X11_Activate; | |
| dd->close = X11_Close; | |
| dd->deactivate = X11_Deactivate; | |
| dd->size = X11_Size; | |
| dd->locator = X11_Locator; | |
| dd->mode = X11_Mode; | |
| dd->useRotatedTextInContour = FALSE; | |
| /* Set required graphics parameters. */ | |
| /* Window Dimensions in Pixels */ | |
| /* Initialise the clipping rect too */ | |
| dd->left = dd->clipLeft = 0; /* left */ | |
| dd->right = dd->clipRight = xd->windowWidth; /* right */ | |
| dd->bottom = dd->clipBottom = xd->windowHeight; /* bottom */ | |
| dd->top = dd->clipTop = 0; /* top */ | |
| /* Nominal Character Sizes in Pixels */ | |
| /* Recommendation from 'R internals': changed for 2.7.0 */ | |
| /* Inches per raster unit */ | |
| /* ps is in points, we want this in device units */ | |
| if(xd->type == PNG || xd->type == JPEG || | |
| xd->type == BMP || xd->type == TIFF) { | |
| dd->cra[0] = 0.9*ps * res0/72.0; | |
| dd->cra[1] = 1.2*ps * res0/72.0; | |
| dd->ipr[0] = dd->ipr[1] = 1.0/res0; | |
| xd->lwdscale = res0/96.0; | |
| } else if(xd->type >= SVG) { /* SVG, PDF, PS -- unused */ | |
| /* Device units are bp */ | |
| dd->cra[0] = 0.9*ps; | |
| dd->cra[1] = 1.2*ps; | |
| dd->ipr[0] = dd->ipr[1] = 1.0/72.0; | |
| xd->lwdscale = 1.0/96.0; | |
| } else { | |
| dd->cra[0] = 0.9*ps * 1.0/(72.0*pixelWidth()); | |
| dd->cra[1] = 1.2*ps * 1.0/(72.0*pixelHeight()); | |
| dd->ipr[0] = pixelWidth(); | |
| dd->ipr[1] = pixelHeight(); | |
| xd->lwdscale = 1.0/(96.0*pixelWidth()); | |
| #ifdef HAVE_WORKING_CAIRO | |
| if(xd->useCairo) { | |
| # ifdef HAVE_PANGOCAIRO | |
| /* Pango's default resolution is 96 dpi */ | |
| ps *= 1.0/(96.0*pixelWidth()); | |
| # else | |
| /* Cairo's default resolution is 72 dpi */ | |
| ps *= 1.0/(72.0*pixelWidth()); | |
| # endif | |
| } | |
| #endif | |
| } | |
| /* Character Addressing Offsets */ | |
| /* These are used to plot a single plotting character */ | |
| /* so that it is exactly over the plotting point */ | |
| dd->xCharOffset = 0.4900; | |
| dd->yCharOffset = 0.3333; | |
| dd->yLineBias = 0.2; | |
| /* Device capabilities */ | |
| dd->canClip = TRUE; | |
| #ifdef HAVE_WORKING_CAIRO | |
| dd->canHAdj = xd->useCairo ? 2 : 0; | |
| #else | |
| dd->canHAdj = 0; | |
| #endif | |
| dd->canChangeGamma = FALSE; | |
| dd->startps = ps; | |
| xd->fontscale = 1.0; | |
| dd->startcol = xd->col; | |
| dd->startfill = xd->fill; | |
| dd->startlty = LTY_SOLID; | |
| dd->startfont = 1; | |
| dd->startgamma = gamma_fac; | |
| /* initialise x11 device description */ | |
| /* (most of the work has been done in X11_Open) */ | |
| xd->resize = 0; | |
| dd->deviceSpecific = (void *) xd; | |
| dd->displayListOn = TRUE; | |
| return TRUE; | |
| } | |
| /** | |
| This allocates an X11Desc instance and sets its default values. | |
| */ | |
| pX11Desc Rf_allocX11DeviceDesc(double ps) | |
| { | |
| pX11Desc xd; | |
| /* allocate new device description */ | |
| if (!(xd = (pX11Desc)calloc(1, sizeof(X11Desc)))) | |
| return NULL; | |
| /* From here on, if we need to bail out with "error", */ | |
| /* then we must also free(xd). */ | |
| /* Font will load at first use. */ | |
| if (ps < 6 || ps > 24) ps = 12; | |
| xd->fontface = -1; | |
| xd->fontsize = -1; | |
| xd->pointsize = ps; | |
| xd->handleOwnEvents = FALSE; | |
| xd->window = (Window) NULL; | |
| return xd; | |
| } | |
| static | |
| Rboolean in_R_GetX11Image(int d, void *pximage, int *pwidth, int *pheight) | |
| { | |
| SEXP dev = elt(findVar(install(".Devices"), R_BaseEnv), d); | |
| if (TYPEOF(dev) != STRSXP || | |
| !(strcmp(CHAR(STRING_ELT(dev, 0)), "XImage") == 0 || | |
| strncmp(CHAR(STRING_ELT(dev, 0)), "PNG", 3) == 0 || | |
| strncmp(CHAR(STRING_ELT(dev, 0)), "X11", 3) == 0)) | |
| return FALSE; | |
| else { | |
| pX11Desc xd = GEgetDevice(d)->dev->deviceSpecific; | |
| *((XImage**) pximage) = | |
| XGetImage(display, xd->window, 0, 0, | |
| xd->windowWidth, xd->windowHeight, | |
| AllPlanes, ZPixmap); | |
| *pwidth = xd->windowWidth; | |
| *pheight = xd->windowHeight; | |
| return TRUE; | |
| } | |
| } | |
| /** | |
| Allows callers to retrieve the current Display setting for the process. | |
| */ | |
| Display* | |
| Rf_getX11Display(void) | |
| { | |
| return(display); | |
| } | |
| /** | |
| Allows the caller to register the X11 Display object for the process. | |
| Typically this will be done when the first X device is created, but this allows | |
| other code to generate the Display object and then register it with the R graphics | |
| engine. | |
| In addition to providing the Display, the caller should also give the default value for the | |
| gamma factor and also the colormodel and color cube size. See the documentation for the x11() | |
| function. | |
| Finally, setHandlers controls whether the code establishes handlers for the X errors. | |
| */ | |
| int | |
| Rf_setX11Display(Display *dpy, double gamma_fac, X_COLORTYPE colormodel, | |
| int maxcube, Rboolean setHandlers) | |
| { | |
| /* static int alreadyDone = 0; | |
| if(alreadyDone) return(TRUE); | |
| alreadyDone = 1; */ | |
| display = dpy; | |
| /* Note: this sets a global gamma, not just for the current device */ | |
| #define SETGAMMA | |
| #ifdef SETGAMMA | |
| RedGamma = gamma_fac; | |
| GreenGamma = gamma_fac; | |
| BlueGamma = gamma_fac; | |
| #endif | |
| screen = DefaultScreen(display); | |
| rootwin = DefaultRootWindow(display); | |
| depth = DefaultDepth(display, screen); | |
| visual = DefaultVisual(display, screen); | |
| colormap = DefaultColormap(display, screen); | |
| Vclass = visual->class; | |
| model = colormodel; | |
| maxcubesize = maxcube; | |
| SetupX11Color(); | |
| devPtrContext = XUniqueContext(); | |
| displayOpen = TRUE; | |
| /* set error handlers */ | |
| if(setHandlers == TRUE) { | |
| XSetErrorHandler(R_X11Err); | |
| XSetIOErrorHandler(R_X11IOErr); | |
| } | |
| return(TRUE); | |
| } | |
| typedef Rboolean (*X11DeviceDriverRoutine)(pDevDesc, char*, | |
| double, double, double, double, | |
| X_COLORTYPE, int, int); | |
| static void | |
| Rf_addX11Device(const char *display, double width, double height, double ps, | |
| double gamma, int colormodel, int maxcubesize, | |
| int bgcolor, int canvascolor, const char *devname, SEXP sfonts, | |
| int res, int xpos, int ypos, const char *title, | |
| int useCairo, int antialias, const char * family, SEXP call) | |
| { | |
| pDevDesc dev = NULL; | |
| pGEDevDesc dd; | |
| R_GE_checkVersionOrDie(R_GE_version); | |
| R_CheckDeviceAvailable(); | |
| BEGIN_SUSPEND_INTERRUPTS { | |
| /* Allocate and initialize the device driver data */ | |
| if (!(dev = (pDevDesc) calloc(1, sizeof(DevDesc)))) return; | |
| if (!X11DeviceDriver(dev, display, width, height, | |
| ps, gamma, colormodel, maxcubesize, | |
| bgcolor, canvascolor, sfonts, res, | |
| xpos, ypos, title, useCairo, antialias, family)) { | |
| free(dev); | |
| errorcall(call, _("unable to start device %s"), devname); | |
| } | |
| dd = GEcreateDevDesc(dev); | |
| GEaddDevice2(dd, devname); | |
| } END_SUSPEND_INTERRUPTS; | |
| } | |
| static SEXP in_do_X11(SEXP call, SEXP op, SEXP args, SEXP env) | |
| { | |
| const char *display, *cname, *devname, *title, *family; | |
| const void *vmax; | |
| double height, width, ps, gamma; | |
| int colormodel, maxcubesize, bgcolor, canvascolor, res, xpos, ypos, | |
| useCairo, antialias; | |
| SEXP sc, sfonts; | |
| checkArity(op, args); | |
| vmax = vmaxget(); | |
| if(R_isForkedChild) | |
| error("a forked child should not open a graphics device"); | |
| /* Decode the arguments */ | |
| display = CHAR(STRING_ELT(CAR(args), 0)); args = CDR(args); | |
| width = asReal(CAR(args)); args = CDR(args); | |
| height = asReal(CAR(args)); args = CDR(args); | |
| if (width <= 0 || height <= 0) | |
| errorcall(call, _("invalid 'width' or 'height'")); | |
| ps = asReal(CAR(args)); args = CDR(args); | |
| gamma = asReal(CAR(args)); args = CDR(args); | |
| if (gamma < 0 || gamma > 100) | |
| errorcall(call, _("invalid '%s' value"), "gamma"); | |
| if (!isValidString(CAR(args))) | |
| error(_("invalid colortype passed to X11 driver")); | |
| cname = CHAR(STRING_ELT(CAR(args), 0)); | |
| if (strcmp(cname, "mono") == 0) | |
| colormodel = 0; | |
| else if (strcmp(cname, "gray") == 0 || strcmp(cname, "grey") == 0) | |
| colormodel = 1; | |
| else if (strcmp(cname, "pseudo.cube") == 0) | |
| colormodel = 2; | |
| else if (strcmp(cname, "pseudo") == 0) | |
| colormodel = 3; | |
| else if (strcmp(cname, "true") == 0) | |
| colormodel = 4; | |
| else { | |
| warningcall(call, | |
| _("unknown X11 color/colour model -- using monochrome")); | |
| colormodel = 0; | |
| } | |
| args = CDR(args); | |
| maxcubesize = asInteger(CAR(args)); | |
| if (maxcubesize < 1 || maxcubesize > 256) | |
| maxcubesize = 256; | |
| args = CDR(args); | |
| sc = CAR(args); | |
| if (!isString(sc) && !isInteger(sc) && !isLogical(sc) && !isReal(sc)) | |
| errorcall(call, _("invalid '%s' value"), "bg"); | |
| bgcolor = RGBpar(sc, 0); | |
| args = CDR(args); | |
| sc = CAR(args); | |
| if (!isString(sc) && !isInteger(sc) && !isLogical(sc) && !isReal(sc)) | |
| errorcall(call, _("invalid '%s' value"), "canvas"); | |
| canvascolor = RGBpar(sc, 0); | |
| args = CDR(args); | |
| sfonts = CAR(args); | |
| if (!isString(sfonts) || LENGTH(sfonts) != 2) | |
| errorcall(call, _("invalid '%s' value"), "fonts"); | |
| args = CDR(args); | |
| res = asInteger(CAR(args)); | |
| args = CDR(args); | |
| xpos = asInteger(CAR(args)); | |
| args = CDR(args); | |
| ypos = asInteger(CAR(args)); | |
| args = CDR(args); | |
| sc = CAR(args); | |
| if (!isString(sc) || LENGTH(sc) != 1) | |
| errorcall(call, _("invalid '%s' value"), "title"); | |
| title = CHAR(STRING_ELT(sc, 0)); | |
| args = CDR(args); | |
| useCairo = asInteger(CAR(args)); | |
| if (useCairo == NA_INTEGER) | |
| errorcall(call, _("invalid '%s' value"), "type"); | |
| args = CDR(args); | |
| antialias = asInteger(CAR(args)); | |
| if (antialias == NA_INTEGER) | |
| errorcall(call, _("invalid '%s' value"), "antialias"); | |
| args = CDR(args); | |
| sc = CAR(args); | |
| if (!isString(sc) || LENGTH(sc) != 1) | |
| errorcall(call, _("invalid '%s' value"), "family"); | |
| family = CHAR(STRING_ELT(sc, 0)); | |
| if (!strncmp(display, "png::", 5)) devname = "PNG"; | |
| else if (!strncmp(display, "jpeg::", 6)) devname = "JPEG"; | |
| else if (!strncmp(display, "tiff::", 6)) devname = "TIFF"; | |
| else if (!strncmp(display, "bmp::", 5)) devname = "BMP"; | |
| else if (!strcmp(display, "XImage")) devname = "XImage"; | |
| else if (useCairo) devname = "X11cairo"; | |
| else devname = "X11"; | |
| Rf_addX11Device(display, width, height, ps, gamma, colormodel, | |
| maxcubesize, bgcolor, canvascolor, devname, sfonts, | |
| res, xpos, ypos, title, useCairo, antialias, family, call); | |
| vmaxset(vmax); | |
| return R_NilValue; | |
| } | |
| #ifdef HAVE_WORKING_CAIRO | |
| static int stride; | |
| static unsigned int Sbitgp(void *xi, int x, int y) | |
| { | |
| unsigned int *data = xi; | |
| return data[x*stride+y] | 0xFF000000; /* force opaque */ | |
| } | |
| /* savePlot(filename, type, device) */ | |
| static SEXP in_do_saveplot(SEXP call, SEXP op, SEXP args, SEXP env) | |
| { | |
| int devNr; | |
| const char *fn, *type; | |
| pGEDevDesc gdd; | |
| pX11Desc xd; | |
| checkArity(op, args); | |
| if (!isString(CAR(args)) || LENGTH(CAR(args)) < 1) | |
| error(_("invalid '%s' argument"), "filename"); | |
| fn = R_ExpandFileName(translateChar(STRING_ELT(CAR(args), 0))); | |
| if (!isString(CADR(args)) || LENGTH(CADR(args)) < 1) | |
| error(_("invalid '%s' argument"), "type"); | |
| type = CHAR(STRING_ELT(CADR(args), 0)); | |
| devNr = asInteger(CADDR(args)); | |
| if (devNr == NA_INTEGER) error(_("invalid '%s' argument"), "device"); | |
| gdd = GEgetDevice(devNr - 1); /* 0-based */ | |
| if (!gdd->dirty) error(_("no plot on device to save")); | |
| xd = gdd->dev->deviceSpecific; | |
| if (!xd->cs || !xd->useCairo) error(_("not an open X11cairo device")); | |
| if (streql(type, "png")) { | |
| cairo_status_t res = cairo_surface_write_to_png(xd->cs, fn); | |
| if (res != CAIRO_STATUS_SUCCESS) | |
| error("cairo error '%s'", cairo_status_to_string(res)); | |
| } | |
| else if (streql(type, "jpeg")) { | |
| void *xi = cairo_image_surface_get_data(xd->cs); | |
| FILE *fp = R_fopen(fn, "w"); | |
| if (!fp) error(_("cannot open file '%s'"), fn); | |
| stride = xd->windowWidth; | |
| R_SaveAsJpeg(xi, xd->windowWidth, xd->windowHeight, | |
| Sbitgp, 0, 75, fp, 0); | |
| fclose(fp); | |
| } else if (streql(type, "tiff")) { | |
| void *xi = cairo_image_surface_get_data(xd->cs); | |
| stride = xd->windowWidth; | |
| R_SaveAsTIFF(xi, xd->windowWidth, xd->windowHeight, | |
| Sbitgp, 0, fn, 0, 1L); | |
| } else | |
| error(_("invalid '%s' argument"), "type"); | |
| return R_NilValue; | |
| } | |
| #else | |
| static SEXP in_do_saveplot(SEXP call, SEXP op, SEXP args, SEXP env) | |
| { | |
| error(_("savePlot() is not supported on this build")); | |
| return R_NilValue; | |
| } | |
| #endif | |
| static int in_R_X11_access(void) | |
| { | |
| char *p; | |
| X11IOhandler old; | |
| if (displayOpen) return TRUE; | |
| if(!(p = getenv("DISPLAY"))) return FALSE; | |
| /* Bill Dunlap sees an error when tunneling to a non-existent | |
| X11 connection that BDR cannot reproduce. We leave a handler set | |
| if we get an error, but that is rare. | |
| */ | |
| old = XSetIOErrorHandler(R_X11IOErrSimple); | |
| if ((display = XOpenDisplay(NULL)) == NULL) { | |
| XSetIOErrorHandler(old); | |
| return FALSE; | |
| } else { | |
| XCloseDisplay(display); | |
| XSetIOErrorHandler(old); | |
| return TRUE; | |
| } | |
| } | |
| static Rboolean in_R_X11readclp(Rclpconn this, char *type) | |
| { | |
| Window clpwin; | |
| Atom sel = XA_PRIMARY, pty, pty_type; | |
| XEvent evt; | |
| unsigned char *buffer; | |
| unsigned long pty_size, pty_items; | |
| int pty_format, ret; | |
| Rboolean res = TRUE; | |
| if (!displayOpen) { | |
| if ((display = XOpenDisplay(NULL)) == NULL) { | |
| warning(_("unable to contact X11 display")); | |
| return FALSE; | |
| } | |
| } | |
| if(strcmp(type, "X11_secondary") == 0) sel = XA_SECONDARY; | |
| if(strcmp(type, "X11_clipboard") == 0) | |
| #ifdef HAVE_X11_Xmu | |
| sel = XA_CLIPBOARD(display); | |
| #else | |
| error("X11 clipboard selection is not supported on this system"); | |
| #endif | |
| pty = XInternAtom(display, "RCLIP_READ", False); | |
| clpwin = XCreateSimpleWindow(display, DefaultRootWindow(display), | |
| 0, 0, 1, 1, 0, 0, 0); | |
| /* <FIXME> this is not optimal in a UTF-8 locale. | |
| What we should do is see if UTF-8 extensions are available | |
| (via X_HAVE_UTF8_STRING) then ask with target TARGETS and see if | |
| UTF8_STRING is available. See | |
| http://www.pps.jussieu.fr/~jch/software/UTF8_STRING/UTF8_STRING.text | |
| */ | |
| /* send a selection request */ | |
| ret = XConvertSelection(display, sel, XA_STRING, pty, clpwin, CurrentTime); | |
| /* wait for the response */ | |
| while(1) { | |
| XNextEvent(display, &evt); | |
| if (evt.type == SelectionNotify) break; | |
| } | |
| /* find the size and format of the data in the selection */ | |
| ret = XGetWindowProperty(display, clpwin, pty, 0, 0, False, AnyPropertyType, | |
| &pty_type, &pty_format, &pty_items, &pty_size, | |
| &buffer); | |
| if (ret) { | |
| warning(_("clipboard cannot be opened or contains no text")); | |
| res = FALSE; | |
| } else { | |
| XFree(buffer); | |
| if (pty_format != 8) { /* bytes */ | |
| warning(_("clipboard cannot be opened or contains no text")); | |
| res = FALSE; | |
| } else { /* read the property */ | |
| ret = XGetWindowProperty(display, clpwin, pty, 0, (long)pty_size, False, | |
| AnyPropertyType, &pty_type, &pty_format, | |
| &pty_items, &pty_size, &buffer); | |
| if (ret) { | |
| warning(_("clipboard cannot be read (error code %d)"), ret); | |
| res = FALSE; | |
| } else { | |
| this->buff = (char *)malloc(pty_items + 1); | |
| this->last = this->len = (int) pty_items; | |
| if(this->buff) { | |
| /* property always ends in 'extra' zero byte */ | |
| memcpy(this->buff, buffer, pty_items + 1); | |
| } else { | |
| warning(_("memory allocation to copy clipboard failed")); | |
| res = FALSE; | |
| } | |
| XFree(buffer); | |
| } | |
| } | |
| } | |
| XDeleteProperty(display, clpwin, pty); | |
| if (!displayOpen) { | |
| XCloseDisplay(display); | |
| strcpy(dspname, ""); | |
| } | |
| return res; | |
| } | |
| #include <R_ext/Rdynload.h> | |
| extern const char * in_R_pngVersion(void); | |
| extern const char * in_R_jpegVersion(void); | |
| extern const char * in_R_tiffVersion(void); | |
| void R_init_R_X11(DllInfo *info) | |
| { | |
| R_X11Routines *tmp; | |
| tmp = (R_X11Routines*) malloc(sizeof(R_X11Routines)); | |
| if(!tmp) { | |
| error(_("cannot allocate memory for X11Routines structure")); | |
| return; | |
| } | |
| tmp->X11 = in_do_X11; | |
| tmp->saveplot = in_do_saveplot; | |
| tmp->image = in_R_GetX11Image; | |
| tmp->access = in_R_X11_access; | |
| tmp->readclp = in_R_X11readclp; | |
| tmp->R_pngVersion = in_R_pngVersion; | |
| tmp->R_jpegVersion = in_R_jpegVersion; | |
| tmp->R_tiffVersion = in_R_tiffVersion; | |
| R_setX11Routines(tmp); | |
| } |