Path: blob/master/src/java.desktop/share/classes/sun/java2d/marlin/Dasher.java
41159 views
/*1* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.java2d.marlin;2627import java.util.Arrays;28import sun.java2d.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer;29import sun.java2d.marlin.TransformingPathConsumer2D.CurveClipSplitter;3031/**32* The <code>Dasher</code> class takes a series of linear commands33* (<code>moveTo</code>, <code>lineTo</code>, <code>close</code> and34* <code>end</code>) and breaks them into smaller segments according to a35* dash pattern array and a starting dash phase.36*37* <p> Issues: in J2Se, a zero length dash segment as drawn as a very38* short dash, whereas Pisces does not draw anything. The PostScript39* semantics are unclear.40*41*/42final class Dasher implements DPathConsumer2D, MarlinConst {4344/* huge circle with radius ~ 2E9 only needs 12 subdivision levels */45static final int REC_LIMIT = 16;46static final double CURVE_LEN_ERR = MarlinProperties.getCurveLengthError(); // 0.01 initial47static final double MIN_T_INC = 1.0d / (1 << REC_LIMIT);4849static final double EPS = 1e-6d;5051// More than 24 bits of mantissa means we can no longer accurately52// measure the number of times cycled through the dash array so we53// punt and override the phase to just be 0 past that point.54static final double MAX_CYCLES = 16000000.0d;5556private DPathConsumer2D out;57private double[] dash;58private int dashLen;59private double startPhase;60private boolean startDashOn;61private int startIdx;6263private boolean starting;64private boolean needsMoveTo;6566private int idx;67private boolean dashOn;68private double phase;6970// The starting point of the path71private double sx0, sy0;72// the current point73private double cx0, cy0;7475// temporary storage for the current curve76private final double[] curCurvepts;7778// per-thread renderer context79final RendererContext rdrCtx;8081// flag to recycle dash array copy82boolean recycleDashes;8384// We don't emit the first dash right away. If we did, caps would be85// drawn on it, but we need joins to be drawn if there's a closePath()86// So, we store the path elements that make up the first dash in the87// buffer below.88private double[] firstSegmentsBuffer; // dynamic array89private int firstSegidx;9091// dashes ref (dirty)92final DoubleArrayCache.Reference dashes_ref;93// firstSegmentsBuffer ref (dirty)94final DoubleArrayCache.Reference firstSegmentsBuffer_ref;9596// Bounds of the drawing region, at pixel precision.97private double[] clipRect;9899// the outcode of the current point100private int cOutCode = 0;101102private boolean subdivide = DO_CLIP_SUBDIVIDER;103104private final LengthIterator li = new LengthIterator();105106private final CurveClipSplitter curveSplitter;107108private double cycleLen;109private boolean outside;110private double totalSkipLen;111112/**113* Constructs a <code>Dasher</code>.114* @param rdrCtx per-thread renderer context115*/116Dasher(final RendererContext rdrCtx) {117this.rdrCtx = rdrCtx;118119dashes_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_ARRAY); // 1K120121firstSegmentsBuffer_ref = rdrCtx.newDirtyDoubleArrayRef(INITIAL_ARRAY); // 1K122firstSegmentsBuffer = firstSegmentsBuffer_ref.initial;123124// we need curCurvepts to be able to contain 2 curves because when125// dashing curves, we need to subdivide it126curCurvepts = new double[8 * 2];127128this.curveSplitter = rdrCtx.curveClipSplitter;129}130131/**132* Initialize the <code>Dasher</code>.133*134* @param out an output <code>DPathConsumer2D</code>.135* @param dash an array of <code>double</code>s containing the dash pattern136* @param dashLen length of the given dash array137* @param phase a <code>double</code> containing the dash phase138* @param recycleDashes true to indicate to recycle the given dash array139* @return this instance140*/141Dasher init(final DPathConsumer2D out, final double[] dash, final int dashLen,142double phase, final boolean recycleDashes)143{144this.out = out;145146// Normalize so 0 <= phase < dash[0]147int sidx = 0;148dashOn = true;149150// note: BasicStroke constructor checks dash elements and sum > 0151double sum = 0.0d;152for (int i = 0; i < dashLen; i++) {153sum += dash[i];154}155this.cycleLen = sum;156157double cycles = phase / sum;158if (phase < 0.0d) {159if (-cycles >= MAX_CYCLES) {160phase = 0.0d;161} else {162int fullcycles = FloatMath.floor_int(-cycles);163if ((fullcycles & dashLen & 1) != 0) {164dashOn = !dashOn;165}166phase += fullcycles * sum;167while (phase < 0.0d) {168if (--sidx < 0) {169sidx = dashLen - 1;170}171phase += dash[sidx];172dashOn = !dashOn;173}174}175} else if (phase > 0.0d) {176if (cycles >= MAX_CYCLES) {177phase = 0.0d;178} else {179int fullcycles = FloatMath.floor_int(cycles);180if ((fullcycles & dashLen & 1) != 0) {181dashOn = !dashOn;182}183phase -= fullcycles * sum;184double d;185while (phase >= (d = dash[sidx])) {186phase -= d;187sidx = (sidx + 1) % dashLen;188dashOn = !dashOn;189}190}191}192193this.dash = dash;194this.dashLen = dashLen;195this.phase = phase;196this.startPhase = phase;197this.startDashOn = dashOn;198this.startIdx = sidx;199this.starting = true;200this.needsMoveTo = false;201this.firstSegidx = 0;202203this.recycleDashes = recycleDashes;204205if (rdrCtx.doClip) {206this.clipRect = rdrCtx.clipRect;207} else {208this.clipRect = null;209this.cOutCode = 0;210}211return this; // fluent API212}213214/**215* Disposes this dasher:216* clean up before reusing this instance217*/218void dispose() {219if (DO_CLEAN_DIRTY) {220// Force zero-fill dirty arrays:221Arrays.fill(curCurvepts, 0.0d);222}223// Return arrays:224if (recycleDashes) {225dash = dashes_ref.putArray(dash);226}227firstSegmentsBuffer = firstSegmentsBuffer_ref.putArray(firstSegmentsBuffer);228}229230double[] copyDashArray(final float[] dashes) {231final int len = dashes.length;232final double[] newDashes;233if (len <= MarlinConst.INITIAL_ARRAY) {234newDashes = dashes_ref.initial;235} else {236if (DO_STATS) {237rdrCtx.stats.stat_array_dasher_dasher.add(len);238}239newDashes = dashes_ref.getArray(len);240}241for (int i = 0; i < len; i++) { newDashes[i] = dashes[i]; }242return newDashes;243}244245@Override246public void moveTo(final double x0, final double y0) {247if (firstSegidx != 0) {248out.moveTo(sx0, sy0);249emitFirstSegments();250}251this.needsMoveTo = true;252this.idx = startIdx;253this.dashOn = this.startDashOn;254this.phase = this.startPhase;255this.cx0 = x0;256this.cy0 = y0;257258// update starting point:259this.sx0 = x0;260this.sy0 = y0;261this.starting = true;262263if (clipRect != null) {264final int outcode = Helpers.outcode(x0, y0, clipRect);265this.cOutCode = outcode;266this.outside = false;267this.totalSkipLen = 0.0d;268}269}270271private void emitSeg(double[] buf, int off, int type) {272switch (type) {273case 4:274out.lineTo(buf[off], buf[off + 1]);275return;276case 8:277out.curveTo(buf[off ], buf[off + 1],278buf[off + 2], buf[off + 3],279buf[off + 4], buf[off + 5]);280return;281case 6:282out.quadTo(buf[off ], buf[off + 1],283buf[off + 2], buf[off + 3]);284return;285default:286}287}288289private void emitFirstSegments() {290final double[] fSegBuf = firstSegmentsBuffer;291292for (int i = 0, len = firstSegidx; i < len; ) {293int type = (int)fSegBuf[i];294emitSeg(fSegBuf, i + 1, type);295i += (type - 1);296}297firstSegidx = 0;298}299300// precondition: pts must be in relative coordinates (relative to x0,y0)301private void goTo(final double[] pts, final int off, final int type,302final boolean on)303{304final int index = off + type;305final double x = pts[index - 4];306final double y = pts[index - 3];307308if (on) {309if (starting) {310goTo_starting(pts, off, type);311} else {312if (needsMoveTo) {313needsMoveTo = false;314out.moveTo(cx0, cy0);315}316emitSeg(pts, off, type);317}318} else {319if (starting) {320// low probability test (hotspot)321starting = false;322}323needsMoveTo = true;324}325this.cx0 = x;326this.cy0 = y;327}328329private void goTo_starting(final double[] pts, final int off, final int type) {330int len = type - 1; // - 2 + 1331int segIdx = firstSegidx;332double[] buf = firstSegmentsBuffer;333334if (segIdx + len > buf.length) {335if (DO_STATS) {336rdrCtx.stats.stat_array_dasher_firstSegmentsBuffer337.add(segIdx + len);338}339firstSegmentsBuffer = buf340= firstSegmentsBuffer_ref.widenArray(buf, segIdx,341segIdx + len);342}343buf[segIdx++] = type;344len--;345// small arraycopy (2, 4 or 6) but with offset:346System.arraycopy(pts, off, buf, segIdx, len);347firstSegidx = segIdx + len;348}349350@Override351public void lineTo(final double x1, final double y1) {352final int outcode0 = this.cOutCode;353354if (clipRect != null) {355final int outcode1 = Helpers.outcode(x1, y1, clipRect);356357// Should clip358final int orCode = (outcode0 | outcode1);359360if (orCode != 0) {361final int sideCode = outcode0 & outcode1;362363// basic rejection criteria:364if (sideCode == 0) {365// overlap clip:366if (subdivide) {367// avoid reentrance368subdivide = false;369// subdivide curve => callback with subdivided parts:370boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1,371orCode, this);372// reentrance is done:373subdivide = true;374if (ret) {375return;376}377}378// already subdivided so render it379} else {380this.cOutCode = outcode1;381skipLineTo(x1, y1);382return;383}384}385386this.cOutCode = outcode1;387388if (this.outside) {389this.outside = false;390// Adjust current index, phase & dash:391skipLen();392}393}394_lineTo(x1, y1);395}396397private void _lineTo(final double x1, final double y1) {398final double dx = x1 - cx0;399final double dy = y1 - cy0;400401double len = dx * dx + dy * dy;402if (len == 0.0d) {403return;404}405len = Math.sqrt(len);406407// The scaling factors needed to get the dx and dy of the408// transformed dash segments.409final double cx = dx / len;410final double cy = dy / len;411412final double[] _curCurvepts = curCurvepts;413final double[] _dash = dash;414final int _dashLen = this.dashLen;415416int _idx = idx;417boolean _dashOn = dashOn;418double _phase = phase;419420double leftInThisDashSegment, rem;421422while (true) {423leftInThisDashSegment = _dash[_idx] - _phase;424rem = len - leftInThisDashSegment;425426if (rem <= EPS) {427_curCurvepts[0] = x1;428_curCurvepts[1] = y1;429430goTo(_curCurvepts, 0, 4, _dashOn);431432// Advance phase within current dash segment433_phase += len;434435// compare values using epsilon:436if (Math.abs(rem) <= EPS) {437_phase = 0.0d;438_idx = (_idx + 1) % _dashLen;439_dashOn = !_dashOn;440}441break;442}443444_curCurvepts[0] = cx0 + leftInThisDashSegment * cx;445_curCurvepts[1] = cy0 + leftInThisDashSegment * cy;446447goTo(_curCurvepts, 0, 4, _dashOn);448449len = rem;450// Advance to next dash segment451_idx = (_idx + 1) % _dashLen;452_dashOn = !_dashOn;453_phase = 0.0d;454}455// Save local state:456idx = _idx;457dashOn = _dashOn;458phase = _phase;459}460461private void skipLineTo(final double x1, final double y1) {462final double dx = x1 - cx0;463final double dy = y1 - cy0;464465double len = dx * dx + dy * dy;466if (len != 0.0d) {467len = Math.sqrt(len);468}469470// Accumulate skipped length:471this.outside = true;472this.totalSkipLen += len;473474// Fix initial move:475this.needsMoveTo = true;476this.starting = false;477478this.cx0 = x1;479this.cy0 = y1;480}481482public void skipLen() {483double len = this.totalSkipLen;484this.totalSkipLen = 0.0d;485486final double[] _dash = dash;487final int _dashLen = this.dashLen;488489int _idx = idx;490boolean _dashOn = dashOn;491double _phase = phase;492493// -2 to ensure having 2 iterations of the post-loop494// to compensate the remaining phase495final long fullcycles = (long)Math.floor(len / cycleLen) - 2L;496497if (fullcycles > 0L) {498len -= cycleLen * fullcycles;499500final long iterations = fullcycles * _dashLen;501_idx = (int) (iterations + _idx) % _dashLen;502_dashOn = (iterations + (_dashOn ? 1L : 0L) & 1L) == 1L;503}504505double leftInThisDashSegment, rem;506507while (true) {508leftInThisDashSegment = _dash[_idx] - _phase;509rem = len - leftInThisDashSegment;510511if (rem <= EPS) {512// Advance phase within current dash segment513_phase += len;514515// compare values using epsilon:516if (Math.abs(rem) <= EPS) {517_phase = 0.0d;518_idx = (_idx + 1) % _dashLen;519_dashOn = !_dashOn;520}521break;522}523524len = rem;525// Advance to next dash segment526_idx = (_idx + 1) % _dashLen;527_dashOn = !_dashOn;528_phase = 0.0d;529}530// Save local state:531idx = _idx;532dashOn = _dashOn;533phase = _phase;534}535536// preconditions: curCurvepts must be an array of length at least 2 * type,537// that contains the curve we want to dash in the first type elements538private void somethingTo(final int type) {539final double[] _curCurvepts = curCurvepts;540if (pointCurve(_curCurvepts, type)) {541return;542}543final LengthIterator _li = li;544final double[] _dash = dash;545final int _dashLen = this.dashLen;546547_li.initializeIterationOnCurve(_curCurvepts, type);548549int _idx = idx;550boolean _dashOn = dashOn;551double _phase = phase;552553// initially the current curve is at curCurvepts[0...type]554int curCurveoff = 0;555double prevT = 0.0d;556double t;557double leftInThisDashSegment = _dash[_idx] - _phase;558559while ((t = _li.next(leftInThisDashSegment)) < 1.0d) {560if (t != 0.0d) {561Helpers.subdivideAt((t - prevT) / (1.0d - prevT),562_curCurvepts, curCurveoff,563_curCurvepts, 0, type);564prevT = t;565goTo(_curCurvepts, 2, type, _dashOn);566curCurveoff = type;567}568// Advance to next dash segment569_idx = (_idx + 1) % _dashLen;570_dashOn = !_dashOn;571_phase = 0.0d;572leftInThisDashSegment = _dash[_idx];573}574575goTo(_curCurvepts, curCurveoff + 2, type, _dashOn);576577_phase += _li.lastSegLen();578579// compare values using epsilon:580if (_phase + EPS >= _dash[_idx]) {581_phase = 0.0d;582_idx = (_idx + 1) % _dashLen;583_dashOn = !_dashOn;584}585// Save local state:586idx = _idx;587dashOn = _dashOn;588phase = _phase;589590// reset LengthIterator:591_li.reset();592}593594private void skipSomethingTo(final int type) {595final double[] _curCurvepts = curCurvepts;596if (pointCurve(_curCurvepts, type)) {597return;598}599final LengthIterator _li = li;600601_li.initializeIterationOnCurve(_curCurvepts, type);602603// In contrary to somethingTo(),604// just estimate properly the curve length:605final double len = _li.totalLength();606607// Accumulate skipped length:608this.outside = true;609this.totalSkipLen += len;610611// Fix initial move:612this.needsMoveTo = true;613this.starting = false;614}615616private static boolean pointCurve(final double[] curve, final int type) {617for (int i = 2; i < type; i++) {618if (curve[i] != curve[i-2]) {619return false;620}621}622return true;623}624625// Objects of this class are used to iterate through curves. They return626// t values where the left side of the curve has a specified length.627// It does this by subdividing the input curve until a certain error628// condition has been met. A recursive subdivision procedure would629// return as many as 1<<limit curves, but this is an iterator and we630// don't need all the curves all at once, so what we carry out a631// lazy inorder traversal of the recursion tree (meaning we only move632// through the tree when we need the next subdivided curve). This saves633// us a lot of memory because at any one time we only need to store634// limit+1 curves - one for each level of the tree + 1.635// NOTE: the way we do things here is not enough to traverse a general636// tree; however, the trees we are interested in have the property that637// every non leaf node has exactly 2 children638static final class LengthIterator {639// Holds the curves at various levels of the recursion. The root640// (i.e. the original curve) is at recCurveStack[0] (but then it641// gets subdivided, the left half is put at 1, so most of the time642// only the right half of the original curve is at 0)643private final double[][] recCurveStack; // dirty644// sidesRight[i] indicates whether the node at level i+1 in the path from645// the root to the current leaf is a left or right child of its parent.646private final boolean[] sidesRight; // dirty647private int curveType;648// lastT and nextT delimit the current leaf.649private double nextT;650private double lenAtNextT;651private double lastT;652private double lenAtLastT;653private double lenAtLastSplit;654private double lastSegLen;655// the current level in the recursion tree. 0 is the root. limit656// is the deepest possible leaf.657private int recLevel;658private boolean done;659660// the lengths of the lines of the control polygon. Only its first661// curveType/2 - 1 elements are valid. This is an optimization. See662// next() for more detail.663private final double[] curLeafCtrlPolyLengths = new double[3];664665LengthIterator() {666this.recCurveStack = new double[REC_LIMIT + 1][8];667this.sidesRight = new boolean[REC_LIMIT];668// if any methods are called without first initializing this object669// on a curve, we want it to fail ASAP.670this.nextT = Double.MAX_VALUE;671this.lenAtNextT = Double.MAX_VALUE;672this.lenAtLastSplit = Double.MIN_VALUE;673this.recLevel = Integer.MIN_VALUE;674this.lastSegLen = Double.MAX_VALUE;675this.done = true;676}677678/**679* Reset this LengthIterator.680*/681void reset() {682// keep data dirty683// as it appears not useful to reset data:684if (DO_CLEAN_DIRTY) {685final int recLimit = recCurveStack.length - 1;686for (int i = recLimit; i >= 0; i--) {687Arrays.fill(recCurveStack[i], 0.0d);688}689Arrays.fill(sidesRight, false);690Arrays.fill(curLeafCtrlPolyLengths, 0.0d);691Arrays.fill(nextRoots, 0.0d);692Arrays.fill(flatLeafCoefCache, 0.0d);693flatLeafCoefCache[2] = -1.0d;694}695}696697void initializeIterationOnCurve(final double[] pts, final int type) {698// optimize arraycopy (8 values faster than 6 = type):699System.arraycopy(pts, 0, recCurveStack[0], 0, 8);700this.curveType = type;701this.recLevel = 0;702this.lastT = 0.0d;703this.lenAtLastT = 0.0d;704this.nextT = 0.0d;705this.lenAtNextT = 0.0d;706goLeft(); // initializes nextT and lenAtNextT properly707this.lenAtLastSplit = 0.0d;708if (recLevel > 0) {709this.sidesRight[0] = false;710this.done = false;711} else {712// the root of the tree is a leaf so we're done.713this.sidesRight[0] = true;714this.done = true;715}716this.lastSegLen = 0.0d;717}718719// 0 == false, 1 == true, -1 == invalid cached value.720private int cachedHaveLowAcceleration = -1;721722private boolean haveLowAcceleration(final double err) {723if (cachedHaveLowAcceleration == -1) {724final double len1 = curLeafCtrlPolyLengths[0];725final double len2 = curLeafCtrlPolyLengths[1];726// the test below is equivalent to !within(len1/len2, 1, err).727// It is using a multiplication instead of a division, so it728// should be a bit faster.729if (!Helpers.within(len1, len2, err * len2)) {730cachedHaveLowAcceleration = 0;731return false;732}733if (curveType == 8) {734final double len3 = curLeafCtrlPolyLengths[2];735// if len1 is close to 2 and 2 is close to 3, that probably736// means 1 is close to 3 so the second part of this test might737// not be needed, but it doesn't hurt to include it.738final double errLen3 = err * len3;739if (!(Helpers.within(len2, len3, errLen3) &&740Helpers.within(len1, len3, errLen3))) {741cachedHaveLowAcceleration = 0;742return false;743}744}745cachedHaveLowAcceleration = 1;746return true;747}748749return (cachedHaveLowAcceleration == 1);750}751752// we want to avoid allocations/gc so we keep this array so we753// can put roots in it,754private final double[] nextRoots = new double[4];755756// caches the coefficients of the current leaf in its flattened757// form (see inside next() for what that means). The cache is758// invalid when it's third element is negative, since in any759// valid flattened curve, this would be >= 0.760private final double[] flatLeafCoefCache = new double[]{0.0d, 0.0d, -1.0d, 0.0d};761762// returns the t value where the remaining curve should be split in763// order for the left subdivided curve to have length len. If len764// is >= than the length of the uniterated curve, it returns 1.765double next(final double len) {766final double targetLength = lenAtLastSplit + len;767while (lenAtNextT < targetLength) {768if (done) {769lastSegLen = lenAtNextT - lenAtLastSplit;770return 1.0d;771}772goToNextLeaf();773}774lenAtLastSplit = targetLength;775final double leaflen = lenAtNextT - lenAtLastT;776double t = (targetLength - lenAtLastT) / leaflen;777778// cubicRootsInAB is a fairly expensive call, so we just don't do it779// if the acceleration in this section of the curve is small enough.780if (!haveLowAcceleration(0.05d)) {781// We flatten the current leaf along the x axis, so that we're782// left with a, b, c which define a 1D Bezier curve. We then783// solve this to get the parameter of the original leaf that784// gives us the desired length.785final double[] _flatLeafCoefCache = flatLeafCoefCache;786787if (_flatLeafCoefCache[2] < 0.0d) {788double x = curLeafCtrlPolyLengths[0],789y = x + curLeafCtrlPolyLengths[1];790if (curveType == 8) {791double z = y + curLeafCtrlPolyLengths[2];792_flatLeafCoefCache[0] = 3.0d * (x - y) + z;793_flatLeafCoefCache[1] = 3.0d * (y - 2.0d * x);794_flatLeafCoefCache[2] = 3.0d * x;795_flatLeafCoefCache[3] = -z;796} else if (curveType == 6) {797_flatLeafCoefCache[0] = 0.0d;798_flatLeafCoefCache[1] = y - 2.0d * x;799_flatLeafCoefCache[2] = 2.0d * x;800_flatLeafCoefCache[3] = -y;801}802}803double a = _flatLeafCoefCache[0];804double b = _flatLeafCoefCache[1];805double c = _flatLeafCoefCache[2];806double d = t * _flatLeafCoefCache[3];807808// we use cubicRootsInAB here, because we want only roots in 0, 1,809// and our quadratic root finder doesn't filter, so it's just a810// matter of convenience.811final int n = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0.0d, 1.0d);812if (n == 1 && !Double.isNaN(nextRoots[0])) {813t = nextRoots[0];814}815}816// t is relative to the current leaf, so we must make it a valid parameter817// of the original curve.818t = t * (nextT - lastT) + lastT;819if (t >= 1.0d) {820t = 1.0d;821done = true;822}823// even if done = true, if we're here, that means targetLength824// is equal to, or very, very close to the total length of the825// curve, so lastSegLen won't be too high. In cases where len826// overshoots the curve, this method will exit in the while827// loop, and lastSegLen will still be set to the right value.828lastSegLen = len;829return t;830}831832double totalLength() {833while (!done) {834goToNextLeaf();835}836// reset LengthIterator:837reset();838839return lenAtNextT;840}841842double lastSegLen() {843return lastSegLen;844}845846// go to the next leaf (in an inorder traversal) in the recursion tree847// preconditions: must be on a leaf, and that leaf must not be the root.848private void goToNextLeaf() {849// We must go to the first ancestor node that has an unvisited850// right child.851final boolean[] _sides = sidesRight;852int _recLevel = recLevel;853_recLevel--;854855while(_sides[_recLevel]) {856if (_recLevel == 0) {857recLevel = 0;858done = true;859return;860}861_recLevel--;862}863864_sides[_recLevel] = true;865// optimize arraycopy (8 values faster than 6 = type):866System.arraycopy(recCurveStack[_recLevel++], 0,867recCurveStack[_recLevel], 0, 8);868recLevel = _recLevel;869goLeft();870}871872// go to the leftmost node from the current node. Return its length.873private void goLeft() {874final double len = onLeaf();875if (len >= 0.0d) {876lastT = nextT;877lenAtLastT = lenAtNextT;878nextT += (1 << (REC_LIMIT - recLevel)) * MIN_T_INC;879lenAtNextT += len;880// invalidate caches881flatLeafCoefCache[2] = -1.0d;882cachedHaveLowAcceleration = -1;883} else {884Helpers.subdivide(recCurveStack[recLevel],885recCurveStack[recLevel + 1],886recCurveStack[recLevel], curveType);887888sidesRight[recLevel] = false;889recLevel++;890goLeft();891}892}893894// this is a bit of a hack. It returns -1 if we're not on a leaf, and895// the length of the leaf if we are on a leaf.896private double onLeaf() {897final double[] curve = recCurveStack[recLevel];898final int _curveType = curveType;899double polyLen = 0.0d;900901double x0 = curve[0], y0 = curve[1];902for (int i = 2; i < _curveType; i += 2) {903final double x1 = curve[i], y1 = curve[i + 1];904final double len = Helpers.linelen(x0, y0, x1, y1);905polyLen += len;906curLeafCtrlPolyLengths[(i >> 1) - 1] = len;907x0 = x1;908y0 = y1;909}910911final double lineLen = Helpers.linelen(curve[0], curve[1], x0, y0);912913if ((polyLen - lineLen) < CURVE_LEN_ERR || recLevel == REC_LIMIT) {914return (polyLen + lineLen) / 2.0d;915}916return -1.0d;917}918}919920@Override921public void curveTo(final double x1, final double y1,922final double x2, final double y2,923final double x3, final double y3)924{925final int outcode0 = this.cOutCode;926927if (clipRect != null) {928final int outcode1 = Helpers.outcode(x1, y1, clipRect);929final int outcode2 = Helpers.outcode(x2, y2, clipRect);930final int outcode3 = Helpers.outcode(x3, y3, clipRect);931932// Should clip933final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);934if (orCode != 0) {935final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;936937// basic rejection criteria:938if (sideCode == 0) {939// overlap clip:940if (subdivide) {941// avoid reentrance942subdivide = false;943// subdivide curve => callback with subdivided parts:944boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, x2, y2, x3, y3,945orCode, this);946// reentrance is done:947subdivide = true;948if (ret) {949return;950}951}952// already subdivided so render it953} else {954this.cOutCode = outcode3;955skipCurveTo(x1, y1, x2, y2, x3, y3);956return;957}958}959960this.cOutCode = outcode3;961962if (this.outside) {963this.outside = false;964// Adjust current index, phase & dash:965skipLen();966}967}968_curveTo(x1, y1, x2, y2, x3, y3);969}970971private void _curveTo(final double x1, final double y1,972final double x2, final double y2,973final double x3, final double y3)974{975final double[] _curCurvepts = curCurvepts;976977// monotonize curve:978final CurveBasicMonotonizer monotonizer979= rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3);980981final int nSplits = monotonizer.nbSplits;982final double[] mid = monotonizer.middle;983984for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {985// optimize arraycopy (8 values faster than 6 = type):986System.arraycopy(mid, off, _curCurvepts, 0, 8);987988somethingTo(8);989}990}991992private void skipCurveTo(final double x1, final double y1,993final double x2, final double y2,994final double x3, final double y3)995{996final double[] _curCurvepts = curCurvepts;997_curCurvepts[0] = cx0; _curCurvepts[1] = cy0;998_curCurvepts[2] = x1; _curCurvepts[3] = y1;999_curCurvepts[4] = x2; _curCurvepts[5] = y2;1000_curCurvepts[6] = x3; _curCurvepts[7] = y3;10011002skipSomethingTo(8);10031004this.cx0 = x3;1005this.cy0 = y3;1006}10071008@Override1009public void quadTo(final double x1, final double y1,1010final double x2, final double y2)1011{1012final int outcode0 = this.cOutCode;10131014if (clipRect != null) {1015final int outcode1 = Helpers.outcode(x1, y1, clipRect);1016final int outcode2 = Helpers.outcode(x2, y2, clipRect);10171018// Should clip1019final int orCode = (outcode0 | outcode1 | outcode2);1020if (orCode != 0) {1021final int sideCode = outcode0 & outcode1 & outcode2;10221023// basic rejection criteria:1024if (sideCode == 0) {1025// overlap clip:1026if (subdivide) {1027// avoid reentrance1028subdivide = false;1029// subdivide curve => call lineTo() with subdivided curves:1030boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,1031x2, y2, orCode, this);1032// reentrance is done:1033subdivide = true;1034if (ret) {1035return;1036}1037}1038// already subdivided so render it1039} else {1040this.cOutCode = outcode2;1041skipQuadTo(x1, y1, x2, y2);1042return;1043}1044}10451046this.cOutCode = outcode2;10471048if (this.outside) {1049this.outside = false;1050// Adjust current index, phase & dash:1051skipLen();1052}1053}1054_quadTo(x1, y1, x2, y2);1055}10561057private void _quadTo(final double x1, final double y1,1058final double x2, final double y2)1059{1060final double[] _curCurvepts = curCurvepts;10611062// monotonize quad:1063final CurveBasicMonotonizer monotonizer1064= rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2);10651066final int nSplits = monotonizer.nbSplits;1067final double[] mid = monotonizer.middle;10681069for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {1070// optimize arraycopy (8 values faster than 6 = type):1071System.arraycopy(mid, off, _curCurvepts, 0, 8);10721073somethingTo(6);1074}1075}10761077private void skipQuadTo(final double x1, final double y1,1078final double x2, final double y2)1079{1080final double[] _curCurvepts = curCurvepts;1081_curCurvepts[0] = cx0; _curCurvepts[1] = cy0;1082_curCurvepts[2] = x1; _curCurvepts[3] = y1;1083_curCurvepts[4] = x2; _curCurvepts[5] = y2;10841085skipSomethingTo(6);10861087this.cx0 = x2;1088this.cy0 = y2;1089}10901091@Override1092public void closePath() {1093if (cx0 != sx0 || cy0 != sy0) {1094lineTo(sx0, sy0);1095}1096if (firstSegidx != 0) {1097if (!dashOn || needsMoveTo) {1098out.moveTo(sx0, sy0);1099}1100emitFirstSegments();1101}1102moveTo(sx0, sy0);1103}11041105@Override1106public void pathDone() {1107if (firstSegidx != 0) {1108out.moveTo(sx0, sy0);1109emitFirstSegments();1110}1111out.pathDone();11121113// Dispose this instance:1114dispose();1115}11161117@Override1118public long getNativeConsumer() {1119throw new InternalError("Dasher does not use a native consumer");1120}1121}1122112311241125