Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/java2d/pisces/Dasher.java
38918 views
/*1* Copyright (c) 2007, 2011, 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.pisces;2627import sun.awt.geom.PathConsumer2D;2829/**30* The <code>Dasher</code> class takes a series of linear commands31* (<code>moveTo</code>, <code>lineTo</code>, <code>close</code> and32* <code>end</code>) and breaks them into smaller segments according to a33* dash pattern array and a starting dash phase.34*35* <p> Issues: in J2Se, a zero length dash segment as drawn as a very36* short dash, whereas Pisces does not draw anything. The PostScript37* semantics are unclear.38*39*/40final class Dasher implements sun.awt.geom.PathConsumer2D {4142private final PathConsumer2D out;43private final float[] dash;44private final float startPhase;45private final boolean startDashOn;46private final int startIdx;4748private boolean starting;49private boolean needsMoveTo;5051private int idx;52private boolean dashOn;53private float phase;5455private float sx, sy;56private float x0, y0;5758// temporary storage for the current curve59private float[] curCurvepts;6061/**62* Constructs a <code>Dasher</code>.63*64* @param out an output <code>PathConsumer2D</code>.65* @param dash an array of <code>float</code>s containing the dash pattern66* @param phase a <code>float</code> containing the dash phase67*/68public Dasher(PathConsumer2D out, float[] dash, float phase) {69if (phase < 0) {70throw new IllegalArgumentException("phase < 0 !");71}7273this.out = out;7475// Normalize so 0 <= phase < dash[0]76int idx = 0;77dashOn = true;78float d;79while (phase >= (d = dash[idx])) {80phase -= d;81idx = (idx + 1) % dash.length;82dashOn = !dashOn;83}8485this.dash = dash;86this.startPhase = this.phase = phase;87this.startDashOn = dashOn;88this.startIdx = idx;89this.starting = true;9091// we need curCurvepts to be able to contain 2 curves because when92// dashing curves, we need to subdivide it93curCurvepts = new float[8 * 2];94}9596public void moveTo(float x0, float y0) {97if (firstSegidx > 0) {98out.moveTo(sx, sy);99emitFirstSegments();100}101needsMoveTo = true;102this.idx = startIdx;103this.dashOn = this.startDashOn;104this.phase = this.startPhase;105this.sx = this.x0 = x0;106this.sy = this.y0 = y0;107this.starting = true;108}109110private void emitSeg(float[] buf, int off, int type) {111switch (type) {112case 8:113out.curveTo(buf[off+0], buf[off+1],114buf[off+2], buf[off+3],115buf[off+4], buf[off+5]);116break;117case 6:118out.quadTo(buf[off+0], buf[off+1],119buf[off+2], buf[off+3]);120break;121case 4:122out.lineTo(buf[off], buf[off+1]);123}124}125126private void emitFirstSegments() {127for (int i = 0; i < firstSegidx; ) {128emitSeg(firstSegmentsBuffer, i+1, (int)firstSegmentsBuffer[i]);129i += (((int)firstSegmentsBuffer[i]) - 1);130}131firstSegidx = 0;132}133134// We don't emit the first dash right away. If we did, caps would be135// drawn on it, but we need joins to be drawn if there's a closePath()136// So, we store the path elements that make up the first dash in the137// buffer below.138private float[] firstSegmentsBuffer = new float[7];139private int firstSegidx = 0;140// precondition: pts must be in relative coordinates (relative to x0,y0)141// fullCurve is true iff the curve in pts has not been split.142private void goTo(float[] pts, int off, final int type) {143float x = pts[off + type - 4];144float y = pts[off + type - 3];145if (dashOn) {146if (starting) {147firstSegmentsBuffer = Helpers.widenArray(firstSegmentsBuffer,148firstSegidx, type - 2 + 1);149firstSegmentsBuffer[firstSegidx++] = type;150System.arraycopy(pts, off, firstSegmentsBuffer, firstSegidx, type - 2);151firstSegidx += type - 2;152} else {153if (needsMoveTo) {154out.moveTo(x0, y0);155needsMoveTo = false;156}157emitSeg(pts, off, type);158}159} else {160starting = false;161needsMoveTo = true;162}163this.x0 = x;164this.y0 = y;165}166167public void lineTo(float x1, float y1) {168float dx = x1 - x0;169float dy = y1 - y0;170171float len = (float) Math.sqrt(dx*dx + dy*dy);172173if (len == 0) {174return;175}176177// The scaling factors needed to get the dx and dy of the178// transformed dash segments.179float cx = dx / len;180float cy = dy / len;181182while (true) {183float leftInThisDashSegment = dash[idx] - phase;184if (len <= leftInThisDashSegment) {185curCurvepts[0] = x1;186curCurvepts[1] = y1;187goTo(curCurvepts, 0, 4);188// Advance phase within current dash segment189phase += len;190if (len == leftInThisDashSegment) {191phase = 0f;192idx = (idx + 1) % dash.length;193dashOn = !dashOn;194}195return;196}197198float dashdx = dash[idx] * cx;199float dashdy = dash[idx] * cy;200if (phase == 0) {201curCurvepts[0] = x0 + dashdx;202curCurvepts[1] = y0 + dashdy;203} else {204float p = leftInThisDashSegment / dash[idx];205curCurvepts[0] = x0 + p * dashdx;206curCurvepts[1] = y0 + p * dashdy;207}208209goTo(curCurvepts, 0, 4);210211len -= leftInThisDashSegment;212// Advance to next dash segment213idx = (idx + 1) % dash.length;214dashOn = !dashOn;215phase = 0;216}217}218219private LengthIterator li = null;220221// preconditions: curCurvepts must be an array of length at least 2 * type,222// that contains the curve we want to dash in the first type elements223private void somethingTo(int type) {224if (pointCurve(curCurvepts, type)) {225return;226}227if (li == null) {228li = new LengthIterator(4, 0.01f);229}230li.initializeIterationOnCurve(curCurvepts, type);231232int curCurveoff = 0; // initially the current curve is at curCurvepts[0...type]233float lastSplitT = 0;234float t = 0;235float leftInThisDashSegment = dash[idx] - phase;236while ((t = li.next(leftInThisDashSegment)) < 1) {237if (t != 0) {238Helpers.subdivideAt((t - lastSplitT) / (1 - lastSplitT),239curCurvepts, curCurveoff,240curCurvepts, 0,241curCurvepts, type, type);242lastSplitT = t;243goTo(curCurvepts, 2, type);244curCurveoff = type;245}246// Advance to next dash segment247idx = (idx + 1) % dash.length;248dashOn = !dashOn;249phase = 0;250leftInThisDashSegment = dash[idx];251}252goTo(curCurvepts, curCurveoff+2, type);253phase += li.lastSegLen();254if (phase >= dash[idx]) {255phase = 0f;256idx = (idx + 1) % dash.length;257dashOn = !dashOn;258}259}260261private static boolean pointCurve(float[] curve, int type) {262for (int i = 2; i < type; i++) {263if (curve[i] != curve[i-2]) {264return false;265}266}267return true;268}269270// Objects of this class are used to iterate through curves. They return271// t values where the left side of the curve has a specified length.272// It does this by subdividing the input curve until a certain error273// condition has been met. A recursive subdivision procedure would274// return as many as 1<<limit curves, but this is an iterator and we275// don't need all the curves all at once, so what we carry out a276// lazy inorder traversal of the recursion tree (meaning we only move277// through the tree when we need the next subdivided curve). This saves278// us a lot of memory because at any one time we only need to store279// limit+1 curves - one for each level of the tree + 1.280// NOTE: the way we do things here is not enough to traverse a general281// tree; however, the trees we are interested in have the property that282// every non leaf node has exactly 2 children283private static class LengthIterator {284private enum Side {LEFT, RIGHT};285// Holds the curves at various levels of the recursion. The root286// (i.e. the original curve) is at recCurveStack[0] (but then it287// gets subdivided, the left half is put at 1, so most of the time288// only the right half of the original curve is at 0)289private float[][] recCurveStack;290// sides[i] indicates whether the node at level i+1 in the path from291// the root to the current leaf is a left or right child of its parent.292private Side[] sides;293private int curveType;294private final int limit;295private final float ERR;296private final float minTincrement;297// lastT and nextT delimit the current leaf.298private float nextT;299private float lenAtNextT;300private float lastT;301private float lenAtLastT;302private float lenAtLastSplit;303private float lastSegLen;304// the current level in the recursion tree. 0 is the root. limit305// is the deepest possible leaf.306private int recLevel;307private boolean done;308309// the lengths of the lines of the control polygon. Only its first310// curveType/2 - 1 elements are valid. This is an optimization. See311// next(float) for more detail.312private float[] curLeafCtrlPolyLengths = new float[3];313314public LengthIterator(int reclimit, float err) {315this.limit = reclimit;316this.minTincrement = 1f / (1 << limit);317this.ERR = err;318this.recCurveStack = new float[reclimit+1][8];319this.sides = new Side[reclimit];320// if any methods are called without first initializing this object on321// a curve, we want it to fail ASAP.322this.nextT = Float.MAX_VALUE;323this.lenAtNextT = Float.MAX_VALUE;324this.lenAtLastSplit = Float.MIN_VALUE;325this.recLevel = Integer.MIN_VALUE;326this.lastSegLen = Float.MAX_VALUE;327this.done = true;328}329330public void initializeIterationOnCurve(float[] pts, int type) {331System.arraycopy(pts, 0, recCurveStack[0], 0, type);332this.curveType = type;333this.recLevel = 0;334this.lastT = 0;335this.lenAtLastT = 0;336this.nextT = 0;337this.lenAtNextT = 0;338goLeft(); // initializes nextT and lenAtNextT properly339this.lenAtLastSplit = 0;340if (recLevel > 0) {341this.sides[0] = Side.LEFT;342this.done = false;343} else {344// the root of the tree is a leaf so we're done.345this.sides[0] = Side.RIGHT;346this.done = true;347}348this.lastSegLen = 0;349}350351// 0 == false, 1 == true, -1 == invalid cached value.352private int cachedHaveLowAcceleration = -1;353354private boolean haveLowAcceleration(float err) {355if (cachedHaveLowAcceleration == -1) {356final float len1 = curLeafCtrlPolyLengths[0];357final float len2 = curLeafCtrlPolyLengths[1];358// the test below is equivalent to !within(len1/len2, 1, err).359// It is using a multiplication instead of a division, so it360// should be a bit faster.361if (!Helpers.within(len1, len2, err*len2)) {362cachedHaveLowAcceleration = 0;363return false;364}365if (curveType == 8) {366final float len3 = curLeafCtrlPolyLengths[2];367// if len1 is close to 2 and 2 is close to 3, that probably368// means 1 is close to 3 so the second part of this test might369// not be needed, but it doesn't hurt to include it.370if (!(Helpers.within(len2, len3, err*len3) &&371Helpers.within(len1, len3, err*len3))) {372cachedHaveLowAcceleration = 0;373return false;374}375}376cachedHaveLowAcceleration = 1;377return true;378}379380return (cachedHaveLowAcceleration == 1);381}382383// we want to avoid allocations/gc so we keep this array so we384// can put roots in it,385private float[] nextRoots = new float[4];386387// caches the coefficients of the current leaf in its flattened388// form (see inside next() for what that means). The cache is389// invalid when it's third element is negative, since in any390// valid flattened curve, this would be >= 0.391private float[] flatLeafCoefCache = new float[] {0, 0, -1, 0};392// returns the t value where the remaining curve should be split in393// order for the left subdivided curve to have length len. If len394// is >= than the length of the uniterated curve, it returns 1.395public float next(final float len) {396final float targetLength = lenAtLastSplit + len;397while(lenAtNextT < targetLength) {398if (done) {399lastSegLen = lenAtNextT - lenAtLastSplit;400return 1;401}402goToNextLeaf();403}404lenAtLastSplit = targetLength;405final float leaflen = lenAtNextT - lenAtLastT;406float t = (targetLength - lenAtLastT) / leaflen;407408// cubicRootsInAB is a fairly expensive call, so we just don't do it409// if the acceleration in this section of the curve is small enough.410if (!haveLowAcceleration(0.05f)) {411// We flatten the current leaf along the x axis, so that we're412// left with a, b, c which define a 1D Bezier curve. We then413// solve this to get the parameter of the original leaf that414// gives us the desired length.415416if (flatLeafCoefCache[2] < 0) {417float x = 0+curLeafCtrlPolyLengths[0],418y = x+curLeafCtrlPolyLengths[1];419if (curveType == 8) {420float z = y + curLeafCtrlPolyLengths[2];421flatLeafCoefCache[0] = 3*(x - y) + z;422flatLeafCoefCache[1] = 3*(y - 2*x);423flatLeafCoefCache[2] = 3*x;424flatLeafCoefCache[3] = -z;425} else if (curveType == 6) {426flatLeafCoefCache[0] = 0f;427flatLeafCoefCache[1] = y - 2*x;428flatLeafCoefCache[2] = 2*x;429flatLeafCoefCache[3] = -y;430}431}432float a = flatLeafCoefCache[0];433float b = flatLeafCoefCache[1];434float c = flatLeafCoefCache[2];435float d = t*flatLeafCoefCache[3];436437// we use cubicRootsInAB here, because we want only roots in 0, 1,438// and our quadratic root finder doesn't filter, so it's just a439// matter of convenience.440int n = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0, 1);441if (n == 1 && !Float.isNaN(nextRoots[0])) {442t = nextRoots[0];443}444}445// t is relative to the current leaf, so we must make it a valid parameter446// of the original curve.447t = t * (nextT - lastT) + lastT;448if (t >= 1) {449t = 1;450done = true;451}452// even if done = true, if we're here, that means targetLength453// is equal to, or very, very close to the total length of the454// curve, so lastSegLen won't be too high. In cases where len455// overshoots the curve, this method will exit in the while456// loop, and lastSegLen will still be set to the right value.457lastSegLen = len;458return t;459}460461public float lastSegLen() {462return lastSegLen;463}464465// go to the next leaf (in an inorder traversal) in the recursion tree466// preconditions: must be on a leaf, and that leaf must not be the root.467private void goToNextLeaf() {468// We must go to the first ancestor node that has an unvisited469// right child.470recLevel--;471while(sides[recLevel] == Side.RIGHT) {472if (recLevel == 0) {473done = true;474return;475}476recLevel--;477}478479sides[recLevel] = Side.RIGHT;480System.arraycopy(recCurveStack[recLevel], 0, recCurveStack[recLevel+1], 0, curveType);481recLevel++;482goLeft();483}484485// go to the leftmost node from the current node. Return its length.486private void goLeft() {487float len = onLeaf();488if (len >= 0) {489lastT = nextT;490lenAtLastT = lenAtNextT;491nextT += (1 << (limit - recLevel)) * minTincrement;492lenAtNextT += len;493// invalidate caches494flatLeafCoefCache[2] = -1;495cachedHaveLowAcceleration = -1;496} else {497Helpers.subdivide(recCurveStack[recLevel], 0,498recCurveStack[recLevel+1], 0,499recCurveStack[recLevel], 0, curveType);500sides[recLevel] = Side.LEFT;501recLevel++;502goLeft();503}504}505506// this is a bit of a hack. It returns -1 if we're not on a leaf, and507// the length of the leaf if we are on a leaf.508private float onLeaf() {509float[] curve = recCurveStack[recLevel];510float polyLen = 0;511512float x0 = curve[0], y0 = curve[1];513for (int i = 2; i < curveType; i += 2) {514final float x1 = curve[i], y1 = curve[i+1];515final float len = Helpers.linelen(x0, y0, x1, y1);516polyLen += len;517curLeafCtrlPolyLengths[i/2 - 1] = len;518x0 = x1;519y0 = y1;520}521522final float lineLen = Helpers.linelen(curve[0], curve[1], curve[curveType-2], curve[curveType-1]);523if (polyLen - lineLen < ERR || recLevel == limit) {524return (polyLen + lineLen)/2;525}526return -1;527}528}529530@Override531public void curveTo(float x1, float y1,532float x2, float y2,533float x3, float y3)534{535curCurvepts[0] = x0; curCurvepts[1] = y0;536curCurvepts[2] = x1; curCurvepts[3] = y1;537curCurvepts[4] = x2; curCurvepts[5] = y2;538curCurvepts[6] = x3; curCurvepts[7] = y3;539somethingTo(8);540}541542@Override543public void quadTo(float x1, float y1, float x2, float y2) {544curCurvepts[0] = x0; curCurvepts[1] = y0;545curCurvepts[2] = x1; curCurvepts[3] = y1;546curCurvepts[4] = x2; curCurvepts[5] = y2;547somethingTo(6);548}549550public void closePath() {551lineTo(sx, sy);552if (firstSegidx > 0) {553if (!dashOn || needsMoveTo) {554out.moveTo(sx, sy);555}556emitFirstSegments();557}558moveTo(sx, sy);559}560561public void pathDone() {562if (firstSegidx > 0) {563out.moveTo(sx, sy);564emitFirstSegments();565}566out.pathDone();567}568569@Override570public long getNativeConsumer() {571throw new InternalError("Dasher does not use a native consumer");572}573}574575576577