History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/java2d/pisces/PiscesRenderingEngine.java (1 of 18352 files). Show all

Unified · Split

java/pkg/sun/java2d/pisces/PiscesRenderingEngine.java+656−0
@@ -0,0 +1,656 @@
1/*
2 * Copyright (c) 2007, 2011, 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
26package sun.java2d.pisces;
27
28import java.awt.Shape;
29import java.awt.BasicStroke;
30import java.awt.geom.Path2D;
31import java.awt.geom.AffineTransform;
32import java.awt.geom.PathIterator;
33
34import sun.awt.geom.PathConsumer2D;
35import sun.java2d.pipe.Region;
36import sun.java2d.pipe.RenderingEngine;
37import sun.java2d.pipe.AATileGenerator;
38
39public class PiscesRenderingEngine extends RenderingEngine {
40 private static enum NormMode {OFF, ON_NO_AA, ON_WITH_AA}
41
42 /**
43 * Create a widened path as specified by the parameters.
44 * <p>
45 * The specified {@code src} {@link Shape} is widened according
46 * to the specified attribute parameters as per the
47 * {@link BasicStroke} specification.
48 *
49 * @param src the source path to be widened
50 * @param width the width of the widened path as per {@code BasicStroke}
51 * @param caps the end cap decorations as per {@code BasicStroke}
52 * @param join the segment join decorations as per {@code BasicStroke}
53 * @param miterlimit the miter limit as per {@code BasicStroke}
54 * @param dashes the dash length array as per {@code BasicStroke}
55 * @param dashphase the initial dash phase as per {@code BasicStroke}
56 * @return the widened path stored in a new {@code Shape} object
57 * @since 1.7
58 */
59 public Shape createStrokedShape(Shape src,
60 float width,
61 int caps,
62 int join,
63 float miterlimit,
64 float dashes[],
65 float dashphase)
66 {
67 final Path2D p2d = new Path2D.Float();
68
69 strokeTo(src,
70 null,
71 width,
72 NormMode.OFF,
73 caps,
74 join,
75 miterlimit,
76 dashes,
77 dashphase,
78 new PathConsumer2D() {
79 public void moveTo(float x0, float y0) {
80 p2d.moveTo(x0, y0);
81 }
82 public void lineTo(float x1, float y1) {
83 p2d.lineTo(x1, y1);
84 }
85 public void closePath() {
86 p2d.closePath();
87 }
88 public void pathDone() {}
89 public void curveTo(float x1, float y1,
90 float x2, float y2,
91 float x3, float y3) {
92 p2d.curveTo(x1, y1, x2, y2, x3, y3);
93 }
94 public void quadTo(float x1, float y1, float x2, float y2) {
95 p2d.quadTo(x1, y1, x2, y2);
96 }
97 public long getNativeConsumer() {
98 throw new InternalError("Not using a native peer");
99 }
100 });
101 return p2d;
102 }
103
104 /**
105 * Sends the geometry for a widened path as specified by the parameters
106 * to the specified consumer.
107 * <p>
108 * The specified {@code src} {@link Shape} is widened according
109 * to the parameters specified by the {@link BasicStroke} object.
110 * Adjustments are made to the path as appropriate for the
111 * {@link VALUE_STROKE_NORMALIZE} hint if the {@code normalize}
112 * boolean parameter is true.
113 * Adjustments are made to the path as appropriate for the
114 * {@link VALUE_ANTIALIAS_ON} hint if the {@code antialias}
115 * boolean parameter is true.
116 * <p>
117 * The geometry of the widened path is forwarded to the indicated
118 * {@link PathConsumer2D} object as it is calculated.
119 *
120 * @param src the source path to be widened
121 * @param bs the {@code BasicSroke} object specifying the
122 * decorations to be applied to the widened path
123 * @param normalize indicates whether stroke normalization should
124 * be applied
125 * @param antialias indicates whether or not adjustments appropriate
126 * to antialiased rendering should be applied
127 * @param consumer the {@code PathConsumer2D} instance to forward
128 * the widened geometry to
129 * @since 1.7
130 */
131 public void strokeTo(Shape src,
132 AffineTransform at,
133 BasicStroke bs,
134 boolean thin,
135 boolean normalize,
136 boolean antialias,
137 final PathConsumer2D consumer)
138 {
139 NormMode norm = (normalize) ?
140 ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA)
141 : NormMode.OFF;
142 strokeTo(src, at, bs, thin, norm, antialias, consumer);
143 }
144
145 void strokeTo(Shape src,
146 AffineTransform at,
147 BasicStroke bs,
148 boolean thin,
149 NormMode normalize,
150 boolean antialias,
151 PathConsumer2D pc2d)
152 {
153 float lw;
154 if (thin) {
155 if (antialias) {
156 lw = userSpaceLineWidth(at, 0.5f);
157 } else {
158 lw = userSpaceLineWidth(at, 1.0f);
159 }
160 } else {
161 lw = bs.getLineWidth();
162 }
163 strokeTo(src,
164 at,
165 lw,
166 normalize,
167 bs.getEndCap(),
168 bs.getLineJoin(),
169 bs.getMiterLimit(),
170 bs.getDashArray(),
171 bs.getDashPhase(),
172 pc2d);
173 }
174
175 private float userSpaceLineWidth(AffineTransform at, float lw) {
176
177 double widthScale;
178
179 if ((at.getType() & (AffineTransform.TYPE_GENERAL_TRANSFORM |
180 AffineTransform.TYPE_GENERAL_SCALE)) != 0) {
181 widthScale = Math.sqrt(at.getDeterminant());
182 } else {
183 /* First calculate the "maximum scale" of this transform. */
184 double A = at.getScaleX(); // m00
185 double C = at.getShearX(); // m01
186 double B = at.getShearY(); // m10
187 double D = at.getScaleY(); // m11
188
189 /*
190 * Given a 2 x 2 affine matrix [ A B ] such that
191 * [ C D ]
192 * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to
193 * find the maximum magnitude (norm) of the vector v'
194 * with the constraint (x^2 + y^2 = 1).
195 * The equation to maximize is
196 * |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2)
197 * or |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2).
198 * Since sqrt is monotonic we can maximize |v'|^2
199 * instead and plug in the substitution y = sqrt(1 - x^2).
200 * Trigonometric equalities can then be used to get
201 * rid of most of the sqrt terms.
202 */
203
204 double EA = A*A + B*B; // x^2 coefficient
205 double EB = 2*(A*C + B*D); // xy coefficient
206 double EC = C*C + D*D; // y^2 coefficient
207
208 /*
209 * There is a lot of calculus omitted here.
210 *
211 * Conceptually, in the interests of understanding the
212 * terms that the calculus produced we can consider
213 * that EA and EC end up providing the lengths along
214 * the major axes and the hypot term ends up being an
215 * adjustment for the additional length along the off-axis
216 * angle of rotated or sheared ellipses as well as an
217 * adjustment for the fact that the equation below
218 * averages the two major axis lengths. (Notice that
219 * the hypot term contains a part which resolves to the
220 * difference of these two axis lengths in the absence
221 * of rotation.)
222 *
223 * In the calculus, the ratio of the EB and (EA-EC) terms
224 * ends up being the tangent of 2*theta where theta is
225 * the angle that the long axis of the ellipse makes
226 * with the horizontal axis. Thus, this equation is
227 * calculating the length of the hypotenuse of a triangle
228 * along that axis.
229 */
230
231 double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC));
232 /* sqrt omitted, compare to squared limits below. */
233 double widthsquared = ((EA + EC + hypot)/2.0);
234
235 widthScale = Math.sqrt(widthsquared);
236 }
237
238 return (float) (lw / widthScale);
239 }
240
241 void strokeTo(Shape src,
242 AffineTransform at,
243 float width,
244 NormMode normalize,
245 int caps,
246 int join,
247 float miterlimit,
248 float dashes[],
249 float dashphase,
250 PathConsumer2D pc2d)
251 {
252 // We use strokerat and outat so that in Stroker and Dasher we can work only
253 // with the pre-transformation coordinates. This will repeat a lot of
254 // computations done in the path iterator, but the alternative is to
255 // work with transformed paths and compute untransformed coordinates
256 // as needed. This would be faster but I do not think the complexity
257 // of working with both untransformed and transformed coordinates in
258 // the same code is worth it.
259 // However, if a path's width is constant after a transformation,
260 // we can skip all this untransforming.
261
262 // If normalization is off we save some transformations by not
263 // transforming the input to pisces. Instead, we apply the
264 // transformation after the path processing has been done.
265 // We can't do this if normalization is on, because it isn't a good
266 // idea to normalize before the transformation is applied.
267 AffineTransform strokerat = null;
268 AffineTransform outat = null;
269
270 PathIterator pi = null;
271
272 if (at != null && !at.isIdentity()) {
273 final double a = at.getScaleX();
274 final double b = at.getShearX();
275 final double c = at.getShearY();
276 final double d = at.getScaleY();
277 final double det = a * d - c * b;
278 if (Math.abs(det) <= 2 * Float.MIN_VALUE) {
279 // this rendering engine takes one dimensional curves and turns
280 // them into 2D shapes by giving them width.
281 // However, if everything is to be passed through a singular
282 // transformation, these 2D shapes will be squashed down to 1D
283 // again so, nothing can be drawn.
284
285 // Every path needs an initial moveTo and a pathDone. If these
286 // are not there this causes a SIGSEGV in libawt.so (at the time
287 // of writing of this comment (September 16, 2010)). Actually,
288 // I am not sure if the moveTo is necessary to avoid the SIGSEGV
289 // but the pathDone is definitely needed.
290 pc2d.moveTo(0, 0);
291 pc2d.pathDone();
292 return;
293 }
294
295 // If the transform is a constant multiple of an orthogonal transformation
296 // then every length is just multiplied by a constant, so we just
297 // need to transform input paths to stroker and tell stroker
298 // the scaled width. This condition is satisfied if
299 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we
300 // leave a bit of room for error.
301 if (nearZero(a*b + c*d, 2) && nearZero(a*a+c*c - (b*b+d*d), 2)) {
302 double scale = Math.sqrt(a*a + c*c);
303 if (dashes != null) {
304 dashes = java.util.Arrays.copyOf(dashes, dashes.length);
305 for (int i = 0; i < dashes.length; i++) {
306 dashes[i] = (float)(scale * dashes[i]);
307 }
308 dashphase = (float)(scale * dashphase);
309 }
310 width = (float)(scale * width);
311 pi = src.getPathIterator(at);
312 if (normalize != NormMode.OFF) {
313 pi = new NormalizingPathIterator(pi, normalize);
314 }
315 // by now strokerat == null && outat == null. Input paths to
316 // stroker (and maybe dasher) will have the full transform at
317 // applied to them and nothing will happen to the output paths.
318 } else {
319 if (normalize != NormMode.OFF) {
320 strokerat = at;
321 pi = src.getPathIterator(at);
322 pi = new NormalizingPathIterator(pi, normalize);
323 // by now strokerat == at && outat == null. Input paths to
324 // stroker (and maybe dasher) will have the full transform at
325 // applied to them, then they will be normalized, and then
326 // the inverse of *only the non translation part of at* will
327 // be applied to the normalized paths. This won't cause problems
328 // in stroker, because, suppose at = T*A, where T is just the
329 // translation part of at, and A is the rest. T*A has already
330 // been applied to Stroker/Dasher's input. Then Ainv will be
331 // applied. Ainv*T*A is not equal to T, but it is a translation,
332 // which means that none of stroker's assumptions about its
333 // input will be violated. After all this, A will be applied
334 // to stroker's output.
335 } else {
336 outat = at;
337 pi = src.getPathIterator(null);
338 // outat == at && strokerat == null. This is because if no
339 // normalization is done, we can just apply all our
340 // transformations to stroker's output.
341 }
342 }
343 } else {
344 // either at is null or it's the identity. In either case
345 // we don't transform the path.
346 pi = src.getPathIterator(null);
347 if (normalize != NormMode.OFF) {
348 pi = new NormalizingPathIterator(pi, normalize);
349 }
350 }
351
352 // by now, at least one of outat and strokerat will be null. Unless at is not
353 // a constant multiple of an orthogonal transformation, they will both be
354 // null. In other cases, outat == at if normalization is off, and if
355 // normalization is on, strokerat == at.
356 pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, outat);
357 pc2d = TransformingPathConsumer2D.deltaTransformConsumer(pc2d, strokerat);
358 pc2d = new Stroker(pc2d, width, caps, join, miterlimit);
359 if (dashes != null) {
360 pc2d = new Dasher(pc2d, dashes, dashphase);
361 }
362 pc2d = TransformingPathConsumer2D.inverseDeltaTransformConsumer(pc2d, strokerat);
363 pathTo(pi, pc2d);
364 }
365
366 private static boolean nearZero(double num, int nulps) {
367 return Math.abs(num) < nulps * Math.ulp(num);
368 }
369
370 private static class NormalizingPathIterator implements PathIterator {
371
372 private final PathIterator src;
373
374 // the adjustment applied to the current position.
375 private float curx_adjust, cury_adjust;
376 // the adjustment applied to the last moveTo position.
377 private float movx_adjust, movy_adjust;
378
379 // constants used in normalization computations
380 private final float lval, rval;
381
382 NormalizingPathIterator(PathIterator src, NormMode mode) {
383 this.src = src;
384 switch (mode) {
385 case ON_NO_AA:
386 // round to nearest (0.25, 0.25) pixel
387 lval = rval = 0.25f;
388 break;
389 case ON_WITH_AA:
390 // round to nearest pixel center
391 lval = 0f;
392 rval = 0.5f;
393 break;
394 case OFF:
395 throw new InternalError("A NormalizingPathIterator should " +
396 "not be created if no normalization is being done");
397 default:
398 throw new InternalError("Unrecognized normalization mode");
399 }
400 }
401
402 public int currentSegment(float[] coords) {
403 int type = src.currentSegment(coords);
404
405 int lastCoord;
406 switch(type) {
407 case PathIterator.SEG_CUBICTO:
408 lastCoord = 4;
409 break;
410 case PathIterator.SEG_QUADTO:
411 lastCoord = 2;
412 break;
413 case PathIterator.SEG_LINETO:
414 case PathIterator.SEG_MOVETO:
415 lastCoord = 0;
416 break;
417 case PathIterator.SEG_CLOSE:
418 // we don't want to deal with this case later. We just exit now
419 curx_adjust = movx_adjust;
420 cury_adjust = movy_adjust;
421 return type;
422 default:
423 throw new InternalError("Unrecognized curve type");
424 }
425
426 // normalize endpoint
427 float x_adjust = (float)Math.floor(coords[lastCoord] + lval) +
428 rval - coords[lastCoord];
429 float y_adjust = (float)Math.floor(coords[lastCoord+1] + lval) +
430 rval - coords[lastCoord + 1];
431
432 coords[lastCoord ] += x_adjust;
433 coords[lastCoord + 1] += y_adjust;
434
435 // now that the end points are done, normalize the control points
436 switch(type) {
437 case PathIterator.SEG_CUBICTO:
438 coords[0] += curx_adjust;
439 coords[1] += cury_adjust;
440 coords[2] += x_adjust;
441 coords[3] += y_adjust;
442 break;
443 case PathIterator.SEG_QUADTO:
444 coords[0] += (curx_adjust + x_adjust) / 2;
445 coords[1] += (cury_adjust + y_adjust) / 2;
446 break;
447 case PathIterator.SEG_LINETO:
448 break;
449 case PathIterator.SEG_MOVETO:
450 movx_adjust = x_adjust;
451 movy_adjust = y_adjust;
452 break;
453 case PathIterator.SEG_CLOSE:
454 throw new InternalError("This should be handled earlier.");
455 }
456 curx_adjust = x_adjust;
457 cury_adjust = y_adjust;
458 return type;
459 }
460
461 public int currentSegment(double[] coords) {
462 float[] tmp = new float[6];
463 int type = this.currentSegment(tmp);
464 for (int i = 0; i < 6; i++) {
465 coords[i] = (float) tmp[i];
466 }
467 return type;
468 }
469
470 public int getWindingRule() {
471 return src.getWindingRule();
472 }
473
474 public boolean isDone() {
475 return src.isDone();
476 }
477
478 public void next() {
479 src.next();
480 }
481 }
482
483 static void pathTo(PathIterator pi, PathConsumer2D pc2d) {
484 RenderingEngine.feedConsumer(pi, pc2d);
485 pc2d.pathDone();
486 }
487
488 /**
489 * Construct an antialiased tile generator for the given shape with
490 * the given rendering attributes and store the bounds of the tile
491 * iteration in the bbox parameter.
492 * The {@code at} parameter specifies a transform that should affect
493 * both the shape and the {@code BasicStroke} attributes.
494 * The {@code clip} parameter specifies the current clip in effect
495 * in device coordinates and can be used to prune the data for the
496 * operation, but the renderer is not required to perform any
497 * clipping.
498 * If the {@code BasicStroke} parameter is null then the shape
499 * should be filled as is, otherwise the attributes of the
500 * {@code BasicStroke} should be used to specify a draw operation.
501 * The {@code thin} parameter indicates whether or not the
502 * transformed {@code BasicStroke} represents coordinates smaller
503 * than the minimum resolution of the antialiasing rasterizer as
504 * specified by the {@code getMinimumAAPenWidth()} method.
505 * <p>
506 * Upon returning, this method will fill the {@code bbox} parameter
507 * with 4 values indicating the bounds of the iteration of the
508 * tile generator.
509 * The iteration order of the tiles will be as specified by the
510 * pseudo-code:
511 * <pre>
512 * for (y = bbox[1]; y < bbox[3]; y += tileheight) {
513 * for (x = bbox[0]; x < bbox[2]; x += tilewidth) {
514 * }
515 * }
516 * </pre>
517 * If there is no output to be rendered, this method may return
518 * null.
519 *
520 * @param s the shape to be rendered (fill or draw)
521 * @param at the transform to be applied to the shape and the
522 * stroke attributes
523 * @param clip the current clip in effect in device coordinates
524 * @param bs if non-null, a {@code BasicStroke} whose attributes
525 * should be applied to this operation
526 * @param thin true if the transformed stroke attributes are smaller
527 * than the minimum dropout pen width
528 * @param normalize true if the {@code VALUE_STROKE_NORMALIZE}
529 * {@code RenderingHint} is in effect
530 * @param bbox returns the bounds of the iteration
531 * @return the {@code AATileGenerator} instance to be consulted
532 * for tile coverages, or null if there is no output to render
533 * @since 1.7
534 */
535 public AATileGenerator getAATileGenerator(Shape s,
536 AffineTransform at,
537 Region clip,
538 BasicStroke bs,
539 boolean thin,
540 boolean normalize,
541 int bbox[])
542 {
543 Renderer r;
544 NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF;
545 if (bs == null) {
546 PathIterator pi;
547 if (normalize) {
548 pi = new NormalizingPathIterator(s.getPathIterator(at), norm);
549 } else {
550 pi = s.getPathIterator(at);
551 }
552 r = new Renderer(3, 3,
553 clip.getLoX(), clip.getLoY(),
554 clip.getWidth(), clip.getHeight(),
555 pi.getWindingRule());
556 pathTo(pi, r);
557 } else {
558 r = new Renderer(3, 3,
559 clip.getLoX(), clip.getLoY(),
560 clip.getWidth(), clip.getHeight(),
561 PathIterator.WIND_NON_ZERO);
562 strokeTo(s, at, bs, thin, norm, true, r);
563 }
564 r.endRendering();
565 PiscesTileGenerator ptg = new PiscesTileGenerator(r, r.MAX_AA_ALPHA);
566 ptg.getBbox(bbox);
567 return ptg;
568 }
569
570 public AATileGenerator getAATileGenerator(double x, double y,
571 double dx1, double dy1,
572 double dx2, double dy2,
573 double lw1, double lw2,
574 Region clip,
575 int bbox[])
576 {
577 // REMIND: Deal with large coordinates!
578 double ldx1, ldy1, ldx2, ldy2;
579 boolean innerpgram = (lw1 > 0 && lw2 > 0);
580
581 if (innerpgram) {
582 ldx1 = dx1 * lw1;
583 ldy1 = dy1 * lw1;
584 ldx2 = dx2 * lw2;
585 ldy2 = dy2 * lw2;
586 x -= (ldx1 + ldx2) / 2.0;
587 y -= (ldy1 + ldy2) / 2.0;
588 dx1 += ldx1;
589 dy1 += ldy1;
590 dx2 += ldx2;
591 dy2 += ldy2;
592 if (lw1 > 1 && lw2 > 1) {
593 // Inner parallelogram was entirely consumed by stroke...
594 innerpgram = false;
595 }
596 } else {
597 ldx1 = ldy1 = ldx2 = ldy2 = 0;
598 }
599
600 Renderer r = new Renderer(3, 3,
601 clip.getLoX(), clip.getLoY(),
602 clip.getWidth(), clip.getHeight(),
603 PathIterator.WIND_EVEN_ODD);
604
605 r.moveTo((float) x, (float) y);
606 r.lineTo((float) (x+dx1), (float) (y+dy1));
607 r.lineTo((float) (x+dx1+dx2), (float) (y+dy1+dy2));
608 r.lineTo((float) (x+dx2), (float) (y+dy2));
609 r.closePath();
610
611 if (innerpgram) {
612 x += ldx1 + ldx2;
613 y += ldy1 + ldy2;
614 dx1 -= 2.0 * ldx1;
615 dy1 -= 2.0 * ldy1;
616 dx2 -= 2.0 * ldx2;
617 dy2 -= 2.0 * ldy2;
618 r.moveTo((float) x, (float) y);
619 r.lineTo((float) (x+dx1), (float) (y+dy1));
620 r.lineTo((float) (x+dx1+dx2), (float) (y+dy1+dy2));
621 r.lineTo((float) (x+dx2), (float) (y+dy2));
622 r.closePath();
623 }
624
625 r.pathDone();
626
627 r.endRendering();
628 PiscesTileGenerator ptg = new PiscesTileGenerator(r, r.MAX_AA_ALPHA);
629 ptg.getBbox(bbox);
630 return ptg;
631 }
632
633 /**
634 * Returns the minimum pen width that the antialiasing rasterizer
635 * can represent without dropouts occuring.
636 * @since 1.7
637 */
638 public float getMinimumAAPenSize() {
639 return 0.5f;
640 }
641
642 static {
643 if (PathIterator.WIND_NON_ZERO != Renderer.WIND_NON_ZERO ||
644 PathIterator.WIND_EVEN_ODD != Renderer.WIND_EVEN_ODD ||
645 BasicStroke.JOIN_MITER != Stroker.JOIN_MITER ||
646 BasicStroke.JOIN_ROUND != Stroker.JOIN_ROUND ||
647 BasicStroke.JOIN_BEVEL != Stroker.JOIN_BEVEL ||
648 BasicStroke.CAP_BUTT != Stroker.CAP_BUTT ||
649 BasicStroke.CAP_ROUND != Stroker.CAP_ROUND ||
650 BasicStroke.CAP_SQUARE != Stroker.CAP_SQUARE)
651 {
652 throw new InternalError("mismatched renderer constants");
653 }
654 }
655}
656