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