| /* |
| * R : A Computer Language for Statistical Data Analysis |
| * Copyright (C) 2001-2018 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/ |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <Defn.h> |
| #include <Internal.h> |
| #include <R_ext/GraphicsEngine.h> |
| #include <R_ext/Applic.h> /* pretty() */ |
| #include <Rmath.h> |
| |
| # include <rlocale.h> |
| |
| int R_GE_getVersion() |
| { |
| return R_GE_version; |
| } |
| |
| void R_GE_checkVersionOrDie(int version) |
| { |
| if (version != R_GE_version) |
| error(_("Graphics API version mismatch")); |
| } |
| |
| /* A note on memory management ... |
| * Here (with GEDevDesc's) I have continued the deplorable tradition of |
| * malloc'ing device structures and maintaining global variables to |
| * record the device structures. I believe that what I should |
| * be doing is recording the device structures in R-level objects |
| * (i.e., SEXP's) using Luke's reference pointers to make sure that |
| * nasty things like duplicate copies of device structures do not |
| * occur. The thing stopping me doing "the right thing" right now |
| * is time. Hopefully, I will get time later to come back and do |
| * it properly -- in the meantime I'll just have to burn in hell. |
| * Paul. |
| */ |
| |
| static int numGraphicsSystems = 0; |
| |
| static GESystemDesc* registeredSystems[MAX_GRAPHICS_SYSTEMS]; |
| |
| |
| /**************************************************************** |
| * GEdestroyDevDesc |
| **************************************************************** |
| */ |
| |
| static void unregisterOne(pGEDevDesc dd, int systemNumber) { |
| if (dd->gesd[systemNumber] != NULL) { |
| (dd->gesd[systemNumber]->callback)(GE_FinaliseState, dd, R_NilValue); |
| free(dd->gesd[systemNumber]); |
| dd->gesd[systemNumber] = NULL; |
| } |
| } |
| |
| /* NOTE that dd->dev has been shut down by a call |
| * to dev->close within devices.c |
| */ |
| void GEdestroyDevDesc(pGEDevDesc dd) |
| { |
| int i; |
| if (dd != NULL) { |
| for (i = 0; i < MAX_GRAPHICS_SYSTEMS; i++) unregisterOne(dd, i); |
| free(dd->dev); |
| dd->dev = NULL; |
| free(dd); |
| } |
| } |
| |
| /**************************************************************** |
| * GEsystemState |
| **************************************************************** |
| |
| Currently unused, but future systems might need it. |
| */ |
| |
| void* GEsystemState(pGEDevDesc dd, int index) |
| { |
| return dd->gesd[index]->systemSpecific; |
| } |
| |
| /**************************************************************** |
| * GEregisterWithDevice |
| **************************************************************** |
| */ |
| |
| /* The guts of adding information about a specific graphics |
| * system to a specific device. |
| */ |
| static void registerOne(pGEDevDesc dd, int systemNumber, GEcallback cb) { |
| SEXP result; |
| dd->gesd[systemNumber] = |
| (GESystemDesc*) calloc(1, sizeof(GESystemDesc)); |
| if (dd->gesd[systemNumber] == NULL) |
| error(_("unable to allocate memory (in GEregister)")); |
| result = cb(GE_InitState, dd, R_NilValue); |
| if (isNull(result)) { |
| /* tidy up */ |
| free(dd->gesd[systemNumber]); |
| error(_("unable to allocate memory (in GEregister)")); |
| } else { |
| dd->gesd[systemNumber]->callback = cb; |
| } |
| } |
| |
| /* Store the graphics system state and callback information |
| * for a specified device. |
| * This is called when a new device is created. |
| */ |
| void GEregisterWithDevice(pGEDevDesc dd) { |
| int i; |
| for (i = 0; i < MAX_GRAPHICS_SYSTEMS; i++) |
| /* If a graphics system has unregistered, there might be |
| * "holes" in the array of registeredSystems. |
| */ |
| if (registeredSystems[i] != NULL) |
| registerOne(dd, i, registeredSystems[i]->callback); |
| } |
| |
| /**************************************************************** |
| * GEregisterSystem |
| **************************************************************** |
| */ |
| |
| /* Record the state and callback information for a new graphics |
| * system. |
| * This is called when a graphics system is loaded. |
| * Return the index of the system's information in the graphic |
| * engine's register. |
| */ |
| void GEregisterSystem(GEcallback cb, int *systemRegisterIndex) { |
| int i, devNum; |
| pGEDevDesc gdd; |
| if (numGraphicsSystems + 1 == MAX_GRAPHICS_SYSTEMS) |
| error(_("too many graphics systems registered")); |
| /* Set the system register index so that, if there are existing |
| * devices, it will know where to put the system-specific |
| * information in those devices |
| * If a graphics system has been unregistered, there might |
| * be "holes" in the list of graphics systems, so start |
| * from zero and look for the first NULL |
| */ |
| *systemRegisterIndex = 0; |
| while (registeredSystems[*systemRegisterIndex] != NULL) { |
| (*systemRegisterIndex)++; |
| } |
| /* Run through the existing devices and add the new information |
| * to any GEDevDesc's |
| */ |
| i = 1; |
| if (!NoDevices()) { |
| devNum = curDevice(); |
| while (i++ < NumDevices()) { |
| gdd = GEgetDevice(devNum); |
| registerOne(gdd, *systemRegisterIndex, cb); |
| devNum = nextDevice(devNum); |
| } |
| } |
| /* Store the information for adding to any new devices |
| */ |
| registeredSystems[*systemRegisterIndex] = |
| (GESystemDesc*) calloc(1, sizeof(GESystemDesc)); |
| if (registeredSystems[*systemRegisterIndex] == NULL) |
| error(_("unable to allocate memory (in GEregister)")); |
| registeredSystems[*systemRegisterIndex]->callback = cb; |
| numGraphicsSystems += 1; |
| } |
| |
| /**************************************************************** |
| * GEunregisterSystem |
| **************************************************************** |
| */ |
| |
| void GEunregisterSystem(int registerIndex) |
| { |
| int i, devNum; |
| pGEDevDesc gdd; |
| |
| /* safety check if called before Ginit() */ |
| if(registerIndex < 0) return; |
| if (numGraphicsSystems == 0) { |
| /* This gets called from KillAllDevices, which is called |
| during shutdown. Prior to 2.14.0 it gave an error, which |
| would inhibit shutdown. This should not happen, but |
| apparently it did after a segfault: |
| https://stat.ethz.ch/pipermail/r-devel/2011-June/061153.html |
| */ |
| warning(_("no graphics system to unregister")); |
| return; |
| } |
| /* Run through the existing devices and remove the information |
| * from any GEDevDesc's |
| */ |
| i = 1; |
| if (!NoDevices()) { |
| devNum = curDevice(); |
| while (i++ < NumDevices()) { |
| gdd = GEgetDevice(devNum); |
| unregisterOne(gdd, registerIndex); |
| devNum = nextDevice(devNum); |
| } |
| } |
| /* Remove the information from the global record |
| * NOTE that there is no systemSpecific information stored |
| * in the global record -- just the system callback pointer. |
| */ |
| if (registeredSystems[registerIndex] != NULL) { |
| free(registeredSystems[registerIndex]); |
| registeredSystems[registerIndex] = NULL; |
| } |
| numGraphicsSystems -= 1; |
| } |
| |
| /**************************************************************** |
| * GEhandleEvent |
| **************************************************************** |
| */ |
| |
| /* This guy can be called by device drivers. |
| * It calls back to registered graphics systems and passes on the event |
| * so that the graphics systems can respond however they want to. |
| * |
| * Currently only used for GE_ScalePS in devWindows.c |
| */ |
| SEXP GEhandleEvent(GEevent event, pDevDesc dev, SEXP data) |
| { |
| int i; |
| pGEDevDesc gdd = desc2GEDesc(dev); |
| for (i = 0; i < MAX_GRAPHICS_SYSTEMS; i++) |
| if (registeredSystems[i] != NULL) |
| (registeredSystems[i]->callback)(event, gdd, data); |
| return R_NilValue; |
| } |
| |
| /**************************************************************** |
| * Some graphics engine transformations |
| **************************************************************** |
| */ |
| |
| double fromDeviceX(double value, GEUnit to, pGEDevDesc dd) |
| { |
| double result = value; |
| switch (to) { |
| case GE_DEVICE: |
| break; |
| case GE_NDC: |
| result = (result - dd->dev->left) / (dd->dev->right - dd->dev->left); |
| break; |
| case GE_INCHES: |
| result = (result - dd->dev->left) / (dd->dev->right - dd->dev->left) * |
| fabs(dd->dev->right - dd->dev->left) * dd->dev->ipr[0]; |
| break; |
| case GE_CM: |
| result = (result - dd->dev->left) / (dd->dev->right - dd->dev->left) * |
| fabs(dd->dev->right - dd->dev->left) * dd->dev->ipr[0] * 2.54; |
| } |
| return result; |
| } |
| |
| double toDeviceX(double value, GEUnit from, pGEDevDesc dd) |
| { |
| double result = value; |
| switch (from) { |
| case GE_CM: |
| /* Convert GE_CM to GE_INCHES */ |
| result = result / 2.54; |
| case GE_INCHES: |
| /* Convert GE_INCHES to GE_NDC */ |
| result = (result / dd->dev->ipr[0]) / fabs(dd->dev->right - dd->dev->left); |
| case GE_NDC: |
| /* Convert GE_NDC to Dev */ |
| result = dd->dev->left + result*(dd->dev->right - dd->dev->left); |
| case GE_DEVICE: |
| /* Do nothing */ |
| break; |
| } |
| return result; |
| } |
| |
| double fromDeviceY(double value, GEUnit to, pGEDevDesc dd) |
| { |
| double result = value; |
| switch (to) { |
| case GE_DEVICE: |
| break; |
| case GE_NDC: |
| result = (result - dd->dev->bottom) / (dd->dev->top - dd->dev->bottom); |
| break; |
| case GE_INCHES: |
| result = (result - dd->dev->bottom) / (dd->dev->top - dd->dev->bottom) * |
| fabs(dd->dev->top - dd->dev->bottom) * dd->dev->ipr[1]; |
| break; |
| case GE_CM: |
| result = (result - dd->dev->bottom) / (dd->dev->top - dd->dev->bottom) * |
| fabs(dd->dev->top - dd->dev->bottom) * dd->dev->ipr[1] * 2.54; |
| } |
| return result; |
| } |
| |
| double toDeviceY(double value, GEUnit from, pGEDevDesc dd) |
| { |
| double result = value; |
| switch (from) { |
| case GE_CM: |
| /* Convert GE_CM to GE_INCHES */ |
| result = result / 2.54; |
| case GE_INCHES: |
| /* Convert GE_INCHES to GE_NDC */ |
| result = (result / dd->dev->ipr[1]) / fabs(dd->dev->top - dd->dev->bottom); |
| case GE_NDC: |
| /* Convert GE_NDC to Dev */ |
| result = dd->dev->bottom + result*(dd->dev->top - dd->dev->bottom); |
| case GE_DEVICE: |
| /* Do nothing */ |
| break; |
| } |
| return result; |
| } |
| |
| double fromDeviceWidth(double value, GEUnit to, pGEDevDesc dd) |
| { |
| double result = value; |
| switch (to) { |
| case GE_DEVICE: |
| break; |
| case GE_NDC: |
| result = result / (dd->dev->right - dd->dev->left); |
| break; |
| case GE_INCHES: |
| result = result * dd->dev->ipr[0]; |
| break; |
| case GE_CM: |
| result = result * dd->dev->ipr[0] * 2.54; |
| } |
| return result; |
| } |
| |
| double toDeviceWidth(double value, GEUnit from, pGEDevDesc dd) |
| { |
| double result = value; |
| switch (from) { |
| case GE_CM: |
| /* Convert GE_CM to GE_INCHES */ |
| result = result / 2.54; |
| case GE_INCHES: |
| /* Convert GE_INCHES to GE_NDC */ |
| result = (result / dd->dev->ipr[0]) / fabs(dd->dev->right - dd->dev->left); |
| case GE_NDC: |
| /* Convert GE_NDC to Dev */ |
| result = result*(dd->dev->right - dd->dev->left); |
| case GE_DEVICE: |
| /* Do nothing */ |
| break; |
| } |
| return result; |
| } |
| |
| double fromDeviceHeight(double value, GEUnit to, pGEDevDesc dd) |
| { |
| double result = value; |
| switch (to) { |
| case GE_DEVICE: |
| break; |
| case GE_NDC: |
| result = result / (dd->dev->top - dd->dev->bottom); |
| break; |
| case GE_INCHES: |
| result = result * dd->dev->ipr[1]; |
| break; |
| case GE_CM: |
| result = result * dd->dev->ipr[1] * 2.54; |
| } |
| return result; |
| } |
| |
| double toDeviceHeight(double value, GEUnit from, pGEDevDesc dd) |
| { |
| double result = value; |
| switch (from) { |
| case GE_CM: |
| /* Convert GE_CM to GE_INCHES */ |
| result = result / 2.54; |
| case GE_INCHES: |
| /* Convert GE_INCHES to GE_NDC */ |
| result = (result / dd->dev->ipr[1]) / fabs(dd->dev->top - dd->dev->bottom); |
| case GE_NDC: |
| /* Convert GE_NDC to Dev */ |
| result = result*(dd->dev->top - dd->dev->bottom); |
| case GE_DEVICE: |
| /* Do nothing */ |
| break; |
| } |
| return result; |
| } |
| |
| /**************************************************************** |
| * Code for converting line ends and joins from SEXP to internal |
| * representation |
| **************************************************************** |
| */ |
| typedef struct { |
| char *name; |
| R_GE_lineend end; |
| } LineEND; |
| |
| static LineEND lineend[] = { |
| { "round", GE_ROUND_CAP }, |
| { "butt", GE_BUTT_CAP }, |
| { "square", GE_SQUARE_CAP }, |
| { NULL, 0 } |
| }; |
| |
| static int nlineend = (sizeof(lineend)/sizeof(LineEND)-2); |
| |
| R_GE_lineend GE_LENDpar(SEXP value, int ind) |
| { |
| int i, code; |
| double rcode; |
| |
| if(isString(value)) { |
| for(i = 0; lineend[i].name; i++) { /* is it the i-th name ? */ |
| if(!strcmp(CHAR(STRING_ELT(value, ind)), lineend[i].name)) /*ASCII */ |
| return lineend[i].end; |
| } |
| error(_("invalid line end")); /*NOTREACHED, for -Wall : */ return 0; |
| } |
| else if(isInteger(value)) { |
| code = INTEGER(value)[ind]; |
| if(code == NA_INTEGER || code < 0) |
| error(_("invalid line end")); |
| if (code > 0) |
| code = (code-1) % nlineend + 1; |
| return lineend[code].end; |
| } |
| else if(isReal(value)) { |
| rcode = REAL(value)[ind]; |
| if(!R_FINITE(rcode) || rcode < 0) |
| error(_("invalid line end")); |
| code = (int) rcode; |
| if (code > 0) |
| code = (code-1) % nlineend + 1; |
| return lineend[code].end; |
| } |
| else { |
| error(_("invalid line end")); /*NOTREACHED, for -Wall : */ return 0; |
| } |
| } |
| |
| SEXP GE_LENDget(R_GE_lineend lend) |
| { |
| SEXP ans = R_NilValue; |
| int i; |
| |
| for (i = 0; lineend[i].name; i++) { |
| if(lineend[i].end == lend) |
| return mkString(lineend[i].name); |
| } |
| |
| error(_("invalid line end")); |
| /* |
| * Should never get here |
| */ |
| return ans; |
| } |
| |
| typedef struct { |
| char *name; |
| R_GE_linejoin join; |
| } LineJOIN; |
| |
| static LineJOIN linejoin[] = { |
| { "round", GE_ROUND_JOIN }, |
| { "mitre", GE_MITRE_JOIN }, |
| { "bevel", GE_BEVEL_JOIN}, |
| { NULL, 0 } |
| }; |
| |
| static int nlinejoin = (sizeof(linejoin)/sizeof(LineJOIN)-2); |
| |
| R_GE_linejoin GE_LJOINpar(SEXP value, int ind) |
| { |
| int i, code; |
| double rcode; |
| |
| if(isString(value)) { |
| for(i = 0; linejoin[i].name; i++) { /* is it the i-th name ? */ |
| if(!strcmp(CHAR(STRING_ELT(value, ind)), linejoin[i].name)) /* ASCII */ |
| return linejoin[i].join; |
| } |
| error(_("invalid line join")); /*NOTREACHED, for -Wall : */ return 0; |
| } |
| else if(isInteger(value)) { |
| code = INTEGER(value)[ind]; |
| if(code == NA_INTEGER || code < 0) |
| error(_("invalid line join")); |
| if (code > 0) |
| code = (code-1) % nlinejoin + 1; |
| return linejoin[code].join; |
| } |
| else if(isReal(value)) { |
| rcode = REAL(value)[ind]; |
| if(!R_FINITE(rcode) || rcode < 0) |
| error(_("invalid line join")); |
| code = (int) rcode; |
| if (code > 0) |
| code = (code-1) % nlinejoin + 1; |
| return linejoin[code].join; |
| } |
| else { |
| error(_("invalid line join")); /*NOTREACHED, for -Wall : */ return 0; |
| } |
| } |
| |
| SEXP GE_LJOINget(R_GE_linejoin ljoin) |
| { |
| SEXP ans = R_NilValue; |
| int i; |
| |
| for (i = 0; linejoin[i].name; i++) { |
| if(linejoin[i].join == ljoin) |
| return mkString(linejoin[i].name); |
| } |
| |
| error(_("invalid line join")); |
| /* |
| * Should never get here |
| */ |
| return ans; |
| } |
| |
| /**************************************************************** |
| * Code to retrieve current clipping rect from device |
| **************************************************************** |
| */ |
| |
| static void getClipRect(double *x1, double *y1, double *x2, double *y2, |
| pGEDevDesc dd) |
| { |
| /* Since these are only set by GESetClip they should be in order */ |
| if (dd->dev->clipLeft < dd->dev->clipRight) { |
| *x1 = dd->dev->clipLeft; |
| *x2 = dd->dev->clipRight; |
| } else { |
| *x2 = dd->dev->clipLeft; |
| *x1 = dd->dev->clipRight; |
| } |
| if (dd->dev->clipBottom < dd->dev->clipTop) { |
| *y1 = dd->dev->clipBottom; |
| *y2 = dd->dev->clipTop; |
| } else { |
| *y2 = dd->dev->clipBottom; |
| *y1 = dd->dev->clipTop; |
| } |
| } |
| |
| static void getClipRectToDevice(double *x1, double *y1, double *x2, double *y2, |
| pGEDevDesc dd) |
| { |
| /* Devices can have flipped coord systems */ |
| if (dd->dev->left < dd->dev->right) { |
| *x1 = dd->dev->left; |
| *x2 = dd->dev->right; |
| } else { |
| *x2 = dd->dev->left; |
| *x1 = dd->dev->right; |
| } |
| if (dd->dev->bottom < dd->dev->top) { |
| *y1 = dd->dev->bottom; |
| *y2 = dd->dev->top; |
| } else { |
| *y2 = dd->dev->bottom; |
| *y1 = dd->dev->top; |
| } |
| } |
| |
| /**************************************************************** |
| * GESetClip |
| **************************************************************** |
| */ |
| void GESetClip(double x1, double y1, double x2, double y2, pGEDevDesc dd) |
| { |
| pDevDesc d = dd->dev; |
| double dx1 = d->left, dx2 = d->right, dy1 = d->bottom, dy2 = d->top; |
| |
| /* clip to device region */ |
| if (dx1 <= dx2) { |
| x1 = fmax2(x1, dx1); |
| x2 = fmin2(x2, dx2); |
| } else { |
| x1 = fmin2(x1, dx1); |
| x2 = fmax2(x2, dx2); |
| } |
| if (dy1 <= dy2) { |
| y1 = fmax2(y1, dy1); |
| y2 = fmin2(y2, dy2); |
| } else { |
| y1 = fmin2(y1, dy1); |
| y2 = fmax2(y2, dy2); |
| } |
| d->clip(x1, x2, y1, y2, dd->dev); |
| /* |
| * Record the current clip rect settings so that calls to |
| * getClipRect get the up-to-date values. |
| */ |
| d->clipLeft = fmin2(x1, x2); |
| d->clipRight = fmax2(x1, x2); |
| d->clipTop = fmax2(y1, y2); |
| d->clipBottom = fmin2(y1, y2); |
| } |
| |
| /**************************************************************** |
| * R code for clipping lines |
| **************************************************************** |
| */ |
| |
| /* Draw Line Segments, Clipping to the Viewport */ |
| /* Cohen-Sutherland Algorithm */ |
| /* Unneeded if the device can do the clipping */ |
| |
| |
| #define CS_BOTTOM 001 |
| #define CS_LEFT 002 |
| #define CS_TOP 004 |
| #define CS_RIGHT 010 |
| |
| typedef struct { |
| double xl; |
| double xr; |
| double yb; |
| double yt; |
| } cliprect; |
| |
| |
| static int clipcode(double x, double y, cliprect *cr) |
| { |
| int c = 0; |
| if(x < cr->xl) |
| c |= CS_LEFT; |
| else if(x > cr->xr) |
| c |= CS_RIGHT; |
| if(y < cr->yb) |
| c |= CS_BOTTOM; |
| else if(y > cr->yt) |
| c |= CS_TOP; |
| return c; |
| } |
| |
| static Rboolean |
| CSclipline(double *x1, double *y1, double *x2, double *y2, |
| cliprect *cr, int *clipped1, int *clipped2, |
| pGEDevDesc dd) |
| { |
| int c, c1, c2; |
| double x, y, xl, xr, yb, yt; |
| |
| *clipped1 = 0; |
| *clipped2 = 0; |
| c1 = clipcode(*x1, *y1, cr); |
| c2 = clipcode(*x2, *y2, cr); |
| if ( !c1 && !c2 ) |
| return TRUE; |
| |
| xl = cr->xl; |
| xr = cr->xr; |
| yb = cr->yb; |
| yt = cr->yt; |
| /* Paul took out the code for (dd->dev->gp.xlog || dd->dev->gp.ylog) |
| * (i) because device holds no state on whether scales are logged |
| * (ii) it appears to be identical to the code for non-log scales !? |
| */ |
| x = xl; /* keep -Wall happy */ |
| y = yb; /* keep -Wall happy */ |
| while( c1 || c2 ) { |
| if(c1 & c2) |
| return FALSE; |
| if( c1 ) |
| c = c1; |
| else |
| c = c2; |
| if( c & CS_LEFT ) { |
| y = *y1 + (*y2 - *y1) * (xl - *x1) / (*x2 - *x1); |
| x = xl; |
| } |
| else if( c & CS_RIGHT ) { |
| y = *y1 + (*y2 - *y1) * (xr - *x1) / (*x2 - *x1); |
| x = xr; |
| } |
| else if( c & CS_BOTTOM ) { |
| x = *x1 + (*x2 - *x1) * (yb - *y1) / (*y2 - *y1); |
| y = yb; |
| } |
| else if( c & CS_TOP ) { |
| x = *x1 + (*x2 - *x1) * (yt - *y1)/(*y2 - *y1); |
| y = yt; |
| } |
| |
| if( c==c1 ) { |
| *x1 = x; |
| *y1 = y; |
| *clipped1 = 1; |
| c1 = clipcode(x, y, cr); |
| } |
| else { |
| *x2 = x; |
| *y2 = y; |
| *clipped2 = 1; |
| c2 = clipcode(x, y, cr); |
| } |
| } |
| return TRUE; |
| } |
| |
| |
| /* Clip the line |
| If toDevice = 1, clip to the device extent (i.e., temporarily ignore |
| dd->dev->gp.xpd) */ |
| static Rboolean |
| clipLine(double *x1, double *y1, double *x2, double *y2, |
| int toDevice, pGEDevDesc dd) |
| { |
| int dummy1, dummy2; |
| cliprect cr; |
| |
| if (toDevice) |
| getClipRectToDevice(&cr.xl, &cr.yb, &cr.xr, &cr.yt, dd); |
| else |
| getClipRect(&cr.xl, &cr.yb, &cr.xr, &cr.yt, dd); |
| |
| return CSclipline(x1, y1, x2, y2, &cr, &dummy1, &dummy2, dd); |
| } |
| |
| /**************************************************************** |
| * GELine |
| **************************************************************** |
| */ |
| /* If the device canClip, R clips line to device extent and |
| device does all other clipping. */ |
| void GELine(double x1, double y1, double x2, double y2, |
| const pGEcontext gc, pGEDevDesc dd) |
| { |
| Rboolean clip_ok; |
| if (gc->lwd == R_PosInf || gc->lwd < 0.0) |
| error(_("'lwd' must be non-negative and finite")); |
| if (ISNAN(gc->lwd) || gc->lty == LTY_BLANK) return; |
| if (dd->dev->canClip) { |
| clip_ok = clipLine(&x1, &y1, &x2, &y2, 1, dd); |
| } |
| else { |
| clip_ok = clipLine(&x1, &y1, &x2, &y2, 0, dd); |
| } |
| if (clip_ok) |
| dd->dev->line(x1, y1, x2, y2, gc, dd->dev); |
| } |
| |
| /**************************************************************** |
| * R code for clipping polylines |
| **************************************************************** |
| */ |
| |
| static void CScliplines(int n, double *x, double *y, |
| const pGEcontext gc, int toDevice, pGEDevDesc dd) |
| { |
| int ind1, ind2; |
| /*int firstPoint = 1;*/ |
| int count = 0; |
| int i = 0; |
| double *xx, *yy; |
| double x1, y1, x2, y2; |
| cliprect cr; |
| const void *vmax = vmaxget(); |
| |
| if (toDevice) |
| getClipRectToDevice(&cr.xl, &cr.yb, &cr.xr, &cr.yt, dd); |
| else |
| getClipRect(&cr.xl, &cr.yb, &cr.xr, &cr.yt, dd); |
| |
| xx = (double *) R_alloc(n, sizeof(double)); |
| yy = (double *) R_alloc(n, sizeof(double)); |
| if (xx == NULL || yy == NULL) |
| error(_("out of memory while clipping polyline")); |
| |
| xx[0] = x1 = x[0]; |
| yy[0] = y1 = y[0]; |
| count = 1; |
| |
| for (i = 1; i < n; i++) { |
| x2 = x[i]; |
| y2 = y[i]; |
| if (CSclipline(&x1, &y1, &x2, &y2, &cr, &ind1, &ind2, dd)) { |
| if (ind1 && ind2) { |
| xx[0] = x1; |
| yy[0] = y1; |
| xx[1] = x2; |
| yy[1] = y2; |
| dd->dev->polyline(2, xx, yy, gc, dd->dev); |
| } |
| else if (ind1) { |
| xx[0] = x1; |
| yy[0] = y1; |
| xx[1] = x2; |
| yy[1] = y2; |
| count = 2; |
| if (i == n - 1) |
| dd->dev->polyline(count, xx, yy, gc, dd->dev); |
| } |
| else if (ind2) { |
| xx[count] = x2; |
| yy[count] = y2; |
| count++; |
| if (count > 1) |
| dd->dev->polyline(count, xx, yy, gc, dd->dev); |
| } |
| else { |
| xx[count] = x2; |
| yy[count] = y2; |
| count++; |
| if (i == n - 1 && count > 1) |
| dd->dev->polyline(count, xx, yy, gc, dd->dev); |
| } |
| } |
| x1 = x[i]; |
| y1 = y[i]; |
| } |
| |
| vmaxset(vmax); |
| } |
| |
| /**************************************************************** |
| * GEPolyline |
| **************************************************************** |
| */ |
| /* Clip and draw the polyline. |
| If clipToDevice = 0, clip according to dd->dev->gp.xpd |
| If clipToDevice = 1, clip to the device extent */ |
| static void clipPolyline(int n, double *x, double *y, |
| const pGEcontext gc, int clipToDevice, pGEDevDesc dd) |
| { |
| CScliplines(n, x, y, gc, clipToDevice, dd); |
| } |
| |
| /* Draw a series of line segments. */ |
| /* If the device canClip, R clips to the device extent and the device |
| does all other clipping */ |
| void GEPolyline(int n, double *x, double *y, const pGEcontext gc, pGEDevDesc dd) |
| { |
| if (gc->lwd == R_PosInf || gc->lwd < 0.0) |
| error(_("'lwd' must be non-negative and finite")); |
| if (ISNAN(gc->lwd) || gc->lty == LTY_BLANK) return; |
| if (dd->dev->canClip) { |
| clipPolyline(n, x, y, gc, 1, dd); /* clips to device extent |
| then draws */ |
| } |
| else |
| clipPolyline(n, x, y, gc, 0, dd); |
| } |
| |
| /**************************************************************** |
| * R code for clipping polygons |
| **************************************************************** |
| */ |
| |
| typedef enum { |
| Left = 0, |
| Right = 1, |
| Bottom = 2, |
| Top = 3 |
| } Edge; |
| |
| /* Clipper State Variables */ |
| typedef struct { |
| int first; /* true if we have seen the first point */ |
| double fx; /* x coord of the first point */ |
| double fy; /* y coord of the first point */ |
| double sx; /* x coord of the most recent point */ |
| double sy; /* y coord of the most recent point */ |
| } |
| GClipState; |
| |
| /* The Clipping Rectangle */ |
| typedef struct { |
| double xmin; |
| double xmax; |
| double ymin; |
| double ymax; |
| } |
| GClipRect; |
| |
| static |
| int inside (Edge b, double px, double py, GClipRect *clip) |
| { |
| switch (b) { |
| case Left: if (px < clip->xmin) return 0; break; |
| case Right: if (px > clip->xmax) return 0; break; |
| case Bottom: if (py < clip->ymin) return 0; break; |
| case Top: if (py > clip->ymax) return 0; break; |
| } |
| return 1; |
| } |
| |
| static |
| int cross (Edge b, double x1, double y1, double x2, double y2, |
| GClipRect *clip) |
| { |
| if (inside (b, x1, y1, clip) == inside (b, x2, y2, clip)) |
| return 0; |
| else return 1; |
| } |
| |
| static |
| void intersect (Edge b, double x1, double y1, double x2, double y2, |
| double *ix, double *iy, GClipRect *clip) |
| { |
| double m = 0; |
| |
| if (x1 != x2) m = (y1 - y2) / (x1 - x2); |
| switch (b) { |
| case Left: |
| *ix = clip->xmin; |
| *iy = y2 + (clip->xmin - x2) * m; |
| break; |
| case Right: |
| *ix = clip->xmax; |
| *iy = y2 + (clip->xmax - x2) * m; |
| break; |
| case Bottom: |
| *iy = clip->ymin; |
| if (x1 != x2) *ix = x2 + (clip->ymin - y2) / m; |
| else *ix = x2; |
| break; |
| case Top: |
| *iy = clip->ymax; |
| if (x1 != x2) *ix = x2 + (clip->ymax - y2) / m; |
| else *ix = x2; |
| break; |
| } |
| } |
| |
| static |
| void clipPoint (Edge b, double x, double y, |
| double *xout, double *yout, int *cnt, int store, |
| GClipRect *clip, GClipState *cs) |
| { |
| double ix = 0.0, iy = 0.0 /* -Wall */; |
| |
| if (!cs[b].first) { |
| /* No previous point exists for this edge. */ |
| /* Save this point. */ |
| cs[b].first = 1; |
| cs[b].fx = x; |
| cs[b].fy = y; |
| } |
| else |
| /* A previous point exists. */ |
| /* If 'p' and previous point cross edge, find intersection. */ |
| /* Clip against next boundary, if any. */ |
| /* If no more edges, add intersection to output list. */ |
| if (cross (b, x, y, cs[b].sx, cs[b].sy, clip)) { |
| intersect (b, x, y, cs[b].sx, cs[b].sy, &ix, &iy, clip); |
| if (b < Top) |
| clipPoint (b + 1, ix, iy, xout, yout, cnt, store, |
| clip, cs); |
| else { |
| if (store) { |
| xout[*cnt] = ix; |
| yout[*cnt] = iy; |
| } |
| (*cnt)++; |
| } |
| } |
| |
| /* Save as most recent point for this edge */ |
| cs[b].sx = x; |
| cs[b].sy = y; |
| |
| /* For all, if point is 'inside' */ |
| /* proceed to next clip edge, if any */ |
| if (inside (b, x, y, clip)) { |
| if (b < Top) |
| clipPoint (b + 1, x, y, xout, yout, cnt, store, clip, cs); |
| else { |
| if (store) { |
| xout[*cnt] = x; |
| yout[*cnt] = y; |
| } |
| (*cnt)++; |
| } |
| } |
| } |
| |
| static |
| void closeClip (double *xout, double *yout, int *cnt, int store, |
| GClipRect *clip, GClipState *cs) |
| { |
| double ix = 0.0, iy = 0.0 /* -Wall */; |
| Edge b; |
| |
| for (b = Left; b <= Top; b++) { |
| if (cross (b, cs[b].sx, cs[b].sy, cs[b].fx, cs[b].fy, clip)) { |
| intersect (b, cs[b].sx, cs[b].sy, |
| cs[b].fx, cs[b].fy, &ix, &iy, clip); |
| if (b < Top) |
| clipPoint (b + 1, ix, iy, xout, yout, cnt, store, clip, cs); |
| else { |
| if (store) { |
| xout[*cnt] = ix; |
| yout[*cnt] = iy; |
| } |
| (*cnt)++; |
| } |
| } |
| } |
| } |
| |
| static int clipPoly(double *x, double *y, int n, int store, int toDevice, |
| double *xout, double *yout, pGEDevDesc dd) |
| { |
| int i, cnt = 0; |
| GClipState cs[4]; |
| GClipRect clip; |
| for (i = 0; i < 4; i++) |
| cs[i].first = 0; |
| if (toDevice) |
| getClipRectToDevice(&clip.xmin, &clip.ymin, &clip.xmax, &clip.ymax, |
| dd); |
| else |
| getClipRect(&clip.xmin, &clip.ymin, &clip.xmax, &clip.ymax, dd); |
| for (i = 0; i < n; i++) |
| clipPoint (Left, x[i], y[i], xout, yout, &cnt, store, &clip, cs); |
| closeClip (xout, yout, &cnt, store, &clip, cs); |
| return (cnt); |
| } |
| |
| static void clipPolygon(int n, double *x, double *y, |
| const pGEcontext gc, int toDevice, pGEDevDesc dd) |
| { |
| double *xc = NULL, *yc = NULL; |
| const void *vmax = vmaxget(); |
| |
| /* if bg not specified then draw as polyline rather than polygon |
| * to avoid drawing line along border of clipping region |
| * If bg was NA then it has been converted to fully transparent */ |
| if (R_TRANSPARENT(gc->fill)) { |
| int i; |
| xc = (double*) R_alloc(n + 1, sizeof(double)); |
| yc = (double*) R_alloc(n + 1, sizeof(double)); |
| for (i=0; i<n; i++) { |
| xc[i] = x[i]; |
| yc[i] = y[i]; |
| } |
| xc[n] = x[0]; |
| yc[n] = y[0]; |
| GEPolyline(n+1, xc, yc, gc, dd); |
| } |
| else { |
| int npts; |
| xc = yc = 0; /* -Wall */ |
| npts = clipPoly(x, y, n, 0, toDevice, xc, yc, dd); |
| if (npts > 1) { |
| xc = (double*) R_alloc(npts, sizeof(double)); |
| yc = (double*) R_alloc(npts, sizeof(double)); |
| npts = clipPoly(x, y, n, 1, toDevice, xc, yc, dd); |
| dd->dev->polygon(npts, xc, yc, gc, dd->dev); |
| } |
| } |
| vmaxset(vmax); |
| } |
| |
| /**************************************************************** |
| * GEPolygon |
| **************************************************************** |
| */ |
| void GEPolygon(int n, double *x, double *y, const pGEcontext gc, pGEDevDesc dd) |
| { |
| /* |
| * Save (and reset below) the heap pointer to clean up |
| * after any R_alloc's done by functions I call. |
| */ |
| const void *vmaxsave = vmaxget(); |
| if (gc->lwd == R_PosInf || gc->lwd < 0.0) |
| error(_("'lwd' must be non-negative and finite")); |
| if (ISNAN(gc->lwd) || gc->lty == LTY_BLANK) |
| /* "transparent" border */ |
| gc->col = R_TRANWHITE; |
| if (dd->dev->canClip) { |
| /* |
| * If the device can clip, then we just clip to the device |
| * boundary and let the device do clipping within that. |
| * We do this to avoid problems where writing WAY off the |
| * device can cause problems for, e.g., ghostview |
| */ |
| clipPolygon(n, x, y, gc, 1, dd); |
| } |
| else |
| /* |
| * If the device can't clip, we have to do all the clipping |
| * ourselves. |
| */ |
| clipPolygon(n, x, y, gc, 0, dd); |
| vmaxset(vmaxsave); |
| } |
| |
| |
| /**************************************************************** |
| * R code for clipping circles |
| **************************************************************** |
| */ |
| /* Convert a circle into a polygon with specified number of vertices */ |
| static void convertCircle(double x, double y, double r, |
| int numVertices, double *xc, double *yc) |
| { |
| int i; |
| double theta = 2*M_PI/numVertices; |
| for (i=0; i<numVertices; i++) { |
| xc[i] = x + r*sin(theta*i); |
| yc[i] = y + r*cos(theta*i); |
| } |
| xc[numVertices] = x; |
| yc[numVertices] = y+r; |
| } |
| |
| /* Takes a specification of a circle as input and returns a code indicating |
| how the circle should be clipped. |
| The return value will be -1 if the circle is to |
| be totally clipped out of existence, -2 if the circle is to be |
| totally left alone, 0 and above if the circle has been converted |
| into a polygon (in which case, the return value indicates the |
| number of vertices of the polygon and the function convertCircle() |
| should be called to obtain the vertices of the polygon). */ |
| static int clipCircleCode(double x, double y, double r, |
| int toDevice, pGEDevDesc dd) |
| { |
| int result; |
| /* determine clipping region */ |
| double xmin, xmax, ymin, ymax; |
| if (toDevice) |
| getClipRectToDevice(&xmin, &ymin, &xmax, &ymax, dd); |
| else |
| getClipRect(&xmin, &ymin, &xmax, &ymax, dd); |
| |
| /* if circle is all within clipping rect */ |
| if (x-r > xmin && x+r < xmax && y-r > ymin && y+r < ymax) { |
| result = -2; |
| } |
| /* if circle is all outside clipping rect */ |
| else { |
| double distance = r*r; |
| if (x-r > xmax || x+r < xmin || y-r > ymax || y+r < ymin || |
| (x < xmin && y < ymin && |
| ((x-xmin)*(x-xmin)+(y-ymin)*(y-ymin) > distance)) || |
| (x > xmax && y < ymin && |
| ((x-xmax)*(x-xmax)+(y-ymin)*(y-ymin) > distance)) || |
| (x < xmin && y > ymax && |
| ((x-xmin)*(x-xmin)+(y-ymax)*(y-ymax) > distance)) || |
| (x > xmax && y > ymax && |
| ((x-xmax)*(x-xmax)+(y-ymax)*(y-ymax) > distance))) { |
| result = -1; |
| } |
| /* otherwise, convert circle to polygon */ |
| else { |
| /* Replace circle with polygon. |
| |
| Heuristic for number of vertices is to use theta so |
| that cos(theta)*r ~ r - 1 in device units. This is |
| roughly const * sqrt(r) so there'd be little point in |
| enforcing an upper limit. */ |
| |
| result = (r <= 6) ? 10 : (int)(2 * M_PI/acos(1 - 1/r)); |
| } |
| } |
| return result; |
| } |
| |
| /**************************************************************** |
| * GECircle |
| **************************************************************** |
| */ |
| void GECircle(double x, double y, double radius, const pGEcontext gc, pGEDevDesc dd) |
| { |
| const void *vmax; |
| double *xc, *yc; |
| int result; |
| |
| /* There is no point in trying to plot a circle of zero radius */ |
| if (radius <= 0.0) return; |
| |
| if (gc->lwd == R_PosInf || gc->lwd < 0.0) |
| error(_("'lwd' must be non-negative and finite")); |
| if (ISNAN(gc->lwd) || gc->lty == LTY_BLANK) |
| /* "transparent" border */ |
| gc->col = R_TRANWHITE; |
| /* |
| * If the device can clip, then we just clip to the device |
| * boundary and let the device do clipping within that. |
| * We do this to avoid problems where writing WAY off the |
| * device can cause problems for, e.g., ghostview |
| * |
| * If the device can't clip, we have to do all the clipping |
| * ourselves. |
| */ |
| result = clipCircleCode(x, y, radius, dd->dev->canClip, dd); |
| |
| switch (result) { |
| case -2: /* No clipping; draw all of circle */ |
| /* |
| * If we did the clipping, then the circle is entirely |
| * within the current clipping rect. |
| * |
| * If the device can clip then we just clipped to the device |
| * boundary so the circle is entirely within the device; the |
| * device will perform the clipping to the current clipping rect. |
| */ |
| dd->dev->circle(x, y, radius, gc, dd->dev); |
| break; |
| case -1: /* Total clipping; draw nothing */ |
| /* |
| * If we did the clipping, then the circle is entirely outside |
| * the current clipping rect, so there is nothing to draw. |
| * |
| * If the device can clip then we just determined that the |
| * circle is entirely outside the device, so again there is |
| * nothing to draw |
| */ |
| break; |
| default: /* Partial clipping; draw poly[line|gon] */ |
| /* |
| * If we did the clipping this means that the circle |
| * intersects the current clipping rect and we need to |
| * convert to a poly[line|gon] and draw that. |
| * |
| * If the device can clip then we just determined that the |
| * circle intersects the device boundary. We assume that the |
| * circle is not so big that other parts may be WAY off the |
| * device and just draw a circle. |
| */ |
| if (dd->dev->canClip) { |
| dd->dev->circle(x, y, radius, gc, dd->dev); |
| } |
| else { |
| vmax = vmaxget(); |
| xc = (double*)R_alloc(result+1, sizeof(double)); |
| yc = (double*)R_alloc(result+1, sizeof(double)); |
| convertCircle(x, y, radius, result, xc, yc); |
| if (R_TRANSPARENT(gc->fill)) { |
| GEPolyline(result+1, xc, yc, gc, dd); |
| } |
| else { |
| int npts; |
| double *xcc, *ycc; |
| xcc = ycc = 0; /* -Wall */ |
| npts = clipPoly(xc, yc, result, 0, !dd->dev->canClip, |
| xcc, ycc, dd); |
| if (npts > 1) { |
| xcc = (double*)R_alloc(npts, sizeof(double)); |
| ycc = (double*)R_alloc(npts, sizeof(double)); |
| npts = clipPoly(xc, yc, result, 1, !dd->dev->canClip, |
| xcc, ycc, dd); |
| dd->dev->polygon(npts, xcc, ycc, gc, dd->dev); |
| } |
| } |
| vmaxset(vmax); |
| } |
| } |
| } |
| |
| /**************************************************************** |
| * R code for clipping rectangles |
| **************************************************************** |
| */ |
| /* Return a code indicating how the rectangle should be clipped. |
| 0 means the rectangle is totally outside the clip region |
| 1 means the rectangle is totally inside the clip region |
| 2 means the rectangle intersects the clip region */ |
| static int clipRectCode(double x0, double y0, double x1, double y1, |
| int toDevice, pGEDevDesc dd) |
| { |
| int result; |
| /* determine clipping region */ |
| double xmin, xmax, ymin, ymax; |
| if (toDevice) |
| getClipRectToDevice(&xmin, &ymin, &xmax, &ymax, dd); |
| else |
| getClipRect(&xmin, &ymin, &xmax, &ymax, dd); |
| |
| if ((x0 < xmin && x1 < xmin) || (x0 > xmax && x1 > xmax) || |
| (y0 < ymin && y1 < ymin) || (y0 > ymax && y1 > ymax)) |
| result = 0; |
| else if ((x0 > xmin && x0 < xmax) && (x1 > xmin && x1 < xmax) && |
| (y0 > ymin && y0 < ymax) && (y1 > ymin && y1 < ymax)) |
| result = 1; |
| else |
| result = 2; |
| |
| return result; |
| } |
| |
| /**************************************************************** |
| * GERect |
| **************************************************************** |
| */ |
| /* Filled with color fill and outlined with color col */ |
| /* These may both be fully transparent */ |
| void GERect(double x0, double y0, double x1, double y1, |
| const pGEcontext gc, pGEDevDesc dd) |
| { |
| const void *vmax; |
| double *xc, *yc; |
| int result; |
| |
| if (gc->lwd == R_PosInf || gc->lwd < 0.0) |
| error(_("'lwd' must be non-negative and finite")); |
| if (ISNAN(gc->lwd) || gc->lty == LTY_BLANK) |
| /* "transparent" border */ |
| gc->col = R_TRANWHITE; |
| /* |
| * For clipping logic, see comments in GECircle |
| */ |
| result = clipRectCode(x0, y0, x1, y1, dd->dev->canClip, dd); |
| switch (result) { |
| case 0: /* rectangle totally clipped; draw nothing */ |
| break; |
| case 1: /* rectangle totally inside; draw all */ |
| dd->dev->rect(x0, y0, x1, y1, gc, dd->dev); |
| break; |
| case 2: /* rectangle intersects clip region; use polygon clipping */ |
| if (dd->dev->canClip) |
| dd->dev->rect(x0, y0, x1, y1, gc, dd->dev); |
| else { |
| vmax = vmaxget(); |
| xc = (double*)R_alloc(5, sizeof(double)); |
| yc = (double*)R_alloc(5, sizeof(double)); |
| xc[0] = x0; yc[0] = y0; |
| xc[1] = x0; yc[1] = y1; |
| xc[2] = x1; yc[2] = y1; |
| xc[3] = x1; yc[3] = y0; |
| xc[4] = x0; yc[4] = y0; |
| if (R_TRANSPARENT(gc->fill)) { |
| GEPolyline(5, xc, yc, gc, dd); |
| } |
| else { /* filled rectangle */ |
| int npts; |
| double *xcc, *ycc; |
| xcc = ycc = 0; /* -Wall */ |
| npts = clipPoly(xc, yc, 4, 0, !dd->dev->canClip, xcc, ycc, dd); |
| if (npts > 1) { |
| xcc = (double*)R_alloc(npts, sizeof(double)); |
| ycc = (double*)R_alloc(npts, sizeof(double)); |
| npts = clipPoly(xc, yc, 4, 1, !dd->dev->canClip, xcc, ycc, dd); |
| dd->dev->polygon(npts, xcc, ycc, gc, dd->dev); |
| } |
| } |
| vmaxset(vmax); |
| } |
| } |
| } |
| |
| /**************************************************************** |
| * GEPath |
| **************************************************************** |
| */ |
| |
| void GEPath(double *x, double *y, |
| int npoly, int *nper, |
| Rboolean winding, |
| const pGEcontext gc, pGEDevDesc dd) |
| { |
| /* safety check: this will be NULL if the device did not set it. */ |
| if (!dd->dev->path) { |
| warning(_("path rendering is not implemented for this device")); |
| return; |
| } |
| /* FIXME: what about clipping? (if the device can't) |
| */ |
| if (gc->lwd == R_PosInf || gc->lwd < 0.0) |
| error(_("'lwd' must be non-negative and finite")); |
| if (ISNAN(gc->lwd) || gc->lty == LTY_BLANK) |
| gc->col = R_TRANWHITE; |
| if (npoly > 0) { |
| int i; |
| int draw = 1; |
| for (i=0; i < npoly; i++) { |
| if (nper[i] < 2) { |
| draw = 0; |
| } |
| } |
| if (draw) { |
| dd->dev->path(x, y, npoly, nper, winding, gc, dd->dev); |
| } else { |
| error(_("Invalid graphics path")); |
| } |
| } |
| } |
| |
| /**************************************************************** |
| * GERaster |
| **************************************************************** |
| */ |
| |
| void GERaster(unsigned int *raster, int w, int h, |
| double x, double y, |
| double width, double height, |
| double angle, |
| Rboolean interpolate, |
| const pGEcontext gc, pGEDevDesc dd) |
| { |
| /* safety check: this will be NULL if the device did not set it. */ |
| if (!dd->dev->raster) { |
| warning(_("raster rendering is not implemented for this device")); |
| return; |
| } |
| |
| /* FIXME: what about clipping? (if the device can't) |
| * Maybe not too bad because it is just a matter of shaving off |
| * some rows and columns from the image? (because R only does |
| * rectangular clipping regions) */ |
| |
| if (width != 0 && height != 0) { |
| dd->dev->raster(raster, w, h, x, y, width, height, |
| angle, interpolate, gc, dd->dev); |
| } |
| } |
| |
| /**************************************************************** |
| * GERaster |
| **************************************************************** |
| */ |
| |
| SEXP GECap(pGEDevDesc dd) |
| { |
| /* safety check: this will be NULL if the device did not set it. */ |
| if (!dd->dev->cap) { |
| warning(_("raster capture is not available for this device")); |
| return R_NilValue; |
| } |
| return dd->dev->cap(dd->dev); |
| } |
| |
| /**************************************************************** |
| * R code for clipping text |
| **************************************************************** |
| */ |
| |
| /* Return a code indicating how the text should be clipped |
| NOTE that x, y indicate the bottom-left of the text |
| NOTE also also that this is a bit crude because it actually uses |
| a bounding box for the entire text to determine the clipping code. |
| This will mean that in certain (very rare ?) cases, a piece of |
| text will be characterised as intersecting with the clipping region |
| when in fact it lies totally outside the clipping region. But |
| this is not a problem because the final output will still be correct. |
| 0 means totally outside clip region |
| 1 means totally inside clip region |
| 2 means intersects clip region */ |
| static int clipTextCode(double x, double y, const char *str, cetype_t enc, |
| double width, double height, double rot, double hadj, |
| const pGEcontext gc, int toDevice, pGEDevDesc dd) |
| { |
| double x0, x1, x2, x3, y0, y1, y2, y3, left, right, bottom, top; |
| double length, theta2; |
| double angle = DEG2RAD * rot; |
| double theta1 = M_PI/2 - angle; |
| double widthInches, heightInches, xInches, yInches; |
| |
| if (!R_FINITE(width)) width = GEStrWidth(str, enc, gc, dd); |
| if (!R_FINITE(height)) height = GEStrHeight(str, enc, gc, dd); |
| |
| /* Work in inches */ |
| widthInches = fromDeviceWidth(width, GE_INCHES, dd); |
| heightInches = fromDeviceHeight(height, GE_INCHES, dd); |
| xInches = fromDeviceX(x, GE_INCHES, dd); |
| yInches = fromDeviceY(y, GE_INCHES, dd); |
| |
| length = hypot(widthInches, heightInches); |
| theta2 = angle + atan2(heightInches, widthInches); |
| |
| x = xInches - hadj*widthInches*cos(angle); |
| y = yInches - hadj*widthInches*sin(angle); |
| x0 = x + heightInches*cos(theta1); |
| x1 = x; |
| x2 = x + length*cos(theta2); |
| x3 = x + widthInches*cos(angle); |
| y0 = y + heightInches*sin(theta1); |
| y1 = y; |
| y2 = y + length*sin(theta2); |
| y3 = y + widthInches*sin(angle); |
| left = fmin2(fmin2(x0, x1), fmin2(x2, x3)); |
| right = fmax2(fmax2(x0, x1), fmax2(x2, x3)); |
| bottom = fmin2(fmin2(y0, y1), fmin2(y2, y3)); |
| top = fmax2(fmax2(y0, y1), fmax2(y2, y3)); |
| return clipRectCode(toDeviceX(left, GE_INCHES, dd), |
| toDeviceY(bottom, GE_INCHES, dd), |
| toDeviceX(right, GE_INCHES, dd), |
| toDeviceY(top, GE_INCHES, dd), |
| toDevice, dd); |
| } |
| |
| static void clipText(double x, double y, const char *str, cetype_t enc, |
| double width, double height, double rot, double hadj, |
| const pGEcontext gc, int toDevice, pGEDevDesc dd) |
| { |
| int result = clipTextCode(x, y, str, enc, width, height, rot, hadj, |
| gc, toDevice, dd); |
| void (*textfn)(double x, double y, const char *str, double rot, |
| double hadj, const pGEcontext gc, pDevDesc dd); |
| /* This guards against uninitialized values, e.g. devices installed |
| in earlier versions of R */ |
| textfn = (dd->dev->hasTextUTF8 ==TRUE) && enc == CE_UTF8 ? |
| dd->dev->textUTF8 : dd->dev->text; |
| |
| switch (result) { |
| case 0: /* text totally clipped; draw nothing */ |
| break; |
| case 1: /* text totally inside; draw all */ |
| textfn(x, y, str, rot, hadj, gc, dd->dev); |
| break; |
| case 2: /* text intersects clip region |
| act according to value of clipToDevice */ |
| if (toDevice) /* Device will do clipping */ |
| textfn(x, y, str, rot, hadj, gc, dd->dev); |
| else /* don't draw anything; this could be made less crude :) */ |
| ; |
| } |
| } |
| |
| /**************************************************************** |
| * Code for determining when to branch to vfont code from GEText |
| **************************************************************** |
| */ |
| |
| typedef struct { |
| char *name; |
| int minface; |
| int maxface; |
| } VFontTab; |
| |
| static VFontTab |
| VFontTable[] = { |
| { "HersheySerif", 1, 7 }, |
| /* |
| HersheySerif |
| HersheySerif-Italic |
| HersheySerif-Bold |
| HersheySerif-BoldItalic |
| HersheyCyrillic |
| HersheyCyrillic-Oblique |
| HersheyEUC |
| */ |
| { "HersheySans", 1, 4 }, |
| /* |
| HersheySans |
| HersheySans-Oblique |
| HersheySans-Bold |
| HersheySans-BoldOblique |
| */ |
| { "HersheyScript", 1, 4 }, |
| /* |
| HersheyScript |
| HersheyScript |
| HersheyScript-Bold |
| HersheyScript-Bold |
| */ |
| { "HersheyGothicEnglish", 1, 1 }, |
| { "HersheyGothicGerman", 1, 1 }, |
| { "HersheyGothicItalian", 1, 1 }, |
| { "HersheySymbol", 1, 4 }, |
| /* |
| HersheySerifSymbol |
| HersheySerifSymbol-Oblique |
| HersheySerifSymbol-Bold |
| HersheySerifSymbol-BoldOblique |
| */ |
| { "HersheySansSymbol", 1, 2 }, |
| /* |
| HersheySansSymbol |
| HersheySansSymbol-Oblique |
| */ |
| |
| { NULL, 0, 0 }, |
| }; |
| |
| /* A Hershey family (all of which have names starting with Hershey) may |
| have had the eighth byte changed to the family code (1...8), so |
| saving further table lookups. |
| |
| (Done by GEText and GEStrWidth/Height, and also set that way in the |
| graphics package's plot.c for C_text, C_strWidth and C_strheight, |
| and in plot3d.c for C_contour.) |
| */ |
| static int VFontFamilyCode(char *fontfamily) |
| { |
| if (strlen(fontfamily) > 7) { |
| unsigned int j = fontfamily[7]; // protect against signed chars |
| if (!strncmp(fontfamily, "Hershey", 7) && j < 9) return 100 + j; |
| for (int i = 0; VFontTable[i].minface; i++) |
| if (!strcmp(fontfamily, VFontTable[i].name)) return i + 1; |
| } |
| return -1; |
| } |
| |
| static int VFontFaceCode(int familycode, int fontface) { |
| int face = fontface; |
| familycode--; /* Table is 0-based, coding is 1-based */ |
| /* |
| * R's "font" par has historically made 2=bold and 3=italic |
| * These must be switched to correspond to Hershey fontfaces |
| */ |
| if (fontface == 2) |
| face = 3; |
| else if (fontface == 3) |
| face = 2; |
| /* |
| * If font face is outside supported set of faces for font |
| * family, either convert or throw and error |
| */ |
| if (!(face >= VFontTable[familycode].minface && |
| face <= VFontTable[familycode].maxface)) { |
| /* |
| * Silently convert standard faces to closest match |
| */ |
| switch (face) { |
| /* |
| * italic becomes plain (gothic only) |
| */ |
| case 2: |
| /* |
| * bold becomes plain |
| */ |
| case 3: |
| face = 1; |
| break; |
| /* |
| * bold-italic becomes italic for gothic fonts |
| * and bold for sans symbol font |
| */ |
| case 4: |
| if (familycode == 7) |
| face = 2; |
| else |
| face = 1; |
| break; |
| default: |
| /* |
| * Other font faces just too wacky so throw an error |
| */ |
| error(_("font face %d not supported for font family '%s'"), |
| fontface, VFontTable[familycode].name); |
| } |
| } |
| return face; |
| } |
| |
| /**************************************************************** |
| * GEText |
| **************************************************************** |
| */ |
| /* If you want EXACT centering of text (e.g., like in GSymbol) */ |
| /* then pass NA_REAL for xc and yc */ |
| void GEText(double x, double y, const char * const str, cetype_t enc, |
| double xc, double yc, double rot, |
| const pGEcontext gc, pGEDevDesc dd) |
| { |
| /* |
| * If the fontfamily is a Hershey font family, call R_GE_VText |
| */ |
| int vfontcode = VFontFamilyCode(gc->fontfamily); |
| if (vfontcode >= 100) { |
| R_GE_VText(x, y, str, enc, xc, yc, rot, gc, dd); |
| } else if (vfontcode >= 0) { |
| gc->fontfamily[7] = (char) vfontcode; |
| gc->fontface = VFontFaceCode(vfontcode, gc->fontface); |
| R_GE_VText(x, y, str, enc, xc, yc, rot, gc, dd); |
| } else { |
| /* PR#7397: this seemed to reset R_Visible */ |
| Rboolean savevis = R_Visible; |
| int noMetricInfo = -1; |
| char *sbuf = NULL; |
| if(str && *str) { |
| const char *s; |
| char *sb; |
| int i, n; |
| cetype_t enc2; |
| double xoff, yoff, hadj; |
| double sin_rot, cos_rot;/* sin() & cos() of rot{ation} in radians */ |
| double xleft, ybottom; |
| const void *vmax = vmaxget(); |
| |
| enc2 = (gc->fontface == 5) ? CE_SYMBOL : enc; |
| if(enc2 != CE_SYMBOL) |
| enc2 = (dd->dev->hasTextUTF8 == TRUE) ? CE_UTF8 : CE_NATIVE; |
| else if(dd->dev->wantSymbolUTF8 == TRUE) enc2 = CE_UTF8; |
| else if(dd->dev->wantSymbolUTF8 == NA_LOGICAL) { |
| enc = CE_LATIN1; |
| enc2 = CE_UTF8; |
| } |
| |
| #ifdef DEBUG_MI |
| printf("string %s, enc %d, %d\n", str, enc, enc2); |
| #endif |
| |
| /* We work in GE_INCHES */ |
| x = fromDeviceX(x, GE_INCHES, dd); |
| y = fromDeviceY(y, GE_INCHES, dd); |
| /* Count the lines of text */ |
| n = 1; |
| for(s = str; *s ; s++) |
| if (*s == '\n') n++; |
| /* Allocate a temporary buffer */ |
| sb = sbuf = (char*) R_alloc(strlen(str) + 1, sizeof(char)); |
| i = 0; |
| sin_rot = DEG2RAD * rot; |
| cos_rot = cos(sin_rot); |
| sin_rot = sin(sin_rot); |
| for(s = str; ; s++) { |
| if (*s == '\n' || *s == '\0') { |
| double w = NA_REAL, h = NA_REAL; |
| const char *str; |
| *sb = '\0'; |
| /* This may R_alloc, but let's assume that |
| there are not many lines of text per string */ |
| str = reEnc(sbuf, enc, enc2, 2); |
| if (n > 1) { |
| /* first determine location of THIS line */ |
| if (!R_FINITE(xc)) |
| xc = 0.5; |
| if (!R_FINITE(yc)) |
| yc = 0.5; |
| yoff = (1 - yc)*(n - 1) - i; |
| /* cra is based on the font pointsize at the |
| * time the device was created. |
| * Adjust for potentially different current pointsize. |
| * This is a crude calculation that might be better |
| * performed using a device call that responds with |
| * the current font pointsize in device coordinates. |
| */ |
| yoff = fromDeviceHeight(yoff * gc->lineheight * |
| gc->cex * dd->dev->cra[1] * |
| gc->ps/dd->dev->startps, |
| GE_INCHES, dd); |
| xoff = - yoff*sin_rot; |
| yoff = yoff*cos_rot; |
| xoff = x + xoff; |
| yoff = y + yoff; |
| } else { |
| xoff = x; |
| yoff = y; |
| } |
| /* now determine bottom-left for THIS line */ |
| if(xc != 0.0 || yc != 0.0) { |
| double width, height = 0.0 /* -Wall */; |
| w = GEStrWidth(str, enc2, gc, dd); |
| width = fromDeviceWidth(w, GE_INCHES, dd); |
| if (!R_FINITE(xc)) |
| xc = 0.5; |
| if (!R_FINITE(yc)) { |
| /* "exact" vertical centering */ |
| /* If font metric info is available AND */ |
| /* there is only one line, use GMetricInfo & yc=0.5 */ |
| /* Otherwise use GEStrHeight and fiddle yc */ |
| double h, d, w; |
| if (noMetricInfo < 0) { |
| GEMetricInfo('M', gc, &h, &d, &w, dd); |
| noMetricInfo = (h == 0 && d == 0 && w == 0) ? 1 : 0; |
| } |
| if (n > 1 || noMetricInfo) { |
| h = GEStrHeight(str, enc2, gc, dd); |
| height = fromDeviceHeight(h, GE_INCHES, dd); |
| yc = dd->dev->yCharOffset; |
| } else { |
| double maxHeight = 0.0; |
| double maxDepth = 0.0; |
| const char *ss = str; |
| int charNum = 0; |
| Rboolean done = FALSE; |
| /* Symbol fonts are not encoded in MBCS ever */ |
| if(enc2 != CE_SYMBOL && !strIsASCII(ss)) { |
| if(mbcslocale && enc2 == CE_NATIVE) { |
| /* FIXME: This assumes that wchar_t is UCS-2/4, |
| since that is what GEMetricInfo expects */ |
| size_t n = strlen(ss), used; |
| wchar_t wc; |
| mbstate_t mb_st; |
| mbs_init(&mb_st); |
| while ((used = mbrtowc(&wc, ss, n, &mb_st)) > 0) { |
| #ifdef DEBUG_MI |
| printf(" centring %s aka %d in MBCS\n", ss, wc); |
| #endif |
| GEMetricInfo((int) wc, gc, &h, &d, &w, dd); |
| h = fromDeviceHeight(h, GE_INCHES, dd); |
| d = fromDeviceHeight(d, GE_INCHES, dd); |
| if (charNum++ == 0) { |
| maxHeight = h; |
| maxDepth = d; |
| } else { |
| if (h > maxHeight) maxHeight = h; |
| if (d > maxDepth) maxDepth = d; |
| } |
| ss += used; n -=used; |
| } |
| done = TRUE; |
| } else if (enc2 == CE_UTF8) { |
| size_t used; |
| wchar_t wc; |
| while ((used = utf8toucs(&wc, ss)) > 0) { |
| if (IS_HIGH_SURROGATE(wc)) |
| GEMetricInfo(-(int)utf8toucs32(wc, ss), gc, &h, &d, &w, dd); |
| else |
| GEMetricInfo(-(int) wc, gc, &h, &d, &w, dd); |
| h = fromDeviceHeight(h, GE_INCHES, dd); |
| d = fromDeviceHeight(d, GE_INCHES, dd); |
| #ifdef DEBUG_MI |
| printf(" centring %s aka %d in UTF-8, %f %f\n", ss, wc, h, d); |
| #endif |
| if (charNum++ == 0) { |
| maxHeight = h; |
| maxDepth = d; |
| } else { |
| if (h > maxHeight) maxHeight = h; |
| if (d > maxDepth) maxDepth = d; |
| } |
| ss += used; |
| } |
| done = TRUE; |
| } |
| } |
| if(!done) { |
| for (ss = str; *ss; ss++) { |
| GEMetricInfo((unsigned char) *ss, gc, |
| &h, &d, &w, dd); |
| h = fromDeviceHeight(h, GE_INCHES, dd); |
| d = fromDeviceHeight(d, GE_INCHES, dd); |
| #ifdef DEBUG_MI |
| printf("metric info for %d, %f %f\n", |
| (unsigned char) *ss, h, d); |
| #endif |
| /* Set maxHeight and maxDepth from height |
| and depth of first char. |
| Must NOT set to 0 in case there is |
| only 1 char and it has negative |
| height or depth |
| */ |
| if (charNum++ == 0) { |
| maxHeight = h; |
| maxDepth = d; |
| } else { |
| if (h > maxHeight) maxHeight = h; |
| if (d > maxDepth) maxDepth = d; |
| } |
| } |
| } |
| height = maxHeight - maxDepth; |
| yc = 0.5; |
| } |
| } else { |
| h = GEStrHeight(str, CE_NATIVE, gc, dd); |
| height = fromDeviceHeight(h, GE_INCHES, dd); |
| } |
| if (dd->dev->canHAdj == 2) hadj = xc; |
| else if (dd->dev->canHAdj == 1) { |
| hadj = 0.5 * floor(2*xc + 0.5); |
| /* limit to 0, 0.5, 1 */ |
| hadj = (hadj > 1.0) ? 1.0 :((hadj < 0.0) ? 0.0 : hadj); |
| } else hadj = 0.0; |
| xleft = xoff - (xc-hadj)*width*cos_rot + yc*height*sin_rot; |
| ybottom = yoff - (xc-hadj)*width*sin_rot - |
| yc*height*cos_rot; |
| } else { /* xc = yc = 0.0 */ |
| xleft = xoff; |
| ybottom = yoff; |
| hadj = 0.0; |
| } |
| /* Convert GE_INCHES back to device. |
| */ |
| xleft = toDeviceX(xleft, GE_INCHES, dd); |
| ybottom = toDeviceY(ybottom, GE_INCHES, dd); |
| clipText(xleft, ybottom, str, enc2, w, h, rot, hadj, |
| gc, dd->dev->canClip, dd); |
| sb = sbuf; |
| i++; |
| } |
| else *sb++ = *s; |
| if (!*s) break; |
| } |
| vmaxset(vmax); |
| } |
| R_Visible = savevis; |
| } |
| } |
| |
| /**************************************************************** |
| * GEXspline |
| **************************************************************** |
| */ |
| |
| #include "xspline.c" |
| |
| /* |
| * Draws a "curve" through the specified control points. |
| * Return the vertices of the line that gets drawn. |
| |
| * NB: this works in device coordinates. To make it work correctly |
| * with non-square 'pixels' we use the x-dimensions only. |
| */ |
| SEXP GEXspline(int n, double *x, double *y, double *s, Rboolean open, |
| Rboolean repEnds, |
| Rboolean draw, /* May be called just to get points */ |
| const pGEcontext gc, pGEDevDesc dd) |
| { |
| /* |
| * Use xspline.c code to generate points to draw |
| * Draw polygon or polyline from points |
| */ |
| SEXP result = R_NilValue; |
| int i; |
| double *ipr = dd->dev->ipr, asp = ipr[0]/ipr[1], *ys; |
| /* |
| * Save (and reset below) the heap pointer to clean up |
| * after any R_alloc's done by functions I call. |
| */ |
| const void *vmaxsave = vmaxget(); |
| ys = (double *) R_alloc(n, sizeof(double)); |
| for (i = 0; i < n; i++) ys[i] = y[i]*asp; |
| if (open) { |
| compute_open_spline(n, x, ys, s, repEnds, LOW_PRECISION, dd); |
| if (draw) { |
| GEPolyline(npoints, xpoints, ypoints, gc, dd); |
| } |
| } else { |
| compute_closed_spline(n, x, ys, s, LOW_PRECISION, dd); |
| if (draw) |
| GEPolygon(npoints, xpoints, ypoints, gc, dd); |
| } |
| if (npoints > 1) { |
| SEXP xpts, ypts; |
| int i; |
| PROTECT(xpts = allocVector(REALSXP, npoints)); |
| PROTECT(ypts = allocVector(REALSXP, npoints)); |
| for (i = 0; i < npoints; i++) { |
| REAL(xpts)[i] = xpoints[i]; |
| REAL(ypts)[i] = ypoints[i]/asp; |
| } |
| PROTECT(result = allocVector(VECSXP, 2)); |
| SET_VECTOR_ELT(result, 0, xpts); |
| SET_VECTOR_ELT(result, 1, ypts); |
| UNPROTECT(3); |
| } |
| vmaxset(vmaxsave); |
| return result; |
| } |
| |
| |
| /**************************************************************** |
| * GEMode |
| **************************************************************** |
| */ |
| /* Check that everything is initialized : |
| Interpretation : |
| mode = 0, graphics off |
| mode = 1, graphics on |
| mode = 2, graphical input on (ignored by most drivers) |
| */ |
| void GEMode(int mode, pGEDevDesc dd) |
| { |
| if (NoDevices()) |
| error(_("no graphics device is active")); |
| if(dd->dev->mode) dd->dev->mode(mode, dd->dev); |
| } |
| |
| /**************************************************************** |
| * GESymbol |
| **************************************************************** |
| */ |
| #define SMALL 0.25 |
| #define RADIUS 0.375 |
| #define SQRC 0.88622692545275801364 /* sqrt(pi / 4) */ |
| #define DMDC 1.25331413731550025119 /* sqrt(pi / 4) * sqrt(2) */ |
| #define TRC0 1.55512030155621416073 /* sqrt(4 * pi/(3 * sqrt(3))) */ |
| #define TRC1 1.34677368708859836060 /* TRC0 * sqrt(3) / 2 */ |
| #define TRC2 0.77756015077810708036 /* TRC0 / 2 */ |
| /* Draw one of the R special symbols. */ |
| /* "size" is in device coordinates and is assumed to be a width |
| * rather than a height. |
| * This could cause a problem for devices which have ipr[0] != ipr[1] |
| * The problem would be evident where calculations are done on |
| * angles -- in those cases, a conversion to and from GE_INCHES is done |
| * to preserve angles. |
| */ |
| void GESymbol(double x, double y, int pch, double size, |
| const pGEcontext gc, pGEDevDesc dd) |
| { |
| double r, xc, yc; |
| double xx[4], yy[4]; |
| unsigned int maxchar; |
| |
| maxchar = (mbcslocale && gc->fontface != 5) ? 127 : 255; |
| /* Special cases for plotting pch="." or pch=<character> |
| */ |
| if(pch == NA_INTEGER) /* do nothing */; |
| else if(pch < 0) { |
| size_t res; |
| char str[16]; // probably 7 would do |
| if(gc->fontface == 5) |
| error("use of negative pch with symbol font is invalid"); |
| res = ucstoutf8(str, -pch); // throws error if unsuccessful |
| str[res] = '\0'; |
| GEText(x, y, str, CE_UTF8, NA_REAL, NA_REAL, 0., gc, dd); |
| } else if(' ' <= pch && pch <= maxchar) { |
| if (pch == '.') { |
| /* |
| * NOTE: we are *filling* a rect with the current |
| * colour (we are not drawing the border AND we are |
| * not using the current fill colour) |
| */ |
| gc->fill = gc->col; |
| gc->col = R_TRANWHITE; |
| /* |
| The idea here is to use a 0.01" square, but to be of |
| at least one device unit in each direction, |
| assuming that corresponds to pixels. That may be odd if |
| pixels are not square, but only on low resolution |
| devices where we can do nothing better. |
| |
| For this symbol only, size is cex (see engine.c). |
| |
| Prior to 2.1.0 the offsets were always 0.5. |
| */ |
| xc = size * fabs(toDeviceWidth(0.005, GE_INCHES, dd)); |
| yc = size * fabs(toDeviceHeight(0.005, GE_INCHES, dd)); |
| if(size > 0 && xc < 0.5) xc = 0.5; |
| if(size > 0 && yc < 0.5) yc = 0.5; |
| GERect(x-xc, y-yc, x+xc, y+yc, gc, dd); |
| } else { |
| char str[2]; |
| str[0] = (char) pch; |
| str[1] = '\0'; |
| GEText(x, y, str, |
| (gc->fontface == 5) ? CE_SYMBOL : CE_NATIVE, |
| NA_REAL, NA_REAL, 0., gc, dd); |
| } |
| } |
| else if(pch > maxchar) |
| warning(_("pch value '%d' is invalid in this locale"), pch); |
| else { |
| double GSTR_0 = fromDeviceWidth(size, GE_INCHES, dd); |
| |
| switch(pch) { |
| |
| case 0: /* S square */ |
| xc = toDeviceWidth(RADIUS * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(RADIUS * GSTR_0, GE_INCHES, dd); |
| gc->fill = R_TRANWHITE; |
| GERect(x-xc, y-yc, x+xc, y+yc, gc, dd); |
| break; |
| |
| case 1: /* S octahedron ( circle) */ |
| xc = RADIUS * size; /* NB: could be zero */ |
| gc->fill = R_TRANWHITE; |
| GECircle(x, y, xc, gc, dd); |
| break; |
| |
| case 2: /* S triangle - point up */ |
| xc = RADIUS * GSTR_0; |
| r = toDeviceHeight(TRC0 * xc, GE_INCHES, dd); |
| yc = toDeviceHeight(TRC2 * xc, GE_INCHES, dd); |
| xc = toDeviceWidth(TRC1 * xc, GE_INCHES, dd); |
| xx[0] = x; yy[0] = y+r; |
| xx[1] = x+xc; yy[1] = y-yc; |
| xx[2] = x-xc; yy[2] = y-yc; |
| gc->fill = R_TRANWHITE; |
| GEPolygon(3, xx, yy, gc, dd); |
| break; |
| |
| case 3: /* S plus */ |
| xc = toDeviceWidth(M_SQRT2*RADIUS*GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(M_SQRT2*RADIUS*GSTR_0, GE_INCHES, dd); |
| GELine(x-xc, y, x+xc, y, gc, dd); |
| GELine(x, y-yc, x, y+yc, gc, dd); |
| break; |
| |
| case 4: /* S times */ |
| xc = toDeviceWidth(RADIUS * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(RADIUS * GSTR_0, GE_INCHES, dd); |
| GELine(x-xc, y-yc, x+xc, y+yc, gc, dd); |
| GELine(x-xc, y+yc, x+xc, y-yc, gc, dd); |
| break; |
| |
| case 5: /* S diamond */ |
| xc = toDeviceWidth(M_SQRT2 * RADIUS * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(M_SQRT2 * RADIUS * GSTR_0, GE_INCHES, dd); |
| xx[0] = x-xc; yy[0] = y; |
| xx[1] = x; yy[1] = y+yc; |
| xx[2] = x+xc; yy[2] = y; |
| xx[3] = x; yy[3] = y-yc; |
| gc->fill = R_TRANWHITE; |
| GEPolygon(4, xx, yy, gc, dd); |
| break; |
| |
| case 6: /* S triangle - point down */ |
| xc = RADIUS * GSTR_0; |
| r = toDeviceHeight(TRC0 * xc, GE_INCHES, dd); |
| yc = toDeviceHeight(TRC2 * xc, GE_INCHES, dd); |
| xc = toDeviceWidth(TRC1 * xc, GE_INCHES, dd); |
| xx[0] = x; yy[0] = y-r; |
| xx[1] = x+xc; yy[1] = y+yc; |
| xx[2] = x-xc; yy[2] = y+yc; |
| gc->fill = R_TRANWHITE; |
| GEPolygon(3, xx, yy, gc, dd); |
| break; |
| |
| case 7: /* S square and times superimposed */ |
| xc = toDeviceWidth(RADIUS * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(RADIUS * GSTR_0, GE_INCHES, dd); |
| gc->fill = R_TRANWHITE; |
| GERect(x-xc, y-yc, x+xc, y+yc, gc, dd); |
| GELine(x-xc, y-yc, x+xc, y+yc, gc, dd); |
| GELine(x-xc, y+yc, x+xc, y-yc, gc, dd); |
| break; |
| |
| case 8: /* S plus and times superimposed */ |
| xc = toDeviceWidth(RADIUS * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(RADIUS * GSTR_0, GE_INCHES, dd); |
| GELine(x-xc, y-yc, x+xc, y+yc, gc, dd); |
| GELine(x-xc, y+yc, x+xc, y-yc, gc, dd); |
| xc = toDeviceWidth(M_SQRT2*RADIUS*GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(M_SQRT2*RADIUS*GSTR_0, GE_INCHES, dd); |
| GELine(x-xc, y, x+xc, y, gc, dd); |
| GELine(x, y-yc, x, y+yc, gc, dd); |
| break; |
| |
| case 9: /* S diamond and plus superimposed */ |
| xc = toDeviceWidth(M_SQRT2 * RADIUS * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(M_SQRT2 * RADIUS * GSTR_0, GE_INCHES, dd); |
| GELine(x-xc, y, x+xc, y, gc, dd); |
| GELine(x, y-yc, x, y+yc, gc, dd); |
| xx[0] = x-xc; yy[0] = y; |
| xx[1] = x; yy[1] = y+yc; |
| xx[2] = x+xc; yy[2] = y; |
| xx[3] = x; yy[3] = y-yc; |
| gc->fill = R_TRANWHITE; |
| GEPolygon(4, xx, yy, gc, dd); |
| break; |
| |
| case 10: /* S hexagon (circle) and plus superimposed */ |
| xc = toDeviceWidth(RADIUS * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(RADIUS * GSTR_0, GE_INCHES, dd); |
| gc->fill = R_TRANWHITE; |
| GECircle(x, y, xc, gc, dd); |
| GELine(x-xc, y, x+xc, y, gc, dd); |
| GELine(x, y-yc, x, y+yc, gc, dd); |
| break; |
| |
| case 11: /* S superimposed triangles */ |
| xc = RADIUS * GSTR_0; |
| r = toDeviceHeight(TRC0 * xc, GE_INCHES, dd); |
| yc = toDeviceHeight(TRC2 * xc, GE_INCHES, dd); |
| yc = 0.5 * (yc + r); |
| xc = toDeviceWidth(TRC1 * xc, GE_INCHES, dd); |
| xx[0] = x; yy[0] = y-r; |
| xx[1] = x+xc; yy[1] = y+yc; |
| xx[2] = x-xc; yy[2] = y+yc; |
| gc->fill = R_TRANWHITE; |
| GEPolygon(3, xx, yy, gc, dd); |
| xx[0] = x; yy[0] = y+r; |
| xx[1] = x+xc; yy[1] = y-yc; |
| xx[2] = x-xc; yy[2] = y-yc; |
| GEPolygon(3, xx, yy, gc, dd); |
| break; |
| |
| case 12: /* S square and plus superimposed */ |
| xc = toDeviceWidth(RADIUS * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(RADIUS * GSTR_0, GE_INCHES, dd); |
| GELine(x-xc, y, x+xc, y, gc, dd); |
| GELine(x, y-yc, x, y+yc, gc, dd); |
| gc->fill = R_TRANWHITE; |
| GERect(x-xc, y-yc, x+xc, y+yc, gc, dd); |
| break; |
| |
| case 13: /* S octagon (circle) and times superimposed */ |
| xc = RADIUS * size; |
| gc->fill = R_TRANWHITE; |
| GECircle(x, y, xc, gc, dd); |
| xc = toDeviceWidth(RADIUS * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(RADIUS * GSTR_0, GE_INCHES, dd); |
| GELine(x-xc, y-yc, x+xc, y+yc, gc, dd); |
| GELine(x-xc, y+yc, x+xc, y-yc, gc, dd); |
| break; |
| |
| case 14: /* S square and point-up triangle superimposed */ |
| xc = toDeviceWidth(RADIUS * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(RADIUS * GSTR_0, GE_INCHES, dd); |
| xx[0] = x; yy[0] = y+yc; |
| xx[1] = x+xc; yy[1] = y-yc; |
| xx[2] = x-xc; yy[2] = y-yc; |
| gc->fill = R_TRANWHITE; |
| GEPolygon(3, xx, yy, gc, dd); |
| GERect(x-xc, y-yc, x+xc, y+yc, gc, dd); |
| break; |
| |
| case 15: /* S filled square */ |
| xc = toDeviceWidth(RADIUS * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(RADIUS * GSTR_0, GE_INCHES, dd); |
| xx[0] = x-xc; yy[0] = y-yc; |
| xx[1] = x+xc; yy[1] = y-yc; |
| xx[2] = x+xc; yy[2] = y+yc; |
| xx[3] = x-xc; yy[3] = y+yc; |
| gc->fill = gc->col; |
| gc->col = R_TRANWHITE; |
| GEPolygon(4, xx, yy, gc, dd); |
| break; |
| |
| case 16: /* S filled octagon (circle) */ |
| xc = RADIUS * size; |
| gc->fill = gc->col; |
| gc->col = R_TRANWHITE; |
| GECircle(x, y, xc, gc, dd); |
| break; |
| |
| case 17: /* S filled point-up triangle */ |
| xc = RADIUS * GSTR_0; |
| r = toDeviceHeight(TRC0 * xc, GE_INCHES, dd); |
| yc = toDeviceHeight(TRC2 * xc, GE_INCHES, dd); |
| xc = toDeviceWidth(TRC1 * xc, GE_INCHES, dd); |
| xx[0] = x; yy[0] = y+r; |
| xx[1] = x+xc; yy[1] = y-yc; |
| xx[2] = x-xc; yy[2] = y-yc; |
| gc->fill = gc->col; |
| gc->col = R_TRANWHITE; |
| GEPolygon(3, xx, yy, gc, dd); |
| break; |
| |
| case 18: /* S filled diamond */ |
| xc = toDeviceWidth(RADIUS * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(RADIUS * GSTR_0, GE_INCHES, dd); |
| xx[0] = x-xc; yy[0] = y; |
| xx[1] = x; yy[1] = y+yc; |
| xx[2] = x+xc; yy[2] = y; |
| xx[3] = x; yy[3] = y-yc; |
| gc->fill = gc->col; |
| gc->col = R_TRANWHITE; |
| GEPolygon(4, xx, yy, gc, dd); |
| break; |
| |
| case 19: /* R filled circle */ |
| xc = RADIUS * size; |
| gc->fill = gc->col; |
| GECircle(x, y, xc, gc, dd); |
| break; |
| |
| |
| case 20: /* R `Dot' (small circle) */ |
| xc = SMALL * size; |
| gc->fill = gc->col; |
| GECircle(x, y, xc, gc, dd); |
| break; |
| |
| |
| case 21: /* circles */ |
| xc = RADIUS * size; |
| GECircle(x, y, xc, gc, dd); |
| break; |
| |
| case 22: /* squares */ |
| xc = toDeviceWidth(RADIUS * SQRC * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(RADIUS * SQRC * GSTR_0, GE_INCHES, dd); |
| GERect(x-xc, y-yc, x+xc, y+yc, gc, dd); |
| break; |
| |
| case 23: /* diamonds */ |
| xc = toDeviceWidth(RADIUS * DMDC * GSTR_0, GE_INCHES, dd); |
| yc = toDeviceHeight(RADIUS * DMDC * GSTR_0, GE_INCHES, dd); |
| xx[0] = x ; yy[0] = y-yc; |
| xx[1] = x+xc; yy[1] = y; |
| xx[2] = x ; yy[2] = y+yc; |
| xx[3] = x-xc; yy[3] = y; |
| GEPolygon(4, xx, yy, gc, dd); |
| break; |
| |
| case 24: /* triangle (point up) */ |
| xc = RADIUS * GSTR_0; |
| r = toDeviceHeight(TRC0 * xc, GE_INCHES, dd); |
| yc = toDeviceHeight(TRC2 * xc, GE_INCHES, dd); |
| xc = toDeviceWidth(TRC1 * xc, GE_INCHES, dd); |
| xx[0] = x; yy[0] = y+r; |
| xx[1] = x+xc; yy[1] = y-yc; |
| xx[2] = x-xc; yy[2] = y-yc; |
| GEPolygon(3, xx, yy, gc, dd); |
| break; |
| |
| case 25: /* triangle (point down) */ |
| xc = RADIUS * GSTR_0; |
| r = toDeviceHeight(TRC0 * xc, GE_INCHES, dd); |
| yc = toDeviceHeight(TRC2 * xc, GE_INCHES, dd); |
| xc = toDeviceWidth(TRC1 * xc, GE_INCHES, dd); |
| xx[0] = x; yy[0] = y-r; |
| xx[1] = x+xc; yy[1] = y+yc; |
| xx[2] = x-xc; yy[2] = y+yc; |
| GEPolygon(3, xx, yy, gc, dd); |
| break; |
| default: |
| warning(_("unimplemented pch value '%d'"), pch); |
| } |
| } |
| } |
| |
| /**************************************************************** |
| * GEPretty |
| **************************************************************** |
| */ |
| void GEPretty(double *lo, double *up, int *ndiv) |
| { |
| /* Set scale and ticks for linear scales. |
| * |
| * Pre: x1 == lo < up == x2 ; ndiv >= 1 |
| * Post: x1 <= y1 := lo < up =: y2 <= x2; ndiv >= 1 |
| */ |
| double unit, ns, nu; |
| double high_u_fact[2] = { .8, 1.7 }; |
| #ifdef DEBUG_PLOT |
| double x1,x2; |
| #endif |
| |
| if(*ndiv <= 0) |
| error(_("invalid axis extents [GEPretty(.,.,n=%d)"), *ndiv); |
| if(*lo == R_PosInf || *up == R_PosInf || |
| *lo == R_NegInf || *up == R_NegInf || |
| !R_FINITE(*up - *lo)) { |
| error(_("infinite axis extents [GEPretty(%g,%g,%d)]"), *lo, *up, *ndiv); |
| return;/*-Wall*/ |
| } |
| |
| ns = *lo; nu = *up; |
| #ifdef DEBUG_PLOT |
| x1 = ns; x2 = nu; |
| #endif |
| // -> ../appl/pretty.c |
| unit = R_pretty(&ns, &nu, ndiv, /* min_n = */ 1, |
| /* shrink_sml = */ 0.25, |
| high_u_fact, |
| 2, /* do eps_correction in any case */ |
| 0 /* return (ns,nu) in (lo,up) */); |
| // The following is ugly since it kind of happens already in R_pretty(..): |
| #define rounding_eps 1e-10 /* <- compatible to seq*(); was 1e-7 till 2017-08-14 */ |
| if(nu >= ns + 1) { |
| int mod = 0; |
| if( ns * unit < *lo - rounding_eps*unit) { ns++; mod++; } |
| if(nu > ns + 1 && nu * unit > *up + rounding_eps*unit) { nu--; mod++; } |
| if(mod) *ndiv = (int)(nu - ns); |
| } |
| *lo = ns * unit; |
| *up = nu * unit; |
| #ifdef non_working_ALTERNATIVE |
| if(ns * unit > *lo) |
| *lo = ns * unit; |
| if(nu * unit < *up) |
| *up = nu * unit; |
| if(nu - ns >= 1) |
| *ndiv = nu - ns; |
| #endif |
| |
| #ifdef DEBUG_PLOT |
| if(*lo < x1) |
| warning(_(" .. GEPretty(.): new *lo = %g < %g = x1"), *lo, x1); |
| if(*up > x2) |
| warning(_(" .. GEPretty(.): new *up = %g > %g = x2"), *up, x2); |
| #endif |
| } |
| |
| /**************************************************************** |
| * GEMetricInfo |
| **************************************************************** |
| */ |
| /* |
| If c is negative, -c is a Unicode point. |
| In a MBCS locale, values > 127 are Unicode points (and so really are |
| values 32 ... 126, 127 being unused). |
| In a SBCS locale, values 32 ... 255 are the characters in the encoding. |
| */ |
| void GEMetricInfo(int c, const pGEcontext gc, |
| double *ascent, double *descent, double *width, |
| pGEDevDesc dd) |
| { |
| /* |
| * If the fontfamily is a Hershey font family, call R_GE_VText |
| */ |
| int vfontcode = VFontFamilyCode(gc->fontfamily); |
| if (vfontcode >= 0) { |
| /* |
| * It should be straightforward to figure this out, but |
| * just haven't got around to it yet |
| */ |
| *ascent = 0.0; |
| *descent = 0.0; |
| *width = 0.0; |
| } else { |
| /* c = 'M' gets called very often, usually to see if there are |
| any char metrics available but also in plotmath. So we |
| cache that value. Depends on the context through cex, ps, |
| fontface, family, and also on the device. |
| |
| PAUL 2008-11-27 |
| The point of checking dd == last_dd is to check for |
| a different TYPE of device (e.g., PDF vs. PNG). |
| Checking just the pGEDevDesc pointer is not a good enough |
| test; it is possible for that to be the same when one |
| device is closed and a new one is opened (I have seen |
| it happen!). |
| So, ALSO compare dd->dev->close function pointer |
| which really should be different for different devices. |
| */ |
| static pGEDevDesc last_dd= NULL; |
| #if R_USE_PROTOTYPES |
| static void (*last_close)(pDevDesc dd); |
| #else |
| static void (*last_close)(); |
| #endif |
| static int last_face = 1; |
| static double last_cex = 0.0, last_ps = 0.0, |
| a = 0.0 , d = 0.0, w = 0.0; |
| static char last_family[201]; |
| if (dd == last_dd && dd->dev->close == last_close && abs(c) == 77 |
| && gc->cex == last_cex && gc->ps == last_ps |
| && gc->fontface == last_face |
| && streql(gc->fontfamily, last_family)) { |
| *ascent = a; *descent = d; *width = w; return; |
| } |
| dd->dev->metricInfo(c, gc, ascent, descent, width, dd->dev); |
| if(abs(c) == 77) { |
| last_dd = dd; last_close = dd->dev->close; |
| last_cex = gc->cex; last_ps = gc->ps; |
| last_face = gc->fontface; |
| strcpy(last_family, gc->fontfamily); |
| a = *ascent; d = *descent; w = *width; |
| } |
| } |
| } |
| |
| /**************************************************************** |
| * GEStrWidth |
| **************************************************************** |
| */ |
| double GEStrWidth(const char *str, cetype_t enc, const pGEcontext gc, pGEDevDesc dd) |
| { |
| /* |
| * If the fontfamily is a Hershey font family, call R_GE_VStrWidth |
| */ |
| int vfontcode = VFontFamilyCode(gc->fontfamily); |
| if (vfontcode >= 100) |
| return R_GE_VStrWidth(str, enc, gc, dd); |
| else if (vfontcode >= 0) { |
| gc->fontfamily[7] = (char) vfontcode; |
| gc->fontface = VFontFaceCode(vfontcode, gc->fontface); |
| return R_GE_VStrWidth(str, enc, gc, dd); |
| } else { |
| double w; |
| char *sbuf = NULL; |
| w = 0; |
| if(str && *str) { |
| const char *s; |
| char *sb; |
| double wdash; |
| cetype_t enc2; |
| const void *vmax = vmaxget(); |
| |
| enc2 = (gc->fontface == 5) ? CE_SYMBOL : enc; |
| if(enc2 != CE_SYMBOL) |
| enc2 = (dd->dev->hasTextUTF8 == TRUE) ? CE_UTF8 : CE_NATIVE; |
| else if(dd->dev->wantSymbolUTF8 == TRUE) enc2 = CE_UTF8; |
| |
| sb = sbuf = (char*) R_alloc(strlen(str) + 1, sizeof(char)); |
| for(s = str; ; s++) { |
| if (*s == '\n' || *s == '\0') { |
| const char *str; |
| *sb = '\0'; |
| /* This may R_alloc, but let's assume that |
| there are not many lines of text per string */ |
| str = reEnc(sbuf, enc, enc2, 2); |
| if(dd->dev->hasTextUTF8 == TRUE && enc2 == CE_UTF8) |
| wdash = dd->dev->strWidthUTF8(str, gc, dd->dev); |
| else |
| wdash = dd->dev->strWidth(str, gc, dd->dev); |
| if (wdash > w) w = wdash; |
| sb = sbuf; |
| } |
| else *sb++ = *s; |
| if (!*s) break; |
| } |
| vmaxset(vmax); |
| } |
| return w; |
| } |
| } |
| |
| /**************************************************************** |
| * GEStrHeight |
| **************************************************************** |
| |
| * This does not (currently) depend on the encoding. It depends on |
| * the string only through the number of lines of text (via embedded |
| * \n) and we assume they are never part of an mbc. |
| */ |
| double GEStrHeight(const char *str, cetype_t enc, const pGEcontext gc, pGEDevDesc dd) |
| { |
| /* |
| * If the fontfamily is a Hershey font family, call R_GE_VStrHeight |
| */ |
| int vfontcode = VFontFamilyCode(gc->fontfamily); |
| if (vfontcode >= 100) |
| return R_GE_VStrHeight(str, enc, gc, dd); |
| else if (vfontcode >= 0) { |
| gc->fontfamily[7] = (char) vfontcode; |
| gc->fontface = VFontFaceCode(vfontcode, gc->fontface); |
| return R_GE_VStrHeight(str, enc, gc, dd); |
| } else { |
| double h; |
| const char *s; |
| double asc, dsc, wid; |
| int n; |
| /* Count the lines of text minus one */ |
| n = 0; |
| for(s = str; *s ; s++) |
| if (*s == '\n') |
| n++; |
| /* cra is based on the font pointsize at the |
| * time the device was created. |
| * Adjust for potentially different current pointsize |
| * This is a crude calculation that might be better |
| * performed using a device call that responds with |
| * the current font pointsize in device coordinates. |
| */ |
| h = n * gc->lineheight * gc->cex * dd->dev->cra[1] * |
| gc->ps/dd->dev->startps; |
| /* Add in the ascent of the font, if available */ |
| GEMetricInfo('M', gc, &asc, &dsc, &wid, dd); |
| if ((asc == 0.0) && (dsc == 0.0) && (wid == 0.0)) |
| asc = gc->lineheight * gc->cex * dd->dev->cra[1] * |
| gc->ps/dd->dev->startps; |
| h += asc; |
| return h; |
| } |
| } |
| |
| /**************************************************************** |
| * GEStrMetric |
| **************************************************************** |
| |
| * Modelled on GEText handling of encodings |
| */ |
| void GEStrMetric(const char *str, cetype_t enc, const pGEcontext gc, |
| double *ascent, double *descent, double *width, |
| pGEDevDesc dd) |
| { |
| /* |
| * If the fontfamily is a Hershey font family, call R_GE_VStrHeight |
| */ |
| int vfontcode = VFontFamilyCode(gc->fontfamily); |
| *ascent = 0.0; |
| *descent = 0.0; |
| *width = 0.0; |
| if (vfontcode >= 0) { |
| /* |
| * It should be straightforward to figure this out, but |
| * just haven't got around to it yet |
| */ |
| } else { |
| double h; |
| const char *s; |
| double asc, dsc, wid; |
| /* cra is based on the font pointsize at the |
| * time the device was created. |
| * Adjust for potentially different current pointsize |
| * This is a crude calculation that might be better |
| * performed using a device call that responds with |
| * the current font pointsize in device coordinates. |
| */ |
| double lineheight = gc->lineheight * gc->cex * dd->dev->cra[1] * |
| gc->ps/dd->dev->startps; |
| int n; |
| char *sb, *sbuf; |
| cetype_t enc2; |
| int noMetricInfo; |
| |
| const void *vmax = vmaxget(); |
| |
| GEMetricInfo('M', gc, &asc, &dsc, &wid, dd); |
| noMetricInfo = (asc == 0 && dsc == 0 && wid == 0) ? 1 : 0; |
| |
| enc2 = (gc->fontface == 5) ? CE_SYMBOL : enc; |
| if(enc2 != CE_SYMBOL) |
| enc2 = (dd->dev->hasTextUTF8 == TRUE) ? CE_UTF8 : CE_NATIVE; |
| else if(dd->dev->wantSymbolUTF8 == TRUE) enc2 = CE_UTF8; |
| else if(dd->dev->wantSymbolUTF8 == NA_LOGICAL) { |
| enc = CE_LATIN1; |
| enc2 = CE_UTF8; |
| } |
| |
| /* Put the first line in a string */ |
| sb = sbuf = (char*) R_alloc(strlen(str) + 1, sizeof(char)); |
| s = str; |
| while (*s != '\n' && *s != '\0') { |
| *sb++ = *s++; |
| } |
| *sb = '\0'; |
| /* Find the largest ascent for the first line */ |
| if (noMetricInfo) { |
| *ascent = GEStrHeight(sbuf, enc2, gc, dd); |
| } else { |
| s = reEnc(sbuf, enc, enc2, 2); |
| if(enc2 != CE_SYMBOL && !strIsASCII(s)) { |
| if(mbcslocale && enc2 == CE_NATIVE) { |
| size_t n = strlen(s), used; |
| wchar_t wc; |
| mbstate_t mb_st; |
| mbs_init(&mb_st); |
| while ((used = mbrtowc(&wc, s, n, &mb_st)) > 0) { |
| GEMetricInfo((int) wc, gc, &asc, &dsc, &wid, dd); |
| if (asc > *ascent) |
| *ascent = asc; |
| s += used; n -=used; |
| } |
| } else if (enc2 == CE_UTF8) { |
| size_t used; |
| wchar_t wc; |
| while ((used = utf8toucs(&wc, s)) > 0) { |
| if (IS_HIGH_SURROGATE(wc)) |
| GEMetricInfo(-utf8toucs32(wc, s), gc, &asc, &dsc, &wid, dd); |
| else |
| GEMetricInfo(-(int) wc, gc, &asc, &dsc, &wid,dd); |
| if (asc > *ascent) |
| *ascent = asc; |
| s += used; |
| } |
| } |
| } else { |
| while (*s != '\0') { |
| GEMetricInfo((unsigned char) *s++, gc, |
| &asc, &dsc, &wid, dd); |
| if (asc > *ascent) |
| *ascent = asc; |
| } |
| } |
| } |
| |
| /* Count the lines of text minus one */ |
| n = 0; |
| for(s = str; *s ; s++) |
| if (*s == '\n') |
| n++; |
| h = n * lineheight; |
| |
| /* Where is the start of the last line? */ |
| if (n > 0) { |
| while (*s != '\n') |
| s--; |
| s++; |
| } else { |
| s = str; |
| } |
| /* Put the last line in a string */ |
| sb = sbuf; |
| while (*s != '\0') { |
| *sb++ = *s++; |
| } |
| *sb = '\0'; |
| /* Find the largest descent for the last line */ |
| if (noMetricInfo) { |
| *descent = 0; |
| } else { |
| s = reEnc(sbuf, enc, enc2, 2); |
| if(enc2 != CE_SYMBOL && !strIsASCII(s)) { |
| if(mbcslocale && enc2 == CE_NATIVE) { |
| size_t n = strlen(s), used; |
| wchar_t wc; |
| mbstate_t mb_st; |
| mbs_init(&mb_st); |
| while ((used = mbrtowc(&wc, s, n, &mb_st)) > 0) { |
| GEMetricInfo((int) wc, gc, &asc, &dsc, &wid, dd); |
| if (dsc > *descent) |
| *descent = dsc; |
| s += used; n -=used; |
| } |
| } else if (enc2 == CE_UTF8) { |
| size_t used; |
| wchar_t wc; |
| while ((used = utf8toucs(&wc, s)) > 0) { |
| if (IS_HIGH_SURROGATE(wc)) |
| GEMetricInfo(-utf8toucs32(wc, s), gc, &asc, &dsc, &wid, dd); |
| else |
| GEMetricInfo(-(int) wc, gc, &asc, &dsc, &wid,dd); |
| if (dsc > *descent) |
| *descent = dsc; |
| s += used; |
| } |
| } |
| } else { |
| while (*s != '\0') { |
| GEMetricInfo((unsigned char) *s++, gc, |
| &asc, &dsc, &wid, dd); |
| if (dsc > *descent) |
| *descent = dsc; |
| } |
| } |
| } |
| |
| *ascent = *ascent + h; |
| *width = GEStrWidth(str, enc, gc ,dd); |
| |
| vmaxset(vmax); |
| } |
| } |
| |
| /**************************************************************** |
| * GENewPage |
| **************************************************************** |
| */ |
| |
| void GENewPage(const pGEcontext gc, pGEDevDesc dd) |
| { |
| dd->dev->newPage(gc, dd->dev); |
| } |
| |
| /**************************************************************** |
| * GEdeviceDirty |
| **************************************************************** |
| * |
| * Has the device received output from any graphics system? |
| */ |
| |
| Rboolean GEdeviceDirty(pGEDevDesc dd) |
| { |
| return dd->dirty; |
| } |
| |
| /**************************************************************** |
| * GEdirtyDevice |
| **************************************************************** |
| * |
| * Indicate that the device has received output from at least one |
| * graphics system. |
| */ |
| |
| void GEdirtyDevice(pGEDevDesc dd) |
| { |
| dd->dirty = TRUE; |
| } |
| |
| void GEcleanDevice(pGEDevDesc dd) |
| { |
| dd->dirty = FALSE; |
| } |
| |
| /**************************************************************** |
| * GEcheckState |
| **************************************************************** |
| * |
| * Check whether all registered graphics systems are in a |
| * "valid" state. |
| */ |
| |
| Rboolean GEcheckState(pGEDevDesc dd) |
| { |
| int i; |
| Rboolean result = TRUE; |
| for (i=0; i < MAX_GRAPHICS_SYSTEMS; i++) |
| if (dd->gesd[i] != NULL) |
| if (!LOGICAL((dd->gesd[i]->callback)(GE_CheckPlot, dd, |
| R_NilValue))[0]) |
| result = FALSE; |
| return result; |
| } |
| |
| /**************************************************************** |
| * GErecording |
| **************************************************************** |
| */ |
| |
| Rboolean GErecording(SEXP call, pGEDevDesc dd) |
| { |
| return (call != R_NilValue && dd->recordGraphics); |
| } |
| |
| /**************************************************************** |
| * GErecordGraphicOperation |
| **************************************************************** |
| */ |
| |
| void GErecordGraphicOperation(SEXP op, SEXP args, pGEDevDesc dd) |
| { |
| SEXP lastOperation = dd->DLlastElt; |
| if (dd->displayListOn) { |
| SEXP newOperation = list2(op, args); |
| if (lastOperation == R_NilValue) { |
| dd->displayList = CONS(newOperation, R_NilValue); |
| dd->DLlastElt = dd->displayList; |
| } else { |
| SETCDR(lastOperation, CONS(newOperation, R_NilValue)); |
| dd->DLlastElt = CDR(lastOperation); |
| } |
| } |
| } |
| |
| /**************************************************************** |
| * GEinitDisplayList |
| **************************************************************** |
| */ |
| |
| void GEinitDisplayList(pGEDevDesc dd) |
| { |
| int i; |
| /* Save the current displayList so that, for example, a device |
| * can maintain a plot history |
| */ |
| dd->savedSnapshot = GEcreateSnapshot(dd); |
| /* Get each graphics system to save state required for |
| * replaying the display list |
| */ |
| for (i = 0; i < MAX_GRAPHICS_SYSTEMS; i++) |
| if (dd->gesd[i] != NULL) |
| (dd->gesd[i]->callback)(GE_SaveState, dd, R_NilValue); |
| dd->displayList = dd->DLlastElt = R_NilValue; |
| } |
| |
| /**************************************************************** |
| * GEplayDisplayList |
| **************************************************************** |
| */ |
| |
| /* from colors.c */ |
| void savePalette(Rboolean save); |
| |
| void GEplayDisplayList(pGEDevDesc dd) |
| { |
| int i, this, savedDevice, plotok; |
| SEXP theList; |
| |
| /* If the device is not registered with the engine (which might |
| happen in a device callback before it has been registered or |
| while it is being killed) we might get the null device and |
| should do nothing. |
| |
| Also do nothing if displayList is empty (which should be the |
| case for the null device). |
| */ |
| this = GEdeviceNumber(dd); |
| if (this == 0) return; |
| theList = dd->displayList; |
| if (theList == R_NilValue) return; |
| |
| /* Get each graphics system to restore state required for |
| * replaying the display list |
| */ |
| for (i = 0; i < MAX_GRAPHICS_SYSTEMS; i++) |
| if (dd->gesd[i] != NULL) |
| (dd->gesd[i]->callback)(GE_RestoreState, dd, theList); |
| /* Play the display list |
| */ |
| PROTECT(theList); |
| plotok = 1; |
| if (theList != R_NilValue) { |
| savePalette(TRUE); |
| savedDevice = curDevice(); |
| selectDevice(this); |
| while (theList != R_NilValue && plotok) { |
| SEXP theOperation = CAR(theList); |
| SEXP op = CAR(theOperation); |
| SEXP args = CADR(theOperation); |
| if (TYPEOF(op) == BUILTINSXP || TYPEOF(op) == SPECIALSXP) { |
| PRIMFUN(op) (R_NilValue, op, args, R_NilValue); |
| /* Check with each graphics system that the plotting went ok |
| */ |
| if (!GEcheckState(dd)) { |
| warning(_("display list redraw incomplete")); |
| plotok = 0; |
| } |
| } else { |
| warning(_("invalid display list")); |
| plotok = 0; |
| } |
| theList = CDR(theList); |
| } |
| selectDevice(savedDevice); |
| savePalette(FALSE); |
| } |
| UNPROTECT(1); |
| } |
| |
| |
| /**************************************************************** |
| * GEcopyDisplayList |
| **************************************************************** |
| */ |
| |
| /* We assume that the device being copied TO is the "current" device |
| */ |
| void GEcopyDisplayList(int fromDevice) |
| { |
| SEXP tmp; |
| pGEDevDesc dd = GEcurrentDevice(), gd = GEgetDevice(fromDevice); |
| int i; |
| |
| tmp = gd->displayList; |
| if(!isNull(tmp)) tmp = duplicate(tmp); |
| dd->displayList = tmp; |
| dd->DLlastElt = lastElt(dd->displayList); |
| /* Get each registered graphics system to copy system state |
| * information from the "from" device to the current device |
| */ |
| for (i=0; i < MAX_GRAPHICS_SYSTEMS; i++) |
| if (dd->gesd[i] != NULL) |
| (dd->gesd[i]->callback)(GE_CopyState, gd, R_NilValue); |
| GEplayDisplayList(dd); |
| if (!dd->displayListOn) GEinitDisplayList(dd); |
| } |
| |
| /**************************************************************** |
| * GEcreateSnapshot |
| **************************************************************** |
| */ |
| |
| /* Create a recording of the current display, |
| * including enough information from each registered |
| * graphics system to be able to recreate the display |
| * The structure created is an SEXP which nicely hides the |
| * internals, because noone should be looking in there anyway |
| * The product of this call can be stored, but should only |
| * be used in a call to GEplaySnapshot. |
| */ |
| |
| SEXP GEcreateSnapshot(pGEDevDesc dd) |
| { |
| int i; |
| SEXP snapshot, tmp; |
| SEXP state; |
| SEXP engineVersion; |
| /* Create a list with one spot for the display list |
| * and one spot each for the registered graphics systems |
| * to put their graphics state |
| */ |
| PROTECT(snapshot = allocVector(VECSXP, 1 + numGraphicsSystems)); |
| /* The first element of the snapshot is the display list. |
| */ |
| if(!isNull(dd->displayList)) { |
| PROTECT(tmp = duplicate(dd->displayList)); |
| SET_VECTOR_ELT(snapshot, 0, tmp); |
| UNPROTECT(1); |
| } |
| /* For each registered system, obtain state information, |
| * and store that in the snapshot. |
| */ |
| for (i = 0; i < MAX_GRAPHICS_SYSTEMS; i++) |
| if (dd->gesd[i] != NULL) { |
| PROTECT(state = (dd->gesd[i]->callback)(GE_SaveSnapshotState, dd, |
| R_NilValue)); |
| SET_VECTOR_ELT(snapshot, i + 1, state); |
| UNPROTECT(1); |
| } |
| PROTECT(engineVersion = allocVector(INTSXP, 1)); |
| INTEGER(engineVersion)[0] = R_GE_getVersion(); |
| setAttrib(snapshot, install("engineVersion"), engineVersion); |
| UNPROTECT(2); |
| return snapshot; |
| } |
| |
| /**************************************************************** |
| * GEplaySnapshot |
| **************************************************************** |
| */ |
| |
| /* Recreate a saved display using the information in a structure |
| * created by GEcreateSnapshot. |
| */ |
| |
| void GEplaySnapshot(SEXP snapshot, pGEDevDesc dd) |
| { |
| /* Only have to set up information for as many graphics systems |
| * as were registered when the snapshot was taken. |
| */ |
| int i; |
| /* Check graphics engine version matches. |
| * If it does not, things still might work, so just a warning. |
| * NOTE though, that if it does not work, the results could be fatal. |
| */ |
| SEXP snapshotEngineVersion; |
| int engineVersion = R_GE_getVersion(); |
| PROTECT(snapshotEngineVersion = getAttrib(snapshot, |
| install("engineVersion"))); |
| if (isNull(snapshotEngineVersion)) { |
| warning(_("snapshot recorded with different graphics engine version (pre 11 - this is version %d)"), |
| engineVersion); |
| } else if (INTEGER(snapshotEngineVersion)[0] != engineVersion) { |
| int snapshotVersion = INTEGER(snapshotEngineVersion)[0]; |
| warning(_("snapshot recorded with different graphics engine version (%d - this is version %d)"), |
| snapshotVersion, engineVersion); |
| } |
| /* "clean" the device |
| */ |
| GEcleanDevice(dd); |
| /* Reset the snapshot state information in each registered |
| * graphics system. |
| * This may try to restore state for a system that was NOT |
| * registered when the snapshot was taken, but the systems |
| * should protect themselves from that situation. |
| */ |
| for (i = 0; i < MAX_GRAPHICS_SYSTEMS; i++) |
| if (dd->gesd[i] != NULL) |
| (dd->gesd[i]->callback)(GE_RestoreSnapshotState, dd, snapshot); |
| /* Replay the display list |
| */ |
| dd->displayList = duplicate(VECTOR_ELT(snapshot, 0)); |
| dd->DLlastElt = lastElt(dd->displayList); |
| GEplayDisplayList(dd); |
| if (!dd->displayListOn) GEinitDisplayList(dd); |
| UNPROTECT(1); |
| } |
| |
| /* recordPlot() */ |
| SEXP do_getSnapshot(SEXP call, SEXP op, SEXP args, SEXP env) |
| { |
| checkArity(op, args); |
| return GEcreateSnapshot(GEcurrentDevice()); |
| } |
| |
| /* replayPlot() */ |
| SEXP do_playSnapshot(SEXP call, SEXP op, SEXP args, SEXP env) |
| { |
| checkArity(op, args); |
| GEplaySnapshot(CAR(args), GEcurrentDevice()); |
| return R_NilValue; |
| } |
| |
| /**************************************************************** |
| * do_recordGraphics |
| * |
| * A ".Internal" R function |
| * |
| **************************************************************** |
| */ |
| |
| SEXP attribute_hidden do_recordGraphics(SEXP call, SEXP op, SEXP args, SEXP env) |
| { |
| SEXP x, evalenv, retval; |
| pGEDevDesc dd = GEcurrentDevice(); |
| Rboolean record = dd->recordGraphics; |
| /* |
| * This function can be run under three conditions: |
| * |
| * (i) a top-level call to do_recordGraphics. |
| * In this case, call != R_NilValue and |
| * dd->recordGraphics = TRUE |
| * [so GErecording() returns TRUE] |
| * |
| * (ii) a nested call to do_recordGraphics. |
| * In this case, call != R_NilValue but |
| * dd->recordGraphics = FALSE |
| * [so GErecording() returns FALSE] |
| * |
| * (iii) a replay of the display list |
| * In this case, call == R_NilValue and |
| * dd->recordGraphics = FALSE |
| * [so GErecording() returns FALSE] |
| */ |
| /* |
| * First arg is an expression, second arg is a list, third arg is an env |
| */ |
| |
| checkArity(op, args); |
| SEXP code = CAR(args); |
| SEXP list = CADR(args); |
| SEXP parentenv = CADDR(args); |
| if (!isLanguage(code)) |
| error(_("'expr' argument must be an expression")); |
| if (TYPEOF(list) != VECSXP) |
| error(_("'list' argument must be a list")); |
| if (isNull(parentenv)) { |
| error(_("use of NULL environment is defunct")); |
| parentenv = R_BaseEnv; |
| } else |
| if (!isEnvironment(parentenv)) |
| error(_("'env' argument must be an environment")); |
| /* |
| * This conversion of list to env taken from do_eval |
| */ |
| PROTECT(x = VectorToPairList(list)); |
| for (SEXP xptr = x ; xptr != R_NilValue ; xptr = CDR(xptr)) |
| ENSURE_NAMEDMAX(CAR(xptr)); |
| /* |
| * The environment passed in as the third arg is used as |
| * the parent of the new evaluation environment. |
| */ |
| PROTECT(evalenv = NewEnvironment(R_NilValue, x, parentenv)); |
| dd->recordGraphics = FALSE; |
| PROTECT(retval = eval(code, evalenv)); |
| /* |
| * If there is an error or user-interrupt in the above |
| * evaluation, dd->recordGraphics is set to TRUE |
| * on all graphics devices (see GEonExit(); called in errors.c) |
| */ |
| dd->recordGraphics = record; |
| if (GErecording(call, dd)) { |
| if (!GEcheckState(dd)) |
| error(_("invalid graphics state")); |
| GErecordGraphicOperation(op, args, dd); |
| } |
| UNPROTECT(3); |
| return retval; |
| } |
| |
| /**************************************************************** |
| * GEonExit |
| * |
| * Reset some important graphics state on an error/interrupt |
| **************************************************************** |
| */ |
| |
| void GEonExit() |
| { |
| /* |
| * Run through all devices and turn graphics recording back on |
| * in case an error occurred in the middle of a do_recordGraphics |
| * call. |
| * Awkward cos device code still in graphics.c |
| * Can be cleaned up when device code moved here. |
| */ |
| int i, devNum; |
| pGEDevDesc gd; |
| pDevDesc dd; |
| i = 1; |
| if (!NoDevices()) { |
| devNum = curDevice(); |
| while (i++ < NumDevices()) { |
| gd = GEgetDevice(devNum); |
| gd->recordGraphics = TRUE; |
| dd = gd->dev; |
| if (dd->onExit) dd->onExit(dd); |
| devNum = nextDevice(devNum); |
| } |
| } |
| } |
| |
| /* This is also used in grid. It may be used millions of times on the |
| * same character */ |
| /* FIXME: should we warn on more than one character here? */ |
| int GEstring_to_pch(SEXP pch) |
| { |
| int ipch = NA_INTEGER; |
| static SEXP last_pch = NULL; |
| static int last_ipch = 0; |
| |
| if (pch == NA_STRING) return NA_INTEGER; |
| if (CHAR(pch)[0] == 0) return NA_INTEGER; /* pch = "" */ |
| if (pch == last_pch) return last_ipch;/* take advantage of CHARSXP cache */ |
| ipch = (unsigned char) CHAR(pch)[0]; |
| if (IS_LATIN1(pch)) { |
| if (ipch > 127) ipch = -ipch; /* record as Unicode */ |
| } else if (IS_UTF8(pch) || utf8locale) { |
| wchar_t wc = 0; |
| if (ipch > 127) { |
| if ( (int) utf8toucs(&wc, CHAR(pch)) > 0) { |
| if (IS_HIGH_SURROGATE(wc)) |
| ipch = -utf8toucs32(wc, CHAR(pch)); |
| else |
| ipch = -wc; |
| } else error(_("invalid multibyte char in pch=\"c\"")); |
| } |
| } else if(mbcslocale) { |
| /* Could we safely assume that 7-bit first byte means ASCII? |
| On Windows this only covers CJK locales, so we could. |
| */ |
| unsigned int ucs = 0; |
| if ( (int) mbtoucs(&ucs, CHAR(pch), MB_CUR_MAX) > 0) ipch = ucs; |
| else error(_("invalid multibyte char in pch=\"c\"")); |
| if (ipch > 127) ipch = -ipch; |
| } |
| |
| last_ipch = ipch; last_pch = pch; |
| return ipch; |
| } |
| |
| /* moved from graphics.c as used by grid */ |
| /* LINE TEXTURE CODE */ |
| |
| /* |
| * LINE TEXTURE SPECIFICATION |
| * |
| * Linetypes are stored internally in integers. An integer |
| * is interpreted as containing a sequence of 8 4-bit integers |
| * which give the lengths of up to 8 on-off line segments. |
| * The lengths are typically interpreted as pixels on a screen |
| * and as "points" in postscript. |
| * |
| * more comments (and LTY_* def.s) in ../include/Rgraphics.h |
| * ---------------------- |
| */ |
| |
| typedef struct { |
| char *name; |
| int pattern; |
| } LineTYPE; |
| |
| static LineTYPE linetype[] = { |
| { "blank", LTY_BLANK },/* -1 */ |
| { "solid", LTY_SOLID },/* 1 */ |
| { "dashed", LTY_DASHED },/* 2 */ |
| { "dotted", LTY_DOTTED },/* 3 */ |
| { "dotdash", LTY_DOTDASH },/* 4 */ |
| { "longdash",LTY_LONGDASH},/* 5 */ |
| { "twodash", LTY_TWODASH },/* 6 */ |
| { NULL, 0 }, |
| }; |
| |
| /* Duplicated from graphics.c */ |
| static char HexDigits[] = "0123456789ABCDEF"; |
| static unsigned int hexdigit(int digit) |
| { |
| if('0' <= digit && digit <= '9') return digit - '0'; |
| if('A' <= digit && digit <= 'F') return 10 + digit - 'A'; |
| if('a' <= digit && digit <= 'f') return 10 + digit - 'a'; |
| /*else */ error(_("invalid hex digit in 'color' or 'lty'")); |
| return digit; /* never occurs (-Wall) */ |
| } |
| |
| static int nlinetype = (sizeof(linetype)/sizeof(LineTYPE)-2); |
| |
| unsigned int GE_LTYpar(SEXP value, int ind) |
| { |
| const char *p; |
| int i, code, shift, digit; |
| double rcode; |
| |
| if(isString(value)) { |
| for(i = 0; linetype[i].name; i++) { /* is it the i-th name ? */ |
| if(!strcmp(CHAR(STRING_ELT(value, ind)), linetype[i].name)) |
| return linetype[i].pattern; |
| } |
| /* otherwise, a string of hex digits: */ |
| code = 0; |
| shift = 0; |
| p = CHAR(STRING_ELT(value, ind)); |
| size_t len = strlen(p); |
| if(len < 2 || len > 8 || len % 2 == 1) |
| error(_("invalid line type: must be length 2, 4, 6 or 8")); |
| for(; *p; p++) { |
| digit = hexdigit(*p); |
| if(digit == 0) |
| error(_("invalid line type: zeroes are not allowed")); |
| code |= (digit<<shift); |
| shift += 4; |
| } |
| return code; |
| } |
| else if(isInteger(value)) { |
| code = INTEGER(value)[ind]; |
| if(code == NA_INTEGER || code < 0) |
| error(_("invalid line type")); |
| if (code > 0) |
| code = (code-1) % nlinetype + 1; |
| return linetype[code].pattern; |
| } |
| else if(isReal(value)) { |
| rcode = REAL(value)[ind]; |
| if(!R_FINITE(rcode) || rcode < 0) |
| error(_("invalid line type")); |
| code = (int) rcode; |
| if (code > 0) |
| code = (code-1) % nlinetype + 1; |
| return linetype[code].pattern; |
| } |
| else { |
| error(_("invalid line type")); /*NOTREACHED, for -Wall : */ return 0; |
| } |
| } |
| |
| SEXP GE_LTYget(unsigned int lty) |
| { |
| int i, ndash; |
| unsigned char dash[8]; |
| unsigned int l; |
| char cbuf[17]; /* 8 hex digits plus nul */ |
| |
| for (i = 0; linetype[i].name; i++) |
| if(linetype[i].pattern == lty) return mkString(linetype[i].name); |
| |
| l = lty; ndash = 0; |
| for (i = 0; i < 8 && l & 15; i++) { |
| dash[ndash++] = l & 15; |
| l = l >> 4; |
| } |
| for(i = 0 ; i < ndash ; i++) cbuf[i] = HexDigits[dash[i]]; |
| return mkString(cbuf); |
| } |
| |
| /**************************************************************** |
| * |
| * Some functions for operations on raster images |
| * (for those devices that cannot do these themselves) |
| **************************************************************** |
| */ |
| |
| /* Some of this code is based on code from the leptonica library |
| * hence the following notice |
| */ |
| |
| /*====================================================================* |
| - Copyright (C) 2001 Leptonica. All rights reserved. |
| - This software is distributed in the hope that it will be |
| - useful, but with NO WARRANTY OF ANY KIND. |
| - No author or distributor accepts responsibility to anyone for the |
| - consequences of using this software, or for whether it serves any |
| - particular purpose or works at all, unless he or she says so in |
| - writing. Everyone is granted permission to copy, modify and |
| - redistribute this source code, for commercial or non-commercial |
| - purposes, with the following restrictions: (1) the origin of this |
| - source code must not be misrepresented; (2) modified versions must |
| - be plainly marked as such; and (3) this notice may not be removed |
| - or altered from any source or modified source distribution. |
| *====================================================================*/ |
| |
| /* |
| * Scale a raster image to a desired size using |
| * nearest-neighbour interpolation |
| |
| * draster must be pre-allocated. |
| */ |
| void R_GE_rasterScale(unsigned int *sraster, int sw, int sh, |
| unsigned int *draster, int dw, int dh) { |
| int i, j; |
| int sx, sy; |
| unsigned int pixel; |
| |
| /* Iterate over the destination pixels */ |
| for (i = 0; i < dh; i++) { |
| for (j = 0; j < dw; j++) { |
| sy = i * sh / dh; |
| sx = j * sw / dw; |
| if ((sx >= 0) && (sx < sw) && (sy >= 0) && sy < sh) { |
| pixel = sraster[sy * sw + sx]; |
| } else { |
| pixel = 0; |
| } |
| draster[i * dw + j] = pixel; |
| } |
| } |
| } |
| |
| /* |
| * Scale a raster image to a desired size using |
| * bilinear interpolation |
| * Code based on scaleColorLILow() from leptonica library |
| |
| * Divide each destination pixel into 16 x 16 sub-pixels. |
| * Linear interpolation is equivalent to finding the |
| * fractional area (i.e., number of sub-pixels divided |
| * by 256) associated with each of the four nearest src pixels, |
| * and weighting each pixel value by this fractional area. |
| |
| * draster must be pre-allocated. |
| */ |
| void R_GE_rasterInterpolate(unsigned int *sraster, int sw, int sh, |
| unsigned int *draster, int dw, int dh) { |
| int i, j; |
| double scx, scy; |
| int wm2, hm2; |
| int xpm, ypm; /* location in src image, to 1/16 of a pixel */ |
| int xp, yp, xf, yf; /* src pixel and pixel fraction coordinates */ |
| int v00r, v01r, v10r, v11r, v00g, v01g, v10g, v11g; |
| int v00b, v01b, v10b, v11b, v00a, v01a, v10a, v11a; |
| int area00, area01, area10, area11; |
| unsigned int pixels1, pixels2, pixels3, pixels4, pixel; |
| unsigned int *sline, *dline; |
| |
| /* (scx, scy) are scaling factors that are applied to the |
| * dest coords to get the corresponding src coords. |
| * We need them because we iterate over dest pixels |
| * and must find the corresponding set of src pixels. */ |
| scx = (16. * sw) / dw; |
| scy = (16. * sh) / dh; |
| |
| wm2 = sw - 2; |
| hm2 = sh - 2; |
| |
| /* Iterate over the destination pixels */ |
| for (i = 0; i < dh; i++) { |
| ypm = (int) fmax2(scy * i - 8, 0); |
| yp = ypm >> 4; |
| yf = ypm & 0x0f; |
| dline = draster + i * dw; |
| sline = sraster + yp * sw; |
| for (j = 0; j < dw; j++) { |
| xpm = (int) fmax2(scx * j - 8, 0); |
| xp = xpm >> 4; |
| xf = xpm & 0x0f; |
| |
| pixels1 = *(sline + xp); |
| |
| if (xp > wm2 || yp > hm2) { |
| if (yp > hm2 && xp <= wm2) { /* pixels near bottom */ |
| pixels2 = *(sline + xp + 1); |
| pixels3 = pixels1; |
| pixels4 = pixels2; |
| } |
| else if (xp > wm2 && yp <= hm2) { /* pixels near right side */ |
| pixels2 = pixels1; |
| pixels3 = *(sline + sw + xp); |
| pixels4 = pixels3; |
| } |
| else { /* pixels at LR corner */ |
| pixels4 = pixels3 = pixels2 = pixels1; |
| } |
| } |
| else { |
| pixels2 = *(sline + xp + 1); |
| pixels3 = *(sline + sw + xp); |
| pixels4 = *(sline + sw + xp + 1); |
| } |
| |
| area00 = (16 - xf) * (16 - yf); |
| area10 = xf * (16 - yf); |
| area01 = (16 - xf) * yf; |
| area11 = xf * yf; |
| v00r = area00 * R_RED(pixels1); |
| v00g = area00 * R_GREEN(pixels1); |
| v00b = area00 * R_BLUE(pixels1); |
| v00a = area00 * R_ALPHA(pixels1); |
| v10r = area10 * R_RED(pixels2); |
| v10g = area10 * R_GREEN(pixels2); |
| v10b = area10 * R_BLUE(pixels2); |
| v10a = area10 * R_ALPHA(pixels2); |
| v01r = area01 * R_RED(pixels3); |
| v01g = area01 * R_GREEN(pixels3); |
| v01b = area01 * R_BLUE(pixels3); |
| v01a = area01 * R_ALPHA(pixels3); |
| v11r = area11 * R_RED(pixels4); |
| v11g = area11 * R_GREEN(pixels4); |
| v11b = area11 * R_BLUE(pixels4); |
| v11a = area11 * R_ALPHA(pixels4); |
| pixel = (((v00r + v10r + v01r + v11r + 128) >> 8) & 0x000000ff) | |
| (((v00g + v10g + v01g + v11g + 128) ) & 0x0000ff00) | |
| (((v00b + v10b + v01b + v11b + 128) << 8) & 0x00ff0000) | |
| (((v00a + v10a + v01a + v11a + 128) << 16) & 0xff000000); |
| *(dline + j) = pixel; |
| } |
| } |
| } |
| |
| /* |
| * Calculate the size needed for rotated image |
| * |
| * Rotate top-right and bottom-right corners |
| * New width/height based on max of rotated corners |
| */ |
| void R_GE_rasterRotatedSize(int w, int h, double angle, |
| int *wnew, int *hnew) { |
| double diag = sqrt(w*w + h*h); |
| double theta = atan2((double) h, (double) w); |
| double trx1 = diag*cos(theta + angle); |
| double trx2 = diag*cos(theta - angle); |
| double try1 = diag*sin(theta + angle); |
| double try2 = diag*sin(angle - theta); |
| *wnew = (int) (fmax2(fabs(trx1), fabs(trx2)) + 0.5); |
| *hnew = (int) (fmax2(fabs(try1), fabs(try2)) + 0.5); |
| /* |
| * Rotated image may be shorter or thinner than original |
| */ |
| *wnew = imax2(w, *wnew); |
| *hnew = imax2(h, *hnew); |
| } |
| |
| /* |
| * Calculate offset for (left, bottom) or |
| * (left, top) of image |
| * to account for image rotation |
| */ |
| void R_GE_rasterRotatedOffset(int w, int h, double angle, |
| int botleft, |
| double *xoff, double *yoff) { |
| double hypot = .5*sqrt(w*w + h*h); |
| double theta, dw, dh; |
| if (botleft) { |
| theta = M_PI + atan2(h, w); |
| dw = hypot*cos(theta + angle); |
| dh = hypot*sin(theta + angle); |
| *xoff = dw + w/2; |
| *yoff = dh + h/2; |
| } else { |
| theta = -M_PI - atan2(h, w); |
| dw = hypot*cos(theta + angle); |
| dh = hypot*sin(theta + angle); |
| *xoff = dw + w/2; |
| *yoff = dh - h/2; |
| } |
| } |
| |
| /* |
| * Copy a raster image into the middle of a larger |
| * raster image (ready for rotation) |
| |
| * newRaster must be pre-allocated. |
| */ |
| void R_GE_rasterResizeForRotation(unsigned int *sraster, |
| int w, int h, |
| unsigned int *newRaster, |
| int wnew, int hnew, |
| const pGEcontext gc) |
| { |
| int i, j, inew, jnew; |
| int xoff = (wnew - w)/2; |
| int yoff = (hnew - h)/2; |
| |
| for (i=0; i<hnew; i++) { |
| for (j=0; j<wnew; j++) { |
| newRaster[i*wnew + j] = gc->fill; |
| } |
| } |
| for (i=0; i<h; i++) { |
| for (j=0; j<w; j++) { |
| inew = i+yoff; |
| jnew = j+xoff; |
| newRaster[inew*wnew + jnew] = sraster[i*w + j]; |
| } |
| |
| } |
| } |
| |
| /* |
| * Rotate a raster image |
| * Code based on rotateAMColorLow() from leptonica library |
| |
| * draster must be pre-allocated. |
| |
| * smoothAlpha allows alpha channel to vary smoothly based on |
| * interpolation. If this is FALSE, then alpha values are |
| * taken from MAX(alpha) of relevant pixels. This means that |
| * areas of full transparency remain fully transparent, |
| * areas of opacity remain opaque, edges between anything less than opacity |
| * and opacity are opaque, and edges between full transparency |
| * and semitransparency become semitransparent. |
| */ |
| void R_GE_rasterRotate(unsigned int *sraster, int w, int h, double angle, |
| unsigned int *draster, const pGEcontext gc, |
| Rboolean smoothAlpha) { |
| int i, j; |
| int xcen, ycen, wm2, hm2; |
| int xdif, ydif, xpm, ypm, xp, yp, xf, yf; |
| int rval, gval, bval, aval; |
| unsigned int word00, word01, word10, word11; |
| unsigned int *sline, *dline; |
| double sina, cosa; |
| |
| /* 'angle' in leptonica is clockwise */ |
| angle = -angle; |
| |
| xcen = w / 2; |
| wm2 = w - 2; |
| ycen = h / 2; |
| hm2 = h - 2; |
| sina = 16. * sin(angle); |
| cosa = 16. * cos(angle); |
| |
| for (i = 0; i < h; i++) { |
| ydif = ycen - i; |
| dline = draster + i * w; |
| for (j = 0; j < w; j++) { |
| xdif = xcen - j; |
| xpm = (int) (-xdif * cosa - ydif * sina); |
| ypm = (int) (-ydif * cosa + xdif * sina); |
| xp = xcen + (xpm >> 4); |
| yp = ycen + (ypm >> 4); |
| xf = xpm & 0x0f; |
| yf = ypm & 0x0f; |
| |
| /* if off the edge, use transparent */ |
| if (xp < 0 || yp < 0 || xp > wm2 || yp > hm2) { |
| *(dline + j) = gc->fill; |
| continue; |
| } |
| |
| sline = sraster + yp * w; |
| |
| word00 = *(sline + xp); |
| word10 = *(sline + xp + 1); |
| word01 = *(sline + w + xp); |
| word11 = *(sline + w + xp + 1); |
| rval = ((16 - xf) * (16 - yf) * R_RED(word00) + |
| xf * (16 - yf) * R_RED(word10) + |
| (16 - xf) * yf * R_RED(word01) + |
| xf * yf * R_RED(word11) + 128) / 256; |
| gval = ((16 - xf) * (16 - yf) * R_GREEN(word00) + |
| xf * (16 - yf) * R_GREEN(word10) + |
| (16 - xf) * yf * R_GREEN(word01) + |
| xf * yf * R_GREEN(word11) + 128) / 256; |
| bval = ((16 - xf) * (16 - yf) * R_BLUE(word00) + |
| xf * (16 - yf) * R_BLUE(word10) + |
| (16 - xf) * yf * R_BLUE(word01) + |
| xf * yf * R_BLUE(word11) + 128) / 256; |
| if (smoothAlpha) { |
| aval = ((16 - xf) * (16 - yf) * R_ALPHA(word00) + |
| xf * (16 - yf) * R_ALPHA(word10) + |
| (16 - xf) * yf * R_ALPHA(word01) + |
| xf * yf * R_ALPHA(word11) + 128) / 256; |
| } else { |
| aval = (int)fmax2(fmax2(R_ALPHA(word00), R_ALPHA(word10)), |
| fmax2(R_ALPHA(word01), R_ALPHA(word11))); |
| } |
| *(dline + j) = R_RGBA(rval, gval, bval, aval); |
| } |
| } |
| } |