diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DoSetViewPort.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DoSetViewPort.java index 591c4e25f4ef7b9780e6870b47f142c4998ecb4f..31540170256e9b30ce9c063c83d3752697c4f48f 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DoSetViewPort.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DoSetViewPort.java @@ -29,7 +29,6 @@ import com.oracle.truffle.r.library.fastrGrid.Unit.IsRelativeUnitNode; import com.oracle.truffle.r.library.fastrGrid.Unit.UnitConversionContext; import com.oracle.truffle.r.library.fastrGrid.ViewPort.LayoutPos; import com.oracle.truffle.r.library.fastrGrid.ViewPort.LayoutSize; -import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext; import com.oracle.truffle.r.library.fastrGrid.device.GridDevice; import com.oracle.truffle.r.nodes.unary.CastNode; import com.oracle.truffle.r.runtime.RError.Message; @@ -65,11 +64,11 @@ class DoSetViewPort extends RBaseNode { } GridDevice currentDevice = GridContext.getContext().getCurrentDevice(); - DrawingContext deviceDrawingContext = GridState.getInitialGPar(currentDevice); + GPar gpar = GridState.getInitialGPar(currentDevice); RList parent = asListOrNull(pushedViewPort.getDataAt(ViewPort.PVP_PARENT)); boolean doNotRecalculateParent = hasParent && !ViewPort.updateDeviceSizeInVP(parent, currentDevice); - calcViewportTransform(pushedViewPort, parent, doNotRecalculateParent, currentDevice, deviceDrawingContext); + calcViewportTransform(pushedViewPort, parent, doNotRecalculateParent, currentDevice, gpar); // TODO: clipping pushedVPData[ViewPort.PVP_CLIPRECT] = RDataFactory.createDoubleVector(new double[]{0, 0, 0, 0}, RDataFactory.COMPLETE_VECTOR); @@ -87,37 +86,37 @@ class DoSetViewPort extends RBaseNode { * @param incremental If {@code true} it is assumed that we can just take the transformation * matrix and other values from the parent without re-calculating them recursively. * @param device This method needs the device in order to convert units - * @param deviceDrawingContext This method needs to know the device default drawing context in + * @param deviceTopLevelGpar This method needs to know the device default drawing context in * order to convert units for the top level view port */ @TruffleBoundary - public void calcViewportTransform(RList viewPort, Object parent, boolean incremental, GridDevice device, DrawingContext deviceDrawingContext) { + public void calcViewportTransform(RList viewPort, Object parent, boolean incremental, GridDevice device, GPar deviceTopLevelGpar) { double[][] parentTransform; ViewPortContext parentContext; ViewPortLocation vpl; Size parentSize; - DrawingContext drawingContext; + GPar drawingContext; double parentAngle; if (parent == null || parent == RNull.instance) { parentTransform = TransformMatrix.identity(); parentContext = ViewPortContext.createDefault(); parentSize = new Size(device.getWidth(), device.getHeight()); vpl = ViewPortLocation.fromViewPort(viewPort); - drawingContext = deviceDrawingContext; + drawingContext = deviceTopLevelGpar; parentAngle = 0; } else { assert parent instanceof RList : "inconsistent data: parent of a viewport must be a list"; RList parentVPList = (RList) parent; Object[] parentData = parentVPList.getDataWithoutCopying(); if (!incremental) { - calcViewportTransform(parentVPList, parentData[ViewPort.PVP_PARENT], false, device, deviceDrawingContext); + calcViewportTransform(parentVPList, parentData[ViewPort.PVP_PARENT], false, device, deviceTopLevelGpar); } parentSize = new Size(Unit.cmToInches(castScalar(parentData[ViewPort.PVP_WIDTHCM])), Unit.cmToInches(castScalar(parentData[ViewPort.PVP_HEIGHTCM]))); parentTransform = fromFlat(castDoubleVector(parentData[ViewPort.PVP_TRANS]).materialize().getDataWithoutCopying()); parentContext = ViewPortContext.fromViewPort(parentVPList); parentAngle = asDouble(parentData[ViewPort.PVP_ROTATION]); - drawingContext = GPar.asDrawingContext(asList(viewPort.getDataAt(ViewPort.PVP_PARENTGPAR))); + drawingContext = GPar.create(asList(viewPort.getDataAt(ViewPort.PVP_PARENTGPAR))); boolean noLayout = (isNull(viewPort.getDataAt(ViewPort.VP_VALIDLPOSROW)) && isNull(viewPort.getDataAt(ViewPort.VP_VALIDLPOSCOL))) || isNull(parentData[ViewPort.VP_LAYOUT]); if (noLayout) { vpl = ViewPortLocation.fromViewPort(viewPort); @@ -157,8 +156,7 @@ class DoSetViewPort extends RBaseNode { // Finally, allocate the rows and columns for this viewport's layout if it has one if (!isNull(viewPort.getDataAt(ViewPort.VP_LAYOUT))) { ViewPortContext vpCtx = ViewPortContext.fromViewPort(viewPort); - DrawingContext drawingCtx = GPar.asDrawingContext(asList(viewPort.getDataAt(ViewPort.PVP_GPAR))); - calcViewPortLayout(viewPort, new Size(width, height), vpCtx, device, drawingCtx); + calcViewPortLayout(viewPort, new Size(width, height), vpCtx, device, GPar.create(asList(viewPort.getDataAt(ViewPort.PVP_GPAR)))); } Object[] viewPortData = viewPort.getDataWithoutCopying(); @@ -168,13 +166,13 @@ class DoSetViewPort extends RBaseNode { viewPortData[ViewPort.PVP_TRANS] = RDataFactory.createDoubleVector(flatten(transform), RDataFactory.COMPLETE_VECTOR, new int[]{3, 3}); } - private void calcViewPortLayout(RList viewPort, Size size, ViewPortContext parentVPCtx, GridDevice device, DrawingContext drawingCtx) { + private void calcViewPortLayout(RList viewPort, Size size, ViewPortContext parentVPCtx, GridDevice device, GPar gpar) { LayoutSize layoutSize = LayoutSize.fromViewPort(viewPort); double[] npcWidths = new double[layoutSize.ncol]; double[] npcHeights = new double[layoutSize.nrow]; boolean[] relativeWidths = new boolean[layoutSize.ncol]; boolean[] relativeHeights = new boolean[layoutSize.nrow]; - UnitConversionContext conversionCtx = new UnitConversionContext(size, parentVPCtx, device, drawingCtx); + UnitConversionContext conversionCtx = new UnitConversionContext(size, parentVPCtx, device, gpar); // For both dimensions we find out which units are other than "null" for those we can // immediately calculate the physical size in npcWidth/npcHeights. The reducedWidth/Height @@ -195,8 +193,8 @@ class DoSetViewPort extends RBaseNode { int respect = RRuntime.asInteger(layoutAsList.getDataAt(ViewPort.LAYOUT_VRESPECT)); int[] layoutRespectMat = ((RAbstractIntVector) layoutAsList.getDataAt(ViewPort.LAYOUT_MRESPECT)).materialize().getDataWithoutCopying(); if ((reducedHeight > 0 || reducedWidth > 0) && respect > 0) { - double sumRelWidth = sumRelativeDimension(layoutWidths, relativeWidths, parentVPCtx, device, drawingCtx, true); - double sumRelHeight = sumRelativeDimension(layoutHeights, relativeHeights, parentVPCtx, device, drawingCtx, false); + double sumRelWidth = sumRelativeDimension(layoutWidths, relativeWidths, parentVPCtx, device, gpar, true); + double sumRelHeight = sumRelativeDimension(layoutHeights, relativeHeights, parentVPCtx, device, gpar, false); double tempWidth = reducedWidth; double tempHeight = reducedHeight; double denom; @@ -250,8 +248,8 @@ class DoSetViewPort extends RBaseNode { } // Secondly, allocate remaining relative widths and heights in the remaining space - allocateRelativeDim(layoutSize, layoutWidths, npcWidths, relativeWidths, reducedWidth, respect, layoutRespectMat, device, drawingCtx, parentVPCtx, true); - allocateRelativeDim(layoutSize, layoutHeights, npcHeights, relativeHeights, reducedHeight, respect, layoutRespectMat, device, drawingCtx, parentVPCtx, false); + allocateRelativeDim(layoutSize, layoutWidths, npcWidths, relativeWidths, reducedWidth, respect, layoutRespectMat, device, gpar, parentVPCtx, true); + allocateRelativeDim(layoutSize, layoutHeights, npcHeights, relativeHeights, reducedHeight, respect, layoutRespectMat, device, gpar, parentVPCtx, false); // Create the result Object[] vpData = viewPort.getDataWithoutCopying(); @@ -260,9 +258,9 @@ class DoSetViewPort extends RBaseNode { } private void allocateRelativeDim(LayoutSize layoutSize, RAbstractContainer layoutItems, double[] npcItems, boolean[] relativeItems, double reducedDim, int respect, int[] layoutRespectMat, - GridDevice device, DrawingContext drawingCtx, ViewPortContext parentVPCtx, boolean isWidth) { + GridDevice device, GPar gpar, ViewPortContext parentVPCtx, boolean isWidth) { assert relativeItems.length == npcItems.length; - UnitConversionContext layoutModeCtx = new UnitConversionContext(new Size(0, 0), parentVPCtx, device, drawingCtx, 1, 0); + UnitConversionContext layoutModeCtx = new UnitConversionContext(new Size(0, 0), parentVPCtx, device, gpar, 1, 0); double totalUnrespectedSize = 0; if (reducedDim > 0) { for (int i = 0; i < relativeItems.length; i++) { @@ -311,9 +309,9 @@ class DoSetViewPort extends RBaseNode { return false; } - private double sumRelativeDimension(RAbstractContainer layoutItems, boolean[] relativeItems, ViewPortContext parentVPCtx, GridDevice device, DrawingContext drawingCtx, + private double sumRelativeDimension(RAbstractContainer layoutItems, boolean[] relativeItems, ViewPortContext parentVPCtx, GridDevice device, GPar gpar, boolean isWidth) { - UnitConversionContext layoutModeCtx = new UnitConversionContext(new Size(0, 0), parentVPCtx, device, drawingCtx, 1, 0); + UnitConversionContext layoutModeCtx = new UnitConversionContext(new Size(0, 0), parentVPCtx, device, gpar, 1, 0); double totalWidth = 0; for (int i = 0; i < relativeItems.length; i++) { if (relativeItems[i]) { diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DrawArrowsNode.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DrawArrowsNode.java index e177c722b19cefe51bc3679c57b7ba22255b7908..cda58b06a3b397ffdb2c31047af692a8d855f52b 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DrawArrowsNode.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DrawArrowsNode.java @@ -66,7 +66,7 @@ class DrawArrowsNode extends Node { arrowLength = Math.max(arrowLength, unitToInches.convertWidth(lengthVec, parentIndex, conversionCtx)); // draw the arrows GridDevice device = conversionCtx.device; - DrawingContext drawingCtx = conversionCtx.drawingContext; + DrawingContext drawingCtx = conversionCtx.gpar.getDrawingContext(parentIndex); if (first && start) { drawArrow(drawingCtx, device, arrowType, x[startIndex], y[startIndex], x[startIndex + 1], y[startIndex + 1], angle, arrowLength); } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java index 6f69df073cbb3035caf1effbb0d77ef62ec23009..de1e6a86ddf46357ab1ea4277748096595a9bc74 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java @@ -11,10 +11,12 @@ */ package com.oracle.truffle.r.library.fastrGrid; -import static com.oracle.truffle.r.library.fastrGrid.GridUtils.asAbstractContainer; import static com.oracle.truffle.r.library.fastrGrid.GridUtils.asDouble; +import static com.oracle.truffle.r.library.fastrGrid.GridUtils.asString; +import static com.oracle.truffle.r.library.fastrGrid.GridUtils.getDataAtMod; import java.util.Arrays; +import java.util.function.Function; import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext; import com.oracle.truffle.r.library.fastrGrid.device.DrawingContextDefaults; @@ -30,10 +32,20 @@ import com.oracle.truffle.r.runtime.data.RStringVector; import com.oracle.truffle.r.runtime.data.model.RAbstractContainer; import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector; import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector; +import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; +import com.oracle.truffle.r.runtime.data.model.RAbstractVector; /** * In the context of grid package, GPar is a list that contains the parameters for the drawing, like - * line style, color, etc. This class wraps the list and provides type-safe access to its elements. + * line style, color, etc. This class wraps the list and provides way to convert it to + * {@link DrawingContext}. First create instance of {@link GPar} and then use + * {@link #getDrawingContext(int)} to get the drawing context. Note that grid's gpar can + * theoretically contain vector as the value of some graphical parameters, in such case, when + * drawing i-th element, e.g. i-th rectangle in {@link LRect}, we should use i-th (mod length) + * element of such vector. Note that this is sort of ignored in layout calculations, where we always + * take the first element. In other words, instance of {@link GPar} represents grid's gpar where the + * graphical parameter may be vectors, whereas {@link DrawingContext} is flattened view where it is + * already determined which index is used to access the vectors. */ public final class GPar { private static final int GP_FILL = 0; @@ -57,14 +69,21 @@ public final class GPar { * fontsize*cex*lineheight. */ private static final int GP_LINEHEIGHT = 7; + /** + * In fact means font style: bold, italic, bolditalic or normal. + */ private static final int GP_FONT = 8; private static final int GP_FONTFAMILY = 9; private static final int GP_ALPHA = 10; private static final int GP_LINEEND = 11; private static final int GP_LINEJOIN = 12; private static final int GP_LINEMITRE = 13; + /** + * Multiplier of line width {@link #GP_LWD}. + */ private static final int GP_LEX = 14; - private static final int GP_FONTFACE = 15; + + // Note: there is last slot "fontface" which is either unused at all, or only used in R code private static final int GP_LENGTH = 16; private static final String[] NAMES = new String[]{ "fill", @@ -82,9 +101,43 @@ public final class GPar { "linejoin", "linemitre", "lex", - "fontface" // TODO: could not find this name in grid sources + "fontface" }; private static final RStringVector NAMES_VECTOR = (RStringVector) RDataFactory.createStringVector(NAMES, RDataFactory.COMPLETE_VECTOR).makeSharedPermanent(); + private final RList gpar; + // majority of gpar instances contains only scalar values, for those we make sure we do not + // create a new drawing context instance for every index. + private final boolean singleDrawingCtx; + private DrawingContext indexZeroDrawingCtx; + + public GPar(RList gpar, boolean singleDrawingCtx) { + this.gpar = gpar; + this.gpar.makeSharedPermanent(); + this.singleDrawingCtx = singleDrawingCtx; + indexZeroDrawingCtx = new GParDrawingContext(gpar, 0); + } + + public static double getCex(RList gpar) { + return asDouble(gpar.getDataAt(GP_CEX)); + } + + public static GPar create(RList gpar) { + boolean singleDrawingCtx = true; + for (int i = 0; i < gpar.getLength(); i++) { + Object item = gpar.getDataAt(i); + if (item instanceof RAbstractVector) { + singleDrawingCtx &= ((RAbstractVector) item).getLength() == 1; + } + } + return new GPar(gpar, singleDrawingCtx); + } + + public DrawingContext getDrawingContext(int cyclicIndex) { + if (singleDrawingCtx || cyclicIndex == 0) { + return indexZeroDrawingCtx; + } + return new GParDrawingContext(gpar, cyclicIndex); + } public static RList createNew(GridDevice device) { Object[] data = new Object[GP_LENGTH]; @@ -92,14 +145,14 @@ public final class GPar { Arrays.fill(data, RNull.instance); data[GP_COL] = defaults.color; data[GP_FILL] = defaults.fillColor; - data[GP_GAMMA] = newDoubleVec(0); + data[GP_GAMMA] = newDoubleVec(0); // Note: we do not use this parameter data[GP_LTY] = "solid"; data[GP_LWD] = newDoubleVec(1); data[GP_CEX] = newDoubleVec(1); data[GP_FONTSIZE] = newDoubleVec(16); data[GP_LINEHEIGHT] = newDoubleVec(1.2); - data[GP_FONT] = RDataFactory.createIntVectorFromScalar(1); // TODO: font constants? - data[GP_FONTFAMILY] = ""; // means default font (probably) + data[GP_FONT] = RDataFactory.createIntVectorFromScalar(1); + data[GP_FONTFAMILY] = ""; // means default font data[GP_ALPHA] = newDoubleVec(1); data[GP_LINEEND] = "round"; data[GP_LINEJOIN] = "round"; @@ -110,53 +163,46 @@ public final class GPar { return result; } - public static double getCex(RList gpar) { - return asDouble(gpar.getDataAt(GP_CEX)); - } - - public static DrawingContext asDrawingContext(RList gpar) { - return new GParDrawingContext(gpar); - } - private static RAbstractDoubleVector newDoubleVec(double val) { return RDataFactory.createDoubleVectorFromScalar(val); } private static final class GParDrawingContext implements DrawingContext { private final Object[] data; + private final int index; - private GParDrawingContext(RList list) { + private GParDrawingContext(RList list, int index) { data = list.getDataWithoutCopying(); - list.makeSharedPermanent(); + this.index = index; } @Override - public GridLineType getLineType() { - Object lty = data[GP_LTY]; - if (lty == null || lty == RNull.instance) { - return GridLineType.SOLID; - } - String name = RRuntime.asString(lty); - if (name != null) { - return lineTypeFromName(name); - } - RAbstractContainer ltyVec = asAbstractContainer(lty); - int num; - if (ltyVec.getLength() == 0) { - num = RRuntime.INT_NA; - } else if (ltyVec instanceof RAbstractDoubleVector) { - double realVal = ((RAbstractDoubleVector) ltyVec).getDataAt(0); - num = RRuntime.isNA(realVal) ? RRuntime.INT_NA : (int) realVal; - } else if (ltyVec instanceof RAbstractIntVector) { - num = ((RAbstractIntVector) ltyVec).getDataAt(0); - } else { - num = RRuntime.INT_NA; - } + public byte[] getLineType() { + return convertNamedValue(data[GP_LTY], LINE_STYLES.length - 1, "line type", GParDrawingContext::lineTypeFromName, num -> LINE_STYLES[num]); + } + + @Override + public double getLineWidth() { + return asDouble(data[GP_LWD], index) * asDouble(data[GP_LEX], index); + } - if (RRuntime.isNA(num)) { - throw RError.error(RError.NO_CALLER, Message.GENERIC, "Invalid line type."); + @Override + public GridLineJoin getLineJoin() { + return convertNamedValue(data[GP_LINEJOIN], GridLineJoin.LAST_VALUE, "line join", GParDrawingContext::lineJoinFromName, GridLineJoin::fromInt); + } + + @Override + public GridLineEnd getLineEnd() { + return convertNamedValue(data[GP_LINEEND], GridLineEnd.LAST_VALUE, "line end", GParDrawingContext::lineEndFromName, GridLineEnd::fromInt); + } + + @Override + public double getLineMitre() { + double value = asDouble(data[GP_LINEMITRE], index); + if (value < 1.) { + throw RError.error(RError.NO_CALLER, Message.GENERIC, "Invalid line mitre."); } - return GridLineType.fromInt(num); + return value; } @Override @@ -166,12 +212,22 @@ public final class GPar { @Override public double getFontSize() { - return asDouble(data[GP_FONTSIZE]) * asDouble(data[GP_CEX]); + return asDouble(data[GP_FONTSIZE], index) * asDouble(data[GP_CEX], index); + } + + @Override + public GridFontStyle getFontStyle() { + return GridFontStyle.fromInt(GridUtils.asInt(data[GP_FONT], index)); + } + + @Override + public String getFontFamily() { + return GridUtils.asString(data[GP_FONTFAMILY], index); } @Override public double getLineHeight() { - return asDouble(data[GP_LINEHEIGHT]); + return asDouble(data[GP_LINEHEIGHT], index); } @Override @@ -179,29 +235,129 @@ public final class GPar { return getGridColor(GP_FILL); } - private GridColor getGridColor(int index) { - return GridColorUtils.gridColorFromString(RRuntime.asString(data[index])); + /** + * Converts value to given enum type using either {@code nameMapper} for String values or + * {@code valueMapper} for integer value, which is first validated to be greater or equal to + * 0 and less or equal to the {@code maxValue} parameter. If {@code nameMapper} returns + * {@code null} or integer validation fails, error with given {@code propertyName} in the + * message is thrown. + */ + public <T> T convertNamedValue(Object value, int maxValue, String propertyName, Function<String, T> nameMapper, Function<Integer, T> valueMapper) { + T result = null; + if (isStringValue(value)) { + String name = asString(value, index); + if (name != null) { + result = nameMapper.apply(name); + } + } else { + int num = getIntAtMod(value, index); + if (RRuntime.isNA(num) || num < 0 || num > maxValue) { + result = null; + } else { + result = valueMapper.apply(num); + } + } + if (result == null) { + throw RError.error(RError.NO_CALLER, Message.GENERIC, "Invalid " + propertyName); + } + return result; } - private GridLineType lineTypeFromName(String name) { + private GridColor getGridColor(int listIndex) { + GridColor color = GridColorUtils.gridColorFromString(GridUtils.asString(data[listIndex], index)); + double alpha = asDouble(data[GP_ALPHA], index); + if (alpha != 1.) { + int newAlpha = Math.min(255, (int) (alpha * ((color.getAlpha() / 255.0) * 255))); + return new GridColor(color.getRed(), color.getGreen(), color.getBlue(), newAlpha); + } else { + return color; + } + } + + private static final byte[] DASHED_LINE = new byte[]{4, 4}; + private static final byte[] DOTTED_LINE = new byte[]{1, 3}; + private static final byte[] DOTDASH_LINE = new byte[]{1, 3, 4, 3}; + private static final byte[] LONGDASH_LINE = new byte[]{7, 3}; + private static final byte[] TWODASH_LINE = new byte[]{2, 2, 6, 2}; + private static final byte[][] LINE_STYLES = new byte[][]{DrawingContext.GRID_LINE_BLANK, DrawingContext.GRID_LINE_SOLID, DASHED_LINE, DOTTED_LINE, DOTDASH_LINE, LONGDASH_LINE, TWODASH_LINE}; + + private static byte[] lineTypeFromName(String name) { switch (name) { case "solid": - return GridLineType.SOLID; + return DrawingContext.GRID_LINE_SOLID; case "dashed": - return GridLineType.DASHED; + return DASHED_LINE; case "dotted": - return GridLineType.DOTTED; + return DOTTED_LINE; case "dotdashed": - return GridLineType.DOTDASHED; + return DOTDASH_LINE; case "longdash": - return GridLineType.LONGDASH; + return LONGDASH_LINE; case "twodash": - return GridLineType.TWODASH; + return TWODASH_LINE; case "blank": - return GridLineType.BLANK; + return DrawingContext.GRID_LINE_BLANK; + } + byte[] result = new byte[name.length()]; + for (int i = 0; i < name.length(); i++) { + result[i] = (byte) Character.digit(name.charAt(i), 16); + if (result[i] == -1) { + return null; + } + } + return result; + } + + private static GridLineEnd lineEndFromName(String name) { + switch (name) { + case "round": + return GridLineEnd.ROUND; + case "butt": + return GridLineEnd.BUTT; + case "square": + return GridLineEnd.SQUARE; + default: + return null; + } + } + + private static GridLineJoin lineJoinFromName(String name) { + switch (name) { + case "round": + return GridLineJoin.ROUND; + case "mitre": + return GridLineJoin.MITRE; + case "bevel": + return GridLineJoin.BEVEL; default: - // TODO: implement hex digits as line style - throw RError.error(RError.NO_CALLER, Message.GENERIC, "Unexpected line type '" + name + "'."); + return null; + } + } + + private static boolean isStringValue(Object lty) { + return lty instanceof String || lty instanceof RAbstractStringVector; + } + + // NA indicates error + private static int getIntAtMod(Object obj, int index) { + if (obj instanceof Integer) { + return (int) obj; + } else if (obj instanceof Double) { + return (int) ((double) obj); + } else if (!(obj instanceof RAbstractContainer)) { + return RRuntime.INT_NA; + } + + RAbstractContainer value = (RAbstractContainer) obj; + if (value.getLength() == 0) { + return RRuntime.INT_NA; + } else if (value instanceof RAbstractDoubleVector) { + double realVal = getDataAtMod((RAbstractDoubleVector) value, index); + return RRuntime.isNA(realVal) ? RRuntime.INT_NA : (int) realVal; + } else if (value instanceof RAbstractIntVector) { + return getDataAtMod((RAbstractIntVector) value, index); + } else { + return RRuntime.INT_NA; } } } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridLinesNode.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridLinesNode.java index 2cceaeb82a9845570abd46d2f9f3b9386e55d9de..d2bc1d2830d9f0fa15e3d3b0df8f0813475080a0 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridLinesNode.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridLinesNode.java @@ -51,10 +51,10 @@ public abstract class GridLinesNode extends Node { GridDevice dev = ctx.getCurrentDevice(); RList currentVP = ctx.getGridState().getViewPort(); - DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar()); + GPar gpar = GPar.create(ctx.getGridState().getGpar()); ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP, dev); ViewPortContext vpContext = ViewPortContext.fromViewPort(currentVP); - UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx); + UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, gpar); // Convert the list of vectors of indexes to type-safe array and calculate the max length of // the vectors. @@ -67,7 +67,9 @@ public abstract class GridLinesNode extends Node { double[] xx = new double[maxIndexesLen + 1]; // plus one for polygons double[] yy = new double[maxIndexesLen + 1]; - for (RAbstractIntVector unitIndexes : unitIndexesList) { + for (int unitIndexesListIdx = 0; unitIndexesListIdx < unitIndexesList.length; unitIndexesListIdx++) { + RAbstractIntVector unitIndexes = unitIndexesList[unitIndexesListIdx]; + DrawingContext drawingCtx = gpar.getDrawingContext(unitIndexesListIdx); boolean oldIsFinite = false; int start = 0; int unitIndexesLen = unitIndexes.getLength(); diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java index fbedb1f85323c1fcee0b6015e237fd051362ec4f..6774a7b7220676b3ded896ea99b0ed6c1efa01a6 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java @@ -11,7 +11,6 @@ */ package com.oracle.truffle.r.library.fastrGrid; -import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext; import com.oracle.truffle.r.library.fastrGrid.device.GridDevice; import com.oracle.truffle.r.runtime.data.RList; import com.oracle.truffle.r.runtime.data.RNull; @@ -53,8 +52,12 @@ public final class GridState { gpar = GPar.createNew(currentDevice); } - public static DrawingContext getInitialGPar(GridDevice device) { - return GPar.asDrawingContext(GPar.createNew(device)); + /** + * Returns something like a canonical gpar, or top level gpar. This is used when we need a + * context to do e.g. unit conversion, but we are in a situation that no context is available. + */ + public static GPar getInitialGPar(GridDevice device) { + return GPar.create(GPar.createNew(device)); } public RList getGpar() { diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridTextNode.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridTextNode.java index b9d862b0fb7964f21d3a5771acafb996586db78c..594bca1676cf8ebb8534c19b5837c25cb4abeb77 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridTextNode.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridTextNode.java @@ -95,10 +95,10 @@ public final class GridTextNode extends RBaseNode { GridDevice dev = ctx.getCurrentDevice(); RList currentVP = ctx.getGridState().getViewPort(); - DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar()); + GPar gpar = GPar.create(ctx.getGridState().getGpar()); ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP, dev); ViewPortContext vpContext = ViewPortContext.fromViewPort(currentVP); - UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx); + UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, gpar); int length = GridUtils.maxLength(unitLength, x, y); @@ -131,7 +131,7 @@ public final class GridTextNode extends RBaseNode { boolean doDraw = true; Rectangle trect = null; if (checkOverlap || !draw) { - trect = textRect(loc, hjust, vjust, rotation, text, drawingCtx, dev); + trect = textRect(loc, hjust, vjust, rotation, text, gpar.getDrawingContext(i), dev); for (int j = 0; j < boundsCount; j++) { if (trect.intersects(bounds[j])) { doDraw = false; @@ -145,7 +145,7 @@ public final class GridTextNode extends RBaseNode { // actual drawing if (draw && doDraw) { - text(loc.x, loc.y, text, hjust, vjust, rotation, drawingCtx, dev); + text(loc.x, loc.y, text, hjust, vjust, rotation, gpar.getDrawingContext(i), dev); } // or bounds checking diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridUtils.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridUtils.java index b57eaaa9942a29cb6a70e1ffdad36095a6c524cd..8b34ba02b15c72d564d0db91d85949090e5da066 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridUtils.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridUtils.java @@ -26,6 +26,7 @@ import com.oracle.truffle.r.runtime.data.RStringVector; import com.oracle.truffle.r.runtime.data.model.RAbstractContainer; import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector; import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector; +import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; import com.oracle.truffle.r.runtime.data.model.RAbstractVector; final class GridUtils { @@ -161,6 +162,18 @@ final class GridUtils { throw RError.error(RError.NO_CALLER, Message.GENERIC, "Unexpected non integer value " + val.getClass().getSimpleName()); } + static String asString(Object val, int cyclicIndex) { + if (val instanceof String) { + return (String) val; + } else if (val instanceof RAbstractStringVector) { + RAbstractStringVector vec = (RAbstractStringVector) val; + if (vec.getLength() > 0) { + return vec.getDataAt(cyclicIndex % vec.getLength()); + } + } + throw RError.error(RError.NO_CALLER, Message.GENERIC, "Unexpected non character value " + val.getClass().getSimpleName()); + } + static RAbstractIntVector asIntVector(Object value) { if (value instanceof Integer) { return RDataFactory.createIntVectorFromScalar((Integer) value); diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LCircle.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LCircle.java index 4d0183bf8f336f6a5c09ea215f8dca8686f8d445..07ff0d4e4044922888dbb6dd11ae21aeb72b3e72 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LCircle.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LCircle.java @@ -17,7 +17,6 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.r.library.fastrGrid.Unit.UnitConversionContext; import com.oracle.truffle.r.library.fastrGrid.ViewPortTransform.GetViewPortTransformNode; -import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext; import com.oracle.truffle.r.library.fastrGrid.device.GridDevice; import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; import com.oracle.truffle.r.runtime.data.RList; @@ -48,10 +47,10 @@ public abstract class LCircle extends RExternalBuiltinNode.Arg3 { GridDevice dev = ctx.getCurrentDevice(); RList currentVP = ctx.getGridState().getViewPort(); - DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar()); + GPar gpar = GPar.create(ctx.getGridState().getGpar()); ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP, dev); ViewPortContext vpContext = ViewPortContext.fromViewPort(currentVP); - UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx); + UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, gpar); int length = GridUtils.maxLength(unitLength, xVec, yVec, radiusVec); for (int i = 0; i < length; i++) { @@ -59,7 +58,7 @@ public abstract class LCircle extends RExternalBuiltinNode.Arg3 { double radius = RMath.fmin2(radiusSizes.getWidth(), radiusSizes.getHeight()); Point origLoc = Point.fromUnits(unitToInches, xVec, yVec, i, conversionCtx); Point loc = TransformMatrix.transLocation(origLoc, vpTransform.transform); - dev.drawCircle(drawingCtx, loc.x, loc.y, radius); + dev.drawCircle(gpar.getDrawingContext(i), loc.x, loc.y, radius); } return RNull.instance; } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LConvert.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LConvert.java index b2300de6ab6313762a0eb3b1d9296566b7af1655..134def4c4e4395475982a20b3d7069f3a79e9522 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LConvert.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LConvert.java @@ -23,7 +23,6 @@ import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.r.library.fastrGrid.Unit.AxisOrDimension; import com.oracle.truffle.r.library.fastrGrid.Unit.UnitConversionContext; import com.oracle.truffle.r.library.fastrGrid.ViewPortTransform.GetViewPortTransformNode; -import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext; import com.oracle.truffle.r.library.fastrGrid.device.GridDevice; import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; import com.oracle.truffle.r.runtime.data.RDataFactory; @@ -57,10 +56,10 @@ public abstract class LConvert extends RExternalBuiltinNode.Arg4 { GridDevice dev = ctx.getCurrentDevice(); RList currentVP = ctx.getGridState().getViewPort(); - DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar()); + GPar gpar = GPar.create(ctx.getGridState().getGpar()); ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP, dev); ViewPortContext vpContext = ViewPortContext.fromViewPort(currentVP); - UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx); + UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, gpar); int length = unitLength.execute(units); double[] result = new double[length]; @@ -94,7 +93,7 @@ public abstract class LConvert extends RExternalBuiltinNode.Arg4 { boolean isX = axisTo.isHorizontal(); double scalemin = isX ? vpContext.xscalemin : vpContext.yscalemin; double scalemax = isX ? vpContext.xscalemax : vpContext.yscalemax; - result[i] = Unit.convertFromInches(inches, unitTo, vpToSize, scalemin, scalemax, axisTo.isDimension(), drawingCtx); + result[i] = Unit.convertFromInches(inches, unitTo, vpToSize, scalemin, scalemax, axisTo.isDimension(), gpar.getDrawingContext(i)); } } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitViewPortStack.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitViewPortStack.java index 82c23f06ae2e1d4bb9f76eff3c7c52e6aedac120..8a28365fc774f9f6c6f8f7114beef7dfc7c514cf 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitViewPortStack.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitViewPortStack.java @@ -11,7 +11,6 @@ */ package com.oracle.truffle.r.library.fastrGrid; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.r.library.fastrGrid.ViewPort.InitViewPortNode; import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LPoints.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LPoints.java index d4b71e0a5baadde23ac35c280a5362ba0fe7f908..477e1e4993e981d0ec48ffd8b75a979052541119 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LPoints.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LPoints.java @@ -64,20 +64,22 @@ public abstract class LPoints extends RExternalBuiltinNode.Arg4 { GridDevice dev = ctx.getCurrentDevice(); RList currentVP = ctx.getGridState().getViewPort(); - RList gpar = ctx.getGridState().getGpar(); - DrawingContext drawingCtx = GPar.asDrawingContext(gpar); - double cex = GPar.getCex(gpar); + RList gparList = ctx.getGridState().getGpar(); + GPar gpar = GPar.create(gparList); + double cex = GPar.getCex(gparList); ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP, dev); ViewPortContext vpContext = ViewPortContext.fromViewPort(currentVP); - UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx); + UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, gpar); // Note: unlike in other drawing primitives, we only consider length of x int length = unitLength.execute(xVec); - PointDrawingContext pointDrawingCtx = new PointDrawingContext(drawingCtx, drawingCtx.getFillColor(), drawingCtx.getFillColor()); + DrawingContext initialDrawingCtx = gpar.getDrawingContext(0); + PointDrawingContext pointDrawingCtx = new PointDrawingContext(initialDrawingCtx, initialDrawingCtx.getFillColor(), initialDrawingCtx.getFillColor()); for (int i = 0; i < length; i++) { Point loc = TransformMatrix.transLocation(Point.fromUnits(unitToInches, xVec, yVec, i, conversionCtx), vpTransform.transform); double size = unitToInches.convertWidth(sizeVec, i, conversionCtx); if (loc.isFinite() && Double.isFinite(size)) { + pointDrawingCtx = pointDrawingCtx.update(gpar.getDrawingContext(i)); pointDrawingCtx = drawSymbol(pointDrawingCtx, dev, cex, pchVec.getDataAt(i % pchVec.getLength()), size, loc.x, loc.y); } } @@ -153,11 +155,38 @@ public abstract class LPoints extends RExternalBuiltinNode.Arg4 { return new PointDrawingContext(inner, color, fillColor); } + private PointDrawingContext update(DrawingContext inner) { + if (this.inner == inner) { + return this; + } + return new PointDrawingContext(inner, this.color, this.fillColor); + } + @Override - public GridLineType getLineType() { + public byte[] getLineType() { return inner.getLineType(); } + @Override + public double getLineWidth() { + return inner.getLineWidth(); + } + + @Override + public GridLineJoin getLineJoin() { + return inner.getLineJoin(); + } + + @Override + public GridLineEnd getLineEnd() { + return inner.getLineEnd(); + } + + @Override + public double getLineMitre() { + return inner.getLineMitre(); + } + @Override public GridColor getColor() { return color; @@ -168,6 +197,16 @@ public abstract class LPoints extends RExternalBuiltinNode.Arg4 { return inner.getFontSize(); } + @Override + public GridFontStyle getFontStyle() { + return inner.getFontStyle(); + } + + @Override + public String getFontFamily() { + return inner.getFontFamily(); + } + @Override public double getLineHeight() { return inner.getLineHeight(); diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LRect.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LRect.java index a666b07edeab7b1d149f15aef7d7e945bdabf69e..7c69b09329958ebcea04cf4367605b532d63a9d4 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LRect.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LRect.java @@ -19,7 +19,6 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.r.library.fastrGrid.Unit.UnitConversionContext; import com.oracle.truffle.r.library.fastrGrid.ViewPortTransform.GetViewPortTransformNode; -import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext; import com.oracle.truffle.r.library.fastrGrid.device.GridDevice; import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; import com.oracle.truffle.r.runtime.data.RList; @@ -53,10 +52,10 @@ public abstract class LRect extends RExternalBuiltinNode.Arg6 { GridDevice dev = ctx.getCurrentDevice(); RList currentVP = ctx.getGridState().getViewPort(); - DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar()); + GPar gpar = GPar.create(ctx.getGridState().getGpar()); ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP, dev); ViewPortContext vpContext = ViewPortContext.fromViewPort(currentVP); - UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx); + UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, gpar); int length = GridUtils.maxLength(unitLength, xVec, yVec, wVec, hVec); for (int i = 0; i < length; i++) { @@ -66,7 +65,7 @@ public abstract class LRect extends RExternalBuiltinNode.Arg6 { Point origLoc = Point.fromUnits(unitToInches, xVec, yVec, i, conversionCtx); Point transLoc = TransformMatrix.transLocation(origLoc, vpTransform.transform); Point loc = transLoc.justify(size, getDataAtMod(hjust, i), getDataAtMod(vjust, i)); - dev.drawRect(drawingCtx, loc.x, loc.y, size.getWidth(), size.getHeight()); + dev.drawRect(gpar.getDrawingContext(i), loc.x, loc.y, size.getWidth(), size.getHeight()); } return RNull.instance; } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LSegments.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LSegments.java index 1a3dda410ed2fef75fcde352584f3f42e18df404..c2e1cf10babae9bfc5f52036450dbb3ac3b6baa2 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LSegments.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LSegments.java @@ -17,7 +17,6 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.r.library.fastrGrid.Unit.UnitConversionContext; import com.oracle.truffle.r.library.fastrGrid.ViewPortTransform.GetViewPortTransformNode; -import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext; import com.oracle.truffle.r.library.fastrGrid.device.GridDevice; import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; import com.oracle.truffle.r.runtime.data.RList; @@ -59,10 +58,10 @@ public abstract class LSegments extends RExternalBuiltinNode.Arg5 { GridDevice dev = ctx.getCurrentDevice(); RList currentVP = ctx.getGridState().getViewPort(); - DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar()); + GPar gpar = GPar.create(ctx.getGridState().getGpar()); ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP, dev); ViewPortContext vpContext = ViewPortContext.fromViewPort(currentVP); - UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx); + UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, gpar); int length = GridUtils.maxLength(unitLength, x0, y0, x1, y1); double[] xx = new double[2]; @@ -77,7 +76,7 @@ public abstract class LSegments extends RExternalBuiltinNode.Arg5 { xx[1] = loc2.x; yy[0] = loc1.y; yy[1] = loc2.y; - dev.drawPolyLines(drawingCtx, xx, yy, 0, 2); + dev.drawPolyLines(gpar.getDrawingContext(i), xx, yy, 0, 2); if (arrow != null) { drawArrowsNode.drawArrows(xx, yy, 0, 2, i, arrow, true, true, conversionCtx); } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/Unit.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/Unit.java index 4cb343dfbff085c8f8d78422d5d458984d28a2da..3c2095610389f3b951c4a40fa66765c7c4bdb2ce 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/Unit.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/Unit.java @@ -183,7 +183,7 @@ public class Unit { } } - private static double convertToInches(double value, int unitId, RList data, UnitConversionContext ctx, AxisOrDimension axisOrDim) { + private static double convertToInches(double value, int index, int unitId, RList data, UnitConversionContext ctx, AxisOrDimension axisOrDim) { double vpSize = ctx.getViewPortSize(axisOrDim); switch (unitId) { case INCHES: @@ -201,16 +201,16 @@ public class Unit { return value / (CM_IN_INCH * 10); case CHAR: case MYCHAR: - return (value * ctx.drawingContext.getFontSize()) / INCH_TO_POINTS_FACTOR; + return (value * ctx.gpar.getDrawingContext(index).getFontSize()) / INCH_TO_POINTS_FACTOR; case LINES: case MYLINES: - return (value * ctx.drawingContext.getFontSize() * ctx.drawingContext.getLineHeight()) / INCH_TO_POINTS_FACTOR; + return (value * ctx.gpar.getDrawingContext(index).getFontSize() * ctx.gpar.getDrawingContext(index).getLineHeight()) / INCH_TO_POINTS_FACTOR; case STRINGWIDTH: case MYSTRINGWIDTH: - return ctx.device.getStringWidth(ctx.drawingContext, RRuntime.asString(data.getDataAt(0))); + return ctx.device.getStringWidth(ctx.gpar.getDrawingContext(index), RRuntime.asString(data.getDataAt(0))); case STRINGHEIGHT: case MYSTRINGHEIGHT: - return ctx.device.getStringHeight(ctx.drawingContext, RRuntime.asString(data.getDataAt(0))); + return ctx.device.getStringHeight(ctx.gpar.getDrawingContext(index), RRuntime.asString(data.getDataAt(0))); case NULL: return evaluateNullUnit(value, vpSize, ctx.nullLayoutMode, ctx.nullArithmeticMode); default: @@ -423,20 +423,20 @@ public class Unit { public static final class UnitConversionContext { public final Size viewPortSize; public final ViewPortContext viewPortContext; - public final DrawingContext drawingContext; + public final GPar gpar; public final GridDevice device; public final int nullLayoutMode; public final int nullArithmeticMode; - public UnitConversionContext(Size viewPortSize, ViewPortContext viewPortContext, GridDevice device, DrawingContext drawingContext) { - this(viewPortSize, viewPortContext, device, drawingContext, 0, 0); + public UnitConversionContext(Size viewPortSize, ViewPortContext viewPortContext, GridDevice device, GPar gpar) { + this(viewPortSize, viewPortContext, device, gpar, 0, 0); } - public UnitConversionContext(Size viewPortSize, ViewPortContext viewPortContext, GridDevice device, DrawingContext drawingContext, int nullLMode, int nullAMode) { + public UnitConversionContext(Size viewPortSize, ViewPortContext viewPortContext, GridDevice device, GPar gpar, int nullLMode, int nullAMode) { this.viewPortSize = viewPortSize; this.viewPortContext = viewPortContext; this.device = device; - this.drawingContext = drawingContext; + this.gpar = gpar; this.nullLayoutMode = nullLMode; this.nullArithmeticMode = nullAMode; } @@ -495,7 +495,7 @@ public class Unit { RList grobList = asList(value.getAttr(UNIT_ATTR_DATA)); return getGrobUnitToInchesNode().execute(scalarValue, unitId, grobList.getDataAt(index % grobList.getLength()), ctx); } - return convertToInches(scalarValue, unitId, asListOrNull(value.getAttr(UNIT_ATTR_DATA)), ctx, axisOrDim); + return convertToInches(scalarValue, index, unitId, asListOrNull(value.getAttr(UNIT_ATTR_DATA)), ctx, axisOrDim); } @Specialization(guards = "isUnitList(value)") @@ -548,7 +548,7 @@ public class Unit { // Note the catch: newNullAMode is applied only if the axisOrDim is dimension private static UnitConversionContext getNewCtx(UnitConversionContext ctx, AxisOrDimension axisOrDim, int newNullAMode) { - return new UnitConversionContext(ctx.viewPortSize, ctx.viewPortContext, ctx.device, ctx.drawingContext, ctx.nullLayoutMode, + return new UnitConversionContext(ctx.viewPortSize, ctx.viewPortContext, ctx.device, ctx.gpar, ctx.nullLayoutMode, axisOrDim.isDimension() ? newNullAMode : ctx.nullArithmeticMode); } @@ -628,7 +628,7 @@ public class Unit { double nullUnitValue = pureNullUnitValue((RAbstractContainer) unitxy.getDataAt(0), 0); result = evaluateNullUnit(nullUnitValue, vpTransform.size.getWidth(), conversionCtx.nullLayoutMode, conversionCtx.nullArithmeticMode); } else { - UnitConversionContext newConversionCtx = new UnitConversionContext(vpTransform.size, vpContext, conversionCtx.device, GPar.asDrawingContext(currentGP)); + UnitConversionContext newConversionCtx = new UnitConversionContext(vpTransform.size, vpContext, conversionCtx.device, GPar.create(currentGP)); initUnitToInchesNode(); if (unitId == GROBWIDTH) { result = unitToInchesNode.convertWidth((RAbstractContainer) unitxy.getDataAt(0), 0, newConversionCtx); diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/BufferedJFrameDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/BufferedJFrameDevice.java index f85a04b4c446380eb41c4c2724692f4214e59215..e36fcac3d78bd24259a48f13998d6451b50baef4 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/BufferedJFrameDevice.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/BufferedJFrameDevice.java @@ -30,7 +30,7 @@ import java.util.ArrayList; * methods open/draw a 2D graphics buffer, while the buffer is open, any drawing is done in the * buffer not on the screen and we also record any drawing code to be able to replay it if the * buffer happens to loose contents, which is a possibility mentioned in the documentation. Note: we - * rely on the fact that {@linkl DrawingContext} is immutable. + * rely on the fact that {@link DrawingContext} is immutable. */ public class BufferedJFrameDevice implements GridDevice { private final JFrameDevice inner; diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/DrawingContext.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/DrawingContext.java index b3baf6db3cbbc86c93dfafeca313c5b5e77b581f..2986d9ffba8f4133eafbfc4dc56672905d80965f 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/DrawingContext.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/DrawingContext.java @@ -29,29 +29,86 @@ package com.oracle.truffle.r.library.fastrGrid.device; public interface DrawingContext { double INCH_TO_POINTS_FACTOR = 72.27; - enum GridLineType { - // The order is important! - BLANK, - SOLID, - DASHED, - DOTTED, - DOTDASHED, - LONGDASH, - TWODASH; - - private static final int LINE_TYPES_COUNT = 7; - private static final GridLineType[] allValues = values(); - - public static GridLineType fromInt(int num) { - if (num == -1) { - return BLANK; - } - assert num >= 1; - return allValues[(num - 1) % LINE_TYPES_COUNT + 1]; + String FONT_FAMILY_MONO = "mono"; + String FONT_FAMILY_SANS = "sans"; + String FONT_FAMILY_SERIF = "serif"; + + byte[] GRID_LINE_BLANK = null; + byte[] GRID_LINE_SOLID = new byte[0]; + + enum GridFontStyle { + PLAIN, + BOLD, + ITALIC, + BOLDITALIC, + /** + * Supposed to be symbol font in Adobe symbol encoding. + */ + SYMBOL; + + /** + * Return enum's value corresponding to R's value. + */ + public static GridFontStyle fromInt(int num) { + assert num > 0 && num <= SYMBOL.ordinal() + 1; + return values()[num - 1]; + } + } + + enum GridLineJoin { + ROUND, + MITRE, + BEVEL; + + public static final int LAST_VALUE = BEVEL.ordinal(); + + /** + * Return enum's value corresponding to R's value. + */ + public static GridLineJoin fromInt(int num) { + return values()[num]; } } - GridLineType getLineType(); + enum GridLineEnd { + ROUND, + BUTT, + SQUARE; + + public static final int LAST_VALUE = SQUARE.ordinal(); + + /** + * Return enum's value corresponding to R's value. + */ + public static GridLineEnd fromInt(int num) { + return values()[num]; + } + } + + /** + * Returns either one of the constants {@link #GRID_LINE_BLANK} or {@link #GRID_LINE_SOLID} or + * an array with a pattern consisting of lengths. Lengths at odd positions are dashes and + * lengths at the even positions are spaces between them, the pattern should be interpreted as + * cyclic. Example: '3,2,10,1' means 3 units of line, 2 units of space, 10 units of line, 1 unit + * of space and repeat. The unit here can be device dependent, but should be something "small", + * like a pixel. + */ + byte[] getLineType(); + + /** + * Line width in multiplies of what is considered the basic "thin" line for given device. + */ + double getLineWidth(); + + GridLineJoin getLineJoin(); + + GridLineEnd getLineEnd(); + + /** + * The mitre limit, larger than 1, default is 10. The unit should be interpreted the way as in + * {@link #getLineType()}. + */ + double getLineMitre(); /** * Drawing color of shape borders, lines and text. @@ -65,6 +122,15 @@ public interface DrawingContext { */ double getFontSize(); + GridFontStyle getFontStyle(); + + /** + * Gets the font family name. The standard values that any device must implement are "serif", + * "sans" and "mono". On top of that the device can recognize name of any font that it can + * support. + */ + String getFontFamily(); + /** * Gets the height of a line in multiplies of the base line height. */ 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 4b42cdce8cc4db3f9ff42e759f6a1f73a12f229c..6b1c9451bd8a4998b61875b0fca15285633ff7ec 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 @@ -58,10 +58,7 @@ public class GridColor { @Override public boolean equals(Object obj) { - if (!(obj instanceof GridColor)) { - return false; - } - return value == ((GridColor) obj).value; + return obj instanceof GridColor && value == ((GridColor) obj).value; } @Override diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/JFrameDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/JFrameDevice.java index 2ac24f74a69f39b470acd028cf1724c54ef24a9d..b27f820139042752633e1a55cba6f4a4f3ac2850 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/JFrameDevice.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/JFrameDevice.java @@ -28,6 +28,7 @@ import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; +import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.HeadlessException; @@ -46,7 +47,9 @@ import java.util.function.Supplier; import javax.swing.JFrame; import javax.swing.JPanel; -import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.GridLineType; +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; import com.oracle.truffle.r.runtime.RInternalError; public class JFrameDevice implements GridDevice { @@ -59,11 +62,6 @@ public class JFrameDevice implements GridDevice { private static BasicStroke solidStroke; private static BasicStroke blankStroke; - private static BasicStroke dashedStroke; - private static BasicStroke longdashedStroke; - private static BasicStroke twodashedStroke; - private static BasicStroke dotdashedStroke; - private static BasicStroke dottedStroke; private FastRFrame currentFrame; private Graphics2D graphics; @@ -116,7 +114,7 @@ public class JFrameDevice implements GridDevice { tr.translate((float) (leftX * POINTS_IN_INCH), (float) (currentFrame.getContentPane().getHeight() - bottomY * POINTS_IN_INCH)); tr.rotate(-rotationAnticlockWise); graphics.setTransform(tr); - setFontSize(ctx); + setFont(ctx); graphics.drawString(text, 0, 0); return null; }); @@ -136,7 +134,7 @@ public class JFrameDevice implements GridDevice { public double getStringWidth(DrawingContext ctx, String text) { setContext(ctx); return noTranform(() -> { - setFontSize(ctx); + setFont(ctx); int swingUnits = graphics.getFontMetrics(graphics.getFont()).stringWidth(text); return swingUnits / POINTS_IN_INCH; }); @@ -146,7 +144,7 @@ public class JFrameDevice implements GridDevice { public double getStringHeight(DrawingContext ctx, String text) { setContext(ctx); return noTranform(() -> { - setFontSize(ctx); + setFont(ctx); int swingUnits = graphics.getFont().getSize(); return swingUnits / POINTS_IN_INCH; }); @@ -178,12 +176,45 @@ public class JFrameDevice implements GridDevice { private void setContext(DrawingContext ctx) { graphics.setColor(fromGridColor(ctx.getColor())); - graphics.setStroke(fromGridLineType(ctx.getLineType())); + graphics.setStroke(getStrokeFromCtx(ctx)); } - private void setFontSize(DrawingContext ctx) { + private void setFont(DrawingContext ctx) { float fontSize = (float) ((ctx.getFontSize() / INCH_TO_POINTS_FACTOR) * POINTS_IN_INCH); - graphics.setFont(graphics.getFont().deriveFont(fontSize)); + Font font = new Font(getFontName(ctx.getFontFamily()), getAwtFontStyle(ctx.getFontStyle()), 1).deriveFont(fontSize); + graphics.setFont(font); + } + + private String getFontName(String gridFontFamily) { + if (gridFontFamily == null) { + return null; + } + switch (gridFontFamily) { + case DrawingContext.FONT_FAMILY_MONO: + return Font.MONOSPACED; + case DrawingContext.FONT_FAMILY_SANS: + return Font.SANS_SERIF; + case DrawingContext.FONT_FAMILY_SERIF: + return Font.SERIF; + case "": + return null; + } + return gridFontFamily; + } + + private int getAwtFontStyle(GridFontStyle fontStyle) { + switch (fontStyle) { + case PLAIN: + return Font.PLAIN; + case BOLD: + return Font.BOLD; + case ITALIC: + return Font.ITALIC; + case BOLDITALIC: + return Font.BOLD | Font.ITALIC; + default: + throw RInternalError.shouldNotReachHere("unexpected value of GridFontStyle enum"); + } } private <T> T noTranform(Supplier<T> action) { @@ -198,24 +229,50 @@ public class JFrameDevice implements GridDevice { return new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); } - private static BasicStroke fromGridLineType(GridLineType type) { - switch (type) { - case SOLID: + private static BasicStroke getStrokeFromCtx(DrawingContext ctx) { + byte[] type = ctx.getLineType(); + double width = ctx.getLineWidth(); + int lineJoin = fromGridLineJoin(ctx.getLineJoin()); + float lineMitre = (float) ctx.getLineMitre(); + int endCap = fromGridLineEnd(ctx.getLineEnd()); + if (type == DrawingContext.GRID_LINE_BLANK) { + return blankStroke; + } else if (type == DrawingContext.GRID_LINE_SOLID) { + if (width == 1. && solidStroke.getLineJoin() == lineJoin && solidStroke.getMiterLimit() == lineMitre && solidStroke.getEndCap() == endCap) { return solidStroke; - case BLANK: - return blankStroke; - case DASHED: - return dashedStroke; - case DOTDASHED: - return dotdashedStroke; - case DOTTED: - return dottedStroke; - case TWODASH: - return twodashedStroke; - case LONGDASH: - return longdashedStroke; + } + return new BasicStroke((float) (width / POINTS_IN_INCH), endCap, lineJoin, lineMitre); + } + float[] pattern = new float[type.length]; + for (int i = 0; i < pattern.length; i++) { + pattern[i] = (float) (type[i] / POINTS_IN_INCH); + } + return new BasicStroke((float) (width / POINTS_IN_INCH), endCap, lineJoin, lineMitre, pattern, 0f); + } + + private static int fromGridLineEnd(GridLineEnd lineEnd) { + switch (lineEnd) { + case ROUND: + return BasicStroke.CAP_ROUND; + case BUTT: + return BasicStroke.CAP_BUTT; + case SQUARE: + return BasicStroke.CAP_SQUARE; + default: + throw RInternalError.shouldNotReachHere("unexpected value of GridLineEnd enum"); + } + } + + private static int fromGridLineJoin(GridLineJoin lineJoin) { + switch (lineJoin) { + case BEVEL: + return BasicStroke.JOIN_BEVEL; + case MITRE: + return BasicStroke.JOIN_MITER; + case ROUND: + return BasicStroke.JOIN_ROUND; default: - throw RInternalError.shouldNotReachHere("unexpected value of GridLineType enum"); + throw RInternalError.shouldNotReachHere("unexpected value of GridLineJoin enum"); } } @@ -223,16 +280,8 @@ public class JFrameDevice implements GridDevice { if (solidStroke != null) { return; } - float defaultWidth = (float) (1. / POINTS_IN_INCH); - float dashSize = (float) (10. / POINTS_IN_INCH); - float dotSize = (float) (2. / POINTS_IN_INCH); solidStroke = new BasicStroke((float) (1f / POINTS_IN_INCH)); blankStroke = new BasicStroke(0f); - dashedStroke = new BasicStroke(defaultWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[]{dashSize}, 0f); - dottedStroke = new BasicStroke(defaultWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[]{dotSize}, 0f); - dotdashedStroke = new BasicStroke(defaultWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[]{dotSize, dashSize}, 0f); - twodashedStroke = new BasicStroke(defaultWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[]{dashSize / 2f, dashSize}, 0f); - longdashedStroke = new BasicStroke(defaultWidth, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[]{2f * dashSize}, 0f); } static class FastRFrame extends JFrame {