< prev index next >

## src/java.desktop/share/classes/sun/java2d/marlin/DStroker.java

```@@ -24,56 +24,31 @@
*/

package sun.java2d.marlin;

import java.util.Arrays;
+import sun.java2d.marlin.DHelpers.PolyStack;

// TODO: some of the arithmetic here is too verbose and prone to hard to
// debug typos. We should consider making a small Point/Vector class that
// has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
final class DStroker implements DPathConsumer2D, MarlinConst {

private static final int MOVE_TO = 0;
private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
private static final int CLOSE = 2;

-    /**
-     * Constant value for join style.
-     */
-    public static final int JOIN_MITER = 0;
-
-    /**
-     * Constant value for join style.
-     */
-    public static final int JOIN_ROUND = 1;
-
-    /**
-     * Constant value for join style.
-     */
-    public static final int JOIN_BEVEL = 2;
-
-    /**
-     * Constant value for end cap style.
-     */
-    public static final int CAP_BUTT = 0;
-
-    /**
-     * Constant value for end cap style.
-     */
-    public static final int CAP_ROUND = 1;
-
-    /**
-     * Constant value for end cap style.
-     */
-    public static final int CAP_SQUARE = 2;
-
// pisces used to use fixed point arithmetic with 16 decimal digits. I
// didn't want to change the values of the constant below when I converted
// it to floating point, so that's why the divisions by 2^16 are there.
private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d;

-    private static final double C = 0.5522847498307933d;
+    // kappa = (4/3) * (SQRT(2) - 1)
+    private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
+
+    // SQRT(2)
+    private static final double SQRT_2 = Math.sqrt(2.0d);

private static final int MAX_N_CURVES = 11;

private DPathConsumer2D out;

```

```@@ -116,18 +91,40 @@
final DRendererContext rdrCtx;

// dirty curve
final DCurve curve;

+    // Bounds of the drawing region, at pixel precision.
+    private double[] clipRect;
+
+    // the outcode of the current point
+    private int cOutCode = 0;
+
+    // the outcode of the starting point
+    private int sOutCode = 0;
+
+    // flag indicating if the path is opened (clipped)
+    private boolean opened = false;
+    // flag indicating if the starting point's cap is done
+    private boolean capStart = false;
+
/**
* Constructs a <code>DStroker</code>.
* @param rdrCtx per-thread renderer context
*/
DStroker(final DRendererContext rdrCtx) {
this.rdrCtx = rdrCtx;

-        this.reverse = new PolyStack(rdrCtx);
+        this.reverse = (rdrCtx.stats != null) ?
+            new PolyStack(rdrCtx,
+                    rdrCtx.stats.stat_str_polystack_types,
+                    rdrCtx.stats.stat_str_polystack_curves,
+                    rdrCtx.stats.hist_str_polystack_curves,
+                    rdrCtx.stats.stat_array_str_polystack_curves,
+                    rdrCtx.stats.stat_array_str_polystack_types)
+            : new PolyStack(rdrCtx);
+
this.curve = rdrCtx.curve;
}

/**
* Inits the <code>DStroker</code>.
```

```@@ -139,42 +136,79 @@
* <code>CAP_SQUARE</code>.
* @param joinStyle the desired line join style, one of
* <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
* <code>JOIN_BEVEL</code>.
* @param miterLimit the desired miter limit
+     * @param scale scaling factor applied to clip boundaries
* @return this instance
*/
-    DStroker init(DPathConsumer2D pc2d,
-              double lineWidth,
-              int capStyle,
-              int joinStyle,
-              double miterLimit)
+    DStroker init(final DPathConsumer2D pc2d,
+                  final double lineWidth,
+                  final int capStyle,
+                  final int joinStyle,
+                  final double miterLimit,
+                  final double scale)
{
this.out = pc2d;

this.lineWidth2 = lineWidth / 2.0d;
this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2);
this.capStyle = capStyle;
this.joinStyle = joinStyle;

-        double limit = miterLimit * lineWidth2;
+        final double limit = miterLimit * lineWidth2;
this.miterLimitSq = limit * limit;

this.prev = CLOSE;

rdrCtx.stroking = 1;

+        if (rdrCtx.doClip) {
+            // Adjust the clipping rectangle with the stroker margin (miter limit, width)
+            double rdrOffX = 0.0d, rdrOffY = 0.0d;
+            double margin = lineWidth2;
+
+            if (capStyle == CAP_SQUARE) {
+                margin *= SQRT_2;
+            }
+            if ((joinStyle == JOIN_MITER) && (margin < limit)) {
+                margin = limit;
+            }
+            if (scale != 1.0d) {
+                margin *= scale;
+                rdrOffX = scale * DRenderer.RDR_OFFSET_X;
+                rdrOffY = scale * DRenderer.RDR_OFFSET_Y;
+            }
+            // add a small rounding error:
+            margin += 1e-3d;
+
+            // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
+            // adjust clip rectangle (ymin, ymax, xmin, xmax):
+            final double[] _clipRect = rdrCtx.clipRect;
+            _clipRect[0] -= margin - rdrOffY;
+            _clipRect[1] += margin + rdrOffY;
+            _clipRect[2] -= margin - rdrOffX;
+            _clipRect[3] += margin + rdrOffX;
+            this.clipRect = _clipRect;
+        } else {
+            this.clipRect = null;
+            this.cOutCode = 0;
+            this.sOutCode = 0;
+        }
return this; // fluent API
}

/**
* Disposes this stroker:
* clean up before reusing this instance
*/
void dispose() {
reverse.dispose();

+        opened   = false;
+        capStart = false;
+
if (DO_CLEAN_DIRTY) {
// Force zero-fill dirty arrays:
Arrays.fill(offset0, 0.0d);
Arrays.fill(offset1, 0.0d);
Arrays.fill(offset2, 0.0d);
```

```@@ -441,87 +475,152 @@
emitLineTo(miterX, miterY, rev);
}
}

@Override
-    public void moveTo(double x0, double y0) {
-        if (prev == DRAWING_OP_TO) {
-            finish();
+    public void moveTo(final double x0, final double y0) {
+        moveTo(x0, y0, cOutCode);
+        // update starting point:
+        this.sx0 = x0;
+        this.sy0 = y0;
+        this.sdx = 1.0d;
+        this.sdy = 0.0d;
+        this.opened   = false;
+        this.capStart = false;
+
+        if (clipRect != null) {
+            final int outcode = DHelpers.outcode(x0, y0, clipRect);
+            this.cOutCode = outcode;
+            this.sOutCode = outcode;
+        }
+    }
+
+    private void moveTo(final double x0, final double y0,
+                        final int outcode)
+    {
+        if (prev == MOVE_TO) {
+            this.cx0 = x0;
+            this.cy0 = y0;
+        } else {
+            if (prev == DRAWING_OP_TO) {
+                finish(outcode);
+            }
+            this.prev = MOVE_TO;
+            this.cx0 = x0;
+            this.cy0 = y0;
+            this.cdx = 1.0d;
+            this.cdy = 0.0d;
}
-        this.sx0 = this.cx0 = x0;
-        this.sy0 = this.cy0 = y0;
-        this.cdx = this.sdx = 1.0d;
-        this.cdy = this.sdy = 0.0d;
-        this.prev = MOVE_TO;
}

@Override
-    public void lineTo(double x1, double y1) {
+    public void lineTo(final double x1, final double y1) {
+        lineTo(x1, y1, false);
+    }
+
+    private void lineTo(final double x1, final double y1,
+                        final boolean force)
+    {
+        final int outcode0 = this.cOutCode;
+        if (!force && clipRect != null) {
+            final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
+            this.cOutCode = outcode1;
+
+            // basic rejection criteria
+            if ((outcode0 & outcode1) != 0) {
+                moveTo(x1, y1, outcode0);
+                opened = true;
+                return;
+            }
+        }
+
double dx = x1 - cx0;
double dy = y1 - cy0;
if (dx == 0.0d && dy == 0.0d) {
dx = 1.0d;
}
computeOffset(dx, dy, lineWidth2, offset0);
final double mx = offset0[0];
final double my = offset0[1];

-        drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my);
+        drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);

emitLineTo(cx0 + mx, cy0 + my);
emitLineTo( x1 + mx,  y1 + my);

emitLineToRev(cx0 - mx, cy0 - my);
emitLineToRev( x1 - mx,  y1 - my);

-        this.cmx = mx;
-        this.cmy = my;
-        this.cdx = dx;
-        this.cdy = dy;
+        this.prev = DRAWING_OP_TO;
this.cx0 = x1;
this.cy0 = y1;
-        this.prev = DRAWING_OP_TO;
+        this.cdx = dx;
+        this.cdy = dy;
+        this.cmx = mx;
+        this.cmy = my;
}

@Override
public void closePath() {
-        if (prev != DRAWING_OP_TO) {
+        // distinguish empty path at all vs opened path ?
+        if (prev != DRAWING_OP_TO && !opened) {
if (prev == CLOSE) {
return;
}
emitMoveTo(cx0, cy0 - lineWidth2);
-            this.cmx = this.smx = 0.0d;
-            this.cmy = this.smy = -lineWidth2;
-            this.cdx = this.sdx = 1.0d;
-            this.cdy = this.sdy = 0.0d;
-            finish();
+
+            this.sdx = 1.0d;
+            this.sdy = 0.0d;
+            this.cdx = 1.0d;
+            this.cdy = 0.0d;
+
+            this.smx = 0.0d;
+            this.smy = -lineWidth2;
+            this.cmx = 0.0d;
+            this.cmy = -lineWidth2;
+
+            finish(cOutCode);
return;
}

-        if (cx0 != sx0 || cy0 != sy0) {
-            lineTo(sx0, sy0);
-        }
+        // basic acceptance criteria
+        if ((sOutCode & cOutCode) == 0) {
+            if (cx0 != sx0 || cy0 != sy0) {
+                lineTo(sx0, sy0, true);
+            }

-        drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
+            drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode);

-        emitLineTo(sx0 + smx, sy0 + smy);
+            emitLineTo(sx0 + smx, sy0 + smy);

-        emitMoveTo(sx0 - smx, sy0 - smy);
+            if (opened) {
+                emitLineTo(sx0 - smx, sy0 - smy);
+            } else {
+                emitMoveTo(sx0 - smx, sy0 - smy);
+            }
+        }
+        // Ignore caps like finish(false)
emitReverse();

this.prev = CLOSE;
-        emitClose();
+
+        if (opened) {
+            // do not emit close
+            opened = false;
+        } else {
+            emitClose();
+        }
}

private void emitReverse() {
reverse.popAll(out);
}

@Override
public void pathDone() {
if (prev == DRAWING_OP_TO) {
-            finish();
+            finish(cOutCode);
}

out.pathDone();

// this shouldn't matter since this object won't be used
```

```@@ -530,27 +629,43 @@

// Dispose this instance:
dispose();
}

-    private void finish() {
-        if (capStyle == CAP_ROUND) {
-            drawRoundCap(cx0, cy0, cmx, cmy);
-        } else if (capStyle == CAP_SQUARE) {
-            emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
-            emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
-        }
+    private void finish(final int outcode) {
+        // Problem: impossible to guess if the path will be closed in advance
+        //          i.e. if caps must be drawn or not ?
+        // Solution: use the ClosedPathDetector before Stroker to determine
+        // if the path is a closed path or not
+        if (!rdrCtx.closedPath) {
+            if (outcode == 0) {
+                // current point = end's cap:
+                if (capStyle == CAP_ROUND) {
+                    drawRoundCap(cx0, cy0, cmx, cmy);
+                } else if (capStyle == CAP_SQUARE) {
+                    emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
+                    emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
+                }
+            }
+            emitReverse();

-        emitReverse();
+            if (!capStart) {
+                capStart = true;

-        if (capStyle == CAP_ROUND) {
-            drawRoundCap(sx0, sy0, -smx, -smy);
-        } else if (capStyle == CAP_SQUARE) {
-            emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
-            emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
+                if (sOutCode == 0) {
+                    // starting point = initial cap:
+                    if (capStyle == CAP_ROUND) {
+                        drawRoundCap(sx0, sy0, -smx, -smy);
+                    } else if (capStyle == CAP_SQUARE) {
+                        emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
+                        emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
+                    }
+                }
+            }
+        } else {
+            emitReverse();
}
-
emitClose();
}

private void emitMoveTo(final double x0, final double y0) {
out.moveTo(x0, y0);
```

```@@ -618,27 +733,32 @@

private void drawJoin(double pdx, double pdy,
double x0, double y0,
double dx, double dy,
double omx, double omy,
-                          double mx, double my)
+                          double mx, double my,
+                          final int outcode)
{
if (prev != DRAWING_OP_TO) {
emitMoveTo(x0 + mx, y0 + my);
-            this.sdx = dx;
-            this.sdy = dy;
-            this.smx = mx;
-            this.smy = my;
+            if (!opened) {
+                this.sdx = dx;
+                this.sdy = dy;
+                this.smx = mx;
+                this.smy = my;
+            }
} else {
-            boolean cw = isCW(pdx, pdy, dx, dy);
-            if (joinStyle == JOIN_MITER) {
-                drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
-            } else if (joinStyle == JOIN_ROUND) {
-                drawRoundJoin(x0, y0,
-                              omx, omy,
-                              mx, my, cw,
-                              ROUND_JOIN_THRESHOLD);
+            final boolean cw = isCW(pdx, pdy, dx, dy);
+            if (outcode == 0) {
+                if (joinStyle == JOIN_MITER) {
+                    drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
+                } else if (joinStyle == JOIN_ROUND) {
+                    drawRoundJoin(x0, y0,
+                                  omx, omy,
+                                  mx, my, cw,
+                                  ROUND_JOIN_THRESHOLD);
+                }
}
emitLineTo(x0, y0, !cw);
}
prev = DRAWING_OP_TO;
}
```

```@@ -939,23 +1059,42 @@
ret = DHelpers.filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d);
DHelpers.isort(ts, 0, ret);
return ret;
}

-    @Override public void curveTo(double x1, double y1,
-                                  double x2, double y2,
-                                  double x3, double y3)
-    {
+    @Override
+    public void curveTo(final double x1, final double y1,
+                        final double x2, final double y2,
+                        final double x3, final double y3)
+    {
+        final int outcode0 = this.cOutCode;
+        if (clipRect != null) {
+            final int outcode3 = DHelpers.outcode(x3, y3, clipRect);
+            this.cOutCode = outcode3;
+
+            if ((outcode0 & outcode3) != 0) {
+                final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
+                final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
+
+                // basic rejection criteria
+                if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
+                    moveTo(x3, y3, outcode0);
+                    opened = true;
+                    return;
+                }
+            }
+        }
+
final double[] mid = middle;

mid[0] = cx0; mid[1] = cy0;
mid[2] = x1;  mid[3] = y1;
mid[4] = x2;  mid[5] = y2;
mid[6] = x3;  mid[7] = y3;

// need these so we can update the state at the end of this method
-        final double xf = mid[6], yf = mid[7];
+        final double xf = x3, yf = y3;
double dxs = mid[2] - mid[0];
double dys = mid[3] - mid[1];
double dxf = mid[6] - mid[4];
double dyf = mid[7] - mid[5];

```

```@@ -977,10 +1116,14 @@
dyf = mid[7] - mid[1];
}
}
if (dxs == 0.0d && dys == 0.0d) {
// this happens if the "curve" is just a point
+            // fix outcode0 for lineTo() call:
+            if (clipRect != null) {
+                this.cOutCode = outcode0;
+            }
lineTo(mid[0], mid[1]);
return;
}

// if these vectors are too small, normalize them, to avoid future
```

```@@ -995,11 +1138,11 @@
dxf /= len;
dyf /= len;
}

computeOffset(dxs, dys, lineWidth2, offset0);
-        drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
+        drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);

final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);

double prevT = 0.0d;
for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
```

```@@ -1030,38 +1173,62 @@
default:
}
emitLineToRev(r[kind - 2], r[kind - 1]);
}

-        this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
-        this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
-        this.cdx = dxf;
-        this.cdy = dyf;
+        this.prev = DRAWING_OP_TO;
this.cx0 = xf;
this.cy0 = yf;
-        this.prev = DRAWING_OP_TO;
+        this.cdx = dxf;
+        this.cdy = dyf;
+        this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
+        this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
}

-    @Override public void quadTo(double x1, double y1, double x2, double y2) {
+    @Override
+    public void quadTo(final double x1, final double y1,
+                       final double x2, final double y2)
+    {
+        final int outcode0 = this.cOutCode;
+        if (clipRect != null) {
+            final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
+            this.cOutCode = outcode2;
+
+            if ((outcode0 & outcode2) != 0) {
+                final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
+
+                // basic rejection criteria
+                if ((outcode0 & outcode1 & outcode2) != 0) {
+                    moveTo(x2, y2, outcode0);
+                    opened = true;
+                    return;
+                }
+            }
+        }
+
final double[] mid = middle;

mid[0] = cx0; mid[1] = cy0;
mid[2] = x1;  mid[3] = y1;
mid[4] = x2;  mid[5] = y2;

// need these so we can update the state at the end of this method
-        final double xf = mid[4], yf = mid[5];
+        final double xf = x2, yf = y2;
double dxs = mid[2] - mid[0];
double dys = mid[3] - mid[1];
double dxf = mid[4] - mid[2];
double dyf = mid[5] - mid[3];
if ((dxs == 0.0d && dys == 0.0d) || (dxf == 0.0d && dyf == 0.0d)) {
dxs = dxf = mid[4] - mid[0];
dys = dyf = mid[5] - mid[1];
}
if (dxs == 0.0d && dys == 0.0d) {
// this happens if the "curve" is just a point
+            // fix outcode0 for lineTo() call:
+            if (clipRect != null) {
+                this.cOutCode = outcode0;
+            }
lineTo(mid[0], mid[1]);
return;
}
// if these vectors are too small, normalize them, to avoid future
// precision problems.
```

```@@ -1075,11 +1242,11 @@
dxf /= len;
dyf /= len;
}

computeOffset(dxs, dys, lineWidth2, offset0);
-        drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1]);
+        drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);

int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);

double prevt = 0.0d;
for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
```

```@@ -1110,216 +1277,18 @@
default:
}
emitLineToRev(r[kind - 2], r[kind - 1]);
}

-        this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
-        this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
-        this.cdx = dxf;
-        this.cdy = dyf;
+        this.prev = DRAWING_OP_TO;
this.cx0 = xf;
this.cy0 = yf;
-        this.prev = DRAWING_OP_TO;
+        this.cdx = dxf;
+        this.cdy = dyf;
+        this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
+        this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
}

@Override public long getNativeConsumer() {
throw new InternalError("Stroker doesn't use a native consumer");
}
-
-    // a stack of polynomial curves where each curve shares endpoints with
-    static final class PolyStack {
-        private static final byte TYPE_LINETO  = (byte) 0;
-        private static final byte TYPE_QUADTO  = (byte) 1;
-        private static final byte TYPE_CUBICTO = (byte) 2;
-
-        // curves capacity = edges count (8192) = edges x 2 (coords)
-        private static final int INITIAL_CURVES_COUNT = INITIAL_EDGES_COUNT << 1;
-
-        // types capacity = edges count (4096)
-        private static final int INITIAL_TYPES_COUNT = INITIAL_EDGES_COUNT;
-
-        double[] curves;
-        int end;
-        byte[] curveTypes;
-        int numCurves;
-
-        final DRendererContext rdrCtx;
-
-        // curves ref (dirty)
-        final DoubleArrayCache.Reference curves_ref;
-        // curveTypes ref (dirty)
-        final ByteArrayCache.Reference curveTypes_ref;
-
-        // used marks (stats only)
-        int curveTypesUseMark;
-        int curvesUseMark;
-
-        /**
-         * Constructor
-         * @param rdrCtx per-thread renderer context
-         */
-        PolyStack(final DRendererContext rdrCtx) {
-            this.rdrCtx = rdrCtx;
-
-            curves_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_CURVES_COUNT); // 32K
-            curves     = curves_ref.initial;
-
-            curveTypes_ref = rdrCtx.newDirtyByteArrayRef(INITIAL_TYPES_COUNT); // 4K
-            curveTypes     = curveTypes_ref.initial;
-            numCurves = 0;
-            end = 0;
-
-            if (DO_STATS) {
-                curveTypesUseMark = 0;
-                curvesUseMark = 0;
-            }
-        }
-
-        /**
-         * Disposes this PolyStack:
-         * clean up before reusing this instance
-         */
-        void dispose() {
-            end = 0;
-            numCurves = 0;
-
-            if (DO_STATS) {
-
-                // reset marks
-                curveTypesUseMark = 0;
-                curvesUseMark = 0;
-            }
-
-            // Return arrays:
-            // curves and curveTypes are kept dirty
-            curves     = curves_ref.putArray(curves);
-            curveTypes = curveTypes_ref.putArray(curveTypes);
-        }
-
-        private void ensureSpace(final int n) {
-            // use substraction to avoid integer overflow:
-            if (curves.length - end < n) {
-                if (DO_STATS) {
-                    rdrCtx.stats.stat_array_stroker_polystack_curves
-                }
-                curves = curves_ref.widenArray(curves, end, end + n);
-            }
-            if (curveTypes.length <= numCurves) {
-                if (DO_STATS) {
-                    rdrCtx.stats.stat_array_stroker_polystack_curveTypes
-                }
-                curveTypes = curveTypes_ref.widenArray(curveTypes,
-                                                       numCurves,
-                                                       numCurves + 1);
-            }
-        }
-
-        void pushCubic(double x0, double y0,
-                       double x1, double y1,
-                       double x2, double y2)
-        {
-            ensureSpace(6);
-            curveTypes[numCurves++] = TYPE_CUBICTO;
-            // we reverse the coordinate order to make popping easier
-            final double[] _curves = curves;
-            int e = end;
-            _curves[e++] = x2;    _curves[e++] = y2;
-            _curves[e++] = x1;    _curves[e++] = y1;
-            _curves[e++] = x0;    _curves[e++] = y0;
-            end = e;
-        }
-
-        void pushQuad(double x0, double y0,
-                      double x1, double y1)
-        {
-            ensureSpace(4);
-            final double[] _curves = curves;
-            int e = end;
-            _curves[e++] = x1;    _curves[e++] = y1;
-            _curves[e++] = x0;    _curves[e++] = y0;
-            end = e;
-        }
-
-        void pushLine(double x, double y) {
-            ensureSpace(2);
-            curveTypes[numCurves++] = TYPE_LINETO;
-            curves[end++] = x;    curves[end++] = y;
-        }
-
-        void popAll(DPathConsumer2D io) {
-            if (DO_STATS) {
-                // update used marks:
-                if (numCurves > curveTypesUseMark) {
-                    curveTypesUseMark = numCurves;
-                }
-                if (end > curvesUseMark) {
-                    curvesUseMark = end;
-                }
-            }
-            final byte[]  _curveTypes = curveTypes;
-            final double[] _curves = curves;
-            int nc = numCurves;
-            int e  = end;
-
-            while (nc != 0) {
-                switch(_curveTypes[--nc]) {
-                case TYPE_LINETO:
-                    e -= 2;
-                    io.lineTo(_curves[e], _curves[e+1]);
-                    continue;
-                    e -= 4;
-                              _curves[e+2], _curves[e+3]);
-                    continue;
-                case TYPE_CUBICTO:
-                    e -= 6;
-                    io.curveTo(_curves[e+0], _curves[e+1],
-                               _curves[e+2], _curves[e+3],
-                               _curves[e+4], _curves[e+5]);
-                    continue;
-                default:
-                }
-            }
-            numCurves = 0;
-            end = 0;
-        }
-
-        @Override
-        public String toString() {
-            String ret = "";
-            int nc = numCurves;
-            int last = end;
-            int len;
-            while (nc != 0) {
-                switch(curveTypes[--nc]) {
-                case TYPE_LINETO:
-                    len = 2;
-                    ret += "line: ";
-                    break;
-                    len = 4;
-                    break;
-                case TYPE_CUBICTO:
-                    len = 6;
-                    ret += "cubic: ";
-                    break;
-                default:
-                    len = 0;
-                }
-                last -= len;
-                ret += Arrays.toString(Arrays.copyOfRange(curves, last, last+len))
-                                       + "\n";
-            }
-            return ret;
-        }
-    }
}
```
< prev index next >