diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java index 217e5a92c015d088d6a94eff1c2b277a406dec3d..d79dbf56e66b9051831aae725ea8b5dea27ca9ec 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java @@ -18,8 +18,8 @@ import java.util.Map; import com.oracle.truffle.r.library.fastrGrid.GridState.GridPalette; import com.oracle.truffle.r.library.fastrGrid.device.GridColor; import com.oracle.truffle.r.runtime.RError; -import com.oracle.truffle.r.runtime.RInternalError; import com.oracle.truffle.r.runtime.RError.Message; +import com.oracle.truffle.r.runtime.RInternalError; import com.oracle.truffle.r.runtime.RRuntime; import com.oracle.truffle.r.runtime.Utils; import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; @@ -96,10 +96,20 @@ public final class GridColorUtils { } public static String gridColorToRString(GridColor color) { + int size = color.getAlpha() == GridColor.OPAQUE_ALPHA ? 7 : 9; + char[] value = new char[size]; + value[0] = '#'; + value[1] = getHexDigit(color.getRed() >> 4); + value[2] = getHexDigit(color.getRed()); + value[3] = getHexDigit(color.getGreen() >> 4); + value[4] = getHexDigit(color.getGreen()); + value[5] = getHexDigit(color.getBlue() >> 4); + value[6] = getHexDigit(color.getBlue()); if (color.getAlpha() == GridColor.OPAQUE_ALPHA) { - return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue()); + value[7] = getHexDigit((color.getAlpha()) >> 4); + value[8] = getHexDigit(color.getAlpha()); } - return String.format("#%02x%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); + return new String(value); } public static GridColor parseHex(String value) { @@ -114,6 +124,12 @@ public final class GridColorUtils { return new GridColor(red, green, blue, alpha); } + private static final char[] HEX_DIGITS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + public static char getHexDigit(int digit) { + return HEX_DIGITS[digit & 0xf]; + } + private static GridColor findByName(String synonym) { return (GridColor) NamesHolder.NAMES.get(normalizeColorName(synonym)); } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/GridColor.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/GridColor.java index ec11023ea1248c6471f9a92947c56543c8f91882..2472e266ca50ff0858821f00543dd620011c3ade 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/GridColor.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/GridColor.java @@ -30,6 +30,7 @@ public final class GridColor { public static final int OPAQUE_ALPHA = 0xff; private static final int TRANSPARENT_ALPHA = 0; public static final GridColor TRANSPARENT = new GridColor(0, 0, 0, TRANSPARENT_ALPHA); + public static final GridColor BLACK = new GridColor(0, 0, 0, OPAQUE_ALPHA); private final int value; diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/SVGDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/SVGDevice.java index 88bd8ff6fd6705e8f59eb7147063fb9be2182f0a..cd82ccda80a24cb1e6dbd302072400114130362b 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/SVGDevice.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/SVGDevice.java @@ -24,6 +24,7 @@ package com.oracle.truffle.r.library.fastrGrid.device; import static com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.GRID_LINE_BLANK; import static com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.INCH_TO_POINTS_FACTOR; +import static java.lang.Math.round; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -33,6 +34,7 @@ import java.text.DecimalFormat; import java.util.Base64; import java.util.Collections; +import com.oracle.truffle.r.library.fastrGrid.GridColorUtils; import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.GridFontStyle; import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.GridLineEnd; import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.GridLineJoin; @@ -67,11 +69,11 @@ public class SVGDevice implements GridDevice, FileGridDevice { // saving it anywhere. data.setLength(0); cachedCtx = null; - append("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); - append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">"); - append("<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' viewBox='0 0 %.3f %.3f'>", - width * COORD_FACTOR, - height * COORD_FACTOR); + data.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); + data.append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"); + append("<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' viewBox='0 0 %d %d' style='fill:transparent'>\n", + trRound(width), + trRound(height)); } @Override @@ -89,47 +91,52 @@ public class SVGDevice implements GridDevice, FileGridDevice { @Override public void drawRect(DrawingContext ctx, double leftX, double bottomY, double newWidth, double newHeight, double rotationAnticlockWise) { appendStyle(ctx); - append("<rect x='%.3f' y='%.3f' width='%.3f' height='%.3f'", leftX * COORD_FACTOR, transY(bottomY + newHeight) * COORD_FACTOR, newWidth * COORD_FACTOR, newHeight * COORD_FACTOR); + data.append("<rect x='").append(trRound(leftX)).append("' y='").append(trRound(transY(bottomY + newHeight))).append("' width='").append(trRound(newWidth)).append("' height='").append( + trRound(newHeight)).append('\''); if (rotationAnticlockWise != 0) { - append("transform='rotate(%.3f %.3f,%.3f)'", toDegrees(rotationAnticlockWise), (leftX + newWidth / 2.) * COORD_FACTOR, transY(bottomY + newHeight / 2.) * COORD_FACTOR); + appendTransform((int) round(toDegrees(rotationAnticlockWise)), trRound(leftX + newWidth / 2.), trRound(transY(bottomY + newHeight / 2.))); } - data.append("/>"); // end of 'rect' tag + appendColorStyle(ctx); + data.append("/>\n"); // end of 'rect' tag } @Override public void drawPolyLines(DrawingContext ctx, double[] x, double[] y, int startIndex, int length) { - drawPoly(ctx, x, y, startIndex, length, "style='fill:transparent'"); + drawPoly(ctx, x, y, startIndex, length, true); } @Override public void drawPolygon(DrawingContext ctx, double[] x, double[] y, int startIndex, int length) { - drawPoly(ctx, x, y, startIndex, length, ""); + drawPoly(ctx, x, y, startIndex, length, false); } @Override public void drawCircle(DrawingContext ctx, double centerX, double centerY, double radius) { appendStyle(ctx); - append("<circle cx='%.3f' cy='%.3f' r='%.3f'/>", centerX * COORD_FACTOR, transY(centerY) * COORD_FACTOR, radius * COORD_FACTOR); + data.append("<circle cx='").append(trRound(centerX)).append("' cy='").append(trRound(transY(centerY))).append("' r='").append(trRound(radius)).append('\''); + appendColorStyle(ctx); + data.append("/>\n"); } @Override public void drawRaster(double leftX, double bottomY, double width, double height, int[] pixels, int pixelsColumnsCount, ImageInterpolation interpolation) { byte[] bitmap = Bitmap.create(pixels, pixelsColumnsCount); String base64 = Base64.getEncoder().encodeToString(bitmap); - append("<image x='%.3f' y='%.3f' width='%.3f' height='%.3f' preserveAspectRatio='none' xlink:href='data:image/bmp;base64,%s'/>", leftX * COORD_FACTOR, transY(bottomY + height) * COORD_FACTOR, - width * COORD_FACTOR, height * COORD_FACTOR, base64); + data.append("<image x='").append(round(leftX * COORD_FACTOR)).append("' y='").append(trRound(transY(bottomY + height))); + data.append("' width='").append(round(width * COORD_FACTOR)).append("' height='").append(trRound(height)); + data.append("' preserveAspectRatio='none' xlink:href='data:image/bmp;base64,").append(base64).append("'/>\n"); } @Override public void drawString(DrawingContext ctx, double leftX, double bottomY, double rotationAnticlockWise, String text) { closeStyle(); - append("<text x='%.3f' y='%.3f' textLength='%.3fpx' lengthAdjust='spacingAndGlyphs' ", leftX * COORD_FACTOR, transY(bottomY) * COORD_FACTOR, getStringWidth(ctx, text) * COORD_FACTOR); - // SVG interprets the "fill" as the color of the text - data.append("style='").append(getStyleColor("fill", ctx.getColor())).append('\''); + data.append("<text x='").append(round(leftX * COORD_FACTOR)).append("' y='").append(trRound(transY(bottomY))); + data.append("' lengthAdjust='spacingAndGlyphs' textLength='").append(round(getStringWidth(ctx, text) * COORD_FACTOR)).append("px'"); + appendFontStyle(ctx); if (rotationAnticlockWise != 0) { - append(" transform='rotate(%.3f %.3f,%.3f)'", toDegrees(rotationAnticlockWise), leftX * COORD_FACTOR, transY(bottomY) * COORD_FACTOR); + appendTransform((int) round(toDegrees(rotationAnticlockWise)), trRound(leftX), trRound(transY(bottomY))); } - data.append(">").append(text).append("</text>"); + data.append(">").append(text).append("</text>\n"); } @Override @@ -173,16 +180,20 @@ public class SVGDevice implements GridDevice, FileGridDevice { return 0.7 * (ctx.getFontSize() / INCH_TO_POINTS_FACTOR); } - private void drawPoly(DrawingContext ctx, double[] x, double[] y, int startIndex, int length, String attributes) { + private void drawPoly(DrawingContext ctx, double[] x, double[] y, int startIndex, int length, boolean noFill) { appendStyle(ctx); data.append("<polyline points='"); for (int i = 0; i < length; i++) { - data.append(DECIMAL_FORMAT.format(x[i + startIndex] * COORD_FACTOR)); + data.append(trRound(x[i + startIndex])); data.append(','); - data.append(DECIMAL_FORMAT.format(transY(y[i + startIndex]) * COORD_FACTOR)); - data.append(' '); + data.append(trRound(transY(y[i + startIndex]))); + if (i != length - 1) { + data.append(' '); + } } - data.append("' ").append(attributes).append(" />"); + data.append('\''); + appendColorStyle(ctx, noFill); + data.append("/>\n"); } private void saveFile() throws DeviceCloseException { @@ -200,40 +211,34 @@ public class SVGDevice implements GridDevice, FileGridDevice { } if (cachedCtx != null) { // see #appendStyle - append("</g>"); + data.append("</g>"); } - append("</svg>"); + data.append("</svg>"); } // closes opened <g> tag if necessary private void closeStyle() { if (cachedCtx != null) { cachedCtx = null; - append("</g>"); + data.append("</g>"); } } private void appendStyle(DrawingContext ctx) { - if (cachedCtx == null || !DrawingContext.areSame(cachedCtx, ctx)) { + if (cachedCtx == null || !areSameGlobalStyles(cachedCtx, ctx)) { if (cachedCtx != null) { - append("</g>"); // close the previous style definition + data.append("</g>"); // close the previous style definition } - append("<g style='"); + data.append("<g"); appendStyleUncached(ctx); - append("'>"); + data.append('>').append('\n'); } cachedCtx = ctx; } private void appendStyleUncached(DrawingContext ctx) { byte[] lineType = ctx.getLineType(); - if (lineType == GRID_LINE_BLANK) { - append("stroke:transparent"); - } else { - append(getStyleColor("stroke", ctx.getColor())); - } - data.append(';').append(getStyleColor("fill", ctx.getFillColor())); - data.append(";stroke-width:").append(ctx.getLineWidth()); + data.append(" style='stroke-width:").append(ctx.getLineWidth()); if (lineType != DrawingContext.GRID_LINE_SOLID && lineType != DrawingContext.GRID_LINE_BLANK) { data.append(";stroke-dasharray:"); for (int i = 0; i < lineType.length; i++) { @@ -248,7 +253,33 @@ public class SVGDevice implements GridDevice, FileGridDevice { if (ctx.getLineJoin() == GridLineJoin.MITRE) { data.append(";stroke-miterlimit:").append(ctx.getLineMitre()); } - data.append(";font-size:").append(ctx.getFontSize()).append("px"); + data.append('\''); + } + + private void appendColorStyle(DrawingContext ctx) { + appendColorStyle(ctx, false); + } + + private void appendColorStyle(DrawingContext ctx, boolean noFill) { + byte[] lineType = ctx.getLineType(); + if (lineType == GRID_LINE_BLANK) { + data.append(" style='stroke:transparent"); + } else { + data.append(" style='"); + appendStyleColorAttrs("stroke", ctx.getColor()); + } + if (!noFill && !ctx.getFillColor().equals(GridColor.TRANSPARENT)) { + data.append(';'); + appendStyleColorAttrs("fill", ctx.getFillColor()); + data.append('\''); + } + data.append('\''); + } + + private void appendFontStyle(DrawingContext ctx) { + // Note: SVG interprets the "fill" as the color of the text + data.append(" style='font-size:").append(ctx.getFontSize()).append("px;"); + appendStyleColorAttrs("fill", ctx.getColor()); if (!ctx.getFontFamily().isEmpty()) { // Font-family strings 'mono', 'sans', and 'serif' are OK for us data.append(";font-family:").append(ctx.getFontFamily()); @@ -259,6 +290,7 @@ public class SVGDevice implements GridDevice, FileGridDevice { if (ctx.getFontStyle().isItalic()) { data.append(";font-style:italic"); } + data.append('\''); } private static String getSVGLineCap(GridLineEnd lineEnd) { @@ -287,12 +319,28 @@ public class SVGDevice implements GridDevice, FileGridDevice { } } - private static String getStyleColor(String prefix, GridColor color) { - return Utils.stringFormat("%s:rgb(%d,%d,%d);%s-opacity:%.3f", prefix, color.getRed(), color.getGreen(), color.getBlue(), prefix, color.getAlpha() / 255d); + private void appendStyleColorAttrs(String prefix, GridColor color) { + data.append(prefix).append(':'); + if (color.getAlpha() == GridColor.OPAQUE_ALPHA) { + data.append('#'); + data.append(GridColorUtils.getHexDigit(color.getRed() >> 4)); + data.append(GridColorUtils.getHexDigit(color.getRed())); + data.append(GridColorUtils.getHexDigit(color.getGreen() >> 4)); + data.append(GridColorUtils.getHexDigit(color.getGreen())); + data.append(GridColorUtils.getHexDigit(color.getBlue() >> 4)); + data.append(GridColorUtils.getHexDigit(color.getBlue())); + } else { + data.append("rgb(").append(color.getRed()).append(',').append(color.getGreen()).append(',').append(color.getBlue()).append(')').append(';'); + data.append(prefix).append("-opacity:").append(DECIMAL_FORMAT.format(color.getAlpha() / 255d)); + } } private void append(String fmt, Object... args) { - data.append(Utils.stringFormat(fmt + "\n", args)); + data.append(Utils.stringFormat(fmt, args)); + } + + private void appendTransform(int a, int b, int c) { + data.append(" transform='rotate(").append(a).append(',').append(b).append(',').append(c).append(")'"); } private double transY(double y) { @@ -303,6 +351,19 @@ public class SVGDevice implements GridDevice, FileGridDevice { return (180. / Math.PI) * -rotationAnticlockWise; } + private int trRound(double value) { + return (int) Math.round(value * COORD_FACTOR); + } + + static boolean areSameGlobalStyles(DrawingContext ctx1, DrawingContext ctx2) { + return ctx1 == ctx2 || (ctx1.getLineEnd() == ctx2.getLineEnd() && + ctx1.getLineJoin() == ctx2.getLineJoin() && + ctx1.getLineType() == ctx2.getLineType() && + ctx1.getLineHeight() == ctx2.getLineHeight() && + ctx1.getLineWidth() == ctx2.getLineWidth() && + ctx1.getLineMitre() == ctx2.getLineMitre()); + } + private static final class Bitmap { private static final int FILE_HEADER_SIZE = 14; private static final int IMAGE_HEADER_SIZE = 40; diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/Graphics2DDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/Graphics2DDevice.java index b9834220b64f3f7ccacf8ebf69a617ba4c1f3243..47726633f390b1329da8da3d9255a426543b73b4 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/Graphics2DDevice.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/Graphics2DDevice.java @@ -23,7 +23,6 @@ package com.oracle.truffle.r.library.fastrGrid.device.awt; import static com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.INCH_TO_POINTS_FACTOR; -import static java.awt.geom.Path2D.WIND_EVEN_ODD; import java.awt.BasicStroke; import java.awt.Color; @@ -33,13 +32,10 @@ import java.awt.Graphics2D; import java.awt.GraphicsEnvironment; import java.awt.Image; import java.awt.Paint; +import java.awt.Rectangle; import java.awt.RenderingHints; -import java.awt.Shape; import java.awt.Toolkit; import java.awt.geom.AffineTransform; -import java.awt.geom.Ellipse2D; -import java.awt.geom.Path2D; -import java.awt.geom.Rectangle2D; import java.awt.image.MemoryImageSource; import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext; @@ -72,6 +68,7 @@ public class Graphics2DDevice implements GridDevice { private Graphics2D graphics; private final boolean graphicsIsExclusive; private DrawingContext cachedContext; + private BasicStroke stokeCache; /** * @param graphics Object that should be used for the drawing. @@ -96,59 +93,76 @@ public class Graphics2DDevice implements GridDevice { @Override public void openNewPage() { - ensureOpen(); graphics.clearRect(0, 0, getWidthAwt(), getHeightAwt()); cachedContext = null; } @Override public void drawRect(DrawingContext ctx, double leftXIn, double bottomYIn, double widthIn, double heightIn, double rotationAnticlockWise) { - ensureOpen(); double leftXReal = transX(leftXIn); double topYReal = transY(bottomYIn + heightIn); int rectWidth = transDim(widthIn, leftXReal); int rectHeight = transDim(heightIn, topYReal); int leftX = iround(leftXReal); int topY = iround(topYReal); - setContext(ctx); + setStroke(ctx); if (rotationAnticlockWise == 0.) { - drawShape(ctx, new Rectangle2D.Double(leftX, topY, rectWidth, rectHeight)); + drawRectInternal(ctx, new Rectangle(leftX, topY, rectWidth, rectHeight)); } else { int halfWidth = iround(rectWidth / 2.); int halfHeight = iround(rectHeight / 2.); - transformed(iround(leftX + halfWidth), iround(topY + halfHeight), rotationAnticlockWise, () -> drawShape(ctx, new Rectangle2D.Double(-halfWidth, -halfHeight, rectWidth, rectHeight))); + transformed(iround(leftX + halfWidth), iround(topY + halfHeight), rotationAnticlockWise, () -> drawRectInternal(ctx, new Rectangle(-halfWidth, -halfHeight, rectWidth, rectHeight))); } } @Override public void drawPolyLines(DrawingContext ctx, double[] x, double[] y, int startIndex, int length) { - ensureOpen(); - Path2D.Double path = getPath2D(x, y, startIndex, length); - setContext(ctx); - graphics.draw(path); + int[] xi = new int[length]; + int[] yi = new int[length]; + getPath2D(x, y, xi, yi, startIndex, length); + setStroke(ctx); + setColor(ctx.getColor()); + graphics.drawPolyline(xi, yi, length); } @Override public void drawPolygon(DrawingContext ctx, double[] x, double[] y, int startIndex, int length) { - ensureOpen(); - Path2D.Double path = getPath2D(x, y, startIndex, length); - setContext(ctx); - drawShape(ctx, path); + int[] xi = new int[length]; + int[] yi = new int[length]; + getPath2D(x, y, xi, yi, startIndex, length); + setStroke(ctx); + GridColor fillColor = ctx.getFillColor(); + if (!fillColor.equals(GridColor.TRANSPARENT)) { + setColor(fillColor); + graphics.fillPolygon(xi, yi, length); + } + if (!fillColor.equals(ctx.getColor())) { + setColor(ctx.getColor()); + graphics.drawPolygon(xi, yi, length); + } } @Override public void drawCircle(DrawingContext ctx, double centerXIn, double centerYIn, double radiusIn) { - ensureOpen(); - setContext(ctx); + setStroke(ctx); double xRel = transX(centerXIn - radiusIn); double yRel = transY(centerYIn + radiusIn); int diameter = transDim(radiusIn * 2d, Math.max(xRel % 1, yRel % 1)); - drawShape(ctx, new Ellipse2D.Double(iround(xRel), iround(yRel), diameter, diameter)); + int xi = iround(xRel); + int yi = iround(yRel); + GridColor fillColor = ctx.getFillColor(); + if (!fillColor.equals(GridColor.TRANSPARENT)) { + setColor(fillColor); + graphics.fillOval(xi, yi, diameter, diameter); + } + if (!fillColor.equals(ctx.getColor())) { + setColor(ctx.getColor()); + graphics.drawOval(xi, yi, diameter, diameter); + } } @Override public void drawRaster(double leftX, double bottomY, double width, double height, int[] pixels, int pixelsColumnsCount, ImageInterpolation interpolation) { - ensureOpen(); graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, fromInterpolation(interpolation)); Image image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(pixelsColumnsCount, pixels.length / pixelsColumnsCount, pixels, 0, pixelsColumnsCount)); double yRel = transY(bottomY + height); @@ -158,8 +172,7 @@ public class Graphics2DDevice implements GridDevice { @Override public void drawString(DrawingContext ctx, double leftXIn, double bottomYIn, double rotationAnticlockWise, String text) { - ensureOpen(); - setContextAndFont(ctx); + setStrokeAndFont(ctx); int leftX = iround(transX(leftXIn)); FontMetrics fontMetrics = graphics.getFontMetrics(graphics.getFont()); int bottomY = iround(transY(bottomYIn)) - fontMetrics.getDescent(); @@ -178,14 +191,14 @@ public class Graphics2DDevice implements GridDevice { @Override public double getStringWidth(DrawingContext ctx, String text) { - setContextAndFont(ctx); + setStrokeAndFont(ctx); int swingUnits = graphics.getFontMetrics(graphics.getFont()).stringWidth(text); return swingUnits / AWT_POINTS_IN_INCH; } @Override public double getStringHeight(DrawingContext ctx, String text) { - setContextAndFont(ctx); + setStrokeAndFont(ctx); FontMetrics fontMetrics = graphics.getFontMetrics(graphics.getFont()); double swingUnits = fontMetrics.getAscent() + fontMetrics.getDescent(); return swingUnits / AWT_POINTS_IN_INCH; @@ -205,15 +218,6 @@ public class Graphics2DDevice implements GridDevice { return height; } - /** - * If the device can be closed by an action outside of the R interpreter (not e.g. dev.close()), - * then the device should be able to re-open itself if any drawing happens after it was closed - * in such way. - */ - void ensureOpen() { - // nop - } - void setGraphics2D(Graphics2D newGraphics) { assert newGraphics != null; graphics = newGraphics; @@ -253,39 +257,51 @@ public class Graphics2DDevice implements GridDevice { graphics.setTransform(oldTransform); } - private Path2D.Double getPath2D(double[] x, double[] y, int startIndex, int length) { + private void getPath2D(double[] x, double[] y, int[] xi, int[] yi, int startIndex, int length) { assert startIndex >= 0 && startIndex < x.length && startIndex < y.length : "startIndex out of bounds"; assert length > 0 && (startIndex + length) <= Math.min(x.length, y.length) : "length out of bounds"; - Path2D.Double path = new Path2D.Double(WIND_EVEN_ODD, x.length); - path.moveTo(transX(x[startIndex]), transY(y[startIndex])); - for (int i = startIndex + 1; i < length; i++) { - path.lineTo(transX(x[i]), transY(y[i])); + for (int i = 0; i < length; i++) { + xi[i] = iround(transX(x[i + startIndex])); + yi[i] = iround(transY(y[i + startIndex])); } - return path; } - private void drawShape(DrawingContext drawingCtx, Shape shape) { + private void drawRectInternal(DrawingContext drawingCtx, Rectangle shape) { + GridColor fillColor = drawingCtx.getFillColor(); + if (!fillColor.equals(GridColor.TRANSPARENT)) { + setColor(fillColor); + graphics.fill(shape); + } + if (!fillColor.equals(drawingCtx.getColor())) { + setColor(drawingCtx.getColor()); + graphics.draw(shape); + } + } + + private void setColor(GridColor color) { + Color awtColor = fromGridColor(color); Paint paint = graphics.getPaint(); - graphics.setPaint(fromGridColor(drawingCtx.getFillColor())); - graphics.fill(shape); - graphics.setPaint(paint); - graphics.draw(shape); + // Note: setting different color intance (even if equal to the original) causes graphical + // pipeline invalidation in Graphics2D implementation + if (!(paint instanceof Color && ((Color) paint).equals(awtColor))) { + graphics.setColor(awtColor); + } } - private void setContext(DrawingContext ctx) { + private void setStroke(DrawingContext ctx) { if (graphicsIsExclusive && cachedContext == ctx) { return; } - graphics.setColor(fromGridColor(ctx.getColor())); graphics.setStroke(getStrokeFromCtx(ctx)); cachedContext = ctx; } - private void setContextAndFont(DrawingContext ctx) { + private void setStrokeAndFont(DrawingContext ctx) { if (graphicsIsExclusive && cachedContext == ctx) { return; } - setContext(ctx); + setStroke(ctx); + setColor(ctx.getColor()); float fontSize = (float) ((ctx.getFontSize() / INCH_TO_POINTS_FACTOR) * AWT_POINTS_IN_INCH); Font font = new Font(getFontName(ctx.getFontFamily()), getAwtFontStyle(ctx.getFontStyle()), 1).deriveFont(fontSize); graphics.setFont(font); @@ -329,7 +345,7 @@ public class Graphics2DDevice implements GridDevice { return new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); } - private static BasicStroke getStrokeFromCtx(DrawingContext ctx) { + private BasicStroke getStrokeFromCtx(DrawingContext ctx) { byte[] type = ctx.getLineType(); double width = ctx.getLineWidth(); int lineJoin = fromGridLineJoin(ctx.getLineJoin()); @@ -338,7 +354,10 @@ public class Graphics2DDevice implements GridDevice { if (type == DrawingContext.GRID_LINE_BLANK) { return blankStroke; } else if (type == DrawingContext.GRID_LINE_SOLID) { - return new BasicStroke((float) (width), endCap, lineJoin, lineMitre); + if (stokeCache == null || !areEqual(stokeCache, (float) width, endCap, lineJoin, lineMitre)) { + stokeCache = new BasicStroke((float) (width), endCap, lineJoin, lineMitre); + } + return stokeCache; } float[] pattern = new float[type.length]; for (int i = 0; i < pattern.length; i++) { @@ -383,4 +402,11 @@ public class Graphics2DDevice implements GridDevice { private static int iround(double val) { return (int) Math.round(val); } + + private static boolean areEqual(BasicStroke s, float width, int endCap, int lineJoin, float lineMitre) { + return s.getLineWidth() == width && + s.getEndCap() == endCap && + s.getLineJoin() == lineJoin && + s.getMiterLimit() == lineMitre; + } } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevCurr.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevCurr.java index 5c9046e35dc561a4f13b74eb43bc67676f16c48c..b386e039569651b90c0ca863c61b8edb240a4ca8 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevCurr.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevCurr.java @@ -23,10 +23,8 @@ package com.oracle.truffle.r.library.fastrGrid.grDevices; import com.oracle.truffle.r.library.fastrGrid.GridContext; -import com.oracle.truffle.r.library.fastrGrid.graphics.RGridGraphicsAdapter; import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; import com.oracle.truffle.r.runtime.data.RDataFactory; -import com.oracle.truffle.r.runtime.data.RStringVector; import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector; public final class DevCurr extends RExternalBuiltinNode.Arg0 { @@ -37,7 +35,6 @@ public final class DevCurr extends RExternalBuiltinNode.Arg0 { @Override public RAbstractIntVector execute() { int index = GridContext.getContext().getCurrentDeviceIndex(); - RStringVector names = RDataFactory.createStringVectorFromScalar(RGridGraphicsAdapter.getDeviceName(index)); - return RDataFactory.createIntVector(new int[]{index + 1}, RDataFactory.COMPLETE_VECTOR, names); + return RDataFactory.createIntVector(new int[]{index + 1}, RDataFactory.COMPLETE_VECTOR); } } diff --git a/com.oracle.truffle.r.native/fficall/src/truffle_common/Rinternals_truffle_common.h b/com.oracle.truffle.r.native/fficall/src/truffle_common/Rinternals_truffle_common.h index cb21b7d4024ae51fc7e398f75664c70bcc9a12b2..9dc397d6325f20785a4450402e83f32fb513b555 100644 --- a/com.oracle.truffle.r.native/fficall/src/truffle_common/Rinternals_truffle_common.h +++ b/com.oracle.truffle.r.native/fficall/src/truffle_common/Rinternals_truffle_common.h @@ -66,6 +66,15 @@ #include <rffiutils.h> +// these two functions are here just to handle casting void* to void function pointers... +DL_FUNC R_ExternalPtrAddrFn(SEXP s) { + return (DL_FUNC) R_ExternalPtrAddr(s); +} + +SEXP R_MakeExternalPtrFn(DL_FUNC p, SEXP tag, SEXP prot) { + return R_MakeExternalPtr((void *) p, tag, prot); +} + // R_GlobalEnv et al are not a variables in FASTR as they are RContext specific SEXP FASTR_R_GlobalEnv() { TRACE0(); @@ -1391,6 +1400,7 @@ SEXP R_MakeExternalPtr(void *p, SEXP tag, SEXP prot) { } void *R_ExternalPtrAddr(SEXP s) { + TRACE0(); SEXP result = ((call_R_ExternalPtrAddr) callbacks[R_ExternalPtrAddr_x])(s); checkExitCall();