jdk/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java
changeset 47126 188ef162f019
parent 40421 d5ee65e2b0fb
equal deleted inserted replaced
45093:c42dc7b58b4d 47126:188ef162f019
     1 /*
     1 /*
     2  * Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
     2  * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Oracle designates this
     7  * published by the Free Software Foundation.  Oracle designates this
    37  * <p> Issues: in J2Se, a zero length dash segment as drawn as a very
    37  * <p> Issues: in J2Se, a zero length dash segment as drawn as a very
    38  * short dash, whereas Pisces does not draw anything.  The PostScript
    38  * short dash, whereas Pisces does not draw anything.  The PostScript
    39  * semantics are unclear.
    39  * semantics are unclear.
    40  *
    40  *
    41  */
    41  */
    42 final class Dasher implements sun.awt.geom.PathConsumer2D, MarlinConst {
    42 final class Dasher implements PathConsumer2D, MarlinConst {
    43 
    43 
    44     static final int REC_LIMIT = 4;
    44     static final int REC_LIMIT = 4;
    45     static final float ERR = 0.01f;
    45     static final float ERR = 0.01f;
    46     static final float MIN_T_INC = 1f / (1 << REC_LIMIT);
    46     static final float MIN_T_INC = 1.0f / (1 << REC_LIMIT);
       
    47 
       
    48     // More than 24 bits of mantissa means we can no longer accurately
       
    49     // measure the number of times cycled through the dash array so we
       
    50     // punt and override the phase to just be 0 past that point.
       
    51     static final float MAX_CYCLES = 16000000.0f;
    47 
    52 
    48     private PathConsumer2D out;
    53     private PathConsumer2D out;
    49     private float[] dash;
    54     private float[] dash;
    50     private int dashLen;
    55     private int dashLen;
    51     private float startPhase;
    56     private float startPhase;
   104      * @return this instance
   109      * @return this instance
   105      */
   110      */
   106     Dasher init(final PathConsumer2D out, float[] dash, int dashLen,
   111     Dasher init(final PathConsumer2D out, float[] dash, int dashLen,
   107                 float phase, boolean recycleDashes)
   112                 float phase, boolean recycleDashes)
   108     {
   113     {
   109         if (phase < 0f) {
       
   110             throw new IllegalArgumentException("phase < 0 !");
       
   111         }
       
   112         this.out = out;
   114         this.out = out;
   113 
   115 
   114         // Normalize so 0 <= phase < dash[0]
   116         // Normalize so 0 <= phase < dash[0]
   115         int idx = 0;
   117         int sidx = 0;
   116         dashOn = true;
   118         dashOn = true;
   117         float d;
   119         float sum = 0.0f;
   118         while (phase >= (d = dash[idx])) {
   120         for (float d : dash) {
   119             phase -= d;
   121             sum += d;
   120             idx = (idx + 1) % dashLen;
   122         }
   121             dashOn = !dashOn;
   123         float cycles = phase / sum;
       
   124         if (phase < 0.0f) {
       
   125             if (-cycles >= MAX_CYCLES) {
       
   126                 phase = 0.0f;
       
   127             } else {
       
   128                 int fullcycles = FloatMath.floor_int(-cycles);
       
   129                 if ((fullcycles & dash.length & 1) != 0) {
       
   130                     dashOn = !dashOn;
       
   131                 }
       
   132                 phase += fullcycles * sum;
       
   133                 while (phase < 0.0f) {
       
   134                     if (--sidx < 0) {
       
   135                         sidx = dash.length - 1;
       
   136                     }
       
   137                     phase += dash[sidx];
       
   138                     dashOn = !dashOn;
       
   139                 }
       
   140             }
       
   141         } else if (phase > 0) {
       
   142             if (cycles >= MAX_CYCLES) {
       
   143                 phase = 0.0f;
       
   144             } else {
       
   145                 int fullcycles = FloatMath.floor_int(cycles);
       
   146                 if ((fullcycles & dash.length & 1) != 0) {
       
   147                     dashOn = !dashOn;
       
   148                 }
       
   149                 phase -= fullcycles * sum;
       
   150                 float d;
       
   151                 while (phase >= (d = dash[sidx])) {
       
   152                     phase -= d;
       
   153                     sidx = (sidx + 1) % dash.length;
       
   154                     dashOn = !dashOn;
       
   155                 }
       
   156             }
   122         }
   157         }
   123 
   158 
   124         this.dash = dash;
   159         this.dash = dash;
   125         this.dashLen = dashLen;
   160         this.dashLen = dashLen;
   126         this.startPhase = this.phase = phase;
   161         this.startPhase = this.phase = phase;
   127         this.startDashOn = dashOn;
   162         this.startDashOn = dashOn;
   128         this.startIdx = idx;
   163         this.startIdx = sidx;
   129         this.starting = true;
   164         this.starting = true;
   130         needsMoveTo = false;
   165         needsMoveTo = false;
   131         firstSegidx = 0;
   166         firstSegidx = 0;
   132 
   167 
   133         this.recycleDashes = recycleDashes;
   168         this.recycleDashes = recycleDashes;
   140      * clean up before reusing this instance
   175      * clean up before reusing this instance
   141      */
   176      */
   142     void dispose() {
   177     void dispose() {
   143         if (DO_CLEAN_DIRTY) {
   178         if (DO_CLEAN_DIRTY) {
   144             // Force zero-fill dirty arrays:
   179             // Force zero-fill dirty arrays:
   145             Arrays.fill(curCurvepts, 0f);
   180             Arrays.fill(curCurvepts, 0.0f);
   146         }
   181         }
   147         // Return arrays:
   182         // Return arrays:
   148         if (recycleDashes) {
   183         if (recycleDashes) {
   149             dash = dashes_ref.putArray(dash);
   184             dash = dashes_ref.putArray(dash);
   150         }
   185         }
   151         firstSegmentsBuffer = firstSegmentsBuffer_ref.putArray(firstSegmentsBuffer);
   186         firstSegmentsBuffer = firstSegmentsBuffer_ref.putArray(firstSegmentsBuffer);
       
   187     }
       
   188 
       
   189     float[] copyDashArray(final float[] dashes) {
       
   190         final int len = dashes.length;
       
   191         final float[] newDashes;
       
   192         if (len <= MarlinConst.INITIAL_ARRAY) {
       
   193             newDashes = dashes_ref.initial;
       
   194         } else {
       
   195             if (DO_STATS) {
       
   196                 rdrCtx.stats.stat_array_dasher_dasher.add(len);
       
   197             }
       
   198             newDashes = dashes_ref.getArray(len);
       
   199         }
       
   200         System.arraycopy(dashes, 0, newDashes, 0, len);
       
   201         return newDashes;
   152     }
   202     }
   153 
   203 
   154     @Override
   204     @Override
   155     public void moveTo(float x0, float y0) {
   205     public void moveTo(float x0, float y0) {
   156         if (firstSegidx > 0) {
   206         if (firstSegidx > 0) {
   200     // buffer below.
   250     // buffer below.
   201     private float[] firstSegmentsBuffer; // dynamic array
   251     private float[] firstSegmentsBuffer; // dynamic array
   202     private int firstSegidx;
   252     private int firstSegidx;
   203 
   253 
   204     // precondition: pts must be in relative coordinates (relative to x0,y0)
   254     // precondition: pts must be in relative coordinates (relative to x0,y0)
   205     // fullCurve is true iff the curve in pts has not been split.
       
   206     private void goTo(float[] pts, int off, final int type) {
   255     private void goTo(float[] pts, int off, final int type) {
   207         float x = pts[off + type - 4];
   256         float x = pts[off + type - 4];
   208         float y = pts[off + type - 3];
   257         float y = pts[off + type - 3];
   209         if (dashOn) {
   258         if (dashOn) {
   210             if (starting) {
   259             if (starting) {
   211                 int len = type - 2 + 1;
   260                 int len = type - 1; // - 2 + 1
   212                 int segIdx = firstSegidx;
   261                 int segIdx = firstSegidx;
   213                 float[] buf = firstSegmentsBuffer;
   262                 float[] buf = firstSegmentsBuffer;
   214                 if (segIdx + len  > buf.length) {
   263                 if (segIdx + len  > buf.length) {
   215                     if (DO_STATS) {
   264                     if (DO_STATS) {
   216                         rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer
   265                         rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer
   245     public void lineTo(float x1, float y1) {
   294     public void lineTo(float x1, float y1) {
   246         float dx = x1 - x0;
   295         float dx = x1 - x0;
   247         float dy = y1 - y0;
   296         float dy = y1 - y0;
   248 
   297 
   249         float len = dx*dx + dy*dy;
   298         float len = dx*dx + dy*dy;
   250         if (len == 0f) {
   299         if (len == 0.0f) {
   251             return;
   300             return;
   252         }
   301         }
   253         len = (float) Math.sqrt(len);
   302         len = (float) Math.sqrt(len);
   254 
   303 
   255         // The scaling factors needed to get the dx and dy of the
   304         // The scaling factors needed to get the dx and dy of the
   273 
   322 
   274                 // Advance phase within current dash segment
   323                 // Advance phase within current dash segment
   275                 phase += len;
   324                 phase += len;
   276                 // TODO: compare float values using epsilon:
   325                 // TODO: compare float values using epsilon:
   277                 if (len == leftInThisDashSegment) {
   326                 if (len == leftInThisDashSegment) {
   278                     phase = 0f;
   327                     phase = 0.0f;
   279                     idx = (idx + 1) % dashLen;
   328                     idx = (idx + 1) % dashLen;
   280                     dashOn = !dashOn;
   329                     dashOn = !dashOn;
   281                 }
   330                 }
   282                 return;
   331                 return;
   283             }
   332             }
   284 
   333 
   285             dashdx = _dash[idx] * cx;
   334             dashdx = _dash[idx] * cx;
   286             dashdy = _dash[idx] * cy;
   335             dashdy = _dash[idx] * cy;
   287 
   336 
   288             if (phase == 0f) {
   337             if (phase == 0.0f) {
   289                 _curCurvepts[0] = x0 + dashdx;
   338                 _curCurvepts[0] = x0 + dashdx;
   290                 _curCurvepts[1] = y0 + dashdy;
   339                 _curCurvepts[1] = y0 + dashdy;
   291             } else {
   340             } else {
   292                 p = leftInThisDashSegment / _dash[idx];
   341                 p = leftInThisDashSegment / _dash[idx];
   293                 _curCurvepts[0] = x0 + p * dashdx;
   342                 _curCurvepts[0] = x0 + p * dashdx;
   298 
   347 
   299             len -= leftInThisDashSegment;
   348             len -= leftInThisDashSegment;
   300             // Advance to next dash segment
   349             // Advance to next dash segment
   301             idx = (idx + 1) % dashLen;
   350             idx = (idx + 1) % dashLen;
   302             dashOn = !dashOn;
   351             dashOn = !dashOn;
   303             phase = 0f;
   352             phase = 0.0f;
   304         }
   353         }
   305     }
   354     }
   306 
   355 
   307     // shared instance in Dasher
   356     // shared instance in Dasher
   308     private final LengthIterator li = new LengthIterator();
   357     private final LengthIterator li = new LengthIterator();
   315         }
   364         }
   316         li.initializeIterationOnCurve(curCurvepts, type);
   365         li.initializeIterationOnCurve(curCurvepts, type);
   317 
   366 
   318         // initially the current curve is at curCurvepts[0...type]
   367         // initially the current curve is at curCurvepts[0...type]
   319         int curCurveoff = 0;
   368         int curCurveoff = 0;
   320         float lastSplitT = 0f;
   369         float lastSplitT = 0.0f;
   321         float t;
   370         float t;
   322         float leftInThisDashSegment = dash[idx] - phase;
   371         float leftInThisDashSegment = dash[idx] - phase;
   323 
   372 
   324         while ((t = li.next(leftInThisDashSegment)) < 1f) {
   373         while ((t = li.next(leftInThisDashSegment)) < 1.0f) {
   325             if (t != 0f) {
   374             if (t != 0.0f) {
   326                 Helpers.subdivideAt((t - lastSplitT) / (1f - lastSplitT),
   375                 Helpers.subdivideAt((t - lastSplitT) / (1.0f - lastSplitT),
   327                                     curCurvepts, curCurveoff,
   376                                     curCurvepts, curCurveoff,
   328                                     curCurvepts, 0,
   377                                     curCurvepts, 0,
   329                                     curCurvepts, type, type);
   378                                     curCurvepts, type, type);
   330                 lastSplitT = t;
   379                 lastSplitT = t;
   331                 goTo(curCurvepts, 2, type);
   380                 goTo(curCurvepts, 2, type);
   332                 curCurveoff = type;
   381                 curCurveoff = type;
   333             }
   382             }
   334             // Advance to next dash segment
   383             // Advance to next dash segment
   335             idx = (idx + 1) % dashLen;
   384             idx = (idx + 1) % dashLen;
   336             dashOn = !dashOn;
   385             dashOn = !dashOn;
   337             phase = 0f;
   386             phase = 0.0f;
   338             leftInThisDashSegment = dash[idx];
   387             leftInThisDashSegment = dash[idx];
   339         }
   388         }
   340         goTo(curCurvepts, curCurveoff+2, type);
   389         goTo(curCurvepts, curCurveoff+2, type);
   341         phase += li.lastSegLen();
   390         phase += li.lastSegLen();
   342         if (phase >= dash[idx]) {
   391         if (phase >= dash[idx]) {
   343             phase = 0f;
   392             phase = 0.0f;
   344             idx = (idx + 1) % dashLen;
   393             idx = (idx + 1) % dashLen;
   345             dashOn = !dashOn;
   394             dashOn = !dashOn;
   346         }
   395         }
   347         // reset LengthIterator:
   396         // reset LengthIterator:
   348         li.reset();
   397         li.reset();
   393         private int recLevel;
   442         private int recLevel;
   394         private boolean done;
   443         private boolean done;
   395 
   444 
   396         // the lengths of the lines of the control polygon. Only its first
   445         // the lengths of the lines of the control polygon. Only its first
   397         // curveType/2 - 1 elements are valid. This is an optimization. See
   446         // curveType/2 - 1 elements are valid. This is an optimization. See
   398         // next(float) for more detail.
   447         // next() for more detail.
   399         private final float[] curLeafCtrlPolyLengths = new float[3];
   448         private final float[] curLeafCtrlPolyLengths = new float[3];
   400 
   449 
   401         LengthIterator() {
   450         LengthIterator() {
   402             this.recCurveStack = new float[REC_LIMIT + 1][8];
   451             this.recCurveStack = new float[REC_LIMIT + 1][8];
   403             this.sides = new Side[REC_LIMIT];
   452             this.sides = new Side[REC_LIMIT];
   418             // keep data dirty
   467             // keep data dirty
   419             // as it appears not useful to reset data:
   468             // as it appears not useful to reset data:
   420             if (DO_CLEAN_DIRTY) {
   469             if (DO_CLEAN_DIRTY) {
   421                 final int recLimit = recCurveStack.length - 1;
   470                 final int recLimit = recCurveStack.length - 1;
   422                 for (int i = recLimit; i >= 0; i--) {
   471                 for (int i = recLimit; i >= 0; i--) {
   423                     Arrays.fill(recCurveStack[i], 0f);
   472                     Arrays.fill(recCurveStack[i], 0.0f);
   424                 }
   473                 }
   425                 Arrays.fill(sides, Side.LEFT);
   474                 Arrays.fill(sides, Side.LEFT);
   426                 Arrays.fill(curLeafCtrlPolyLengths, 0f);
   475                 Arrays.fill(curLeafCtrlPolyLengths, 0.0f);
   427                 Arrays.fill(nextRoots, 0f);
   476                 Arrays.fill(nextRoots, 0.0f);
   428                 Arrays.fill(flatLeafCoefCache, 0f);
   477                 Arrays.fill(flatLeafCoefCache, 0.0f);
   429                 flatLeafCoefCache[2] = -1f;
   478                 flatLeafCoefCache[2] = -1.0f;
   430             }
   479             }
   431         }
   480         }
   432 
   481 
   433         void initializeIterationOnCurve(float[] pts, int type) {
   482         void initializeIterationOnCurve(float[] pts, int type) {
   434             // optimize arraycopy (8 values faster than 6 = type):
   483             // optimize arraycopy (8 values faster than 6 = type):
   435             System.arraycopy(pts, 0, recCurveStack[0], 0, 8);
   484             System.arraycopy(pts, 0, recCurveStack[0], 0, 8);
   436             this.curveType = type;
   485             this.curveType = type;
   437             this.recLevel = 0;
   486             this.recLevel = 0;
   438             this.lastT = 0f;
   487             this.lastT = 0.0f;
   439             this.lenAtLastT = 0f;
   488             this.lenAtLastT = 0.0f;
   440             this.nextT = 0f;
   489             this.nextT = 0.0f;
   441             this.lenAtNextT = 0f;
   490             this.lenAtNextT = 0.0f;
   442             goLeft(); // initializes nextT and lenAtNextT properly
   491             goLeft(); // initializes nextT and lenAtNextT properly
   443             this.lenAtLastSplit = 0f;
   492             this.lenAtLastSplit = 0.0f;
   444             if (recLevel > 0) {
   493             if (recLevel > 0) {
   445                 this.sides[0] = Side.LEFT;
   494                 this.sides[0] = Side.LEFT;
   446                 this.done = false;
   495                 this.done = false;
   447             } else {
   496             } else {
   448                 // the root of the tree is a leaf so we're done.
   497                 // the root of the tree is a leaf so we're done.
   449                 this.sides[0] = Side.RIGHT;
   498                 this.sides[0] = Side.RIGHT;
   450                 this.done = true;
   499                 this.done = true;
   451             }
   500             }
   452             this.lastSegLen = 0f;
   501             this.lastSegLen = 0.0f;
   453         }
   502         }
   454 
   503 
   455         // 0 == false, 1 == true, -1 == invalid cached value.
   504         // 0 == false, 1 == true, -1 == invalid cached value.
   456         private int cachedHaveLowAcceleration = -1;
   505         private int cachedHaveLowAcceleration = -1;
   457 
   506 
   460                 final float len1 = curLeafCtrlPolyLengths[0];
   509                 final float len1 = curLeafCtrlPolyLengths[0];
   461                 final float len2 = curLeafCtrlPolyLengths[1];
   510                 final float len2 = curLeafCtrlPolyLengths[1];
   462                 // the test below is equivalent to !within(len1/len2, 1, err).
   511                 // the test below is equivalent to !within(len1/len2, 1, err).
   463                 // It is using a multiplication instead of a division, so it
   512                 // It is using a multiplication instead of a division, so it
   464                 // should be a bit faster.
   513                 // should be a bit faster.
   465                 if (!Helpers.within(len1, len2, err*len2)) {
   514                 if (!Helpers.within(len1, len2, err * len2)) {
   466                     cachedHaveLowAcceleration = 0;
   515                     cachedHaveLowAcceleration = 0;
   467                     return false;
   516                     return false;
   468                 }
   517                 }
   469                 if (curveType == 8) {
   518                 if (curveType == 8) {
   470                     final float len3 = curLeafCtrlPolyLengths[2];
   519                     final float len3 = curLeafCtrlPolyLengths[2];
   491 
   540 
   492         // caches the coefficients of the current leaf in its flattened
   541         // caches the coefficients of the current leaf in its flattened
   493         // form (see inside next() for what that means). The cache is
   542         // form (see inside next() for what that means). The cache is
   494         // invalid when it's third element is negative, since in any
   543         // invalid when it's third element is negative, since in any
   495         // valid flattened curve, this would be >= 0.
   544         // valid flattened curve, this would be >= 0.
   496         private final float[] flatLeafCoefCache = new float[]{0f, 0f, -1f, 0f};
   545         private final float[] flatLeafCoefCache = new float[]{0.0f, 0.0f, -1.0f, 0.0f};
   497 
   546 
   498         // returns the t value where the remaining curve should be split in
   547         // returns the t value where the remaining curve should be split in
   499         // order for the left subdivided curve to have length len. If len
   548         // order for the left subdivided curve to have length len. If len
   500         // is >= than the length of the uniterated curve, it returns 1.
   549         // is >= than the length of the uniterated curve, it returns 1.
   501         float next(final float len) {
   550         float next(final float len) {
   502             final float targetLength = lenAtLastSplit + len;
   551             final float targetLength = lenAtLastSplit + len;
   503             while (lenAtNextT < targetLength) {
   552             while (lenAtNextT < targetLength) {
   504                 if (done) {
   553                 if (done) {
   505                     lastSegLen = lenAtNextT - lenAtLastSplit;
   554                     lastSegLen = lenAtNextT - lenAtLastSplit;
   506                     return 1f;
   555                     return 1.0f;
   507                 }
   556                 }
   508                 goToNextLeaf();
   557                 goToNextLeaf();
   509             }
   558             }
   510             lenAtLastSplit = targetLength;
   559             lenAtLastSplit = targetLength;
   511             final float leaflen = lenAtNextT - lenAtLastT;
   560             final float leaflen = lenAtNextT - lenAtLastT;
   518                 // left with a, b, c which define a 1D Bezier curve. We then
   567                 // left with a, b, c which define a 1D Bezier curve. We then
   519                 // solve this to get the parameter of the original leaf that
   568                 // solve this to get the parameter of the original leaf that
   520                 // gives us the desired length.
   569                 // gives us the desired length.
   521                 final float[] _flatLeafCoefCache = flatLeafCoefCache;
   570                 final float[] _flatLeafCoefCache = flatLeafCoefCache;
   522 
   571 
   523                 if (_flatLeafCoefCache[2] < 0) {
   572                 if (_flatLeafCoefCache[2] < 0.0f) {
   524                     float x = 0f + curLeafCtrlPolyLengths[0],
   573                     float x =     curLeafCtrlPolyLengths[0],
   525                           y = x  + curLeafCtrlPolyLengths[1];
   574                           y = x + curLeafCtrlPolyLengths[1];
   526                     if (curveType == 8) {
   575                     if (curveType == 8) {
   527                         float z = y + curLeafCtrlPolyLengths[2];
   576                         float z = y + curLeafCtrlPolyLengths[2];
   528                         _flatLeafCoefCache[0] = 3f * (x - y) + z;
   577                         _flatLeafCoefCache[0] = 3.0f * (x - y) + z;
   529                         _flatLeafCoefCache[1] = 3f * (y - 2f * x);
   578                         _flatLeafCoefCache[1] = 3.0f * (y - 2.0f * x);
   530                         _flatLeafCoefCache[2] = 3f * x;
   579                         _flatLeafCoefCache[2] = 3.0f * x;
   531                         _flatLeafCoefCache[3] = -z;
   580                         _flatLeafCoefCache[3] = -z;
   532                     } else if (curveType == 6) {
   581                     } else if (curveType == 6) {
   533                         _flatLeafCoefCache[0] = 0f;
   582                         _flatLeafCoefCache[0] = 0.0f;
   534                         _flatLeafCoefCache[1] = y - 2f * x;
   583                         _flatLeafCoefCache[1] = y - 2.0f * x;
   535                         _flatLeafCoefCache[2] = 2f * x;
   584                         _flatLeafCoefCache[2] = 2.0f * x;
   536                         _flatLeafCoefCache[3] = -y;
   585                         _flatLeafCoefCache[3] = -y;
   537                     }
   586                     }
   538                 }
   587                 }
   539                 float a = _flatLeafCoefCache[0];
   588                 float a = _flatLeafCoefCache[0];
   540                 float b = _flatLeafCoefCache[1];
   589                 float b = _flatLeafCoefCache[1];
   542                 float d = t * _flatLeafCoefCache[3];
   591                 float d = t * _flatLeafCoefCache[3];
   543 
   592 
   544                 // we use cubicRootsInAB here, because we want only roots in 0, 1,
   593                 // we use cubicRootsInAB here, because we want only roots in 0, 1,
   545                 // and our quadratic root finder doesn't filter, so it's just a
   594                 // and our quadratic root finder doesn't filter, so it's just a
   546                 // matter of convenience.
   595                 // matter of convenience.
   547                 int n = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0, 1);
   596                 int n = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0.0f, 1.0f);
   548                 if (n == 1 && !Float.isNaN(nextRoots[0])) {
   597                 if (n == 1 && !Float.isNaN(nextRoots[0])) {
   549                     t = nextRoots[0];
   598                     t = nextRoots[0];
   550                 }
   599                 }
   551             }
   600             }
   552             // t is relative to the current leaf, so we must make it a valid parameter
   601             // t is relative to the current leaf, so we must make it a valid parameter
   553             // of the original curve.
   602             // of the original curve.
   554             t = t * (nextT - lastT) + lastT;
   603             t = t * (nextT - lastT) + lastT;
   555             if (t >= 1f) {
   604             if (t >= 1.0f) {
   556                 t = 1f;
   605                 t = 1.0f;
   557                 done = true;
   606                 done = true;
   558             }
   607             }
   559             // even if done = true, if we're here, that means targetLength
   608             // even if done = true, if we're here, that means targetLength
   560             // is equal to, or very, very close to the total length of the
   609             // is equal to, or very, very close to the total length of the
   561             // curve, so lastSegLen won't be too high. In cases where len
   610             // curve, so lastSegLen won't be too high. In cases where len
   598         }
   647         }
   599 
   648 
   600         // go to the leftmost node from the current node. Return its length.
   649         // go to the leftmost node from the current node. Return its length.
   601         private void goLeft() {
   650         private void goLeft() {
   602             float len = onLeaf();
   651             float len = onLeaf();
   603             if (len >= 0f) {
   652             if (len >= 0.0f) {
   604                 lastT = nextT;
   653                 lastT = nextT;
   605                 lenAtLastT = lenAtNextT;
   654                 lenAtLastT = lenAtNextT;
   606                 nextT += (1 << (REC_LIMIT - recLevel)) * MIN_T_INC;
   655                 nextT += (1 << (REC_LIMIT - recLevel)) * MIN_T_INC;
   607                 lenAtNextT += len;
   656                 lenAtNextT += len;
   608                 // invalidate caches
   657                 // invalidate caches
   609                 flatLeafCoefCache[2] = -1f;
   658                 flatLeafCoefCache[2] = -1.0f;
   610                 cachedHaveLowAcceleration = -1;
   659                 cachedHaveLowAcceleration = -1;
   611             } else {
   660             } else {
   612                 Helpers.subdivide(recCurveStack[recLevel], 0,
   661                 Helpers.subdivide(recCurveStack[recLevel], 0,
   613                                   recCurveStack[recLevel+1], 0,
   662                                   recCurveStack[recLevel+1], 0,
   614                                   recCurveStack[recLevel], 0, curveType);
   663                                   recCurveStack[recLevel], 0, curveType);
   620 
   669 
   621         // this is a bit of a hack. It returns -1 if we're not on a leaf, and
   670         // this is a bit of a hack. It returns -1 if we're not on a leaf, and
   622         // the length of the leaf if we are on a leaf.
   671         // the length of the leaf if we are on a leaf.
   623         private float onLeaf() {
   672         private float onLeaf() {
   624             float[] curve = recCurveStack[recLevel];
   673             float[] curve = recCurveStack[recLevel];
   625             float polyLen = 0f;
   674             float polyLen = 0.0f;
   626 
   675 
   627             float x0 = curve[0], y0 = curve[1];
   676             float x0 = curve[0], y0 = curve[1];
   628             for (int i = 2; i < curveType; i += 2) {
   677             for (int i = 2; i < curveType; i += 2) {
   629                 final float x1 = curve[i], y1 = curve[i+1];
   678                 final float x1 = curve[i], y1 = curve[i+1];
   630                 final float len = Helpers.linelen(x0, y0, x1, y1);
   679                 final float len = Helpers.linelen(x0, y0, x1, y1);
   636 
   685 
   637             final float lineLen = Helpers.linelen(curve[0], curve[1],
   686             final float lineLen = Helpers.linelen(curve[0], curve[1],
   638                                                   curve[curveType-2],
   687                                                   curve[curveType-2],
   639                                                   curve[curveType-1]);
   688                                                   curve[curveType-1]);
   640             if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) {
   689             if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) {
   641                 return (polyLen + lineLen) / 2f;
   690                 return (polyLen + lineLen) / 2.0f;
   642             }
   691             }
   643             return -1f;
   692             return -1.0f;
   644         }
   693         }
   645     }
   694     }
   646 
   695 
   647     @Override
   696     @Override
   648     public void curveTo(float x1, float y1,
   697     public void curveTo(float x1, float y1,