From 7b9503d67cd0d7860f733d66eed6e7af07318e63 Mon Sep 17 00:00:00 2001 From: stepan <stepan.sindelar@oracle.com> Date: Thu, 23 Mar 2017 17:58:06 +0100 Subject: [PATCH] FastR Grid: support line join, mitre, end and lex (line width multiplier). --- .../truffle/r/library/fastrGrid/GPar.java | 150 ++++++++++++++---- .../truffle/r/library/fastrGrid/LPoints.java | 15 ++ .../fastrGrid/device/DrawingContext.java | 40 +++++ .../fastrGrid/device/JFrameDevice.java | 38 ++++- 4 files changed, 206 insertions(+), 37 deletions(-) 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 2cc88aa2fa..de1e6a86dd 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,11 +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; @@ -68,12 +69,18 @@ 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; // Note: there is last slot "fontface" which is either unused at all, or only used in R code @@ -138,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"; @@ -171,39 +178,31 @@ public final class GPar { @Override public byte[] getLineType() { - Object lty = data[GP_LTY]; - if (lty == null || lty == RNull.instance) { - return DrawingContext.GRID_LINE_SOLID; - } - // convert string values - if (lty instanceof String || lty instanceof RAbstractStringVector) { - String name = GridUtils.asString(lty, index); - if (name != null) { - return lineTypeFromName(name); - } - } - // convert numeric values - RAbstractContainer ltyVec = asAbstractContainer(lty); - int num; // NA will indicate error - if (ltyVec.getLength() == 0) { - num = RRuntime.INT_NA; - } else if (ltyVec instanceof RAbstractDoubleVector) { - double realVal = getDataAtMod((RAbstractDoubleVector) ltyVec, index); - num = RRuntime.isNA(realVal) ? RRuntime.INT_NA : (int) realVal; - } else if (ltyVec instanceof RAbstractIntVector) { - num = getDataAtMod((RAbstractIntVector) ltyVec, index); - } else { - num = RRuntime.INT_NA; - } - if (RRuntime.isNA(num) || num < 0 || num >= LINE_STYLES.length) { - throw RError.error(RError.NO_CALLER, Message.GENERIC, "Invalid line type."); - } - return LINE_STYLES[num]; + 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); + return asDouble(data[GP_LWD], index) * asDouble(data[GP_LEX], index); + } + + @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 value; } @Override @@ -236,6 +235,34 @@ public final class GPar { return getGridColor(GP_FILL); } + /** + * 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 GridColor getGridColor(int listIndex) { GridColor color = GridColorUtils.gridColorFromString(GridUtils.asString(data[listIndex], index)); double alpha = asDouble(data[GP_ALPHA], index); @@ -254,7 +281,7 @@ public final class GPar { 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 byte[] lineTypeFromName(String name) { + private static byte[] lineTypeFromName(String name) { switch (name) { case "solid": return DrawingContext.GRID_LINE_SOLID; @@ -275,10 +302,63 @@ public final class GPar { for (int i = 0; i < name.length(); i++) { result[i] = (byte) Character.digit(name.charAt(i), 16); if (result[i] == -1) { - throw RError.error(RError.NO_CALLER, Message.GENERIC, "Unexpected line type '" + name + "'."); + 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: + 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/LPoints.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LPoints.java index eef994103f..477e1e4993 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 @@ -172,6 +172,21 @@ public abstract class LPoints extends RExternalBuiltinNode.Arg4 { 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; 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 df49377bc2..2986d9ffba 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 @@ -55,6 +55,36 @@ public interface DrawingContext { } } + 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]; + } + } + + 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 @@ -70,6 +100,16 @@ public interface DrawingContext { */ 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. */ 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 a8e4b4bb46..b27f820139 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 @@ -48,6 +48,8 @@ import javax.swing.JFrame; import javax.swing.JPanel; 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 { @@ -230,16 +232,48 @@ public class JFrameDevice implements GridDevice { 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) { - return width == 1. ? solidStroke : new BasicStroke((float) (width / POINTS_IN_INCH)); + if (width == 1. && solidStroke.getLineJoin() == lineJoin && solidStroke.getMiterLimit() == lineMitre && solidStroke.getEndCap() == endCap) { + return solidStroke; + } + 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), BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, pattern, 0f); + 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 GridLineJoin enum"); + } } private static void initStrokes() { -- GitLab