1 /*
2  * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
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
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.java2d.marlin;
27 
28 import java.util.Arrays;
29 import sun.java2d.marlin.DHelpers.PolyStack;
30 import sun.java2d.marlin.DTransformingPathConsumer2D.CurveBasicMonotonizer;
31 import sun.java2d.marlin.DTransformingPathConsumer2D.CurveClipSplitter;
32 
33 // TODO: some of the arithmetic here is too verbose and prone to hard to
34 // debug typos. We should consider making a small Point/Vector class that
35 // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
36 final class DStroker implements DPathConsumer2D, MarlinConst {
37 
38     private static final int MOVE_TO = 0;
39     private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
40     private static final int CLOSE = 2;
41 
42     // round join threshold = 1 subpixel
43     private static final double ERR_JOIN = (1.0f / MIN_SUBPIXELS);
44     private static final double ROUND_JOIN_THRESHOLD = ERR_JOIN * ERR_JOIN;
45 
46     // kappa = (4/3) * (SQRT(2) - 1)
47     private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
48 
49     // SQRT(2)
50     private static final double SQRT_2 = Math.sqrt(2.0d);
51 
52     private DPathConsumer2D out;
53 
54     private int capStyle;
55     private int joinStyle;
56 
57     private double lineWidth2;
58     private double invHalfLineWidth2Sq;
59 
60     private final double[] offset0 = new double[2];
61     private final double[] offset1 = new double[2];
62     private final double[] offset2 = new double[2];
63     private final double[] miter = new double[2];
64     private double miterLimitSq;
65 
66     private int prev;
67 
68     // The starting point of the path, and the slope there.
69     private double sx0, sy0, sdx, sdy;
70     // the current point and the slope there.
71     private double cx0, cy0, cdx, cdy; // c stands for current
72     // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the
73     // first and last points on the left parallel path. Since this path is
74     // parallel, it's slope at any point is parallel to the slope of the
75     // original path (thought they may have different directions), so these
76     // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
77     // would be error prone and hard to read, so we keep these anyway.
78     private double smx, smy, cmx, cmy;
79 
80     private final PolyStack reverse;
81 
82     private final double[] lp = new double[8];
83     private final double[] rp = new double[8];
84 
85     // per-thread renderer context
86     final DRendererContext rdrCtx;
87 
88     // dirty curve
89     final DCurve curve;
90 
91     // Bounds of the drawing region, at pixel precision.
92     private double[] clipRect;
93 
94     // the outcode of the current point
95     private int cOutCode = 0;
96 
97     // the outcode of the starting point
98     private int sOutCode = 0;
99 
100     // flag indicating if the path is opened (clipped)
101     private boolean opened = false;
102     // flag indicating if the starting point's cap is done
103     private boolean capStart = false;
104     // flag indicating to monotonize curves
105     private boolean monotonize;
106 
107     private boolean subdivide = false;
108     private final CurveClipSplitter curveSplitter;
109 
110     /**
111      * Constructs a <code>DStroker</code>.
112      * @param rdrCtx per-thread renderer context
113      */
DStroker(final DRendererContext rdrCtx)114     DStroker(final DRendererContext rdrCtx) {
115         this.rdrCtx = rdrCtx;
116 
117         this.reverse = (rdrCtx.stats != null) ?
118             new PolyStack(rdrCtx,
119                     rdrCtx.stats.stat_str_polystack_types,
120                     rdrCtx.stats.stat_str_polystack_curves,
121                     rdrCtx.stats.hist_str_polystack_curves,
122                     rdrCtx.stats.stat_array_str_polystack_curves,
123                     rdrCtx.stats.stat_array_str_polystack_types)
124             : new PolyStack(rdrCtx);
125 
126         this.curve = rdrCtx.curve;
127         this.curveSplitter = rdrCtx.curveClipSplitter;
128     }
129 
130     /**
131      * Inits the <code>DStroker</code>.
132      *
133      * @param pc2d an output <code>DPathConsumer2D</code>.
134      * @param lineWidth the desired line width in pixels
135      * @param capStyle the desired end cap style, one of
136      * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
137      * <code>CAP_SQUARE</code>.
138      * @param joinStyle the desired line join style, one of
139      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
140      * <code>JOIN_BEVEL</code>.
141      * @param miterLimit the desired miter limit
142      * @param subdivideCurves true to indicate to subdivide curves, false if dasher does
143      * @return this instance
144      */
init(final DPathConsumer2D pc2d, final double lineWidth, final int capStyle, final int joinStyle, final double miterLimit, final boolean subdivideCurves)145     DStroker init(final DPathConsumer2D pc2d,
146                   final double lineWidth,
147                   final int capStyle,
148                   final int joinStyle,
149                   final double miterLimit,
150                   final boolean subdivideCurves)
151     {
152         this.out = pc2d;
153 
154         this.lineWidth2 = lineWidth / 2.0d;
155         this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2);
156         this.monotonize = subdivideCurves;
157 
158         this.capStyle = capStyle;
159         this.joinStyle = joinStyle;
160 
161         final double limit = miterLimit * lineWidth2;
162         this.miterLimitSq = limit * limit;
163 
164         this.prev = CLOSE;
165 
166         rdrCtx.stroking = 1;
167 
168         if (rdrCtx.doClip) {
169             // Adjust the clipping rectangle with the stroker margin (miter limit, width)
170             double margin = lineWidth2;
171 
172             if (capStyle == CAP_SQUARE) {
173                 margin *= SQRT_2;
174             }
175             if ((joinStyle == JOIN_MITER) && (margin < limit)) {
176                 margin = limit;
177             }
178 
179             // bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
180             // adjust clip rectangle (ymin, ymax, xmin, xmax):
181             final double[] _clipRect = rdrCtx.clipRect;
182             _clipRect[0] -= margin;
183             _clipRect[1] += margin;
184             _clipRect[2] -= margin;
185             _clipRect[3] += margin;
186             this.clipRect = _clipRect;
187 
188             if (MarlinConst.DO_LOG_CLIP) {
189                 MarlinUtils.logInfo("clipRect (stroker): "
190                                     + Arrays.toString(rdrCtx.clipRect));
191             }
192 
193             // initialize curve splitter here for stroker & dasher:
194             if (DO_CLIP_SUBDIVIDER) {
195                 subdivide = subdivideCurves;
196                 // adjust padded clip rectangle:
197                 curveSplitter.init();
198             } else {
199                 subdivide = false;
200             }
201         } else {
202             this.clipRect = null;
203             this.cOutCode = 0;
204             this.sOutCode = 0;
205         }
206         return this; // fluent API
207     }
208 
disableClipping()209     void disableClipping() {
210         this.clipRect = null;
211         this.cOutCode = 0;
212         this.sOutCode = 0;
213     }
214 
215     /**
216      * Disposes this stroker:
217      * clean up before reusing this instance
218      */
dispose()219     void dispose() {
220         reverse.dispose();
221 
222         opened   = false;
223         capStart = false;
224 
225         if (DO_CLEAN_DIRTY) {
226             // Force zero-fill dirty arrays:
227             Arrays.fill(offset0, 0.0d);
228             Arrays.fill(offset1, 0.0d);
229             Arrays.fill(offset2, 0.0d);
230             Arrays.fill(miter, 0.0d);
231             Arrays.fill(lp, 0.0d);
232             Arrays.fill(rp, 0.0d);
233         }
234     }
235 
computeOffset(final double lx, final double ly, final double w, final double[] m)236     private static void computeOffset(final double lx, final double ly,
237                                       final double w, final double[] m)
238     {
239         double len = lx*lx + ly*ly;
240         if (len == 0.0d) {
241             m[0] = 0.0d;
242             m[1] = 0.0d;
243         } else {
244             len = Math.sqrt(len);
245             m[0] =  (ly * w) / len;
246             m[1] = -(lx * w) / len;
247         }
248     }
249 
250     // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are
251     // clockwise (if dx1,dy1 needs to be rotated clockwise to close
252     // the smallest angle between it and dx2,dy2).
253     // This is equivalent to detecting whether a point q is on the right side
254     // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and
255     // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a
256     // clockwise order.
257     // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left.
isCW(final double dx1, final double dy1, final double dx2, final double dy2)258     private static boolean isCW(final double dx1, final double dy1,
259                                 final double dx2, final double dy2)
260     {
261         return dx1 * dy2 <= dy1 * dx2;
262     }
263 
mayDrawRoundJoin(double cx, double cy, double omx, double omy, double mx, double my, boolean rev)264     private void mayDrawRoundJoin(double cx, double cy,
265                                   double omx, double omy,
266                                   double mx, double my,
267                                   boolean rev)
268     {
269         if ((omx == 0.0d && omy == 0.0d) || (mx == 0.0d && my == 0.0d)) {
270             return;
271         }
272 
273         final double domx = omx - mx;
274         final double domy = omy - my;
275         final double lenSq = domx*domx + domy*domy;
276 
277         if (lenSq < ROUND_JOIN_THRESHOLD) {
278             return;
279         }
280 
281         if (rev) {
282             omx = -omx;
283             omy = -omy;
284             mx  = -mx;
285             my  = -my;
286         }
287         drawRoundJoin(cx, cy, omx, omy, mx, my, rev);
288     }
289 
drawRoundJoin(double cx, double cy, double omx, double omy, double mx, double my, boolean rev)290     private void drawRoundJoin(double cx, double cy,
291                                double omx, double omy,
292                                double mx, double my,
293                                boolean rev)
294     {
295         // The sign of the dot product of mx,my and omx,omy is equal to the
296         // the sign of the cosine of ext
297         // (ext is the angle between omx,omy and mx,my).
298         final double cosext = omx * mx + omy * my;
299         // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only
300         // need 1 curve to approximate the circle section that joins omx,omy
301         // and mx,my.
302         if (cosext >= 0.0d) {
303             drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev);
304         } else {
305             // we need to split the arc into 2 arcs spanning the same angle.
306             // The point we want will be one of the 2 intersections of the
307             // perpendicular bisector of the chord (omx,omy)->(mx,my) and the
308             // circle. We could find this by scaling the vector
309             // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies
310             // on the circle), but that can have numerical problems when the angle
311             // between omx,omy and mx,my is close to 180 degrees. So we compute a
312             // normal of (omx,omy)-(mx,my). This will be the direction of the
313             // perpendicular bisector. To get one of the intersections, we just scale
314             // this vector that its length is lineWidth2 (this works because the
315             // perpendicular bisector goes through the origin). This scaling doesn't
316             // have numerical problems because we know that lineWidth2 divided by
317             // this normal's length is at least 0.5 and at most sqrt(2)/2 (because
318             // we know the angle of the arc is > 90 degrees).
319             double nx = my - omy, ny = omx - mx;
320             double nlen = Math.sqrt(nx*nx + ny*ny);
321             double scale = lineWidth2/nlen;
322             double mmx = nx * scale, mmy = ny * scale;
323 
324             // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've
325             // computed the wrong intersection so we get the other one.
326             // The test above is equivalent to if (rev).
327             if (rev) {
328                 mmx = -mmx;
329                 mmy = -mmy;
330             }
331             drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev);
332             drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev);
333         }
334     }
335 
336     // the input arc defined by omx,omy and mx,my must span <= 90 degrees.
drawBezApproxForArc(final double cx, final double cy, final double omx, final double omy, final double mx, final double my, boolean rev)337     private void drawBezApproxForArc(final double cx, final double cy,
338                                      final double omx, final double omy,
339                                      final double mx, final double my,
340                                      boolean rev)
341     {
342         final double cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq;
343 
344         // check round off errors producing cos(ext) > 1 and a NaN below
345         // cos(ext) == 1 implies colinear segments and an empty join anyway
346         if (cosext2 >= 0.5d) {
347             // just return to avoid generating a flat curve:
348             return;
349         }
350 
351         // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc
352         // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that
353         // define the bezier curve we're computing.
354         // It is computed using the constraints that P1-P0 and P3-P2 are parallel
355         // to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|.
356         double cv = ((4.0d / 3.0d) * Math.sqrt(0.5d - cosext2) /
357                             (1.0d + Math.sqrt(cosext2 + 0.5d)));
358         // if clockwise, we need to negate cv.
359         if (rev) { // rev is equivalent to isCW(omx, omy, mx, my)
360             cv = -cv;
361         }
362         final double x1 = cx + omx;
363         final double y1 = cy + omy;
364         final double x2 = x1 - cv * omy;
365         final double y2 = y1 + cv * omx;
366 
367         final double x4 = cx + mx;
368         final double y4 = cy + my;
369         final double x3 = x4 + cv * my;
370         final double y3 = y4 - cv * mx;
371 
372         emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev);
373     }
374 
drawRoundCap(double cx, double cy, double mx, double my)375     private void drawRoundCap(double cx, double cy, double mx, double my) {
376         final double Cmx = C * mx;
377         final double Cmy = C * my;
378         emitCurveTo(cx + mx - Cmy, cy + my + Cmx,
379                     cx - my + Cmx, cy + mx + Cmy,
380                     cx - my,       cy + mx);
381         emitCurveTo(cx - my - Cmx, cy + mx - Cmy,
382                     cx - mx - Cmy, cy - my + Cmx,
383                     cx - mx,       cy - my);
384     }
385 
386     // Return the intersection point of the lines (x0, y0) -> (x1, y1)
387     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
computeMiter(final double x0, final double y0, final double x1, final double y1, final double x0p, final double y0p, final double x1p, final double y1p, final double[] m)388     private static void computeMiter(final double x0, final double y0,
389                                      final double x1, final double y1,
390                                      final double x0p, final double y0p,
391                                      final double x1p, final double y1p,
392                                      final double[] m)
393     {
394         double x10 = x1 - x0;
395         double y10 = y1 - y0;
396         double x10p = x1p - x0p;
397         double y10p = y1p - y0p;
398 
399         // if this is 0, the lines are parallel. If they go in the
400         // same direction, there is no intersection so m[off] and
401         // m[off+1] will contain infinity, so no miter will be drawn.
402         // If they go in the same direction that means that the start of the
403         // current segment and the end of the previous segment have the same
404         // tangent, in which case this method won't even be involved in
405         // miter drawing because it won't be called by drawMiter (because
406         // (mx == omx && my == omy) will be true, and drawMiter will return
407         // immediately).
408         double den = x10*y10p - x10p*y10;
409         double t = x10p*(y0-y0p) - y10p*(x0-x0p);
410         t /= den;
411         m[0] = x0 + t*x10;
412         m[1] = y0 + t*y10;
413     }
414 
415     // Return the intersection point of the lines (x0, y0) -> (x1, y1)
416     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1]
safeComputeMiter(final double x0, final double y0, final double x1, final double y1, final double x0p, final double y0p, final double x1p, final double y1p, final double[] m)417     private static void safeComputeMiter(final double x0, final double y0,
418                                          final double x1, final double y1,
419                                          final double x0p, final double y0p,
420                                          final double x1p, final double y1p,
421                                          final double[] m)
422     {
423         double x10 = x1 - x0;
424         double y10 = y1 - y0;
425         double x10p = x1p - x0p;
426         double y10p = y1p - y0p;
427 
428         // if this is 0, the lines are parallel. If they go in the
429         // same direction, there is no intersection so m[off] and
430         // m[off+1] will contain infinity, so no miter will be drawn.
431         // If they go in the same direction that means that the start of the
432         // current segment and the end of the previous segment have the same
433         // tangent, in which case this method won't even be involved in
434         // miter drawing because it won't be called by drawMiter (because
435         // (mx == omx && my == omy) will be true, and drawMiter will return
436         // immediately).
437         double den = x10*y10p - x10p*y10;
438         if (den == 0.0d) {
439             m[2] = (x0 + x0p) / 2.0d;
440             m[3] = (y0 + y0p) / 2.0d;
441         } else {
442             double t = x10p*(y0-y0p) - y10p*(x0-x0p);
443             t /= den;
444             m[2] = x0 + t*x10;
445             m[3] = y0 + t*y10;
446         }
447     }
448 
drawMiter(final double pdx, final double pdy, final double x0, final double y0, final double dx, final double dy, double omx, double omy, double mx, double my, boolean rev)449     private void drawMiter(final double pdx, final double pdy,
450                            final double x0, final double y0,
451                            final double dx, final double dy,
452                            double omx, double omy,
453                            double mx, double my,
454                            boolean rev)
455     {
456         if ((mx == omx && my == omy) ||
457             (pdx == 0.0d && pdy == 0.0d) ||
458             (dx == 0.0d && dy == 0.0d))
459         {
460             return;
461         }
462 
463         if (rev) {
464             omx = -omx;
465             omy = -omy;
466             mx  = -mx;
467             my  = -my;
468         }
469 
470         computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
471                      (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, miter);
472 
473         final double miterX = miter[0];
474         final double miterY = miter[1];
475         double lenSq = (miterX-x0)*(miterX-x0) + (miterY-y0)*(miterY-y0);
476 
477         // If the lines are parallel, lenSq will be either NaN or +inf
478         // (actually, I'm not sure if the latter is possible. The important
479         // thing is that -inf is not possible, because lenSq is a square).
480         // For both of those values, the comparison below will fail and
481         // no miter will be drawn, which is correct.
482         if (lenSq < miterLimitSq) {
483             emitLineTo(miterX, miterY, rev);
484         }
485     }
486 
487     @Override
moveTo(final double x0, final double y0)488     public void moveTo(final double x0, final double y0) {
489         _moveTo(x0, y0, cOutCode);
490         // update starting point:
491         this.sx0 = x0;
492         this.sy0 = y0;
493         this.sdx = 1.0d;
494         this.sdy = 0.0d;
495         this.opened   = false;
496         this.capStart = false;
497 
498         if (clipRect != null) {
499             final int outcode = DHelpers.outcode(x0, y0, clipRect);
500             this.cOutCode = outcode;
501             this.sOutCode = outcode;
502         }
503     }
504 
_moveTo(final double x0, final double y0, final int outcode)505     private void _moveTo(final double x0, final double y0,
506                         final int outcode)
507     {
508         if (prev == MOVE_TO) {
509             this.cx0 = x0;
510             this.cy0 = y0;
511         } else {
512             if (prev == DRAWING_OP_TO) {
513                 finish(outcode);
514             }
515             this.prev = MOVE_TO;
516             this.cx0 = x0;
517             this.cy0 = y0;
518             this.cdx = 1.0d;
519             this.cdy = 0.0d;
520         }
521     }
522 
523     @Override
lineTo(final double x1, final double y1)524     public void lineTo(final double x1, final double y1) {
525         lineTo(x1, y1, false);
526     }
527 
lineTo(final double x1, final double y1, final boolean force)528     private void lineTo(final double x1, final double y1,
529                         final boolean force)
530     {
531         final int outcode0 = this.cOutCode;
532 
533         if (!force && clipRect != null) {
534             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
535 
536             // Should clip
537             final int orCode = (outcode0 | outcode1);
538             if (orCode != 0) {
539                 final int sideCode = outcode0 & outcode1;
540 
541                 // basic rejection criteria:
542                 if (sideCode == 0) {
543                     // overlap clip:
544                     if (subdivide) {
545                         // avoid reentrance
546                         subdivide = false;
547                         // subdivide curve => callback with subdivided parts:
548                         boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1,
549                                                               orCode, this);
550                         // reentrance is done:
551                         subdivide = true;
552                         if (ret) {
553                             return;
554                         }
555                     }
556                     // already subdivided so render it
557                 } else {
558                     this.cOutCode = outcode1;
559                     _moveTo(x1, y1, outcode0);
560                     opened = true;
561                     return;
562                 }
563             }
564 
565             this.cOutCode = outcode1;
566         }
567 
568         double dx = x1 - cx0;
569         double dy = y1 - cy0;
570         if (dx == 0.0d && dy == 0.0d) {
571             dx = 1.0d;
572         }
573         computeOffset(dx, dy, lineWidth2, offset0);
574         final double mx = offset0[0];
575         final double my = offset0[1];
576 
577         drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my, outcode0);
578 
579         emitLineTo(cx0 + mx, cy0 + my);
580         emitLineTo( x1 + mx,  y1 + my);
581 
582         emitLineToRev(cx0 - mx, cy0 - my);
583         emitLineToRev( x1 - mx,  y1 - my);
584 
585         this.prev = DRAWING_OP_TO;
586         this.cx0 = x1;
587         this.cy0 = y1;
588         this.cdx = dx;
589         this.cdy = dy;
590         this.cmx = mx;
591         this.cmy = my;
592     }
593 
594     @Override
closePath()595     public void closePath() {
596         // distinguish empty path at all vs opened path ?
597         if (prev != DRAWING_OP_TO && !opened) {
598             if (prev == CLOSE) {
599                 return;
600             }
601             emitMoveTo(cx0, cy0 - lineWidth2);
602 
603             this.sdx = 1.0d;
604             this.sdy = 0.0d;
605             this.cdx = 1.0d;
606             this.cdy = 0.0d;
607 
608             this.smx = 0.0d;
609             this.smy = -lineWidth2;
610             this.cmx = 0.0d;
611             this.cmy = -lineWidth2;
612 
613             finish(cOutCode);
614             return;
615         }
616 
617         // basic acceptance criteria
618         if ((sOutCode & cOutCode) == 0) {
619             if (cx0 != sx0 || cy0 != sy0) {
620                 lineTo(sx0, sy0, true);
621             }
622 
623             drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy, sOutCode);
624 
625             emitLineTo(sx0 + smx, sy0 + smy);
626 
627             if (opened) {
628                 emitLineTo(sx0 - smx, sy0 - smy);
629             } else {
630                 emitMoveTo(sx0 - smx, sy0 - smy);
631             }
632         }
633         // Ignore caps like finish(false)
634         emitReverse();
635 
636         this.prev = CLOSE;
637         this.cx0 = sx0;
638         this.cy0 = sy0;
639         this.cOutCode = sOutCode;
640 
641         if (opened) {
642             // do not emit close
643             opened = false;
644         } else {
645             emitClose();
646         }
647     }
648 
emitReverse()649     private void emitReverse() {
650         reverse.popAll(out);
651     }
652 
653     @Override
pathDone()654     public void pathDone() {
655         if (prev == DRAWING_OP_TO) {
656             finish(cOutCode);
657         }
658 
659         out.pathDone();
660 
661         // this shouldn't matter since this object won't be used
662         // after the call to this method.
663         this.prev = CLOSE;
664 
665         // Dispose this instance:
666         dispose();
667     }
668 
finish(final int outcode)669     private void finish(final int outcode) {
670         // Problem: impossible to guess if the path will be closed in advance
671         //          i.e. if caps must be drawn or not ?
672         // Solution: use the ClosedPathDetector before Stroker to determine
673         // if the path is a closed path or not
674         if (rdrCtx.closedPath) {
675             emitReverse();
676         } else {
677             if (outcode == 0) {
678                 // current point = end's cap:
679                 if (capStyle == CAP_ROUND) {
680                     drawRoundCap(cx0, cy0, cmx, cmy);
681                 } else if (capStyle == CAP_SQUARE) {
682                     emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
683                     emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
684                 }
685             }
686             emitReverse();
687 
688             if (!capStart) {
689                 capStart = true;
690 
691                 if (sOutCode == 0) {
692                     // starting point = initial cap:
693                     if (capStyle == CAP_ROUND) {
694                         drawRoundCap(sx0, sy0, -smx, -smy);
695                     } else if (capStyle == CAP_SQUARE) {
696                         emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
697                         emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
698                     }
699                 }
700             }
701         }
702         emitClose();
703     }
704 
emitMoveTo(final double x0, final double y0)705     private void emitMoveTo(final double x0, final double y0) {
706         out.moveTo(x0, y0);
707     }
708 
emitLineTo(final double x1, final double y1)709     private void emitLineTo(final double x1, final double y1) {
710         out.lineTo(x1, y1);
711     }
712 
emitLineToRev(final double x1, final double y1)713     private void emitLineToRev(final double x1, final double y1) {
714         reverse.pushLine(x1, y1);
715     }
716 
emitLineTo(final double x1, final double y1, final boolean rev)717     private void emitLineTo(final double x1, final double y1,
718                             final boolean rev)
719     {
720         if (rev) {
721             emitLineToRev(x1, y1);
722         } else {
723             emitLineTo(x1, y1);
724         }
725     }
726 
emitQuadTo(final double x1, final double y1, final double x2, final double y2)727     private void emitQuadTo(final double x1, final double y1,
728                             final double x2, final double y2)
729     {
730         out.quadTo(x1, y1, x2, y2);
731     }
732 
emitQuadToRev(final double x0, final double y0, final double x1, final double y1)733     private void emitQuadToRev(final double x0, final double y0,
734                                final double x1, final double y1)
735     {
736         reverse.pushQuad(x0, y0, x1, y1);
737     }
738 
emitCurveTo(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3)739     private void emitCurveTo(final double x1, final double y1,
740                              final double x2, final double y2,
741                              final double x3, final double y3)
742     {
743         out.curveTo(x1, y1, x2, y2, x3, y3);
744     }
745 
emitCurveToRev(final double x0, final double y0, final double x1, final double y1, final double x2, final double y2)746     private void emitCurveToRev(final double x0, final double y0,
747                                 final double x1, final double y1,
748                                 final double x2, final double y2)
749     {
750         reverse.pushCubic(x0, y0, x1, y1, x2, y2);
751     }
752 
emitCurveTo(final double x0, final double y0, final double x1, final double y1, final double x2, final double y2, final double x3, final double y3, final boolean rev)753     private void emitCurveTo(final double x0, final double y0,
754                              final double x1, final double y1,
755                              final double x2, final double y2,
756                              final double x3, final double y3, final boolean rev)
757     {
758         if (rev) {
759             reverse.pushCubic(x0, y0, x1, y1, x2, y2);
760         } else {
761             out.curveTo(x1, y1, x2, y2, x3, y3);
762         }
763     }
764 
emitClose()765     private void emitClose() {
766         out.closePath();
767     }
768 
drawJoin(double pdx, double pdy, double x0, double y0, double dx, double dy, double omx, double omy, double mx, double my, final int outcode)769     private void drawJoin(double pdx, double pdy,
770                           double x0, double y0,
771                           double dx, double dy,
772                           double omx, double omy,
773                           double mx, double my,
774                           final int outcode)
775     {
776         if (prev != DRAWING_OP_TO) {
777             emitMoveTo(x0 + mx, y0 + my);
778             if (!opened) {
779                 this.sdx = dx;
780                 this.sdy = dy;
781                 this.smx = mx;
782                 this.smy = my;
783             }
784         } else {
785             final boolean cw = isCW(pdx, pdy, dx, dy);
786             if (outcode == 0) {
787                 if (joinStyle == JOIN_MITER) {
788                     drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
789                 } else if (joinStyle == JOIN_ROUND) {
790                     mayDrawRoundJoin(x0, y0, omx, omy, mx, my, cw);
791                 }
792             }
793             emitLineTo(x0, y0, !cw);
794         }
795         prev = DRAWING_OP_TO;
796     }
797 
within(final double x1, final double y1, final double x2, final double y2, final double err)798     private static boolean within(final double x1, final double y1,
799                                   final double x2, final double y2,
800                                   final double err)
801     {
802         assert err > 0 : "";
803         // compare taxicab distance. ERR will always be small, so using
804         // true distance won't give much benefit
805         return (DHelpers.within(x1, x2, err) && // we want to avoid calling Math.abs
806                 DHelpers.within(y1, y2, err));  // this is just as good.
807     }
808 
getLineOffsets(final double x1, final double y1, final double x2, final double y2, final double[] left, final double[] right)809     private void getLineOffsets(final double x1, final double y1,
810                                 final double x2, final double y2,
811                                 final double[] left, final double[] right)
812     {
813         computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0);
814         final double mx = offset0[0];
815         final double my = offset0[1];
816         left[0] = x1 + mx;
817         left[1] = y1 + my;
818         left[2] = x2 + mx;
819         left[3] = y2 + my;
820 
821         right[0] = x1 - mx;
822         right[1] = y1 - my;
823         right[2] = x2 - mx;
824         right[3] = y2 - my;
825     }
826 
computeOffsetCubic(final double[] pts, final int off, final double[] leftOff, final double[] rightOff)827     private int computeOffsetCubic(final double[] pts, final int off,
828                                    final double[] leftOff,
829                                    final double[] rightOff)
830     {
831         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
832         // vanishes, which creates problems with computeOffset. Usually
833         // this happens when this stroker object is trying to widen
834         // a curve with a cusp. What happens is that curveTo splits
835         // the input curve at the cusp, and passes it to this function.
836         // because of inaccuracies in the splitting, we consider points
837         // equal if they're very close to each other.
838         final double x1 = pts[off    ], y1 = pts[off + 1];
839         final double x2 = pts[off + 2], y2 = pts[off + 3];
840         final double x3 = pts[off + 4], y3 = pts[off + 5];
841         final double x4 = pts[off + 6], y4 = pts[off + 7];
842 
843         double dx4 = x4 - x3;
844         double dy4 = y4 - y3;
845         double dx1 = x2 - x1;
846         double dy1 = y2 - y1;
847 
848         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
849         // in which case ignore if p1 == p2
850         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2));
851         final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0d * Math.ulp(y4));
852 
853         if (p1eqp2 && p3eqp4) {
854             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
855             return 4;
856         } else if (p1eqp2) {
857             dx1 = x3 - x1;
858             dy1 = y3 - y1;
859         } else if (p3eqp4) {
860             dx4 = x4 - x2;
861             dy4 = y4 - y2;
862         }
863 
864         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
865         double dotsq = (dx1 * dx4 + dy1 * dy4);
866         dotsq *= dotsq;
867         double l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4;
868 
869         if (DHelpers.within(dotsq, l1sq * l4sq, 4.0d * Math.ulp(dotsq))) {
870             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
871             return 4;
872         }
873 
874 //      What we're trying to do in this function is to approximate an ideal
875 //      offset curve (call it I) of the input curve B using a bezier curve Bp.
876 //      The constraints I use to get the equations are:
877 //
878 //      1. The computed curve Bp should go through I(0) and I(1). These are
879 //      x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find
880 //      4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p).
881 //
882 //      2. Bp should have slope equal in absolute value to I at the endpoints. So,
883 //      (by the way, the operator || in the comments below means "aligned with".
884 //      It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that
885 //      vectors I'(0) and Bp'(0) are aligned, which is the same as saying
886 //      that the tangent lines of I and Bp at 0 are parallel. Mathematically
887 //      this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some
888 //      nonzero constant.)
889 //      I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and
890 //      I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1).
891 //      We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same
892 //      is true for any bezier curve; therefore, we get the equations
893 //          (1) p2p = c1 * (p2-p1) + p1p
894 //          (2) p3p = c2 * (p4-p3) + p4p
895 //      We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number
896 //      of unknowns from 4 to 2 (i.e. just c1 and c2).
897 //      To eliminate these 2 unknowns we use the following constraint:
898 //
899 //      3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note
900 //      that I(0.5) is *the only* reason for computing dxm,dym. This gives us
901 //          (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to
902 //          (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3
903 //      We can substitute (1) and (2) from above into (4) and we get:
904 //          (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p
905 //      which is equivalent to
906 //          (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p)
907 //
908 //      The right side of this is a 2D vector, and we know I(0.5), which gives us
909 //      Bp(0.5), which gives us the value of the right side.
910 //      The left side is just a matrix vector multiplication in disguise. It is
911 //
912 //      [x2-x1, x4-x3][c1]
913 //      [y2-y1, y4-y3][c2]
914 //      which, is equal to
915 //      [dx1, dx4][c1]
916 //      [dy1, dy4][c2]
917 //      At this point we are left with a simple linear system and we solve it by
918 //      getting the inverse of the matrix above. Then we use [c1,c2] to compute
919 //      p2p and p3p.
920 
921         double x = (x1 + 3.0d * (x2 + x3) + x4) / 8.0d;
922         double y = (y1 + 3.0d * (y2 + y3) + y4) / 8.0d;
923         // (dxm,dym) is some tangent of B at t=0.5. This means it's equal to
924         // c*B'(0.5) for some constant c.
925         double dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2;
926 
927         // this computes the offsets at t=0, 0.5, 1, using the property that
928         // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to
929         // the (dx/dt, dy/dt) vectors at the endpoints.
930         computeOffset(dx1, dy1, lineWidth2, offset0);
931         computeOffset(dxm, dym, lineWidth2, offset1);
932         computeOffset(dx4, dy4, lineWidth2, offset2);
933         double x1p = x1 + offset0[0]; // start
934         double y1p = y1 + offset0[1]; // point
935         double xi  = x  + offset1[0]; // interpolation
936         double yi  = y  + offset1[1]; // point
937         double x4p = x4 + offset2[0]; // end
938         double y4p = y4 + offset2[1]; // point
939 
940         double invdet43 = 4.0d / (3.0d * (dx1 * dy4 - dy1 * dx4));
941 
942         double two_pi_m_p1_m_p4x = 2.0d * xi - x1p - x4p;
943         double two_pi_m_p1_m_p4y = 2.0d * yi - y1p - y4p;
944         double c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);
945         double c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);
946 
947         double x2p, y2p, x3p, y3p;
948         x2p = x1p + c1*dx1;
949         y2p = y1p + c1*dy1;
950         x3p = x4p + c2*dx4;
951         y3p = y4p + c2*dy4;
952 
953         leftOff[0] = x1p; leftOff[1] = y1p;
954         leftOff[2] = x2p; leftOff[3] = y2p;
955         leftOff[4] = x3p; leftOff[5] = y3p;
956         leftOff[6] = x4p; leftOff[7] = y4p;
957 
958         x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
959         xi = xi - 2.0d * offset1[0]; yi = yi - 2.0d * offset1[1];
960         x4p = x4 - offset2[0]; y4p = y4 - offset2[1];
961 
962         two_pi_m_p1_m_p4x = 2.0d * xi - x1p - x4p;
963         two_pi_m_p1_m_p4y = 2.0d * yi - y1p - y4p;
964         c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);
965         c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);
966 
967         x2p = x1p + c1*dx1;
968         y2p = y1p + c1*dy1;
969         x3p = x4p + c2*dx4;
970         y3p = y4p + c2*dy4;
971 
972         rightOff[0] = x1p; rightOff[1] = y1p;
973         rightOff[2] = x2p; rightOff[3] = y2p;
974         rightOff[4] = x3p; rightOff[5] = y3p;
975         rightOff[6] = x4p; rightOff[7] = y4p;
976         return 8;
977     }
978 
979     // compute offset curves using bezier spline through t=0.5 (i.e.
980     // ComputedCurve(0.5) == IdealParallelCurve(0.5))
981     // return the kind of curve in the right and left arrays.
computeOffsetQuad(final double[] pts, final int off, final double[] leftOff, final double[] rightOff)982     private int computeOffsetQuad(final double[] pts, final int off,
983                                   final double[] leftOff,
984                                   final double[] rightOff)
985     {
986         final double x1 = pts[off    ], y1 = pts[off + 1];
987         final double x2 = pts[off + 2], y2 = pts[off + 3];
988         final double x3 = pts[off + 4], y3 = pts[off + 5];
989 
990         final double dx3 = x3 - x2;
991         final double dy3 = y3 - y2;
992         final double dx1 = x2 - x1;
993         final double dy1 = y2 - y1;
994 
995         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
996         // vanishes, which creates problems with computeOffset. Usually
997         // this happens when this stroker object is trying to widen
998         // a curve with a cusp. What happens is that curveTo splits
999         // the input curve at the cusp, and passes it to this function.
1000         // because of inaccuracies in the splitting, we consider points
1001         // equal if they're very close to each other.
1002 
1003         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
1004         // in which case ignore.
1005         final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2));
1006         final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0d * Math.ulp(y3));
1007 
1008         if (p1eqp2 || p2eqp3) {
1009             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
1010             return 4;
1011         }
1012 
1013         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
1014         double dotsq = (dx1 * dx3 + dy1 * dy3);
1015         dotsq *= dotsq;
1016         double l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3;
1017 
1018         if (DHelpers.within(dotsq, l1sq * l3sq, 4.0d * Math.ulp(dotsq))) {
1019             getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
1020             return 4;
1021         }
1022 
1023         // this computes the offsets at t=0, 0.5, 1, using the property that
1024         // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to
1025         // the (dx/dt, dy/dt) vectors at the endpoints.
1026         computeOffset(dx1, dy1, lineWidth2, offset0);
1027         computeOffset(dx3, dy3, lineWidth2, offset1);
1028 
1029         double x1p = x1 + offset0[0]; // start
1030         double y1p = y1 + offset0[1]; // point
1031         double x3p = x3 + offset1[0]; // end
1032         double y3p = y3 + offset1[1]; // point
1033         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff);
1034         leftOff[0] = x1p; leftOff[1] = y1p;
1035         leftOff[4] = x3p; leftOff[5] = y3p;
1036 
1037         x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
1038         x3p = x3 - offset1[0]; y3p = y3 - offset1[1];
1039         safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff);
1040         rightOff[0] = x1p; rightOff[1] = y1p;
1041         rightOff[4] = x3p; rightOff[5] = y3p;
1042         return 6;
1043     }
1044 
1045     @Override
curveTo(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3)1046     public void curveTo(final double x1, final double y1,
1047                         final double x2, final double y2,
1048                         final double x3, final double y3)
1049     {
1050         final int outcode0 = this.cOutCode;
1051 
1052         if (clipRect != null) {
1053             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1054             final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
1055             final int outcode3 = DHelpers.outcode(x3, y3, clipRect);
1056 
1057             // Should clip
1058             final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
1059             if (orCode != 0) {
1060                 final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
1061 
1062                 // basic rejection criteria:
1063                 if (sideCode == 0) {
1064                     // overlap clip:
1065                     if (subdivide) {
1066                         // avoid reentrance
1067                         subdivide = false;
1068                         // subdivide curve => callback with subdivided parts:
1069                         boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
1070                                                                x2, y2, x3, y3,
1071                                                                orCode, this);
1072                         // reentrance is done:
1073                         subdivide = true;
1074                         if (ret) {
1075                             return;
1076                         }
1077                     }
1078                     // already subdivided so render it
1079                 } else {
1080                     this.cOutCode = outcode3;
1081                     _moveTo(x3, y3, outcode0);
1082                     opened = true;
1083                     return;
1084                 }
1085             }
1086 
1087             this.cOutCode = outcode3;
1088         }
1089         _curveTo(x1, y1, x2, y2, x3, y3, outcode0);
1090     }
1091 
_curveTo(final double x1, final double y1, final double x2, final double y2, final double x3, final double y3, final int outcode0)1092     private void _curveTo(final double x1, final double y1,
1093                           final double x2, final double y2,
1094                           final double x3, final double y3,
1095                           final int outcode0)
1096     {
1097         // need these so we can update the state at the end of this method
1098         double dxs = x1 - cx0;
1099         double dys = y1 - cy0;
1100         double dxf = x3 - x2;
1101         double dyf = y3 - y2;
1102 
1103         if ((dxs == 0.0d) && (dys == 0.0d)) {
1104             dxs = x2 - cx0;
1105             dys = y2 - cy0;
1106             if ((dxs == 0.0d) && (dys == 0.0d)) {
1107                 dxs = x3 - cx0;
1108                 dys = y3 - cy0;
1109             }
1110         }
1111         if ((dxf == 0.0d) && (dyf == 0.0d)) {
1112             dxf = x3 - x1;
1113             dyf = y3 - y1;
1114             if ((dxf == 0.0d) && (dyf == 0.0d)) {
1115                 dxf = x3 - cx0;
1116                 dyf = y3 - cy0;
1117             }
1118         }
1119         if ((dxs == 0.0d) && (dys == 0.0d)) {
1120             // this happens if the "curve" is just a point
1121             // fix outcode0 for lineTo() call:
1122             if (clipRect != null) {
1123                 this.cOutCode = outcode0;
1124             }
1125             lineTo(cx0, cy0);
1126             return;
1127         }
1128 
1129         // if these vectors are too small, normalize them, to avoid future
1130         // precision problems.
1131         if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1132             final double len = Math.sqrt(dxs * dxs + dys * dys);
1133             dxs /= len;
1134             dys /= len;
1135         }
1136         if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1137             final double len = Math.sqrt(dxf * dxf + dyf * dyf);
1138             dxf /= len;
1139             dyf /= len;
1140         }
1141 
1142         computeOffset(dxs, dys, lineWidth2, offset0);
1143         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1144 
1145         int nSplits = 0;
1146         final double[] mid;
1147         final double[] l = lp;
1148 
1149         if (monotonize) {
1150             // monotonize curve:
1151             final CurveBasicMonotonizer monotonizer
1152                 = rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3);
1153 
1154             nSplits = monotonizer.nbSplits;
1155             mid = monotonizer.middle;
1156         } else {
1157             // use left instead:
1158             mid = l;
1159             mid[0] = cx0; mid[1] = cy0;
1160             mid[2] = x1;  mid[3] = y1;
1161             mid[4] = x2;  mid[5] = y2;
1162             mid[6] = x3;  mid[7] = y3;
1163         }
1164         final double[] r = rp;
1165 
1166         int kind = 0;
1167         for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
1168             kind = computeOffsetCubic(mid, off, l, r);
1169 
1170             emitLineTo(l[0], l[1]);
1171 
1172             switch(kind) {
1173             case 8:
1174                 emitCurveTo(l[2], l[3], l[4], l[5], l[6], l[7]);
1175                 emitCurveToRev(r[0], r[1], r[2], r[3], r[4], r[5]);
1176                 break;
1177             case 4:
1178                 emitLineTo(l[2], l[3]);
1179                 emitLineToRev(r[0], r[1]);
1180                 break;
1181             default:
1182             }
1183             emitLineToRev(r[kind - 2], r[kind - 1]);
1184         }
1185 
1186         this.prev = DRAWING_OP_TO;
1187         this.cx0 = x3;
1188         this.cy0 = y3;
1189         this.cdx = dxf;
1190         this.cdy = dyf;
1191         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1192         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1193     }
1194 
1195     @Override
quadTo(final double x1, final double y1, final double x2, final double y2)1196     public void quadTo(final double x1, final double y1,
1197                        final double x2, final double y2)
1198     {
1199         final int outcode0 = this.cOutCode;
1200 
1201         if (clipRect != null) {
1202             final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
1203             final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
1204 
1205             // Should clip
1206             final int orCode = (outcode0 | outcode1 | outcode2);
1207             if (orCode != 0) {
1208                 final int sideCode = outcode0 & outcode1 & outcode2;
1209 
1210                 // basic rejection criteria:
1211                 if (sideCode == 0) {
1212                     // overlap clip:
1213                     if (subdivide) {
1214                         // avoid reentrance
1215                         subdivide = false;
1216                         // subdivide curve => call lineTo() with subdivided curves:
1217                         boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
1218                                                               x2, y2, orCode, this);
1219                         // reentrance is done:
1220                         subdivide = true;
1221                         if (ret) {
1222                             return;
1223                         }
1224                     }
1225                     // already subdivided so render it
1226                 } else {
1227                     this.cOutCode = outcode2;
1228                     _moveTo(x2, y2, outcode0);
1229                     opened = true;
1230                     return;
1231                 }
1232             }
1233 
1234             this.cOutCode = outcode2;
1235         }
1236         _quadTo(x1, y1, x2, y2, outcode0);
1237     }
1238 
_quadTo(final double x1, final double y1, final double x2, final double y2, final int outcode0)1239     private void _quadTo(final double x1, final double y1,
1240                          final double x2, final double y2,
1241                          final int outcode0)
1242     {
1243         // need these so we can update the state at the end of this method
1244         double dxs = x1 - cx0;
1245         double dys = y1 - cy0;
1246         double dxf = x2 - x1;
1247         double dyf = y2 - y1;
1248 
1249         if (((dxs == 0.0d) && (dys == 0.0d)) || ((dxf == 0.0d) && (dyf == 0.0d))) {
1250             dxs = dxf = x2 - cx0;
1251             dys = dyf = y2 - cy0;
1252         }
1253         if ((dxs == 0.0d) && (dys == 0.0d)) {
1254             // this happens if the "curve" is just a point
1255             // fix outcode0 for lineTo() call:
1256             if (clipRect != null) {
1257                 this.cOutCode = outcode0;
1258             }
1259             lineTo(cx0, cy0);
1260             return;
1261         }
1262         // if these vectors are too small, normalize them, to avoid future
1263         // precision problems.
1264         if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
1265             final double len = Math.sqrt(dxs * dxs + dys * dys);
1266             dxs /= len;
1267             dys /= len;
1268         }
1269         if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
1270             final double len = Math.sqrt(dxf * dxf + dyf * dyf);
1271             dxf /= len;
1272             dyf /= len;
1273         }
1274         computeOffset(dxs, dys, lineWidth2, offset0);
1275         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
1276 
1277         int nSplits = 0;
1278         final double[] mid;
1279         final double[] l = lp;
1280 
1281         if (monotonize) {
1282             // monotonize quad:
1283             final CurveBasicMonotonizer monotonizer
1284                 = rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2);
1285 
1286             nSplits = monotonizer.nbSplits;
1287             mid = monotonizer.middle;
1288         } else {
1289             // use left instead:
1290             mid = l;
1291             mid[0] = cx0; mid[1] = cy0;
1292             mid[2] = x1;  mid[3] = y1;
1293             mid[4] = x2;  mid[5] = y2;
1294         }
1295         final double[] r = rp;
1296 
1297         int kind = 0;
1298         for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
1299             kind = computeOffsetQuad(mid, off, l, r);
1300 
1301             emitLineTo(l[0], l[1]);
1302 
1303             switch(kind) {
1304             case 6:
1305                 emitQuadTo(l[2], l[3], l[4], l[5]);
1306                 emitQuadToRev(r[0], r[1], r[2], r[3]);
1307                 break;
1308             case 4:
1309                 emitLineTo(l[2], l[3]);
1310                 emitLineToRev(r[0], r[1]);
1311                 break;
1312             default:
1313             }
1314             emitLineToRev(r[kind - 2], r[kind - 1]);
1315         }
1316 
1317         this.prev = DRAWING_OP_TO;
1318         this.cx0 = x2;
1319         this.cy0 = y2;
1320         this.cdx = dxf;
1321         this.cdy = dyf;
1322         this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
1323         this.cmy = (l[kind - 1] - r[kind - 1]) / 2.0d;
1324     }
1325 
getNativeConsumer()1326     @Override public long getNativeConsumer() {
1327         throw new InternalError("Stroker doesn't use a native consumer");
1328     }
1329 }
1330