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 
  26 package sun.java2d.pisces;
  27 
  28 import java.awt.Shape;
  29 import java.awt.BasicStroke;
  30 import java.awt.geom.Path2D;
  31 import java.awt.geom.AffineTransform;
  32 import java.awt.geom.PathIterator;
  33 
  34 import sun.awt.geom.PathConsumer2D;
  35 import sun.java2d.pipe.Region;
  36 import sun.java2d.pipe.RenderingEngine;
  37 import sun.java2d.pipe.AATileGenerator;
  38 
  39 public 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