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