diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/EdgeDetection.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/EdgeDetection.java new file mode 100644 index 0000000000000000000000000000000000000000..4b556280fc3c8f49f05710f291e07328e5a80f84 --- /dev/null +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/EdgeDetection.java @@ -0,0 +1,223 @@ +/* + * This material is distributed under the GNU General Public License + * Version 2. You may review the terms of this license at + * http://www.gnu.org/licenses/gpl-2.0.html + * + * Copyright (C) 2001-3 Paul Murrell + * Copyright (c) 1998-2013, The R Core Team + * Copyright (c) 2017, Oracle and/or its affiliates + * + * All rights reserved. + */ +package com.oracle.truffle.r.library.fastrGrid; + +import static com.oracle.truffle.r.library.fastrGrid.GridUtils.fmax; +import static com.oracle.truffle.r.library.fastrGrid.GridUtils.fmin; +import static com.oracle.truffle.r.runtime.nmath.MathConstants.M_PI; +import static com.oracle.truffle.r.runtime.nmath.RMath.fmax2; +import static com.oracle.truffle.r.runtime.nmath.RMath.fmin2; +import static com.oracle.truffle.r.runtime.nmath.TOMS708.fabs; + +import com.oracle.truffle.r.runtime.RError; +import com.oracle.truffle.r.runtime.RError.Message; +import com.oracle.truffle.r.runtime.RInternalError; + +/** + * Contains static method related to edge detection for bounds calculations. + */ +public final class EdgeDetection { + private EdgeDetection() { + // only static members + } + + /** + * Do two lines intersect? Algorithm from Paul Bourke + * (http://www.swin.edu.au/astronomy/pbourke/geometry/lineline2d/index.html) + */ + static boolean linesIntersect(double x1, double x2, double x3, double x4, + double y1, double y2, double y3, double y4) { + double denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); + double ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)); + // If the lines are parallel ... + if (denom == 0) { + // If the lines are coincident ... + if (ua == 0) { + // If the lines are vertical ... + if (x1 == x2) { + // Compare y-values + if (!((y1 < y3 && fmax2(y1, y2) < fmin2(y3, y4)) || + (y3 < y1 && fmax2(y3, y4) < fmin2(y1, y2)))) + return true; + } else { + // Compare x-values + if (!((x1 < x3 && fmax2(x1, x2) < fmin2(x3, x4)) || + (x3 < x1 && fmax2(x3, x4) < fmin2(x1, x2)))) + return true; + } + } + } + // ... otherwise, calculate where the lines intersect ... + else { + double ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)); + ua = ua / denom; + ub = ub / denom; + // Check for overlap + if ((ua > 0 && ua < 1) && (ub > 0 && ub < 1)) + return true; + } + return false; + } + + static boolean edgesIntersect(double x1, double x2, double y1, double y2, Rectangle r) { + return linesIntersect(x1, x2, r.x[0], r.x[1], y1, y2, r.y[0], r.y[1]) || + linesIntersect(x1, x2, r.x[1], r.x[2], y1, y2, r.y[1], r.y[2]) || + linesIntersect(x1, x2, r.x[2], r.x[3], y1, y2, r.y[2], r.y[3]) || + linesIntersect(x1, x2, r.x[3], r.x[0], y1, y2, r.y[3], r.y[0]); + } + + static Point rectEdge(double xmin, double ymin, double xmax, double ymax, double theta) { + double xm = (xmin + xmax) / 2; + double ym = (ymin + ymax) / 2; + double dx = (xmax - xmin) / 2; + double dy = (ymax - ymin) / 2; + /* + * GNUR fixme: Special case 0 width or 0 height + */ + // Special case angles + if (theta == 0) { + return new Point(xmax, ym); + } else if (theta == 270) { + return new Point(xm, ymin); + } else if (theta == 180) { + return new Point(xmin, ym); + } else if (theta == 90) { + return new Point(xm, ymax); + } else { + double cutoff = dy / dx; + double angle = theta / 180 * M_PI; + double tanTheta = Math.tan(angle); + double cosTheta = Math.cos(angle); + double sinTheta = Math.sin(angle); + if (fabs(tanTheta) < cutoff) { /* Intersect with side */ + if (cosTheta > 0) { /* Right side */ + return new Point(xmax, ym + tanTheta * dx); + } else { /* Left side */ + return new Point(xmin, ym - tanTheta * dx); + } + } else { /* Intersect with top/bottom */ + if (sinTheta > 0) { /* Top */ + return new Point(ymax, xm + dy / tanTheta); + } else { /* Bottom */ + return new Point(ymin, xm - dy / tanTheta); + } + } + } + } + + static Point polygonEdge(double[] x, double[] y, int n, double theta) { + // centre of the polygon + double xmin = fmin(Double.MAX_VALUE, x); + double xmax = fmax(Double.MIN_VALUE, x); + double ymin = fmin(Double.MAX_VALUE, y); + double ymax = fmax(Double.MIN_VALUE, y); + double xm = (xmin + xmax) / 2; + double ym = (ymin + ymax) / 2; + + // Special case zero-width or zero-height + if (fabs(xmin - xmax) < 1e-6) { + double resultY = theta == 90 ? ymax : theta == 270 ? ymin : ym; + return new Point(xmin, ymin); + } + if (fabs(ymin - ymax) < 1e-6) { + double resultY = theta == 0 ? xmax : theta == 180 ? xmin : xm; + return new Point(ymin, resultY); + } + + /* + * Find edge that intersects line from centre at angle theta + */ + boolean found = false; + double angle = theta / 180 * M_PI; + double vangle1; + double vangle2; + int v1 = 0; + int v2 = 1; + for (int i = 0; i < n; i++) { + v1 = i; + v2 = v1 + 1; + if (v2 == n) { + v2 = 0; + } + /* + * Result of atan2 is in range -PI, PI so convert to 0, 360 to correspond to angle + */ + vangle1 = Math.atan2(y[v1] - ym, x[v1] - xm); + if (vangle1 < 0) { + vangle1 = vangle1 + 2 * M_PI; + } + vangle2 = Math.atan2(y[v2] - ym, x[v2] - xm); + if (vangle2 < 0) { + vangle2 = vangle2 + 2 * M_PI; + } + /* + * If vangle1 < vangle2 then angles are either side of 0 so check is more complicated + */ + if ((vangle1 >= vangle2 && + vangle1 >= angle && vangle2 < angle) || + (vangle1 < vangle2 && + ((vangle1 >= angle && 0 <= angle) || + (vangle2 < angle && 2 * M_PI >= angle)))) { + found = true; + break; + } + } + /* + * Find intersection point of "line from centre to bounding rect" and edge + */ + if (!found) { + throw RError.error(RError.NO_CALLER, Message.GENERIC, "polygon edge not found"); + } + double x3 = x[v1]; + double y3 = y[v1]; + double x4 = x[v2]; + double y4 = y[v2]; + Point tmp = rectEdge(xmin, ymin, xmax, ymax, theta); + double x2 = tmp.x; + double y2 = tmp.y; + double numa = ((x4 - x3) * (ym - y3) - (y4 - y3) * (xm - x3)); + double denom = ((y4 - y3) * (x2 - xm) - (x4 - x3) * (y2 - ym)); + double ua = numa / denom; + if (!Double.isFinite(ua)) { + /* + * Should only happen if lines are parallel, which shouldn't happen! Unless, perhaps the + * polygon has zero extent vertically or horizontally ... ? + */ + throw RInternalError.shouldNotReachHere("polygon edge not found (zero-width or zero-height?)"); + } + /* + * numb = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)); ub = numb/denom; + */ + return new Point(xm + ua * (x2 - xm), ym + ua * (y2 - ym)); + } + + /** + * An arbitrarily-oriented rectangle. The vertices are assumed to be in order going + * anticlockwise around the rectangle. + */ + public static final class Rectangle { + public final double[] x; + public final double[] y; + + public Rectangle(Point p1, Point p2, Point p3, Point p4) { + x = new double[]{p1.x, p2.x, p3.x, p4.x}; + y = new double[]{p1.y, p2.y, p3.y, p4.y}; + } + + public boolean intersects(Rectangle r2) { + return edgesIntersect(this.x[0], this.x[1], this.y[0], this.y[1], r2) || + edgesIntersect(this.x[1], this.x[2], this.y[1], this.y[2], r2) || + edgesIntersect(this.x[2], this.x[3], this.y[2], this.y[3], r2) || + edgesIntersect(this.x[3], this.x[0], this.y[3], this.y[0], r2); + } + } +} 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 4d77162e3d7140899a7d057d57aaaaeba42e63d5..d2c548069afaa968ba03bc40324b6156d65e83b2 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 @@ -20,6 +20,7 @@ public final class GridState { private RList gpar; private RList viewPort; private REnvironment gridEnv; + private double scale = 1; private boolean deviceInitialized; /** @@ -79,4 +80,9 @@ public final class GridState { public void setCurrentGrob(Object currentGrob) { this.currentGrob = currentGrob; } + + public double getScale() { + return scale; + } + } 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 new file mode 100644 index 0000000000000000000000000000000000000000..f60dc1dff88c99f552120ecc823407b6090eea6f --- /dev/null +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridTextNode.java @@ -0,0 +1,247 @@ +/* + * This material is distributed under the GNU General Public License + * Version 2. You may review the terms of this license at + * http://www.gnu.org/licenses/gpl-2.0.html + * + * Copyright (C) 2001-3 Paul Murrell + * Copyright (c) 1998-2013, The R Core Team + * Copyright (c) 2017, Oracle and/or its affiliates + * + * All rights reserved. + */ +/* + * This material is distributed under the GNU General Public License + * Version 2. You may review the terms of this license at + * http://www.gnu.org/licenses/gpl-2.0.html + * + * Copyright (c) 2001-2015, The R Core Team + * Copyright (c) 2017, Oracle and/or its affiliates + * + * All rights reserved. + */ +package com.oracle.truffle.r.library.fastrGrid; + +import static com.oracle.truffle.r.library.fastrGrid.GridUtils.fmax; +import static com.oracle.truffle.r.library.fastrGrid.GridUtils.fmin; +import static com.oracle.truffle.r.library.fastrGrid.GridUtils.getDataAtMod; +import static com.oracle.truffle.r.library.fastrGrid.TransformMatrix.multiply; +import static com.oracle.truffle.r.library.fastrGrid.TransformMatrix.transLocation; +import static com.oracle.truffle.r.library.fastrGrid.TransformMatrix.translation; +import static com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.INCH_TO_POINTS_FACTOR; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.abstractVectorValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.numericValue; + +import com.oracle.truffle.api.profiles.ConditionProfile; +import com.oracle.truffle.r.library.fastrGrid.EdgeDetection.Rectangle; +import com.oracle.truffle.r.library.fastrGrid.Unit.UnitConversionContext; +import com.oracle.truffle.r.library.fastrGrid.ViewPortContext.VPContextFromVPNode; +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.NodeWithArgumentCasts.Casts; +import com.oracle.truffle.r.runtime.RInternalError; +import com.oracle.truffle.r.runtime.data.RDataFactory; +import com.oracle.truffle.r.runtime.data.RList; +import com.oracle.truffle.r.runtime.data.RNull; +import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector; +import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; +import com.oracle.truffle.r.runtime.data.model.RAbstractVector; +import com.oracle.truffle.r.runtime.nodes.RBaseNode; + +/** + * Implements what is in the original grid code implemented by {@code gridText} function. + * + * Note: the third parameter contains sequences {@code 1:max(length(x),length(y))}, where the + * 'length' dispatches to S3 method giving us unit length like {@link Unit.UnitLengthNode}. + */ +public final class GridTextNode extends RBaseNode { + @Child private Unit.UnitToInchesNode unitToInches = Unit.createToInchesNode(); + @Child private Unit.UnitLengthNode unitLength = Unit.createLengthNode(); + @Child private GetViewPortTransformNode getViewPortTransform = new GetViewPortTransformNode(); + @Child private VPContextFromVPNode vpContextFromVP = new VPContextFromVPNode(); + private final ConditionProfile checkOverlapProfile = ConditionProfile.createBinaryProfile(); + private final boolean draw; + + static void addGridTextCasts(Casts casts) { + casts.arg(0).asStringVector(); + casts.arg(1).mustBe(abstractVectorValue()); + casts.arg(2).mustBe(abstractVectorValue()); + casts.arg(3).mustBe(numericValue()).asDoubleVector(); + casts.arg(4).mustBe(numericValue()).asDoubleVector(); + casts.arg(5).mustBe(numericValue()).asDoubleVector(); + } + + private GridTextNode(boolean draw) { + this.draw = draw; + } + + public static GridTextNode createDraw() { + return new GridTextNode(true); + } + + public static GridTextNode createCalculateBounds() { + return new GridTextNode(false); + } + + public Object gridText(RAbstractStringVector textVec, RAbstractVector x, RAbstractVector y, RAbstractDoubleVector hjustVec, RAbstractDoubleVector vjustVec, RAbstractDoubleVector rotationVec, + boolean checkOverlapIn, double theta) { + if (textVec.getLength() == 0) { + return RNull.instance; + } + + boolean checkOverlap = checkOverlapProfile.profile(checkOverlapIn); + GridContext ctx = GridContext.getContext(); + GridDevice dev = ctx.getCurrentDevice(); + + RList currentVP = ctx.getGridState().getViewPort(); + DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar()); + ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP); + ViewPortContext vpContext = vpContextFromVP.execute(currentVP); + UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, drawingCtx); + + int length = GridUtils.maxLength(unitLength, x, y); + + // following variables will hold the (intermediate) results of bounds checking + int boundsCount = 0; + Point edge = null; + double xmin = Double.MAX_VALUE; + double xmax = Double.MIN_VALUE; + double ymin = Double.MAX_VALUE; + double ymax = Double.MIN_VALUE; + int ntxt = 0; // number of texts that were actually used for bounds computation + EdgeDetection.Rectangle[] bounds = null; + if (checkOverlap || !draw) { + bounds = new EdgeDetection.Rectangle[length]; + } + + for (int i = 0; i < length; i++) { + Point loc = Point.fromUnits(unitToInches, x, y, i, conversionCtx); + if (draw) { + // transformation not necessary for bounds calculation + loc = transLocation(loc, vpTransform.transform); + } + + String text = textVec.getDataAt(i % textVec.getLength()); + double hjust = getDataAtMod(hjustVec, i); + double vjust = getDataAtMod(vjustVec, i); + double rotation = getDataAtMod(rotationVec, i); + + // update bounds if necessary + boolean doDraw = true; + Rectangle trect = null; + if (checkOverlap || !draw) { + trect = textRect(loc, hjust, vjust, rotation, text, drawingCtx, dev); + for (int j = 0; j < boundsCount; j++) { + if (trect.intersects(bounds[j])) { + doDraw = false; + break; + } + } + if (doDraw) { + bounds[boundsCount++] = trect; + } + } + + // actual drawing + if (draw && doDraw) { + text(loc.x, loc.y, text, hjust, vjust, rotation, drawingCtx, dev); + } + + // or bounds checking + if (!draw) { + if (Double.isFinite(loc.x) && Double.isFinite(loc.y)) { + xmin = fmin(xmin, trect.x); + xmax = fmax(xmax, trect.x); + ymin = fmin(ymin, trect.y); + ymax = fmax(ymax, trect.y); + // Calculate edgex and edgey for case where this is the only rect + edge = EdgeDetection.polygonEdge(trect.x, trect.y, 4, theta); + ntxt++; + } + } + } + + if (!draw && ntxt > 0) { + // If there is more than one text, just produce edge based on bounding rect of all text + if (ntxt > 1) { + // Produce edge of rect bounding all text + edge = EdgeDetection.rectEdge(xmin, ymin, xmax, ymax, theta); + } + + double scale = GridContext.getContext().getGridState().getScale(); + double[] result = new double[4]; + result[0] = edge.x / scale; + result[1] = edge.y / scale; + result[2] = (xmax - xmin) / scale; + result[3] = (ymax - ymin) / scale; + return RDataFactory.createDoubleVector(result, RDataFactory.COMPLETE_VECTOR); + } + + // NULL is OK result even for bound checking if there was no text actually "drawn", the R + // wrapper deals with NULL in such case. For actual drawing case, we should always return + // NULL + return RNull.instance; + } + + // transcribed from utils.c + + private EdgeDetection.Rectangle textRect(Point loc, double xadj, double yadj, double rotation, String text, DrawingContext drawingCtx, GridDevice device) { + // TODO: for expressions the h and w are calculated differently + double h = device.getStringHeight(drawingCtx, text); + double w = device.getStringWidth(drawingCtx, text); + + double[][] thisJustification = translation(-xadj * w, -yadj * h); + double[][] thisRotation = TransformMatrix.rotation(rotation); + double[][] transform = multiply(multiply(thisJustification, thisRotation), translation(loc.x, loc.y)); + + Point bl = transLocation(new Point(0, 0), transform); + Point br = transLocation(new Point(w, 0), transform); + Point tr = transLocation(new Point(w, h), transform); + Point tl = transLocation(new Point(0, h), transform); + return new Rectangle(bl, br, tr, tl); + } + + // transcribed from engine.c + + private void text(double x, double y, String text, double xadjIn, double yadj, double rotation, DrawingContext drawingCtx, GridDevice device) { + if (!Double.isFinite(yadj)) { + throw RInternalError.unimplemented("'exact' vertical centering, see engine.c:1700"); + } + double xadj = Double.isFinite(xadjIn) ? xadjIn : 0.5; + + double radRotation = Math.toRadians(rotation); + double cosRot = Math.cos(radRotation); + double sinRot = Math.sin(radRotation); + String[] lines = text.split("\n"); + for (int lineIdx = 0; lineIdx < lines.length; lineIdx++) { + double xoff; + double yoff; + if (lines.length == 1) { + // simplification for single line + xoff = x; + yoff = y; + } else { + yoff = (1 - yadj) * (lines.length - 1) - lineIdx; + // TODO: in the original the following formula uses "dd->dev->cra[1]" + yoff *= (drawingCtx.getFontSize() * drawingCtx.getLineHeight()) / INCH_TO_POINTS_FACTOR; + xoff = -yoff * sinRot; + yoff = yoff * cosRot; + xoff = x + xoff; + yoff = y + yoff; + } + + double xleft = xoff; + double ybottom = yoff; + // now determine bottom-left for THIS line + if (xadj != 0.0 || yadj != 0.0) { + // otherwise simply the initial values for xleft and ybottom are OK + double width = device.getStringWidth(drawingCtx, lines[lineIdx]); + double height = device.getStringHeight(drawingCtx, lines[lineIdx]); + xleft = xoff - (xadj) * width * cosRot + yadj * height * sinRot; + ybottom = yoff - (xadj) * width * sinRot - yadj * height * cosRot; + } + + device.drawString(drawingCtx, xleft, ybottom, rotation, lines[lineIdx]); + } + } +} 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 89bf1500029e5eea1e2f113566f59ec7d0ba9157..b6acae809c45fb63a0facef92f8d3fd4151e895d 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 @@ -11,6 +11,9 @@ */ package com.oracle.truffle.r.library.fastrGrid; +import static com.oracle.truffle.r.runtime.nmath.RMath.fmax2; +import static com.oracle.truffle.r.runtime.nmath.RMath.fmin2; + import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.r.library.fastrGrid.Unit.UnitLengthNode; import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector; @@ -46,4 +49,22 @@ final class GridUtils { } return result; } + + @ExplodeLoop + static double fmax(double firstVal, double... vals) { + double result = firstVal; + for (double val : vals) { + result = fmax2(result, val); + } + return result; + } + + @ExplodeLoop + static double fmin(double firstVal, double... vals) { + double result = firstVal; + for (double val : vals) { + result = fmin2(result, val); + } + return result; + } } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LText.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LText.java index cc3b9082a49f1a7aaf43494347ad287d187a73ce..c22cf068da8963e2ec89eaa5ba5c1778029b4705 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LText.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LText.java @@ -1,54 +1,46 @@ /* - * This material is distributed under the GNU General Public License - * Version 2. You may review the terms of this license at - * http://www.gnu.org/licenses/gpl-2.0.html + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * - * Copyright (C) 2001-3 Paul Murrell - * Copyright (c) 1998-2013, The R Core Team - * Copyright (c) 2017, Oracle and/or its affiliates + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. * - * All rights reserved. + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. */ package com.oracle.truffle.r.library.fastrGrid; -import static com.oracle.truffle.r.library.fastrGrid.GridUtils.getDataAtMod; -import static com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.INCH_TO_POINTS_FACTOR; -import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.abstractVectorValue; -import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.numericValue; +import static com.oracle.truffle.r.library.fastrGrid.GridTextNode.addGridTextCasts; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.logicalValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.toBoolean; +import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.r.library.fastrGrid.Unit.UnitConversionContext; -import com.oracle.truffle.r.library.fastrGrid.ViewPortContext.VPContextFromVPNode; -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.api.nodes.NodeCost; +import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; -import com.oracle.truffle.r.runtime.data.RList; -import com.oracle.truffle.r.runtime.data.RNull; import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector; import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; import com.oracle.truffle.r.runtime.data.model.RAbstractVector; -/** - * Note: the third parameter contains sequences {@code 1:max(length(x),length(y))}, where the - * 'length' dispatches to S3 method giving us unit length like {@link Unit.UnitLengthNode}. - */ +@NodeInfo(cost = NodeCost.NONE) public abstract class LText extends RExternalBuiltinNode.Arg7 { - @Child private Unit.UnitToInchesNode unitToInches = Unit.createToInchesNode(); - @Child private Unit.UnitLengthNode unitLength = Unit.createLengthNode(); - @Child private GetViewPortTransformNode getViewPortTransform = new GetViewPortTransformNode(); - @Child private VPContextFromVPNode vpContextFromVP = new VPContextFromVPNode(); - static { Casts casts = new Casts(LText.class); - // TODO: expressions and maybe other types should have special handling, not only simple - // String coercion - casts.arg(0).asStringVector(); - casts.arg(1).mustBe(abstractVectorValue()); - casts.arg(2).mustBe(abstractVectorValue()); - casts.arg(3).mustBe(numericValue()).asDoubleVector(); - casts.arg(4).mustBe(numericValue()).asDoubleVector(); - casts.arg(5).mustBe(numericValue()).asDoubleVector(); + addGridTextCasts(casts); + casts.arg(6).mustBe(logicalValue()).asLogicalVector().findFirst().map(toBoolean()); } public static LText create() { @@ -56,70 +48,8 @@ public abstract class LText extends RExternalBuiltinNode.Arg7 { } @Specialization - Object drawText(RAbstractStringVector text, RAbstractVector x, RAbstractVector y, RAbstractDoubleVector hjust, RAbstractDoubleVector vjust, RAbstractDoubleVector rotation, - Object checkOverlapIgnored) { - if (text.getLength() == 0) { - return RNull.instance; - } - - GridContext ctx = GridContext.getContext(); - GridDevice dev = ctx.getCurrentDevice(); - - RList currentVP = ctx.getGridState().getViewPort(); - DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar()); - ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP); - ViewPortContext vpContext = vpContextFromVP.execute(currentVP); - UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, drawingCtx); - - int length = GridUtils.maxLength(unitLength, x, y); - for (int i = 0; i < length; i++) { - Point loc = TransformMatrix.transLocation(Point.fromUnits(unitToInches, x, y, i, conversionCtx), vpTransform.transform); - text(loc.x, loc.y, text.getDataAt(i % text.getLength()), getDataAtMod(hjust, i), getDataAtMod(vjust, i), getDataAtMod(rotation, i), drawingCtx, dev); - } - return RNull.instance; - } - - // transcribed from engine.c - - private void text(double x, double y, String text, double xadjIn, double yadj, double rotation, DrawingContext drawingCtx, GridDevice device) { - if (!Double.isFinite(yadj)) { - throw new RuntimeException("Not implemented: 'exact' vertical centering, see engine.c:1700"); - } - double xadj = Double.isFinite(xadjIn) ? xadjIn : 0.5; - - double radRotation = Math.toRadians(rotation); - double cosRot = Math.cos(radRotation); - double sinRot = Math.sin(radRotation); - String[] lines = text.split("\n"); - for (int lineIdx = 0; lineIdx < lines.length; lineIdx++) { - double xoff; - double yoff; - if (lines.length == 1) { - // simplification for single line - xoff = x; - yoff = y; - } else { - yoff = (1 - yadj) * (lines.length - 1) - lineIdx; - // TODO: in the original the following formula uses "dd->dev->cra[1]" - yoff *= (drawingCtx.getFontSize() * drawingCtx.getLineHeight()) / INCH_TO_POINTS_FACTOR; - xoff = -yoff * sinRot; - yoff = yoff * cosRot; - xoff = x + xoff; - yoff = y + yoff; - } - - double xleft = xoff; - double ybottom = yoff; - // now determine bottom-left for THIS line - if (xadj != 0.0 || yadj != 0.0) { - // otherwise simply the initial values for xleft and ybottom are OK - double width = device.getStringWidth(drawingCtx, lines[lineIdx]); - double height = device.getStringHeight(drawingCtx, lines[lineIdx]); - xleft = xoff - (xadj) * width * cosRot + yadj * height * sinRot; - ybottom = yoff - (xadj) * width * sinRot - yadj * height * cosRot; - } - - device.drawString(drawingCtx, xleft, ybottom, rotation, lines[lineIdx]); - } + Object drawText(RAbstractStringVector text, RAbstractVector x, RAbstractVector y, RAbstractDoubleVector hjust, RAbstractDoubleVector vjust, RAbstractDoubleVector rotation, boolean checkOverlap, + @Cached("createDraw()") GridTextNode gridText) { + return gridText.gridText(text, x, y, hjust, vjust, rotation, checkOverlap, 0); } } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LTextBounds.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LTextBounds.java new file mode 100644 index 0000000000000000000000000000000000000000..fa1ec97d723de0ecc9ce446b956b9307558f32a5 --- /dev/null +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LTextBounds.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.r.library.fastrGrid; + +import static com.oracle.truffle.r.library.fastrGrid.GridTextNode.addGridTextCasts; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.constant; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.missingValue; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.nullValue; + +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.NodeCost; +import com.oracle.truffle.api.nodes.NodeInfo; +import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; +import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector; +import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector; +import com.oracle.truffle.r.runtime.data.model.RAbstractVector; + +@NodeInfo(cost = NodeCost.NONE) +public abstract class LTextBounds extends RExternalBuiltinNode.Arg7 { + static { + Casts casts = new Casts(LTextBounds.class); + addGridTextCasts(casts); + casts.arg(6).returnIf(missingValue().or(nullValue()), constant(0)).asDoubleVector().findFirst(); + } + + public static LTextBounds create() { + return LTextBoundsNodeGen.create(); + } + + @Specialization + public Object textBounds(RAbstractStringVector text, RAbstractVector x, RAbstractVector y, RAbstractDoubleVector hjust, RAbstractDoubleVector vjust, RAbstractDoubleVector rotation, double theta, + @Cached("createCalculateBounds()") GridTextNode gridText) { + return gridText.gridText(text, x, y, hjust, vjust, rotation, false, theta); + } +} diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/TransformMatrix.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/TransformMatrix.java index 7b66ab2bae3ce622c2ab8cf2672f51824147b9c7..e1051c98a46937db800261a3ea626a87c04061f9 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/TransformMatrix.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/TransformMatrix.java @@ -11,6 +11,8 @@ */ package com.oracle.truffle.r.library.fastrGrid; +import static com.oracle.truffle.r.runtime.nmath.MathConstants.M_PI; + /** * Operations on transformation (3x3) matrices. */ @@ -40,6 +42,21 @@ final class TransformMatrix { return m; } + static double[][] rotation(double theta) { + double thetarad = theta / 180 * M_PI; + double costheta = Math.cos(thetarad); + double sintheta = Math.sin(thetarad); + double[][] result = identity(); + if (theta == 0) { + return result; + } + result[0][0] = costheta; + result[0][1] = sintheta; + result[1][0] = -sintheta; + result[1][1] = costheta; + return result; + } + static double[][] identity() { double[][] result = new double[3][3]; result[0][0] = 1; diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java index f5e5f53006eef22525b622fa99be8f9feac1812f..9a516347373b13452010018b42660e6e07b3c6b6 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java @@ -22,6 +22,7 @@ import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.r.library.fastrGrid.GridState; import com.oracle.truffle.r.library.fastrGrid.GridStateGetNode; import com.oracle.truffle.r.library.fastrGrid.GridStateSetNode; import com.oracle.truffle.r.library.fastrGrid.IgnoredGridExternal; @@ -34,6 +35,7 @@ import com.oracle.truffle.r.library.fastrGrid.LNewPage; import com.oracle.truffle.r.library.fastrGrid.LRect; import com.oracle.truffle.r.library.fastrGrid.LSegments; import com.oracle.truffle.r.library.fastrGrid.LText; +import com.oracle.truffle.r.library.fastrGrid.LTextBounds; import com.oracle.truffle.r.library.fastrGrid.LUpViewPort; import com.oracle.truffle.r.library.grDevices.DevicesCCalls; import com.oracle.truffle.r.library.graphics.GraphicsCCalls; @@ -695,6 +697,8 @@ public class CallAndExternalFunctions { return LLines.create(); case "L_text": return LText.create(); + case "L_textBounds": + return LTextBounds.create(); case "L_segments": return LSegments.create(); case "L_circle": @@ -702,15 +706,15 @@ public class CallAndExternalFunctions { // Simple grid state access case "L_getGPar": - return new GridStateGetNode(state -> state.getGpar()); + return new GridStateGetNode(GridState::getGpar); case "L_setGPar": return GridStateSetNode.create((state, val) -> state.setGpar((RList) val)); case "L_getCurrentGrob": - return new GridStateGetNode(state -> state.getCurrentGrob()); + return new GridStateGetNode(GridState::getCurrentGrob); case "L_setCurrentGrob": - return GridStateSetNode.create((state, val) -> state.setCurrentGrob(val)); + return GridStateSetNode.create(GridState::setCurrentGrob); case "L_currentViewport": - return new GridStateGetNode(state -> state.getViewPort()); + return new GridStateGetNode(GridState::getViewPort); // Display list stuff: not implemented atm case "L_getDisplayList": diff --git a/mx.fastr/copyrights/overrides b/mx.fastr/copyrights/overrides index 86618d197ae52cb4283b5053f27ece09aae742a3..670e335899564bfb5cac3164a298164b86793a38 100644 --- a/mx.fastr/copyrights/overrides +++ b/mx.fastr/copyrights/overrides @@ -773,7 +773,6 @@ com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LUpViewP com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LNewPage.java,gnu_r_murrel_core.copyright com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitGrid.java,gnu_r_murrel_core.copyright com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitViewPortStack.java,gnu_r_murrel_core.copyright -com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LText.java,gnu_r_murrel_core.copyright com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java,gnu_r_murrel_core.copyright com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/TransformMatrix.java,gnu_r_murrel_core.copyright com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LSegments.java,gnu_r_murrel_core.copyright @@ -788,3 +787,5 @@ com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPort com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LGridDirty.java,gnu_r_murrel_core.copyright com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ColorNames.java,gnu_r.copyright com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LCircle.java,gnu_r_murrel_core.copyright +com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/EdgeDetection.java,gnu_r_murrel_core.copyright +com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridTextNode.java,gnu_r_murrel_core.copyright