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