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 |
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; |
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); |