blob: 45e4ac7f051de78dce7a9133e05174586699de64 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.java2d.pisces;
27
28public class Stroker extends LineSink {
29
30 private static final int MOVE_TO = 0;
31 private static final int LINE_TO = 1;
32 private static final int CLOSE = 2;
33
34 /**
35 * Constant value for join style.
36 */
37 public static final int JOIN_MITER = 0;
38
39 /**
40 * Constant value for join style.
41 */
42 public static final int JOIN_ROUND = 1;
43
44 /**
45 * Constant value for join style.
46 */
47 public static final int JOIN_BEVEL = 2;
48
49 /**
50 * Constant value for end cap style.
51 */
52 public static final int CAP_BUTT = 0;
53
54 /**
55 * Constant value for end cap style.
56 */
57 public static final int CAP_ROUND = 1;
58
59 /**
60 * Constant value for end cap style.
61 */
62 public static final int CAP_SQUARE = 2;
63
64 LineSink output;
65
66 int lineWidth;
67 int capStyle;
68 int joinStyle;
69 int miterLimit;
70
71 Transform4 transform;
72 int m00, m01;
73 int m10, m11;
74
75 int lineWidth2;
76 long scaledLineWidth2;
77
78 // For any pen offset (pen_dx, pen_dy) that does not depend on
79 // the line orientation, the pen should be transformed so that:
80 //
81 // pen_dx' = m00*pen_dx + m01*pen_dy
82 // pen_dy' = m10*pen_dx + m11*pen_dy
83 //
84 // For a round pen, this means:
85 //
86 // pen_dx(r, theta) = r*cos(theta)
87 // pen_dy(r, theta) = r*sin(theta)
88 //
89 // pen_dx'(r, theta) = r*(m00*cos(theta) + m01*sin(theta))
90 // pen_dy'(r, theta) = r*(m10*cos(theta) + m11*sin(theta))
91 int numPenSegments;
92 int[] pen_dx;
93 int[] pen_dy;
94 boolean[] penIncluded;
95 int[] join;
96
97 int[] offset = new int[2];
98 int[] reverse = new int[100];
99 int[] miter = new int[2];
100 long miterLimitSq;
101
102 int prev;
103 int rindex;
104 boolean started;
105 boolean lineToOrigin;
106 boolean joinToOrigin;
107
108 int sx0, sy0, sx1, sy1, x0, y0, x1, y1;
109 int mx0, my0, mx1, my1, omx, omy;
110 int lx0, ly0, lx1, ly1, lx0p, ly0p, px0, py0;
111
112 double m00_2_m01_2;
113 double m10_2_m11_2;
114 double m00_m10_m01_m11;
115
116 /**
117 * Empty constructor. <code>setOutput</code> and
118 * <code>setParameters</code> must be called prior to calling any
119 * other methods.
120 */
121 public Stroker() {}
122
123 /**
124 * Constructs a <code>Stroker</code>.
125 *
126 * @param output an output <code>LineSink</code>.
127 * @param lineWidth the desired line width in pixels, in S15.16
128 * format.
129 * @param capStyle the desired end cap style, one of
130 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
131 * <code>CAP_SQUARE</code>.
132 * @param joinStyle the desired line join style, one of
133 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
134 * <code>JOIN_BEVEL</code>.
135 * @param miterLimit the desired miter limit, in S15.16 format.
136 * @param transform a <code>Transform4</code> object indicating
137 * the transform that has been previously applied to all incoming
138 * coordinates. This is required in order to produce consistently
139 * shaped end caps and joins.
140 */
141 public Stroker(LineSink output,
142 int lineWidth,
143 int capStyle,
144 int joinStyle,
145 int miterLimit,
146 Transform4 transform) {
147 setOutput(output);
148 setParameters(lineWidth, capStyle, joinStyle, miterLimit, transform);
149 }
150
151 /**
152 * Sets the output <code>LineSink</code> of this
153 * <code>Stroker</code>.
154 *
155 * @param output an output <code>LineSink</code>.
156 */
157 public void setOutput(LineSink output) {
158 this.output = output;
159 }
160
161 /**
162 * Sets the parameters of this <code>Stroker</code>.
163 * @param lineWidth the desired line width in pixels, in S15.16
164 * format.
165 * @param capStyle the desired end cap style, one of
166 * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
167 * <code>CAP_SQUARE</code>.
168 * @param joinStyle the desired line join style, one of
169 * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
170 * <code>JOIN_BEVEL</code>.
171 * @param miterLimit the desired miter limit, in S15.16 format.
172 * @param transform a <code>Transform4</code> object indicating
173 * the transform that has been previously applied to all incoming
174 * coordinates. This is required in order to produce consistently
175 * shaped end caps and joins.
176 */
177 public void setParameters(int lineWidth,
178 int capStyle,
179 int joinStyle,
180 int miterLimit,
181 Transform4 transform) {
182 this.lineWidth = lineWidth;
183 this.lineWidth2 = lineWidth >> 1;
184 this.scaledLineWidth2 = (long)transform.m00*lineWidth2;
185 this.capStyle = capStyle;
186 this.joinStyle = joinStyle;
187 this.miterLimit = miterLimit;
188
189 this.transform = transform;
190 this.m00 = transform.m00;
191 this.m01 = transform.m01;
192 this.m10 = transform.m10;
193 this.m11 = transform.m11;
194
195 this.m00_2_m01_2 = (double)m00*m00 + (double)m01*m01;
196 this.m10_2_m11_2 = (double)m10*m10 + (double)m11*m11;
197 this.m00_m10_m01_m11 = (double)m00*m10 + (double)m01*m11;
198
199 double dm00 = m00/65536.0;
200 double dm01 = m01/65536.0;
201 double dm10 = m10/65536.0;
202 double dm11 = m11/65536.0;
203 double determinant = dm00*dm11 - dm01*dm10;
204
205 if (joinStyle == JOIN_MITER) {
206 double limit =
207 (miterLimit/65536.0)*(lineWidth2/65536.0)*determinant;
208 double limitSq = limit*limit;
209 this.miterLimitSq = (long)(limitSq*65536.0*65536.0);
210 }
211
212 this.numPenSegments = (int)(3.14159f*lineWidth/65536.0f);
213 if (pen_dx == null || pen_dx.length < numPenSegments) {
214 this.pen_dx = new int[numPenSegments];
215 this.pen_dy = new int[numPenSegments];
216 this.penIncluded = new boolean[numPenSegments];
217 this.join = new int[2*numPenSegments];
218 }
219
220 for (int i = 0; i < numPenSegments; i++) {
221 double r = lineWidth/2.0;
222 double theta = (double)i*2.0*Math.PI/numPenSegments;
223
224 double cos = Math.cos(theta);
225 double sin = Math.sin(theta);
226 pen_dx[i] = (int)(r*(dm00*cos + dm01*sin));
227 pen_dy[i] = (int)(r*(dm10*cos + dm11*sin));
228 }
229
230 prev = CLOSE;
231 rindex = 0;
232 started = false;
233 lineToOrigin = false;
234 }
235
236 private void computeOffset(int x0, int y0, int x1, int y1, int[] m) {
237 long lx = (long)x1 - (long)x0;
238 long ly = (long)y1 - (long)y0;
239
240 int dx, dy;
241 if (m00 > 0 && m00 == m11 && m01 == 0 & m10 == 0) {
242 long ilen = PiscesMath.hypot(lx, ly);
243 if (ilen == 0) {
244 dx = dy = 0;
245 } else {
246 dx = (int)( (ly*scaledLineWidth2)/ilen >> 16);
247 dy = (int)(-(lx*scaledLineWidth2)/ilen >> 16);
248 }
249 } else {
250 double dlx = x1 - x0;
251 double dly = y1 - y0;
252 double det = (double)m00*m11 - (double)m01*m10;
253 int sdet = (det > 0) ? 1 : -1;
254 double a = dly*m00 - dlx*m10;
255 double b = dly*m01 - dlx*m11;
256 double dh = PiscesMath.hypot(a, b);
257 double div = sdet*lineWidth2/(65536.0*dh);
258 double ddx = dly*m00_2_m01_2 - dlx*m00_m10_m01_m11;
259 double ddy = dly*m00_m10_m01_m11 - dlx*m10_2_m11_2;
260 dx = (int)(ddx*div);
261 dy = (int)(ddy*div);
262 }
263
264 m[0] = dx;
265 m[1] = dy;
266 }
267
268 private void ensureCapacity(int newrindex) {
269 if (reverse.length < newrindex) {
270 int[] tmp = new int[Math.max(newrindex, 6*reverse.length/5)];
271 System.arraycopy(reverse, 0, tmp, 0, rindex);
272 this.reverse = tmp;
273 }
274 }
275
276 private boolean isCCW(int x0, int y0,
277 int x1, int y1,
278 int x2, int y2) {
279 int dx0 = x1 - x0;
280 int dy0 = y1 - y0;
281 int dx1 = x2 - x1;
282 int dy1 = y2 - y1;
283 return (long)dx0*dy1 < (long)dy0*dx1;
284 }
285
286 private boolean side(int x, int y, int x0, int y0, int x1, int y1) {
287 long lx = x;
288 long ly = y;
289 long lx0 = x0;
290 long ly0 = y0;
291 long lx1 = x1;
292 long ly1 = y1;
293
294 return (ly0 - ly1)*lx + (lx1 - lx0)*ly + (lx0*ly1 - lx1*ly0) > 0;
295 }
296
297 private int computeRoundJoin(int cx, int cy,
298 int xa, int ya,
299 int xb, int yb,
300 int side,
301 boolean flip,
302 int[] join) {
303 int px, py;
304 int ncoords = 0;
305
306 boolean centerSide;
307 if (side == 0) {
308 centerSide = side(cx, cy, xa, ya, xb, yb);
309 } else {
310 centerSide = (side == 1) ? true : false;
311 }
312 for (int i = 0; i < numPenSegments; i++) {
313 px = cx + pen_dx[i];
314 py = cy + pen_dy[i];
315
316 boolean penSide = side(px, py, xa, ya, xb, yb);
317 if (penSide != centerSide) {
318 penIncluded[i] = true;
319 } else {
320 penIncluded[i] = false;
321 }
322 }
323
324 int start = -1, end = -1;
325 for (int i = 0; i < numPenSegments; i++) {
326 if (penIncluded[i] &&
327 !penIncluded[(i + numPenSegments - 1) % numPenSegments]) {
328 start = i;
329 }
330 if (penIncluded[i] &&
331 !penIncluded[(i + 1) % numPenSegments]) {
332 end = i;
333 }
334 }
335
336 if (end < start) {
337 end += numPenSegments;
338 }
339
340 if (start != -1 && end != -1) {
341 long dxa = cx + pen_dx[start] - xa;
342 long dya = cy + pen_dy[start] - ya;
343 long dxb = cx + pen_dx[start] - xb;
344 long dyb = cy + pen_dy[start] - yb;
345
346 boolean rev = (dxa*dxa + dya*dya > dxb*dxb + dyb*dyb);
347 int i = rev ? end : start;
348 int incr = rev ? -1 : 1;
349 while (true) {
350 int idx = i % numPenSegments;
351 px = cx + pen_dx[idx];
352 py = cy + pen_dy[idx];
353 join[ncoords++] = px;
354 join[ncoords++] = py;
355 if (i == (rev ? start : end)) {
356 break;
357 }
358 i += incr;
359 }
360 }
361
362 return ncoords/2;
363 }
364
365 private static final long ROUND_JOIN_THRESHOLD = 1000L;
366 private static final long ROUND_JOIN_INTERNAL_THRESHOLD = 1000000000L;
367
368 private void drawRoundJoin(int x, int y,
369 int omx, int omy, int mx, int my,
370 int side,
371 boolean flip,
372 boolean rev,
373 long threshold) {
374 if ((omx == 0 && omy == 0) || (mx == 0 && my == 0)) {
375 return;
376 }
377
378 long domx = (long)omx - mx;
379 long domy = (long)omy - my;
380 long len = domx*domx + domy*domy;
381 if (len < threshold) {
382 return;
383 }
384
385 if (rev) {
386 omx = -omx;
387 omy = -omy;
388 mx = -mx;
389 my = -my;
390 }
391
392 int bx0 = x + omx;
393 int by0 = y + omy;
394 int bx1 = x + mx;
395 int by1 = y + my;
396
397 int npoints = computeRoundJoin(x, y,
398 bx0, by0, bx1, by1, side, flip,
399 join);
400 for (int i = 0; i < npoints; i++) {
401 emitLineTo(join[2*i], join[2*i + 1], rev);
402 }
403 }
404
405 // Return the intersection point of the lines (ix0, iy0) -> (ix1, iy1)
406 // and (ix0p, iy0p) -> (ix1p, iy1p) in m[0] and m[1]
407 private void computeMiter(int ix0, int iy0, int ix1, int iy1,
408 int ix0p, int iy0p, int ix1p, int iy1p,
409 int[] m) {
410 long x0 = ix0;
411 long y0 = iy0;
412 long x1 = ix1;
413 long y1 = iy1;
414
415 long x0p = ix0p;
416 long y0p = iy0p;
417 long x1p = ix1p;
418 long y1p = iy1p;
419
420 long x10 = x1 - x0;
421 long y10 = y1 - y0;
422 long x10p = x1p - x0p;
423 long y10p = y1p - y0p;
424
425 long den = (x10*y10p - x10p*y10) >> 16;
426 if (den == 0) {
427 m[0] = ix0;
428 m[1] = iy0;
429 return;
430 }
431
432 long t = (x1p*(y0 - y0p) - x0*y10p + x0p*(y1p - y0)) >> 16;
433 m[0] = (int)(x0 + (t*x10)/den);
434 m[1] = (int)(y0 + (t*y10)/den);
435 }
436
437 private void drawMiter(int px0, int py0,
438 int x0, int y0,
439 int x1, int y1,
440 int omx, int omy, int mx, int my,
441 boolean rev) {
442 if (mx == omx && my == omy) {
443 return;
444 }
445 if (px0 == x0 && py0 == y0) {
446 return;
447 }
448 if (x0 == x1 && y0 == y1) {
449 return;
450 }
451
452 if (rev) {
453 omx = -omx;
454 omy = -omy;
455 mx = -mx;
456 my = -my;
457 }
458
459 computeMiter(px0 + omx, py0 + omy, x0 + omx, y0 + omy,
460 x0 + mx, y0 + my, x1 + mx, y1 + my,
461 miter);
462
463 // Compute miter length in untransformed coordinates
464 long dx = (long)miter[0] - x0;
465 long dy = (long)miter[1] - y0;
466 long a = (dy*m00 - dx*m10) >> 16;
467 long b = (dy*m01 - dx*m11) >> 16;
468 long lenSq = a*a + b*b;
469
470 if (lenSq < miterLimitSq) {
471 emitLineTo(miter[0], miter[1], rev);
472 }
473 }
474
475
476 public void moveTo(int x0, int y0) {
477 // System.out.println("Stroker.moveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")");
478
479 if (lineToOrigin) {
480 // not closing the path, do the previous lineTo
481 lineToImpl(sx0, sy0, joinToOrigin);
482 lineToOrigin = false;
483 }
484
485 if (prev == LINE_TO) {
486 finish();
487 }
488
489 this.sx0 = this.x0 = x0;
490 this.sy0 = this.y0 = y0;
491 this.rindex = 0;
492 this.started = false;
493 this.joinSegment = false;
494 this.prev = MOVE_TO;
495 }
496
497 boolean joinSegment = false;
498
499 public void lineJoin() {
500 // System.out.println("Stroker.lineJoin()");
501 this.joinSegment = true;
502 }
503
504 public void lineTo(int x1, int y1) {
505 // System.out.println("Stroker.lineTo(" + x1/65536.0 + ", " + y1/65536.0 + ")");
506
507 if (lineToOrigin) {
508 if (x1 == sx0 && y1 == sy0) {
509 // staying in the starting point
510 return;
511 }
512
513 // not closing the path, do the previous lineTo
514 lineToImpl(sx0, sy0, joinToOrigin);
515 lineToOrigin = false;
516 } else if (x1 == x0 && y1 == y0) {
517 return;
518 } else if (x1 == sx0 && y1 == sy0) {
519 lineToOrigin = true;
520 joinToOrigin = joinSegment;
521 joinSegment = false;
522 return;
523 }
524
525 lineToImpl(x1, y1, joinSegment);
526 joinSegment = false;
527 }
528
529 private void lineToImpl(int x1, int y1, boolean joinSegment) {
530 computeOffset(x0, y0, x1, y1, offset);
531 int mx = offset[0];
532 int my = offset[1];
533
534 if (!started) {
535 emitMoveTo(x0 + mx, y0 + my);
536 this.sx1 = x1;
537 this.sy1 = y1;
538 this.mx0 = mx;
539 this.my0 = my;
540 started = true;
541 } else {
542 boolean ccw = isCCW(px0, py0, x0, y0, x1, y1);
543 if (joinSegment) {
544 if (joinStyle == JOIN_MITER) {
545 drawMiter(px0, py0, x0, y0, x1, y1, omx, omy, mx, my,
546 ccw);
547 } else if (joinStyle == JOIN_ROUND) {
548 drawRoundJoin(x0, y0,
549 omx, omy,
550 mx, my, 0, false, ccw,
551 ROUND_JOIN_THRESHOLD);
552 }
553 } else {
554 // Draw internal joins as round
555 drawRoundJoin(x0, y0,
556 omx, omy,
557 mx, my, 0, false, ccw,
558 ROUND_JOIN_INTERNAL_THRESHOLD);
559 }
560
561 emitLineTo(x0, y0, !ccw);
562 }
563
564 emitLineTo(x0 + mx, y0 + my, false);
565 emitLineTo(x1 + mx, y1 + my, false);
566
567 emitLineTo(x0 - mx, y0 - my, true);
568 emitLineTo(x1 - mx, y1 - my, true);
569
570 lx0 = x1 + mx; ly0 = y1 + my;
571 lx0p = x1 - mx; ly0p = y1 - my;
572 lx1 = x1; ly1 = y1;
573
574 this.omx = mx;
575 this.omy = my;
576 this.px0 = x0;
577 this.py0 = y0;
578 this.x0 = x1;
579 this.y0 = y1;
580 this.prev = LINE_TO;
581 }
582
583 public void close() {
584 // System.out.println("Stroker.close()");
585
586 if (lineToOrigin) {
587 // ignore the previous lineTo
588 lineToOrigin = false;
589 }
590
591 if (!started) {
592 finish();
593 return;
594 }
595
596 computeOffset(x0, y0, sx0, sy0, offset);
597 int mx = offset[0];
598 int my = offset[1];
599
600 // Draw penultimate join
601 boolean ccw = isCCW(px0, py0, x0, y0, sx0, sy0);
602 if (joinSegment) {
603 if (joinStyle == JOIN_MITER) {
604 drawMiter(px0, py0, x0, y0, sx0, sy0, omx, omy, mx, my, ccw);
605 } else if (joinStyle == JOIN_ROUND) {
606 drawRoundJoin(x0, y0, omx, omy, mx, my, 0, false, ccw,
607 ROUND_JOIN_THRESHOLD);
608 }
609 } else {
610 // Draw internal joins as round
611 drawRoundJoin(x0, y0,
612 omx, omy,
613 mx, my, 0, false, ccw,
614 ROUND_JOIN_INTERNAL_THRESHOLD);
615 }
616
617 emitLineTo(x0 + mx, y0 + my);
618 emitLineTo(sx0 + mx, sy0 + my);
619
620 ccw = isCCW(x0, y0, sx0, sy0, sx1, sy1);
621
622 // Draw final join on the outside
623 if (!ccw) {
624 if (joinStyle == JOIN_MITER) {
625 drawMiter(x0, y0, sx0, sy0, sx1, sy1,
626 mx, my, mx0, my0, false);
627 } else if (joinStyle == JOIN_ROUND) {
628 drawRoundJoin(sx0, sy0, mx, my, mx0, my0, 0, false, false,
629 ROUND_JOIN_THRESHOLD);
630 }
631 }
632
633 emitLineTo(sx0 + mx0, sy0 + my0);
634 emitLineTo(sx0 - mx0, sy0 - my0); // same as reverse[0], reverse[1]
635
636 // Draw final join on the inside
637 if (ccw) {
638 if (joinStyle == JOIN_MITER) {
639 drawMiter(x0, y0, sx0, sy0, sx1, sy1,
640 -mx, -my, -mx0, -my0, false);
641 } else if (joinStyle == JOIN_ROUND) {
642 drawRoundJoin(sx0, sy0, -mx, -my, -mx0, -my0, 0,
643 true, false,
644 ROUND_JOIN_THRESHOLD);
645 }
646 }
647
648 emitLineTo(sx0 - mx, sy0 - my);
649 emitLineTo(x0 - mx, y0 - my);
650 for (int i = rindex - 2; i >= 0; i -= 2) {
651 emitLineTo(reverse[i], reverse[i + 1]);
652 }
653
654 this.x0 = this.sx0;
655 this.y0 = this.sy0;
656 this.rindex = 0;
657 this.started = false;
658 this.joinSegment = false;
659 this.prev = CLOSE;
660 emitClose();
661 }
662
663 public void end() {
664 // System.out.println("Stroker.end()");
665
666 if (lineToOrigin) {
667 // not closing the path, do the previous lineTo
668 lineToImpl(sx0, sy0, joinToOrigin);
669 lineToOrigin = false;
670 }
671
672 if (prev == LINE_TO) {
673 finish();
674 }
675
676 output.end();
677 this.joinSegment = false;
678 this.prev = MOVE_TO;
679 }
680
681 long lineLength(long ldx, long ldy) {
682 long ldet = ((long)m00*m11 - (long)m01*m10) >> 16;
683 long la = ((long)ldy*m00 - (long)ldx*m10)/ldet;
684 long lb = ((long)ldy*m01 - (long)ldx*m11)/ldet;
685 long llen = (int)PiscesMath.hypot(la, lb);
686 return llen;
687 }
688
689 private void finish() {
690 if (capStyle == CAP_ROUND) {
691 drawRoundJoin(x0, y0,
692 omx, omy, -omx, -omy, 1, false, false,
693 ROUND_JOIN_THRESHOLD);
694 } else if (capStyle == CAP_SQUARE) {
695 long ldx = (long)(px0 - x0);
696 long ldy = (long)(py0 - y0);
697 long llen = lineLength(ldx, ldy);
698 long s = (long)lineWidth2*65536/llen;
699
700 int capx = x0 - (int)(ldx*s >> 16);
701 int capy = y0 - (int)(ldy*s >> 16);
702
703 emitLineTo(capx + omx, capy + omy);
704 emitLineTo(capx - omx, capy - omy);
705 }
706
707 for (int i = rindex - 2; i >= 0; i -= 2) {
708 emitLineTo(reverse[i], reverse[i + 1]);
709 }
710 this.rindex = 0;
711
712 if (capStyle == CAP_ROUND) {
713 drawRoundJoin(sx0, sy0,
714 -mx0, -my0, mx0, my0, 1, false, false,
715 ROUND_JOIN_THRESHOLD);
716 } else if (capStyle == CAP_SQUARE) {
717 long ldx = (long)(sx1 - sx0);
718 long ldy = (long)(sy1 - sy0);
719 long llen = lineLength(ldx, ldy);
720 long s = (long)lineWidth2*65536/llen;
721
722 int capx = sx0 - (int)(ldx*s >> 16);
723 int capy = sy0 - (int)(ldy*s >> 16);
724
725 emitLineTo(capx - mx0, capy - my0);
726 emitLineTo(capx + mx0, capy + my0);
727 }
728
729 emitClose();
730 this.joinSegment = false;
731 }
732
733 private void emitMoveTo(int x0, int y0) {
734 // System.out.println("Stroker.emitMoveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")");
735 output.moveTo(x0, y0);
736 }
737
738 private void emitLineTo(int x1, int y1) {
739 // System.out.println("Stroker.emitLineTo(" + x0/65536.0 + ", " + y0/65536.0 + ")");
740 output.lineTo(x1, y1);
741 }
742
743 private void emitLineTo(int x1, int y1, boolean rev) {
744 if (rev) {
745 ensureCapacity(rindex + 2);
746 reverse[rindex++] = x1;
747 reverse[rindex++] = y1;
748 } else {
749 emitLineTo(x1, y1);
750 }
751 }
752
753 private void emitClose() {
754 // System.out.println("Stroker.emitClose()");
755 output.close();
756 }
757}