Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java
41159 views
1
/*
2
* Copyright (c) 2007, 2021, 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.Helpers.PolyStack;
30
import sun.java2d.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer;
31
import sun.java2d.marlin.TransformingPathConsumer2D.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 Stroker 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 RendererContext rdrCtx;
87
88
// dirty curve
89
final Curve 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>Stroker</code>.
112
* @param rdrCtx per-thread renderer context
113
*/
114
Stroker(final RendererContext 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>Stroker</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
*/
145
Stroker 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
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
*/
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
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.
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
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
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.
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
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]
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]
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
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
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 = Helpers.outcode(x0, y0, clipRect);
500
this.cOutCode = outcode;
501
this.sOutCode = outcode;
502
}
503
}
504
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
524
public void lineTo(final double x1, final double y1) {
525
lineTo(x1, y1, false);
526
}
527
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 = Helpers.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
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
649
private void emitReverse() {
650
reverse.popAll(out);
651
}
652
653
@Override
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
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
705
private void emitMoveTo(final double x0, final double y0) {
706
out.moveTo(x0, y0);
707
}
708
709
private void emitLineTo(final double x1, final double y1) {
710
out.lineTo(x1, y1);
711
}
712
713
private void emitLineToRev(final double x1, final double y1) {
714
reverse.pushLine(x1, y1);
715
}
716
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
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
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
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
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
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
765
private void emitClose() {
766
out.closePath();
767
}
768
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
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 (Helpers.within(x1, x2, err) && // we want to avoid calling Math.abs
806
Helpers.within(y1, y2, err)); // this is just as good.
807
}
808
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
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 (Helpers.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.
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 (Helpers.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
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 = Helpers.outcode(x1, y1, clipRect);
1054
final int outcode2 = Helpers.outcode(x2, y2, clipRect);
1055
final int outcode3 = Helpers.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
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
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 = Helpers.outcode(x1, y1, clipRect);
1203
final int outcode2 = Helpers.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
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
1326
@Override public long getNativeConsumer() {
1327
throw new InternalError("Stroker doesn't use a native consumer");
1328
}
1329
}
1330
1331