Source for gnu.java.awt.peer.gtk.GdkGraphics2D

   1: /* GdkGraphics2D.java --
   2:    Copyright (C) 2003, 2004, 2005  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package gnu.java.awt.peer.gtk;
  40: 
  41: import gnu.classpath.Configuration;
  42: import gnu.java.awt.ClasspathToolkit;
  43: 
  44: import java.awt.AlphaComposite;
  45: import java.awt.BasicStroke;
  46: import java.awt.Color;
  47: import java.awt.Composite;
  48: import java.awt.Dimension;
  49: import java.awt.Font;
  50: import java.awt.FontMetrics;
  51: import java.awt.GradientPaint;
  52: import java.awt.Graphics;
  53: import java.awt.Graphics2D;
  54: import java.awt.GraphicsConfiguration;
  55: import java.awt.Image;
  56: import java.awt.Paint;
  57: import java.awt.Rectangle;
  58: import java.awt.RenderingHints;
  59: import java.awt.Shape;
  60: import java.awt.Stroke;
  61: import java.awt.TexturePaint;
  62: import java.awt.Toolkit;
  63: import java.awt.font.FontRenderContext;
  64: import java.awt.font.GlyphVector;
  65: import java.awt.geom.AffineTransform;
  66: import java.awt.geom.Arc2D;
  67: import java.awt.geom.GeneralPath;
  68: import java.awt.geom.NoninvertibleTransformException;
  69: import java.awt.geom.PathIterator;
  70: import java.awt.geom.Point2D;
  71: import java.awt.geom.Rectangle2D;
  72: import java.awt.image.AffineTransformOp;
  73: import java.awt.image.BufferedImage;
  74: import java.awt.image.BufferedImageOp;
  75: import java.awt.image.ColorModel;
  76: import java.awt.image.CropImageFilter;
  77: import java.awt.image.DataBuffer;
  78: import java.awt.image.DataBufferInt;
  79: import java.awt.image.DirectColorModel;
  80: import java.awt.image.FilteredImageSource;
  81: import java.awt.image.ImageObserver;
  82: import java.awt.image.ImagingOpException;
  83: import java.awt.image.MultiPixelPackedSampleModel;
  84: import java.awt.image.Raster;
  85: import java.awt.image.RenderedImage;
  86: import java.awt.image.SampleModel;
  87: import java.awt.image.WritableRaster;
  88: import java.awt.image.renderable.RenderContext;
  89: import java.awt.image.renderable.RenderableImage;
  90: import java.text.AttributedCharacterIterator;
  91: import java.util.HashMap;
  92: import java.util.Map;
  93: import java.util.Stack;
  94: 
  95: public class GdkGraphics2D extends Graphics2D
  96: {
  97:   //////////////////////////////////////
  98:   ////// State Management Methods //////
  99:   //////////////////////////////////////
 100: 
 101:   static 
 102:   {
 103:     if (! Configuration.GTK_CAIRO_ENABLED)
 104:       throw new Error("Graphics2D not implemented. "
 105:               + "Cairo was not found or disabled at configure time");
 106: 
 107:     if (Configuration.INIT_LOAD_LIBRARY)
 108:       System.loadLibrary("gtkpeer");
 109: 
 110:     initStaticState();
 111:   }
 112:   
 113:   static native void initStaticState();
 114:   
 115:   private final int native_state = GtkGenericPeer.getUniqueInteger();  
 116: 
 117:   // These are package-private to avoid accessor methods.
 118:   Paint paint;
 119:   Stroke stroke;
 120:   Color fg;
 121:   Color bg;
 122:   Shape clip;
 123:   AffineTransform transform;
 124:   private GtkComponentPeer component;
 125:   // This is package-private to avoid an accessor method.
 126:   Font font;
 127:   private RenderingHints hints;
 128:   private BufferedImage bimage;
 129:   private boolean pixelConversionRequired;
 130:   private int[] pixelBuffer;
 131:   // This is package-private to avoid an accessor method.
 132:   Composite comp;
 133:   private Stack stateStack;
 134: 
 135:   private native void initStateUnlocked(GtkComponentPeer component);
 136:   private native void initState(GtkComponentPeer component);
 137:   private native void initState(int width, int height);
 138:   private native void initState(int[] pixes, int width, int height);
 139:   private native void copyState(GdkGraphics2D g);
 140:   public native void dispose();
 141:   private native void cairoSurfaceSetFilter(int filter);
 142:   private native void cairoSurfaceSetFilterUnlocked(int filter);
 143:   native void connectSignals(GtkComponentPeer component);
 144: 
 145:   public void finalize()
 146:   {
 147:     dispose();
 148:   }
 149: 
 150:   public Graphics create()
 151:   {
 152:     return new GdkGraphics2D(this);
 153:   }
 154: 
 155:   public Graphics create(int x, int y, int width, int height)
 156:   {
 157:     return new GdkGraphics2D(this, x, y, width, height);
 158:   }
 159: 
 160:   private void fail_g2d ()
 161:   {
 162:     System.err.println ("Attempted to instantiate GdkGraphics2D"
 163:             + " but Graphics2D not enabled.  Try again with"
 164:             + " -Dgnu.java.awt.peer.gtk.Graphics=Graphics2D");
 165:     System.exit (1);
 166:   }
 167: 
 168:   GdkGraphics2D(GdkGraphics2D g)
 169:   {
 170:     if (!GtkToolkit.useGraphics2D ())
 171:       fail_g2d ();
 172: 
 173:     paint = g.paint;
 174:     stroke = g.stroke;
 175:     setRenderingHints(g.hints);
 176: 
 177:     if (g.fg.getAlpha() != -1)
 178:       fg = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(),
 179:                      g.fg.getAlpha());
 180:     else
 181:       fg = new Color(g.fg.getRGB());
 182: 
 183:     if (g.bg != null)
 184:       {
 185:         if (g.bg.getAlpha() != -1)
 186:           bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(),
 187:                          g.bg.getAlpha());
 188:         else
 189:           bg = new Color(g.bg.getRGB());
 190:       }
 191: 
 192:     if (g.clip == null)
 193:       clip = null;
 194:     else
 195:       clip = new Rectangle(g.getClipBounds());
 196: 
 197:     if (g.transform == null)
 198:       transform = new AffineTransform();
 199:     else
 200:       transform = new AffineTransform(g.transform);
 201: 
 202:     font = g.font;
 203:     component = g.component;
 204:     copyState(g);
 205: 
 206:     setColor(fg);
 207:     setBackground(bg);
 208:     setPaint(paint);
 209:     setStroke(stroke);
 210:     setTransform(transform);
 211:     setClip(clip);
 212:     stateStack = new Stack();
 213:   }
 214: 
 215:   GdkGraphics2D(GdkGraphics2D g, int x, int y, int widht, int height)
 216:   {
 217:     this(g);
 218:     translate(x, y);
 219:     clipRect(0, 0, widht, height);
 220:   }
 221: 
 222:   GdkGraphics2D(int width, int height)
 223:   {
 224:     if (!GtkToolkit.useGraphics2D ())
 225:       fail_g2d ();
 226: 
 227:     initState(width, height);
 228: 
 229:     setColor(Color.black);
 230:     setBackground(new Color(0, 0, 0, 0));
 231:     setPaint(getColor());
 232:     setFont(new Font("SansSerif", Font.PLAIN, 12));
 233:     setTransform(new AffineTransform());
 234:     setStroke(new BasicStroke());
 235:     setRenderingHints(getDefaultHints());
 236: 
 237:     stateStack = new Stack();
 238:   }
 239: 
 240:   GdkGraphics2D(GtkComponentPeer component)
 241:   {
 242:     if (!GtkToolkit.useGraphics2D ())
 243:       fail_g2d ();
 244: 
 245:     this.component = component;
 246:     
 247:     if (component.isRealized())
 248:       initComponentGraphics2D();
 249:     else
 250:       connectSignals(component);
 251:   }
 252: 
 253:   void initComponentGraphics2D()
 254:   {
 255:     initState(component);
 256: 
 257:     setColor(component.awtComponent.getForeground());
 258:     setBackground(component.awtComponent.getBackground());
 259:     setPaint(getColor());
 260:     setTransform(new AffineTransform());
 261:     setStroke(new BasicStroke());
 262:     setRenderingHints(getDefaultHints());
 263:     setFont(new Font("SansSerif", Font.PLAIN, 12));
 264: 
 265:     stateStack = new Stack();
 266:   }
 267: 
 268:   void initComponentGraphics2DUnlocked()
 269:   {
 270:     initStateUnlocked(component);
 271: 
 272:     setColorUnlocked(component.awtComponent.getForeground());
 273:     setBackgroundUnlocked(component.awtComponent.getBackground());
 274:     setPaintUnlocked(getColorUnlocked());
 275:     setTransformUnlocked(new AffineTransform());
 276:     setStrokeUnlocked(new BasicStroke());
 277:     setRenderingHintsUnlocked(getDefaultHints());
 278:     setFontUnlocked(new Font("SansSerif", Font.PLAIN, 12));
 279: 
 280:     stateStack = new Stack();
 281:   }
 282: 
 283:   GdkGraphics2D(BufferedImage bimage)
 284:   {
 285:     this.bimage = bimage;
 286:     this.pixelBuffer = findSimpleIntegerArray(bimage.getColorModel(),
 287:                                               bimage.getRaster());
 288:     if (this.pixelBuffer == null)
 289:       {
 290:     this.pixelBuffer = new int[bimage.getRaster().getWidth() * bimage.getRaster()
 291:                                                                      .getHeight()];
 292:     this.pixelConversionRequired = true;
 293:       }
 294:     else
 295:       {
 296:         this.pixelConversionRequired = false;
 297:       }
 298: 
 299:     initState(this.pixelBuffer, bimage.getWidth(), bimage.getHeight());
 300: 
 301:     setColor(Color.black);
 302:     setBackground(new Color(0, 0, 0, 0));
 303:     setPaint(getColor());
 304:     setFont(new Font("SansSerif", Font.PLAIN, 12));
 305:     setTransform(new AffineTransform());
 306:     setStroke(new BasicStroke());
 307:     setRenderingHints(getDefaultHints());
 308: 
 309:     stateStack = new Stack();
 310: 
 311:     // draw current buffered image to the pixmap associated 
 312:     // with it, if the image is not equal to our paint buffer.
 313:     if (pixelConversionRequired)
 314:       drawImage(bimage, new AffineTransform(1, 0, 0, 1, 0, 0), bg, null);
 315:   }
 316: 
 317:   ////////////////////////////////////
 318:   ////// Native Drawing Methods //////
 319:   ////////////////////////////////////
 320: 
 321:   // GDK drawing methods
 322:   private native void gdkDrawDrawable(GdkGraphics2D other, int x, int y);
 323: 
 324:   // drawing utility methods
 325:   private native void drawPixels(int[] pixels, int w, int h, int stride,
 326:                                  double[] i2u);
 327:   private native void setTexturePixelsUnlocked(int[] pixels, int w, int h, int stride);
 328:   private native void setTexturePixels(int[] pixels, int w, int h, int stride);
 329:   private native void setGradient(double x1, double y1, double x2, double y2,
 330:                                   int r1, int g1, int b1, int a1, int r2,
 331:                                   int g2, int b2, int a2, boolean cyclic);
 332:   private native void setGradientUnlocked(double x1, double y1, double x2, double y2,
 333:                                   int r1, int g1, int b1, int a1, int r2,
 334:                                   int g2, int b2, int a2, boolean cyclic);
 335: 
 336:   // simple passthroughs to cairo
 337:   private native void cairoSave();
 338:   private native void cairoRestore();
 339:   private native void cairoSetMatrix(double[] m);
 340:   private native void cairoSetMatrixUnlocked(double[] m);
 341:   private native void cairoSetOperator(int cairoOperator);
 342:   private native void cairoSetRGBAColor(double red, double green,
 343:                                         double blue, double alpha);
 344:   private native void cairoSetRGBAColorUnlocked(double red, double green,
 345:                                         double blue, double alpha);
 346:   private native void cairoSetFillRule(int cairoFillRule);
 347:   private native void cairoSetLineWidth(double width);
 348:   private native void cairoSetLineWidthUnlocked(double width);
 349:   private native void cairoSetLineCap(int cairoLineCap);
 350:   private native void cairoSetLineCapUnlocked(int cairoLineCap);
 351:   private native void cairoSetLineJoin(int cairoLineJoin);
 352:   private native void cairoSetLineJoinUnlocked(int cairoLineJoin);
 353:   private native void cairoSetDash(double[] dashes, int ndash, double offset);
 354:   private native void cairoSetDashUnlocked(double[] dashes, int ndash, double offset);
 355: 
 356:   private native void cairoSetMiterLimit(double limit);
 357:   private native void cairoSetMiterLimitUnlocked(double limit);
 358:   private native void cairoNewPath();
 359:   private native void cairoMoveTo(double x, double y);
 360:   private native void cairoLineTo(double x, double y);
 361:   private native void cairoCurveTo(double x1, double y1, double x2, double y2,
 362:                                    double x3, double y3);
 363:   private native void cairoRelMoveTo(double dx, double dy);
 364:   private native void cairoRelLineTo(double dx, double dy);
 365:   private native void cairoRelCurveTo(double dx1, double dy1, double dx2,
 366:                                       double dy2, double dx3, double dy3);
 367:   private native void cairoRectangle(double x, double y, double width,
 368:                                      double height);
 369:   private native void cairoClosePath();
 370:   private native void cairoStroke();
 371:   private native void cairoFill();
 372:   private native void cairoClip();
 373: 
 374:   /////////////////////////////////////////////
 375:   ////// General Drawing Support Methods //////
 376:   /////////////////////////////////////////////
 377: 
 378:   private class DrawState
 379:   {
 380:     private Paint paint;
 381:     private Stroke stroke;
 382:     private Color fg;
 383:     private Color bg;
 384:     private Shape clip;
 385:     private AffineTransform transform;
 386:     private Font font;
 387:     private Composite comp;
 388: 
 389:     DrawState(GdkGraphics2D g)
 390:     {
 391:       this.paint = g.paint;
 392:       this.stroke = g.stroke;
 393:       this.fg = g.fg;
 394:       this.bg = g.bg;
 395:       this.clip = g.clip;
 396:       if (g.transform != null)
 397:     this.transform = (AffineTransform) g.transform.clone();
 398:       this.font = g.font;
 399:       this.comp = g.comp;
 400:     }
 401: 
 402:     public void restore(GdkGraphics2D g)
 403:     {
 404:       g.paint = this.paint;
 405:       g.stroke = this.stroke;
 406:       g.fg = this.fg;
 407:       g.bg = this.bg;
 408:       g.clip = this.clip;
 409:       g.transform = this.transform;
 410:       g.font = this.font;
 411:       g.comp = this.comp;
 412:     }
 413:   }
 414: 
 415:   private void stateSave()
 416:   {
 417:     stateStack.push(new DrawState(this));
 418:     cairoSave();
 419:   }
 420: 
 421:   private void stateRestore()
 422:   {
 423:     ((DrawState) (stateStack.pop())).restore(this);
 424:     cairoRestore();
 425:   }
 426: 
 427:   // Some operations (drawing rather than filling) require that their
 428:   // coords be shifted to land on 0.5-pixel boundaries, in order to land on
 429:   // "middle of pixel" coordinates and light up complete pixels.
 430:   private boolean shiftDrawCalls = false;
 431: 
 432:   private double shifted(double coord, boolean doShift)
 433:   {
 434:     if (doShift)
 435:       return Math.floor(coord) + 0.5;
 436:     else
 437:       return coord;
 438:   }
 439: 
 440:   private void walkPath(PathIterator p, boolean doShift)
 441:   {
 442:     double x = 0;
 443:     double y = 0;
 444:     double[] coords = new double[6];
 445: 
 446:     cairoSetFillRule(p.getWindingRule());
 447:     for (; ! p.isDone(); p.next())
 448:       {
 449:     int seg = p.currentSegment(coords);
 450:     switch (seg)
 451:       {
 452:       case PathIterator.SEG_MOVETO:
 453:         x = shifted(coords[0], doShift);
 454:         y = shifted(coords[1], doShift);
 455:         cairoMoveTo(x, y);
 456:         break;
 457:       case PathIterator.SEG_LINETO:
 458:         x = shifted(coords[0], doShift);
 459:         y = shifted(coords[1], doShift);
 460:         cairoLineTo(x, y);
 461:         break;
 462:       case PathIterator.SEG_QUADTO:
 463:         // splitting a quadratic bezier into a cubic:
 464:         // see: http://pfaedit.sourceforge.net/bezier.html
 465:         double x1 = x + (2.0 / 3.0) * (shifted(coords[0], doShift) - x);
 466:         double y1 = y + (2.0 / 3.0) * (shifted(coords[1], doShift) - y);
 467: 
 468:         double x2 = x1 + (1.0 / 3.0) * (shifted(coords[2], doShift) - x);
 469:         double y2 = y1 + (1.0 / 3.0) * (shifted(coords[3], doShift) - y);
 470: 
 471:         x = shifted(coords[2], doShift);
 472:         y = shifted(coords[3], doShift);
 473:         cairoCurveTo(x1, y1, x2, y2, x, y);
 474:         break;
 475:       case PathIterator.SEG_CUBICTO:
 476:         x = shifted(coords[4], doShift);
 477:         y = shifted(coords[5], doShift);
 478:         cairoCurveTo(shifted(coords[0], doShift),
 479:                      shifted(coords[1], doShift),
 480:                      shifted(coords[2], doShift),
 481:                      shifted(coords[3], doShift), x, y);
 482:         break;
 483:       case PathIterator.SEG_CLOSE:
 484:         cairoClosePath();
 485:         break;
 486:       }
 487:       }
 488:   }
 489: 
 490:   private Map getDefaultHints()
 491:   {
 492:     HashMap defaultHints = new HashMap();
 493: 
 494:     defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
 495:                      RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
 496: 
 497:     defaultHints.put(RenderingHints.KEY_STROKE_CONTROL,
 498:                      RenderingHints.VALUE_STROKE_DEFAULT);
 499: 
 500:     defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS,
 501:                      RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
 502: 
 503:     defaultHints.put(RenderingHints.KEY_ANTIALIASING,
 504:                      RenderingHints.VALUE_ANTIALIAS_OFF);
 505: 
 506:     defaultHints.put(RenderingHints.KEY_RENDERING,
 507:                      RenderingHints.VALUE_RENDER_DEFAULT);
 508: 
 509:     return defaultHints;
 510:   }
 511: 
 512:   public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster)
 513:   {
 514:     if (cm == null || raster == null)
 515:       return null;
 516: 
 517:     if (! cm.getColorSpace().isCS_sRGB())
 518:       return null;
 519: 
 520:     if (! (cm instanceof DirectColorModel))
 521:       return null;
 522: 
 523:     DirectColorModel dcm = (DirectColorModel) cm;
 524: 
 525:     if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00
 526:         || dcm.getBlueMask() != 0x000000FF)
 527:       return null;
 528: 
 529:     if (! (raster instanceof WritableRaster))
 530:       return null;
 531: 
 532:     if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT)
 533:       return null;
 534: 
 535:     if (! (raster.getDataBuffer() instanceof DataBufferInt))
 536:       return null;
 537: 
 538:     DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
 539: 
 540:     if (db.getNumBanks() != 1)
 541:       return null;
 542: 
 543:     // Finally, we have determined that this is a single bank, [A]RGB-int
 544:     // buffer in sRGB space. It's worth checking all this, because it means
 545:     // that cairo can paint directly into the data buffer, which is very
 546:     // fast compared to all the normal copying and converting.
 547: 
 548:     return db.getData();
 549:   }
 550: 
 551:   private void updateBufferedImage()
 552:   {
 553:     if (bimage != null && pixelConversionRequired)
 554:       {
 555:         int height = bimage.getHeight();
 556:         int width = bimage.getWidth();
 557:         int index = 0;
 558:         for (int y = 0; y < height; ++y)
 559:           for (int x = 0; x < width; ++x)
 560:             bimage.setRGB(x, y, pixelBuffer[index++]);
 561:       }
 562:   }
 563: 
 564:   private boolean drawImage(Image img, AffineTransform xform,
 565:                             Color bgcolor, ImageObserver obs)
 566:   {
 567:     if (img == null)
 568:       return false;
 569: 
 570:     // FIXME: I'll fix this, /Sven
 571: //     if (img instanceof GtkOffScreenImage
 572: //         && img.getGraphics() instanceof GdkGraphics2D
 573: //         && (xform == null || xform.getType() == AffineTransform.TYPE_IDENTITY
 574: //         || xform.getType() == AffineTransform.TYPE_TRANSLATION))
 575: //       {
 576: //     // we are being asked to flush a double buffer from Gdk
 577: //     GdkGraphics2D g2 = (GdkGraphics2D) img.getGraphics();
 578: //     gdkDrawDrawable(g2, (int) xform.getTranslateX(),
 579: //                     (int) xform.getTranslateY());
 580: 
 581: //     updateBufferedImage();
 582: 
 583: //     return true;
 584: //       }
 585: //     else
 586:       {
 587:     // In this case, xform is an AffineTransform that transforms bounding
 588:     // box of the specified image from image space to user space. However
 589:     // when we pass this transform to cairo, cairo will use this transform
 590:     // to map "user coordinates" to "pixel" coordinates, which is the 
 591:     // other way around. Therefore to get the "user -> pixel" transform 
 592:     // that cairo wants from "image -> user" transform that we currently
 593:     // have, we will need to invert the transformation matrix.
 594:     AffineTransform invertedXform = new AffineTransform();
 595: 
 596:     try
 597:       {
 598:         invertedXform = xform.createInverse();
 599:         if (img instanceof BufferedImage)
 600:           {
 601:         // draw an image which has actually been loaded 
 602:         // into memory fully
 603:         BufferedImage b = (BufferedImage) img;
 604:         return drawRaster(b.getColorModel(), b.getTile(0, 0),
 605:                           invertedXform, bgcolor);
 606:           }
 607:         else
 608:           return this.drawImage(GdkPixbufDecoder.createBufferedImage(img
 609:                                                                      .getSource()),
 610:                                 xform, bgcolor, obs);
 611:       }
 612:     catch (NoninvertibleTransformException e)
 613:       {
 614:         throw new ImagingOpException("Unable to invert transform "
 615:                                      + xform.toString());
 616:       }
 617:       }
 618:   }
 619: 
 620:   //////////////////////////////////////////////////
 621:   ////// Implementation of Graphics2D Methods //////
 622:   //////////////////////////////////////////////////
 623: 
 624:   public void draw(Shape s)
 625:   {
 626:     if (stroke != null && ! (stroke instanceof BasicStroke))
 627:       {
 628:     fill(stroke.createStrokedShape(s));
 629:     return;
 630:       }
 631: 
 632:     cairoNewPath();
 633: 
 634:     if (s instanceof Rectangle2D)
 635:       {
 636:     Rectangle2D r = (Rectangle2D) s;
 637:     cairoRectangle(shifted(r.getX(), shiftDrawCalls),
 638:                    shifted(r.getY(), shiftDrawCalls), r.getWidth(),
 639:                    r.getHeight());
 640:       }
 641:     else
 642:       walkPath(s.getPathIterator(null), shiftDrawCalls);
 643:     cairoStroke();
 644: 
 645:     updateBufferedImage();
 646:   }
 647: 
 648:   public void fill(Shape s)
 649:   {
 650:     cairoNewPath();
 651:     if (s instanceof Rectangle2D)
 652:       {
 653:     Rectangle2D r = (Rectangle2D) s;
 654:     cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
 655:       }
 656:     else
 657:       walkPath(s.getPathIterator(null), false);
 658: 
 659:     cairoFill();
 660: 
 661:     updateBufferedImage();
 662:   }
 663: 
 664:   public void clip(Shape s)
 665:   {
 666:     // update it
 667:     if (clip == null || s == null)
 668:       clip = s;
 669:     else if (s instanceof Rectangle2D && clip instanceof Rectangle2D)
 670:       {
 671:     Rectangle2D r = (Rectangle2D) s;
 672:     Rectangle2D curr = (Rectangle2D) clip;
 673:     clip = curr.createIntersection(r);
 674:       }
 675:     else
 676:       throw new UnsupportedOperationException();
 677: 
 678:     // draw it
 679:     if (clip != null)
 680:       {
 681:     cairoNewPath();
 682:     if (clip instanceof Rectangle2D)
 683:       {
 684:         Rectangle2D r = (Rectangle2D) clip;
 685:         cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
 686:       }
 687:     else
 688:       walkPath(clip.getPathIterator(null), false);
 689: 
 690:     // cairoClosePath ();
 691:     cairoClip();
 692:       }
 693:   }
 694: 
 695:   public Paint getPaint()
 696:   {
 697:     return paint;
 698:   }
 699: 
 700:   public AffineTransform getTransform()
 701:   {
 702:     return (AffineTransform) transform.clone();
 703:   }
 704: 
 705:   public void setPaint(Paint p)
 706:   {
 707:     if (paint == null)
 708:       return;
 709: 
 710:     paint = p;
 711:     if (paint instanceof Color)
 712:       {
 713:         setColor((Color) paint);
 714:       }
 715:     else if (paint instanceof TexturePaint)
 716:       {
 717:     TexturePaint tp = (TexturePaint) paint;
 718:     BufferedImage img = tp.getImage();
 719: 
 720:     // map the image to the anchor rectangle  
 721:     int width = (int) tp.getAnchorRect().getWidth();
 722:     int height = (int) tp.getAnchorRect().getHeight();
 723: 
 724:     double scaleX = width / (double) img.getWidth();
 725:     double scaleY = width / (double) img.getHeight();
 726: 
 727:     AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
 728:     AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
 729:     BufferedImage texture = op.filter(img, null);
 730:     int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
 731:     setTexturePixels(pixels, width, height, width);
 732:       }
 733:     else if (paint instanceof GradientPaint)
 734:       {
 735:     GradientPaint gp = (GradientPaint) paint;
 736:     Point2D p1 = gp.getPoint1();
 737:     Point2D p2 = gp.getPoint2();
 738:     Color c1 = gp.getColor1();
 739:     Color c2 = gp.getColor2();
 740:     setGradient(p1.getX(), p1.getY(), p2.getX(), p2.getY(), c1.getRed(),
 741:                 c1.getGreen(), c1.getBlue(), c1.getAlpha(), c2.getRed(),
 742:                 c2.getGreen(), c2.getBlue(), c2.getAlpha(), gp.isCyclic());
 743:       }
 744:     else
 745:       throw new java.lang.UnsupportedOperationException();
 746:   }
 747: 
 748:   public void setPaintUnlocked(Paint p)
 749:   {
 750:     if (paint == null)
 751:       return;
 752: 
 753:     paint = p;
 754:     if (paint instanceof Color)
 755:       {
 756:         setColorUnlocked((Color) paint);
 757:       }
 758:     else if (paint instanceof TexturePaint)
 759:       {
 760:     TexturePaint tp = (TexturePaint) paint;
 761:     BufferedImage img = tp.getImage();
 762: 
 763:     // map the image to the anchor rectangle  
 764:     int width = (int) tp.getAnchorRect().getWidth();
 765:     int height = (int) tp.getAnchorRect().getHeight();
 766: 
 767:     double scaleX = width / (double) img.getWidth();
 768:     double scaleY = width / (double) img.getHeight();
 769: 
 770:     AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
 771:     AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
 772:     BufferedImage texture = op.filter(img, null);
 773:     int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
 774:     setTexturePixelsUnlocked(pixels, width, height, width);
 775:       }
 776:     else if (paint instanceof GradientPaint)
 777:       {
 778:     GradientPaint gp = (GradientPaint) paint;
 779:     Point2D p1 = gp.getPoint1();
 780:     Point2D p2 = gp.getPoint2();
 781:     Color c1 = gp.getColor1();
 782:     Color c2 = gp.getColor2();
 783:     setGradientUnlocked(p1.getX(), p1.getY(), p2.getX(), p2.getY(), c1.getRed(),
 784:                 c1.getGreen(), c1.getBlue(), c1.getAlpha(), c2.getRed(),
 785:                 c2.getGreen(), c2.getBlue(), c2.getAlpha(), gp.isCyclic());
 786:       }
 787:     else
 788:       throw new java.lang.UnsupportedOperationException();
 789:   }
 790: 
 791:   public void setTransform(AffineTransform tx)
 792:   {
 793:     transform = tx;
 794:     if (transform != null)
 795:       {
 796:     double[] m = new double[6];
 797:     transform.getMatrix(m);
 798:     cairoSetMatrix(m);
 799:       }
 800:   }
 801: 
 802:   public void setTransformUnlocked(AffineTransform tx)
 803:   {
 804:     transform = tx;
 805:     if (transform != null)
 806:       {
 807:     double[] m = new double[6];
 808:     transform.getMatrix(m);
 809:     cairoSetMatrixUnlocked(m);
 810:       }
 811:   }
 812: 
 813:   public void transform(AffineTransform tx)
 814:   {
 815:     if (transform == null)
 816:       transform = new AffineTransform(tx);
 817:     else
 818:       transform.concatenate(tx);
 819:     setTransform(transform);
 820:     if (clip != null)
 821:       {
 822:     // FIXME: this should actuall try to transform the shape
 823:     // rather than degrade to bounds.
 824:     Rectangle2D r = clip.getBounds2D();
 825:     double[] coords = new double[]
 826:                       {
 827:                         r.getX(), r.getY(), r.getX() + r.getWidth(),
 828:                         r.getY() + r.getHeight()
 829:                       };
 830:     try
 831:       {
 832:         tx.createInverse().transform(coords, 0, coords, 0, 2);
 833:         r.setRect(coords[0], coords[1], coords[2] - coords[0],
 834:                   coords[3] - coords[1]);
 835:         clip = r;
 836:       }
 837:     catch (java.awt.geom.NoninvertibleTransformException e)
 838:       {
 839:       }
 840:       }
 841:   }
 842: 
 843:   public void rotate(double theta)
 844:   {
 845:     transform(AffineTransform.getRotateInstance(theta));
 846:   }
 847: 
 848:   public void rotate(double theta, double x, double y)
 849:   {
 850:     transform(AffineTransform.getRotateInstance(theta, x, y));
 851:   }
 852: 
 853:   public void scale(double sx, double sy)
 854:   {
 855:     transform(AffineTransform.getScaleInstance(sx, sy));
 856:   }
 857: 
 858:   public void translate(double tx, double ty)
 859:   {
 860:     transform(AffineTransform.getTranslateInstance(tx, ty));
 861:   }
 862: 
 863:   public void translate(int x, int y)
 864:   {
 865:     translate((double) x, (double) y);
 866:   }
 867: 
 868:   public void shear(double shearX, double shearY)
 869:   {
 870:     transform(AffineTransform.getShearInstance(shearX, shearY));
 871:   }
 872: 
 873:   public Stroke getStroke()
 874:   {
 875:     return stroke;
 876:   }
 877: 
 878:   public void setStroke(Stroke st)
 879:   {
 880:     stroke = st;
 881:     if (stroke instanceof BasicStroke)
 882:       {
 883:     BasicStroke bs = (BasicStroke) stroke;
 884:     cairoSetLineCap(bs.getEndCap());
 885:     cairoSetLineWidth(bs.getLineWidth());
 886:     cairoSetLineJoin(bs.getLineJoin());
 887:     cairoSetMiterLimit(bs.getMiterLimit());
 888:     float[] dashes = bs.getDashArray();
 889:     if (dashes != null)
 890:       {
 891:         double[] double_dashes = new double[dashes.length];
 892:         for (int i = 0; i < dashes.length; i++)
 893:           double_dashes[i] = dashes[i];
 894:         cairoSetDash(double_dashes, double_dashes.length,
 895:                      (double) bs.getDashPhase());
 896:       }
 897:     else
 898:       cairoSetDash(new double[0], 0, 0.0);
 899:       }
 900:   }
 901: 
 902:   public void setStrokeUnlocked(Stroke st)
 903:   {
 904:     stroke = st;
 905:     if (stroke instanceof BasicStroke)
 906:       {
 907:     BasicStroke bs = (BasicStroke) stroke;
 908:     cairoSetLineCapUnlocked(bs.getEndCap());
 909:     cairoSetLineWidthUnlocked(bs.getLineWidth());
 910:     cairoSetLineJoinUnlocked(bs.getLineJoin());
 911:     cairoSetMiterLimitUnlocked(bs.getMiterLimit());
 912:     float[] dashes = bs.getDashArray();
 913:     if (dashes != null)
 914:       {
 915:         double[] double_dashes = new double[dashes.length];
 916:         for (int i = 0; i < dashes.length; i++)
 917:           double_dashes[i] = dashes[i];
 918:         cairoSetDashUnlocked(double_dashes, double_dashes.length,
 919:                                  (double) bs.getDashPhase());
 920:       }
 921:     else
 922:       cairoSetDashUnlocked(new double[0], 0, 0.0);
 923:       }
 924:   }
 925: 
 926:   ////////////////////////////////////////////////
 927:   ////// Implementation of Graphics Methods //////
 928:   ////////////////////////////////////////////////
 929: 
 930:   public void setPaintMode()
 931:   {
 932:     setComposite(java.awt.AlphaComposite.SrcOver);
 933:   }
 934: 
 935:   public void setXORMode(Color c)
 936:   {
 937:     setComposite(new gnu.java.awt.BitwiseXORComposite(c));
 938:   }
 939: 
 940:   public void setColor(Color c)
 941:   {
 942:     if (c == null)
 943:       c = Color.BLACK;
 944: 
 945:     fg = c;
 946:     paint = c;
 947:     cairoSetRGBAColor(fg.getRed() / 255.0, fg.getGreen() / 255.0,
 948:                       fg.getBlue() / 255.0, fg.getAlpha() / 255.0);
 949:   }
 950: 
 951:   public void setColorUnlocked(Color c)
 952:   {
 953:     if (c == null)
 954:       c = Color.BLACK;
 955: 
 956:     fg = c;
 957:     paint = c;
 958:     cairoSetRGBAColorUnlocked(fg.getRed() / 255.0, fg.getGreen() / 255.0,
 959:                       fg.getBlue() / 255.0, fg.getAlpha() / 255.0);
 960:   }
 961: 
 962:   public Color getColor()
 963:   {
 964:     return fg;
 965:   }
 966: 
 967:   public Color getColorUnlocked()
 968:   {
 969:     return getColor();
 970:   }
 971: 
 972:   public void clipRect(int x, int y, int width, int height)
 973:   {
 974:     clip(new Rectangle(x, y, width, height));
 975:   }
 976: 
 977:   public Shape getClip()
 978:   {
 979:     if (clip == null)
 980:       return null;
 981:     else
 982:       return clip.getBounds2D(); //getClipInDevSpace();
 983:   }
 984: 
 985:   public Rectangle getClipBounds()
 986:   {
 987:     if (clip == null)
 988:       return null;
 989:     else
 990:       return clip.getBounds();
 991:   }
 992: 
 993:   protected Rectangle2D getClipInDevSpace()
 994:   {
 995:     Rectangle2D uclip = clip.getBounds2D();
 996:     if (transform == null)
 997:       return uclip;
 998:     else
 999:       {
1000:     Point2D pos = transform.transform(new Point2D.Double(uclip.getX(),
1001:                                                          uclip.getY()),
1002:                                       (Point2D) null);
1003:     Point2D extent = transform.deltaTransform(new Point2D.Double(uclip
1004:                                                                  .getWidth(),
1005:                                                                  uclip
1006:                                                                  .getHeight()),
1007:                                               (Point2D) null);
1008:     return new Rectangle2D.Double(pos.getX(), pos.getY(), extent.getX(),
1009:                                   extent.getY());
1010:       }
1011:   }
1012: 
1013:   public void setClip(int x, int y, int width, int height)
1014:   {
1015:     setClip(new Rectangle2D.Double((double) x, (double) y, (double) width,
1016:                                    (double) height));
1017:   }
1018: 
1019:   public void setClip(Shape s)
1020:   {
1021:     clip = s;
1022:     if (clip == null)
1023:       {
1024:     // Reset clipping.
1025:         if (component != null)
1026:           {
1027:             Dimension d = component.awtComponent.getSize();
1028:             setClip(0, 0, d.width, d.height);
1029:           }
1030:       }
1031:     else
1032:       {
1033:     cairoNewPath();
1034:     if (s instanceof Rectangle2D)
1035:       {
1036:         Rectangle2D r = (Rectangle2D) s;
1037:         cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight());
1038:       }
1039:     else
1040:       walkPath(s.getPathIterator(null), false);
1041: 
1042:     // cairoClosePath ();
1043:     cairoClip();
1044:       }
1045:   }
1046: 
1047:   private static BasicStroke draw3DRectStroke = new BasicStroke();
1048: 
1049:   public void draw3DRect(int x, int y, int width, int height, boolean raised)
1050:   {
1051:     Stroke tmp = stroke;
1052:     setStroke(draw3DRectStroke);
1053:     super.draw3DRect(x, y, width, height, raised);
1054:     setStroke(tmp);
1055:     updateBufferedImage();
1056:   }
1057: 
1058:   public void fill3DRect(int x, int y, int width, int height, boolean raised)
1059:   {
1060:     Stroke tmp = stroke;
1061:     setStroke(draw3DRectStroke);
1062:     super.fill3DRect(x, y, width, height, raised);
1063:     setStroke(tmp);
1064:     updateBufferedImage();
1065:   }
1066: 
1067:   public void drawRect(int x, int y, int width, int height)
1068:   {
1069:     draw(new Rectangle(x, y, width, height));
1070:   }
1071: 
1072:   public void fillRect(int x, int y, int width, int height)
1073:   {
1074:     cairoNewPath();
1075:     cairoRectangle(x, y, width, height);
1076:     cairoFill();
1077:   }
1078: 
1079:   public void clearRect(int x, int y, int width, int height)
1080:   {
1081:     if (bg != null)
1082:       cairoSetRGBAColor(bg.getRed() / 255.0, bg.getGreen() / 255.0,
1083:             bg.getBlue() / 255.0, 1.0);
1084:     cairoNewPath();
1085:     cairoRectangle(x, y, width, height);
1086:     cairoFill();
1087:     setColor(fg);
1088: 
1089:     updateBufferedImage();
1090:   }
1091: 
1092:   public void setBackground(Color c)
1093:   {
1094:     if (c == null)
1095:       c = Color.WHITE;
1096:     bg = c;
1097:   }
1098: 
1099:   public void setBackgroundUnlocked(Color c)
1100:   {
1101:     setBackground(c);
1102:   }
1103: 
1104:   public Color getBackground()
1105:   {
1106:     return bg;
1107:   }
1108: 
1109:   private void doPolygon(int[] xPoints, int[] yPoints, int nPoints,
1110:                          boolean close, boolean fill)
1111:   {
1112:     if (nPoints < 1)
1113:       return;
1114:     GeneralPath gp = new GeneralPath(PathIterator.WIND_EVEN_ODD);
1115:     gp.moveTo((float) xPoints[0], (float) yPoints[0]);
1116:     for (int i = 1; i < nPoints; i++)
1117:       gp.lineTo((float) xPoints[i], (float) yPoints[i]);
1118: 
1119:     if (close)
1120:       gp.closePath();
1121: 
1122:     Shape sh = gp;
1123:     if (fill == false && stroke != null && ! (stroke instanceof BasicStroke))
1124:       {
1125:     sh = stroke.createStrokedShape(gp);
1126:     fill = true;
1127:       }
1128: 
1129:     if (fill)
1130:       fill(sh);
1131:     else
1132:       draw(sh);
1133:   }
1134: 
1135:   public void drawLine(int x1, int y1, int x2, int y2)
1136:   {
1137:     int[] xp = new int[2];
1138:     int[] yp = new int[2];
1139: 
1140:     xp[0] = x1;
1141:     xp[1] = x2;
1142:     yp[0] = y1;
1143:     yp[1] = y2;
1144: 
1145:     doPolygon(xp, yp, 2, false, false);
1146:   }
1147: 
1148:   public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
1149:   {
1150:     doPolygon(xPoints, yPoints, nPoints, true, true);
1151:   }
1152: 
1153:   public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
1154:   {
1155:     doPolygon(xPoints, yPoints, nPoints, true, false);
1156:   }
1157: 
1158:   public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
1159:   {
1160:     doPolygon(xPoints, yPoints, nPoints, false, false);
1161:   }
1162: 
1163:   private boolean drawRaster(ColorModel cm, Raster r,
1164:                              AffineTransform imageToUser, Color bgcolor)
1165:   {
1166:     if (r == null)
1167:       return false;
1168: 
1169:     SampleModel sm = r.getSampleModel();
1170:     DataBuffer db = r.getDataBuffer();
1171: 
1172:     if (db == null || sm == null)
1173:       return false;
1174: 
1175:     if (cm == null)
1176:       cm = ColorModel.getRGBdefault();
1177: 
1178:     double[] i2u = new double[6];
1179:     if (imageToUser != null)
1180:       imageToUser.getMatrix(i2u);
1181:     else
1182:       {
1183:     i2u[0] = 1;
1184:     i2u[1] = 0;
1185:     i2u[2] = 0;
1186:     i2u[3] = 1;
1187:     i2u[4] = 0;
1188:     i2u[5] = 0;
1189:       }
1190: 
1191:     int[] pixels = findSimpleIntegerArray(cm, r);
1192: 
1193:     if (pixels == null)
1194:       {
1195:     // FIXME: I don't think this code will work correctly with a non-RGB
1196:     // MultiPixelPackedSampleModel. Although this entire method should 
1197:     // probably be rewritten to better utilize Cairo's different supported
1198:     // data formats.
1199:     if (sm instanceof MultiPixelPackedSampleModel)
1200:       {
1201:         pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels);
1202:         for (int i = 0; i < pixels.length; i++)
1203:           pixels[i] = cm.getRGB(pixels[i]);
1204:       }
1205:     else
1206:       {
1207:         pixels = new int[r.getWidth() * r.getHeight()];
1208:         for (int i = 0; i < pixels.length; i++)
1209:           pixels[i] = cm.getRGB(db.getElem(i));
1210:       }
1211:       }
1212: 
1213:     // Change all transparent pixels in the image to the specified bgcolor,
1214:     // or (if there's no alpha) fill in an alpha channel so that it paints
1215:     // correctly.
1216:     if (cm.hasAlpha())
1217:       {
1218:     if (bgcolor != null && cm.hasAlpha())
1219:       for (int i = 0; i < pixels.length; i++)
1220:         {
1221:           if (cm.getAlpha(pixels[i]) == 0)
1222:         pixels[i] = bgcolor.getRGB();
1223:         }
1224:       }
1225:     else
1226:       for (int i = 0; i < pixels.length; i++)
1227:     pixels[i] |= 0xFF000000;
1228: 
1229:     drawPixels(pixels, r.getWidth(), r.getHeight(), r.getWidth(), i2u);
1230: 
1231:     updateBufferedImage();
1232: 
1233:     return true;
1234:   }
1235: 
1236:   public void drawRenderedImage(RenderedImage image, AffineTransform xform)
1237:   {
1238:     drawRaster(image.getColorModel(), image.getData(), xform, bg);
1239:   }
1240: 
1241:   public void drawRenderableImage(RenderableImage image, AffineTransform xform)
1242:   {
1243:     drawRenderedImage(image.createRendering(new RenderContext(xform)), xform);
1244:   }
1245: 
1246:   public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
1247:   {
1248:     return drawImage(img, xform, bg, obs);
1249:   }
1250: 
1251:   public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y)
1252:   {
1253:     Image filtered = op.filter(image, null);
1254:     drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), bg, null);
1255:   }
1256: 
1257:   public boolean drawImage(Image img, int x, int y, ImageObserver observer)
1258:   {
1259:     return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), bg,
1260:                      observer);
1261:   }
1262: 
1263:   ///////////////////////////////////////////////
1264:   ////// Unimplemented Stubs and Overloads //////
1265:   ///////////////////////////////////////////////
1266: 
1267:   public boolean hit(Rectangle rect, Shape text, boolean onStroke)
1268:   {
1269:     throw new java.lang.UnsupportedOperationException();
1270:   }
1271: 
1272:   public GraphicsConfiguration getDeviceConfiguration()
1273:   {
1274:     throw new java.lang.UnsupportedOperationException();
1275:   }
1276: 
1277:   public void setComposite(Composite comp)
1278:   {
1279:     this.comp = comp;
1280: 
1281:     if (comp instanceof AlphaComposite)
1282:       {
1283:     AlphaComposite a = (AlphaComposite) comp;
1284:     cairoSetOperator(a.getRule());
1285:     Color c = getColor();
1286:     setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(),
1287:                        (int) (a.getAlpha() * ((float) c.getAlpha()))));
1288:       }
1289:     else
1290:       throw new java.lang.UnsupportedOperationException();
1291:   }
1292: 
1293:   public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
1294:   {
1295:     hints.put(hintKey, hintValue);
1296: 
1297:     if (hintKey.equals(RenderingHints.KEY_INTERPOLATION)
1298:         || hintKey.equals(RenderingHints.KEY_ALPHA_INTERPOLATION))
1299:       {
1300:     if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1301:       cairoSurfaceSetFilter(0);
1302: 
1303:     else if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1304:       cairoSurfaceSetFilter(1);
1305: 
1306:     else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1307:       cairoSurfaceSetFilter(2);
1308: 
1309:     else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1310:       cairoSurfaceSetFilter(3);
1311: 
1312:     else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1313:       cairoSurfaceSetFilter(4);
1314:       }
1315: 
1316:     shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1317:                      || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1318:   }
1319: 
1320:   public Object getRenderingHint(RenderingHints.Key hintKey)
1321:   {
1322:     return hints.get(hintKey);
1323:   }
1324: 
1325:   public void setRenderingHints(Map hints)
1326:   {
1327:     this.hints = new RenderingHints(getDefaultHints());
1328:     this.hints.add(new RenderingHints(hints));
1329: 
1330:     if (hints.containsKey(RenderingHints.KEY_INTERPOLATION))
1331:       {
1332:     if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1333:       cairoSurfaceSetFilter(0);
1334: 
1335:     else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1336:       cairoSurfaceSetFilter(1);
1337:       }
1338: 
1339:     if (hints.containsKey(RenderingHints.KEY_ALPHA_INTERPOLATION))
1340:       {
1341:     if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1342:       cairoSurfaceSetFilter(2);
1343: 
1344:     else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1345:       cairoSurfaceSetFilter(3);
1346: 
1347:     else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1348:       cairoSurfaceSetFilter(4);
1349:       }
1350: 
1351:     shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1352:                      || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1353:   }
1354: 
1355:   public void setRenderingHintsUnlocked(Map hints)
1356:   {
1357:     this.hints = new RenderingHints(getDefaultHints());
1358:     this.hints.add(new RenderingHints(hints));
1359: 
1360:     if (hints.containsKey(RenderingHints.KEY_INTERPOLATION))
1361:       {
1362:     if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1363:       cairoSurfaceSetFilterUnlocked(0);
1364: 
1365:     else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1366:       cairoSurfaceSetFilterUnlocked(1);
1367:       }
1368: 
1369:     if (hints.containsKey(RenderingHints.KEY_ALPHA_INTERPOLATION))
1370:       {
1371:     if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1372:       cairoSurfaceSetFilterUnlocked(2);
1373: 
1374:     else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1375:       cairoSurfaceSetFilterUnlocked(3);
1376: 
1377:     else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1378:       cairoSurfaceSetFilterUnlocked(4);
1379:       }
1380: 
1381:     shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1382:                      || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1383:   }
1384: 
1385:   public void addRenderingHints(Map hints)
1386:   {
1387:     this.hints.add(new RenderingHints(hints));
1388:   }
1389: 
1390:   public RenderingHints getRenderingHints()
1391:   {
1392:     return hints;
1393:   }
1394: 
1395:   public Composite getComposite()
1396:   {
1397:     if (comp == null)
1398:       return AlphaComposite.SrcOver;
1399:     else
1400:       return comp;
1401:   }
1402: 
1403:   public FontRenderContext getFontRenderContext()
1404:   {
1405:     return new FontRenderContext(transform, true, true);
1406:   }
1407: 
1408:   public void copyArea(int x, int y, int width, int height, int dx, int dy)
1409:   {
1410:     GdkGraphics2D g = (GdkGraphics2D) create(x, y, width, height);
1411:     gdkDrawDrawable(g, x + dx, y + dy);
1412:   }
1413: 
1414:   public void drawArc(int x, int y, int width, int height, int startAngle,
1415:                       int arcAngle)
1416:   {
1417:     draw(new Arc2D.Double((double) x, (double) y, (double) width,
1418:                           (double) height, (double) startAngle,
1419:                           (double) arcAngle, Arc2D.OPEN));
1420:   }
1421: 
1422:   public boolean drawImage(Image img, int x, int y, Color bgcolor,
1423:                            ImageObserver observer)
1424:   {
1425:     return drawImage(img, x, y, img.getWidth(observer),
1426:                      img.getHeight(observer), bgcolor, observer);
1427:   }
1428: 
1429:   public boolean drawImage(Image img, int x, int y, int width, int height,
1430:                            Color bgcolor, ImageObserver observer)
1431:   {
1432:     double scaleX = width / (double) img.getWidth(observer);
1433:     double scaleY = height / (double) img.getHeight(observer);
1434: 
1435:     return drawImage(img, new AffineTransform(scaleX, 0f, 0f, scaleY, x, y),
1436:                      bgcolor, observer);
1437:   }
1438: 
1439:   public boolean drawImage(Image img, int x, int y, int width, int height,
1440:                            ImageObserver observer)
1441:   {
1442:     return drawImage(img, x, y, width, height, bg, observer);
1443:   }
1444: 
1445:   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1446:                            int sx1, int sy1, int sx2, int sy2, Color bgcolor,
1447:                            ImageObserver observer)
1448:   {
1449:     if (img == null)
1450:       return false;
1451: 
1452:     Image subImage;
1453: 
1454:     int sourceWidth = sx2 - sx1;
1455:     int sourceHeight = sy2 - sy1;
1456: 
1457:     int destWidth = dx2 - dx1;
1458:     int destHeight = dy2 - dy1;
1459: 
1460:     double scaleX = destWidth / (double) sourceWidth;
1461:     double scaleY = destHeight / (double) sourceHeight;
1462: 
1463:     // Get the subimage of the source enclosed in the 
1464:     // rectangle specified by sx1, sy1, sx2, sy2
1465:     
1466:     if (img instanceof BufferedImage)
1467:       {
1468:     BufferedImage b = (BufferedImage) img;
1469:     subImage = b.getSubimage(sx1, sy1, sx2, sy2);
1470:       }
1471:     else
1472:       {
1473:     // FIXME: This code currently doesn't work. Null Pointer 
1474:     // exception is thrown in this case. This happens 
1475:     // because img.getSource() always returns null, since source gets 
1476:     // never initialized when it is created with the help of 
1477:     // createImage(int width, int height). 
1478:     CropImageFilter filter = new CropImageFilter(sx1, sx2, sx2, sy2);
1479:     FilteredImageSource src = new FilteredImageSource(img.getSource(),
1480:                                                       filter);
1481: 
1482:     subImage = Toolkit.getDefaultToolkit().createImage(src);
1483:       }
1484: 
1485:     return drawImage(subImage,
1486:                      new AffineTransform(scaleX, 0, 0, scaleY, dx1, dy1),
1487:                      bgcolor, observer);
1488:   }
1489: 
1490:   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1491:                            int sx1, int sy1, int sx2, int sy2,
1492:                            ImageObserver observer)
1493:   {
1494:     return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bg, observer);
1495:   }
1496: 
1497:   public void drawOval(int x, int y, int width, int height)
1498:   {
1499:     drawArc(x, y, width, height, 0, 360);
1500:   }
1501: 
1502:   public void drawRoundRect(int x, int y, int width, int height, int arcWidth,
1503:                             int arcHeight)
1504:   {
1505:     if (arcWidth > width)
1506:       arcWidth = width;
1507:     if (arcHeight > height)
1508:       arcHeight = height;
1509: 
1510:     int xx = x + width - arcWidth;
1511:     int yy = y + height - arcHeight;
1512: 
1513:     drawArc(x, y, arcWidth, arcHeight, 90, 90);
1514:     drawArc(xx, y, arcWidth, arcHeight, 0, 90);
1515:     drawArc(xx, yy, arcWidth, arcHeight, 270, 90);
1516:     drawArc(x, yy, arcWidth, arcHeight, 180, 90);
1517: 
1518:     int y1 = y + arcHeight / 2;
1519:     int y2 = y + height - arcHeight / 2;
1520:     drawLine(x, y1, x, y2);
1521:     drawLine(x + width, y1, x + width, y2);
1522: 
1523:     int x1 = x + arcWidth / 2;
1524:     int x2 = x + width - arcWidth / 2;
1525:     drawLine(x1, y, x2, y);
1526:     drawLine(x1, y + height, x2, y + height);
1527:   }
1528: 
1529:   // these are the most accelerated painting paths
1530:   native void cairoDrawGlyphVector(GdkFontPeer font, 
1531:                                    float x, float y, int n, 
1532:                                    int[] codes, float[] positions);
1533: 
1534:   native void cairoDrawGdkTextLayout(GdkTextLayout gl, 
1535:                                      float x, float y);
1536: 
1537:   GdkFontPeer getFontPeer()
1538:   {
1539:     return (GdkFontPeer) getFont().getPeer();
1540:   }
1541: 
1542:   public void drawGdkTextLayout(GdkTextLayout gl, float x, float y)
1543:   {
1544:     cairoDrawGdkTextLayout (gl, x, y);
1545:     updateBufferedImage ();
1546:   }
1547: 
1548:   public void drawString(String str, float x, float y)
1549:   {
1550:     if (str == null || str.length() == 0)
1551:       return;
1552: 
1553:     drawGlyphVector(getFont().createGlyphVector(null, str), x, y);
1554:     updateBufferedImage ();
1555:   }
1556: 
1557:   public void drawString(String str, int x, int y)
1558:   {
1559:     drawString (str, (float) x, (float) y);
1560:   }
1561: 
1562:   public void drawString(AttributedCharacterIterator ci, int x, int y)
1563:   {
1564:     drawString (ci, (float) x, (float) y);
1565:   }
1566: 
1567:   public void drawGlyphVector(GlyphVector gv, float x, float y)
1568:   {
1569:     int n = gv.getNumGlyphs ();
1570:     int[] codes = gv.getGlyphCodes (0, n, null);
1571:     float[] positions = gv.getGlyphPositions (0, n, null);
1572:     
1573:     setFont (gv.getFont ());
1574:     cairoDrawGlyphVector (getFontPeer(), x, y, n, codes, positions);
1575:     updateBufferedImage ();
1576:   }
1577: 
1578:   public void drawString(AttributedCharacterIterator ci, float x, float y)
1579:   {
1580:     GlyphVector gv = getFont().createGlyphVector(getFontRenderContext(), ci);
1581:     drawGlyphVector(gv, x, y);
1582:   }
1583: 
1584:   public void fillArc(int x, int y, int width, int height, int startAngle,
1585:                       int arcAngle)
1586:   {
1587:     fill(new Arc2D.Double((double) x, (double) y, (double) width,
1588:                           (double) height, (double) startAngle,
1589:                           (double) arcAngle, Arc2D.OPEN));
1590:   }
1591: 
1592:   public void fillOval(int x, int y, int width, int height)
1593:   {
1594:     fillArc(x, y, width, height, 0, 360);
1595:   }
1596: 
1597:   public void fillRoundRect(int x, int y, int width, int height, int arcWidth,
1598:                             int arcHeight)
1599:   {
1600:     if (arcWidth > width)
1601:       arcWidth = width;
1602:     if (arcHeight > height)
1603:       arcHeight = height;
1604: 
1605:     int xx = x + width - arcWidth;
1606:     int yy = y + height - arcHeight;
1607: 
1608:     fillArc(x, y, arcWidth, arcHeight, 90, 90);
1609:     fillArc(xx, y, arcWidth, arcHeight, 0, 90);
1610:     fillArc(xx, yy, arcWidth, arcHeight, 270, 90);
1611:     fillArc(x, yy, arcWidth, arcHeight, 180, 90);
1612: 
1613:     fillRect(x, y + arcHeight / 2, width, height - arcHeight + 1);
1614:     fillRect(x + arcWidth / 2, y, width - arcWidth + 1, height);
1615:   }
1616: 
1617:   public Font getFont()
1618:   {
1619:     if (font == null)
1620:       return new Font("SansSerif", Font.PLAIN, 12);
1621:     return font;
1622:   }
1623: 
1624:   // Until such time as pango is happy to talk directly to cairo, we
1625:   // actually need to redirect some calls from the GtkFontPeer and
1626:   // GtkFontMetrics into the drawing kit and ask cairo ourselves.
1627: 
1628:   static native void releasePeerGraphicsResource(GdkFontPeer f);
1629: 
1630:   public FontMetrics getFontMetrics()
1631:   {
1632:     return getFontMetrics(getFont());
1633:   }
1634: 
1635:   public FontMetrics getFontMetrics(Font f)
1636:   {
1637:     // the reason we go via the toolkit here is to try to get
1638:     // a cached object. the toolkit keeps such a cache.
1639:     return Toolkit.getDefaultToolkit().getFontMetrics(f);
1640:   }
1641: 
1642:   public void setFont(Font f)
1643:   {
1644:     // Sun's JDK does not throw NPEs, instead it leaves the current setting
1645:     // unchanged. So do we.
1646:     if (f == null)
1647:       return;
1648: 
1649:     if (f.getPeer() instanceof GdkFontPeer)
1650:       font = f;
1651:     else
1652:       font = 
1653:         ((ClasspathToolkit)(Toolkit.getDefaultToolkit()))
1654:         .getFont(f.getName(), f.getAttributes());    
1655:   }
1656: 
1657:   public void setFontUnlocked(Font f)
1658:   {
1659:     setFont (f);
1660:   }
1661: 
1662:   public String toString()
1663:   {
1664:     return  (getClass().getName()
1665:              + "[font=" + getFont().toString()
1666:              + ",color=" + fg.toString()
1667:          + "]");
1668:   }
1669: }