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

   1: /* CairoGraphics2D.java --
   2:    Copyright (C) 2006  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.java.awt.ClasspathToolkit;
  42: 
  43: import java.awt.AWTPermission;
  44: import java.awt.AlphaComposite;
  45: import java.awt.BasicStroke;
  46: import java.awt.Color;
  47: import java.awt.Composite;
  48: import java.awt.Font;
  49: import java.awt.FontMetrics;
  50: import java.awt.GradientPaint;
  51: import java.awt.Graphics;
  52: import java.awt.Graphics2D;
  53: import java.awt.GraphicsConfiguration;
  54: import java.awt.Image;
  55: import java.awt.Paint;
  56: import java.awt.Polygon;
  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.font.TextLayout;
  66: import java.awt.geom.AffineTransform;
  67: import java.awt.geom.Arc2D;
  68: import java.awt.geom.Area;
  69: import java.awt.geom.Ellipse2D;
  70: import java.awt.geom.GeneralPath;
  71: import java.awt.geom.NoninvertibleTransformException;
  72: import java.awt.geom.PathIterator;
  73: import java.awt.geom.Point2D;
  74: import java.awt.geom.Rectangle2D;
  75: import java.awt.geom.RoundRectangle2D;
  76: import java.awt.image.AffineTransformOp;
  77: import java.awt.image.BufferedImage;
  78: import java.awt.image.BufferedImageOp;
  79: import java.awt.image.ColorModel;
  80: import java.awt.image.DataBuffer;
  81: import java.awt.image.DataBufferInt;
  82: import java.awt.image.DirectColorModel;
  83: import java.awt.image.ImageObserver;
  84: import java.awt.image.ImageProducer;
  85: import java.awt.image.ImagingOpException;
  86: import java.awt.image.MultiPixelPackedSampleModel;
  87: import java.awt.image.Raster;
  88: import java.awt.image.RenderedImage;
  89: import java.awt.image.SampleModel;
  90: import java.awt.image.WritableRaster;
  91: import java.awt.image.renderable.RenderContext;
  92: import java.awt.image.renderable.RenderableImage;
  93: import java.text.AttributedCharacterIterator;
  94: import java.util.HashMap;
  95: import java.util.Map;
  96: 
  97: /**
  98:  * This is an abstract implementation of Graphics2D on Cairo. 
  99:  *
 100:  * It should be subclassed for different Cairo contexts.
 101:  *
 102:  * Note for subclassers: Apart from the constructor (see comments below),
 103:  * The following abstract methods must be implemented:
 104:  *
 105:  * Graphics create()
 106:  * GraphicsConfiguration getDeviceConfiguration()
 107:  * copyArea(int x, int y, int width, int height, int dx, int dy)
 108:  *
 109:  * Also, dispose() must be overloaded to free any native datastructures 
 110:  * used by subclass and in addition call super.dispose() to free the
 111:  * native cairographics2d structure and cairo_t.
 112:  *
 113:  * @author Sven de Marothy
 114:  */
 115: public abstract class CairoGraphics2D extends Graphics2D
 116: {
 117:   static 
 118:   {
 119:     System.loadLibrary("gtkpeer");
 120:   }
 121: 
 122:   /**
 123:    * Important: This is a pointer to the native cairographics2d structure
 124:    *
 125:    * DO NOT CHANGE WITHOUT CHANGING NATIVE CODE.
 126:    */
 127:   long nativePointer;
 128: 
 129:   // Drawing state variables
 130:   /**
 131:    * The current paint
 132:    */
 133:   Paint paint;
 134: 
 135:   /**
 136:    * The current stroke
 137:    */
 138:   Stroke stroke;
 139: 
 140:   /*
 141:    * Current foreground and background color.
 142:    */
 143:   Color fg, bg;
 144: 
 145:   /**
 146:    * Current clip shape.
 147:    */
 148:   Shape clip;
 149: 
 150:   /**
 151:    * Current transform.
 152:    */
 153:   AffineTransform transform;
 154: 
 155:   /**
 156:    * Current font.
 157:    */
 158:   Font font;
 159: 
 160:   /**
 161:    * The current compositing context, if any.
 162:    */
 163:   Composite comp;
 164: 
 165:   /**
 166:    * Rendering hint map.
 167:    */
 168:   private RenderingHints hints;
 169: 
 170:   /**
 171:    * Some operations (drawing rather than filling) require that their
 172:    * coords be shifted to land on 0.5-pixel boundaries, in order to land on
 173:    * "middle of pixel" coordinates and light up complete pixels. 
 174:    */
 175:   private boolean shiftDrawCalls = false;
 176: 
 177:   /**
 178:    * Keep track if the first clip to be set, which is restored on setClip(null);
 179:    */
 180:   private boolean firstClip = true;
 181:   private Shape originalClip;
 182: 
 183:   /**
 184:    * Stroke used for 3DRects
 185:    */
 186:   private static BasicStroke draw3DRectStroke = new BasicStroke();
 187: 
 188:   static ColorModel rgb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF);
 189:   static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF, 
 190:                           0xFF000000);
 191: 
 192:   /**
 193:    * Constructor does nothing.
 194:    */
 195:   public CairoGraphics2D()
 196:   {
 197:   }
 198: 
 199:   /**
 200:    * Sets up the default values and allocates the native cairographics2d structure
 201:    * @param cairo_t_pointer, a native pointer to a cairo_t of the context.
 202:    */
 203:   public void setup(long cairo_t_pointer)
 204:   { 
 205:     nativePointer = init(cairo_t_pointer);
 206:     setRenderingHints(new RenderingHints(getDefaultHints()));
 207:     font = new Font("SansSerif", Font.PLAIN, 12);
 208:     setColor(Color.black);
 209:     setBackground(Color.white);
 210:     setPaint(Color.black);
 211:     setStroke(new BasicStroke());
 212:     setTransform(new AffineTransform());
 213:   }
 214: 
 215:   /**
 216:    * Same as above, but copies the state of another CairoGraphics2D.
 217:    */
 218:   public void copy(CairoGraphics2D g, long cairo_t_pointer)
 219:   {
 220:     nativePointer = init(cairo_t_pointer);
 221:     paint = g.paint;
 222:     stroke = g.stroke;
 223:     setRenderingHints(g.hints);
 224:     
 225:     Color foreground;
 226: 
 227:     if (g.fg.getAlpha() != -1)
 228:       foreground = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(),
 229:                      g.fg.getAlpha());
 230:     else
 231:       foreground = new Color(g.fg.getRGB());
 232: 
 233:     if (g.bg != null)
 234:       {
 235:         if (g.bg.getAlpha() != -1)
 236:           bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(),
 237:                          g.bg.getAlpha());
 238:         else
 239:           bg = new Color(g.bg.getRGB());
 240:       }
 241: 
 242:     clip = g.getClip();
 243: 
 244:     if (g.transform == null)
 245:       transform = null;
 246:     else
 247:       transform = new AffineTransform(g.transform);
 248: 
 249:     font = g.font;
 250: 
 251:     setColor(foreground);
 252:     setBackground(bg);
 253:     setPaint(paint);
 254:     setStroke(stroke);
 255:     setTransformImpl(transform);
 256:     setClip(clip);
 257:   }
 258: 
 259:   /**
 260:    * Generic destructor - call the native dispose() method.
 261:    */
 262:   public void finalize()
 263:   {
 264:     dispose();
 265:   }
 266: 
 267:   /**
 268:    * Disposes the native cairographics2d structure, including the 
 269:    * cairo_t and any gradient stuff, if allocated. 
 270:    * Subclasses should of course overload and call this if 
 271:    * they have additional native structures.
 272:    */
 273:   public void dispose()
 274:   {
 275:     disposeNative(nativePointer);
 276:     nativePointer = 0;
 277:   }
 278: 
 279:   /**
 280:    * Allocate the cairographics2d structure and set the cairo_t pointer in it.
 281:    * @param pointer - a cairo_t pointer, casted to a long.
 282:    */
 283:   private native long init(long pointer);
 284: 
 285:   /**
 286:    * These are declared abstract as there may be context-specific issues.
 287:    */
 288:   public abstract Graphics create();
 289: 
 290:   public abstract GraphicsConfiguration getDeviceConfiguration();
 291: 
 292:   protected abstract void copyAreaImpl(int x, int y, 
 293:                        int width, int height, int dx, int dy);
 294: 
 295: 
 296:   protected abstract Rectangle2D getRealBounds();
 297: 
 298:   ////// Native Methods ////////////////////////////////////////////////////
 299: 
 300:   /**
 301:    * Dispose of allocate native resouces.
 302:    */
 303:   public native void disposeNative(long pointer);
 304: 
 305:   /**
 306:    * Draw pixels as an RGBA int matrix
 307:    * @param w, h - width and height
 308:    * @param stride - stride of the array width
 309:    * @param i2u - affine transform array
 310:    */
 311:   private native void drawPixels(long pointer, int[] pixels, int w, int h,
 312:                                  int stride, double[] i2u, double alpha);
 313: 
 314:   private native void setGradient(long pointer, double x1, double y1,
 315:                                   double x2, double y2,
 316:                                   int r1, int g1, int b1, int a1, int r2,
 317:                                   int g2, int b2, int a2, boolean cyclic);
 318:   
 319:   private native void setTexturePixels(long pointer, int[] pixels, int w,
 320:                                        int h, int stride);
 321: 
 322:   /**
 323:    * Set the current transform matrix
 324:    */
 325:   private native void cairoSetMatrix(long pointer, double[] m);
 326:   
 327:   /**
 328:    * Scaling method
 329:    */
 330:   private native void cairoScale(long pointer, double x, double y);
 331: 
 332:   /**
 333:    * Set the compositing operator
 334:    */
 335:   private native void cairoSetOperator(long pointer, int cairoOperator);
 336: 
 337:   /**
 338:    * Sets the current color in RGBA as a 0.0-1.0 double
 339:    */
 340:   private native void cairoSetRGBAColor(long pointer, double red, double green,
 341:                                         double blue, double alpha);
 342: 
 343:   /**
 344:    * Sets the current winding rule in Cairo
 345:    */
 346:   private native void cairoSetFillRule(long pointer, int cairoFillRule);
 347: 
 348:   /**
 349:    * Set the line style, cap, join and miter limit.
 350:    * Cap and join parameters are in the BasicStroke enumerations.
 351:    */
 352:   private native void cairoSetLine(long pointer, double width, int cap,
 353:                                    int join, double miterLimit);
 354: 
 355:   /**
 356:    * Set the dash style
 357:    */
 358:   private native void cairoSetDash(long pointer, double[] dashes, int ndash,
 359:                                    double offset);
 360: 
 361:   /*
 362:    * Draws a Glyph Vector
 363:    */
 364:   native void cairoDrawGlyphVector(long pointer, GdkFontPeer font, 
 365:                                    float x, float y, int n, 
 366:                                    int[] codes, float[] positions);
 367: 
 368: 
 369:   private native void cairoRelCurveTo(long pointer, double dx1, double dy1,
 370:                                       double dx2, double dy2, double dx3,
 371:                                       double dy3);
 372: 
 373:   /**
 374:    * Appends a rectangle to the current path
 375:    */
 376:   private native void cairoRectangle(long pointer, double x, double y,
 377:                                      double width, double height);
 378:   
 379:   /**
 380:    * Appends an arc to the current path
 381:    */
 382:   private native void cairoArc(long pointer, double x, double y,
 383:                                double radius, double angle1, double angle2);
 384: 
 385:   /**
 386:    * Save / restore a cairo path
 387:    */
 388:   private native void cairoSave(long pointer);
 389:   private native void cairoRestore(long pointer);
 390: 
 391:   /**
 392:    * New current path
 393:    */
 394:   private native void cairoNewPath(long pointer);
 395: 
 396:   /** 
 397:    * Close current path
 398:    */
 399:   private native void cairoClosePath(long pointer);
 400: 
 401:   /** moveTo */
 402:   private native void cairoMoveTo(long pointer, double x, double y);
 403: 
 404:   /** relative moveTo */
 405:   private native void cairoRelMoveTo(long pointer, double dx, double dy);
 406: 
 407:   /** lineTo */
 408:   private native void cairoLineTo(long pointer, double x, double y);
 409: 
 410:   /** relative lineTo */
 411:   private native void cairoRelLineTo(long pointer, double dx, double dy);
 412: 
 413:   /** Cubic curve-to */
 414:   private native void cairoCurveTo(long pointer, double x1, double y1,
 415:                                    double x2, double y2,
 416:                                    double x3, double y3);
 417: 
 418:   /**
 419:    * Stroke current path
 420:    */
 421:   private native void cairoStroke(long pointer);
 422: 
 423:   /**
 424:    * Fill current path
 425:    */
 426:   private native void cairoFill(long pointer, double alpha);
 427: 
 428:   /** 
 429:    * Clip current path
 430:    */
 431:   private native void cairoClip(long pointer);
 432: 
 433:   /** 
 434:    * Save clip
 435:    */
 436:   private native void cairoPreserveClip(long pointer);
 437: 
 438:   /** 
 439:    * Save clip
 440:    */
 441:   private native void cairoResetClip(long pointer);
 442: 
 443:   /**
 444:    * Set interpolation types
 445:    */
 446:   private native void cairoSurfaceSetFilter(long pointer, int filter);
 447: 
 448:   /**
 449:    * Draws a line from (x1,y1) to (x2,y2).
 450:    *
 451:    * @param pointer the native pointer
 452:    *
 453:    * @param x1 the x coordinate of the starting point
 454:    * @param y1 the y coordinate of the starting point
 455:    * @param x2 the x coordinate of the end point
 456:    * @param y2 the y coordinate of the end point
 457:    */
 458:   private native void cairoDrawLine(long pointer, double x1, double y1,
 459:                                     double x2, double y2);
 460: 
 461:   /**
 462:    * Draws a rectangle at starting point (x,y) and with the specified width
 463:    * and height.
 464:    *
 465:    * @param pointer the native pointer
 466:    * @param x the x coordinate of the upper left corner
 467:    * @param y the y coordinate of the upper left corner
 468:    * @param w the width of the rectangle
 469:    * @param h the height of the rectangle
 470:    */
 471:   private native void cairoDrawRect(long pointer, double x, double y, double w,
 472:                                     double h);
 473: 
 474:   /**
 475:    * Fills a rectangle at starting point (x,y) and with the specified width
 476:    * and height.
 477:    *
 478:    * @param pointer the native pointer
 479:    * @param x the x coordinate of the upper left corner
 480:    * @param y the y coordinate of the upper left corner
 481:    * @param w the width of the rectangle
 482:    * @param h the height of the rectangle
 483:    */
 484:   private native void cairoFillRect(long pointer, double x, double y, double w,
 485:                                     double h);
 486: 
 487: 
 488:   ///////////////////////// TRANSFORMS ///////////////////////////////////
 489:   /**
 490:    * Set the current transform
 491:    */ 
 492:   public void setTransform(AffineTransform tx)
 493:   {
 494:     // Transform clip into target space using the old transform.
 495:     updateClip(transform);
 496: 
 497:     // Update the native transform.
 498:     setTransformImpl(tx);
 499: 
 500:     // Transform the clip back into user space using the inverse new transform.
 501:     try
 502:       {
 503:         updateClip(transform.createInverse());
 504:       }
 505:     catch (NoninvertibleTransformException ex)
 506:       {
 507:         // TODO: How can we deal properly with this?
 508:         ex.printStackTrace();
 509:       }
 510: 
 511:     if (clip != null)
 512:       setClip(clip);
 513:   }
 514: 
 515:   private void setTransformImpl(AffineTransform tx)
 516:   {
 517:     transform = tx;
 518:     if (transform != null)
 519:       {
 520:         double[] m = new double[6];
 521:         transform.getMatrix(m);
 522:         cairoSetMatrix(nativePointer, m);
 523:       }
 524:   }
 525: 
 526:   public void transform(AffineTransform tx)
 527:   {
 528:     if (transform == null)
 529:       transform = new AffineTransform(tx);
 530:     else
 531:       transform.concatenate(tx);
 532: 
 533:     if (clip != null)
 534:       {
 535:         try
 536:           {
 537:             AffineTransform clipTransform = tx.createInverse();
 538:             updateClip(clipTransform);
 539:           }
 540:         catch (NoninvertibleTransformException ex)
 541:           {
 542:             // TODO: How can we deal properly with this?
 543:             ex.printStackTrace();
 544:           }
 545:       }
 546: 
 547:     setTransformImpl(transform);
 548:   }
 549: 
 550:   public void rotate(double theta)
 551:   {
 552:     transform(AffineTransform.getRotateInstance(theta));
 553:   }
 554: 
 555:   public void rotate(double theta, double x, double y)
 556:   {
 557:     transform(AffineTransform.getRotateInstance(theta, x, y));
 558:   }
 559: 
 560:   public void scale(double sx, double sy)
 561:   {
 562:     transform(AffineTransform.getScaleInstance(sx, sy));
 563:   }
 564: 
 565:   /**
 566:    * Translate the system of the co-ordinates. As translation is a frequent
 567:    * operation, it is done in an optimised way, unlike scaling and rotating.
 568:    */
 569:   public void translate(double tx, double ty)
 570:   {
 571:     if (transform != null)
 572:       transform.translate(tx, ty);
 573:     else
 574:       transform = AffineTransform.getTranslateInstance(tx, ty);
 575: 
 576:     if (clip != null)
 577:       {
 578:         // FIXME: this should actuall try to transform the shape
 579:         // rather than degrade to bounds.
 580:         if (clip instanceof Rectangle2D)
 581:           {
 582:             Rectangle2D r = (Rectangle2D) clip;
 583:             r.setRect(r.getX() - tx, r.getY() - ty, r.getWidth(),
 584:                       r.getHeight());
 585:           }
 586:         else
 587:           {
 588:             AffineTransform clipTransform =
 589:               AffineTransform.getTranslateInstance(-tx, -ty);
 590:             updateClip(clipTransform);
 591:           }
 592:       }
 593: 
 594:     setTransformImpl(transform);
 595:   }
 596:   
 597:   public void translate(int x, int y)
 598:   {
 599:     translate((double) x, (double) y);
 600:   }
 601: 
 602:   public void shear(double shearX, double shearY)
 603:   {
 604:     transform(AffineTransform.getShearInstance(shearX, shearY));
 605:   }
 606: 
 607:   ///////////////////////// DRAWING STATE ///////////////////////////////////
 608: 
 609:   public void clip(Shape s)
 610:   {
 611:     // Do not touch clip when s == null.
 612:     if (s == null)
 613:       {
 614:         // The spec says this should clear the clip. The reference
 615:         // implementation throws a NullPointerException instead. I think,
 616:         // in this case we should conform to the specs, as it shouldn't
 617:         // affect compatibility.
 618:         setClip(null);
 619:         return;
 620:       }
 621: 
 622:     // If the current clip is still null, initialize it.
 623:     if (clip == null)
 624:       {
 625:         clip = getRealBounds();
 626:       }
 627: 
 628:     // This is so common, let's optimize this.
 629:     if (clip instanceof Rectangle2D && s instanceof Rectangle2D)
 630:       {
 631:         Rectangle2D clipRect = (Rectangle2D) clip;
 632:         Rectangle2D r = (Rectangle2D) s;
 633:         Rectangle2D.intersect(clipRect, r, clipRect);
 634:         setClip(clipRect);
 635:       }
 636:    else
 637:      {
 638:        Area current;
 639:        if (clip instanceof Area)
 640:          current = (Area) clip;
 641:        else
 642:          current = new Area(clip);
 643: 
 644:        Area intersect;
 645:        if (s instanceof Area)
 646:          intersect = (Area) s;
 647:        else
 648:          intersect = new Area(s);
 649: 
 650:        current.intersect(intersect);
 651:        clip = current;
 652:        // Call setClip so that the native side gets notified.
 653:        setClip(clip);
 654:      }
 655:   }
 656: 
 657:   public Paint getPaint()
 658:   {
 659:     return paint;
 660:   }
 661: 
 662:   public AffineTransform getTransform()
 663:   {
 664:     return (AffineTransform) transform.clone();
 665:   }
 666: 
 667:   public void setPaint(Paint p)
 668:   {
 669:     if (paint == null)
 670:       return;
 671: 
 672:     paint = p;
 673:     if (paint instanceof Color)
 674:       {
 675:         setColor((Color) paint);
 676:       }
 677:     else if (paint instanceof TexturePaint)
 678:       {
 679:     TexturePaint tp = (TexturePaint) paint;
 680:     BufferedImage img = tp.getImage();
 681: 
 682:     // map the image to the anchor rectangle  
 683:     int width = (int) tp.getAnchorRect().getWidth();
 684:     int height = (int) tp.getAnchorRect().getHeight();
 685: 
 686:     double scaleX = width / (double) img.getWidth();
 687:     double scaleY = height / (double) img.getHeight();
 688: 
 689:     AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
 690:     AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
 691:     BufferedImage texture = op.filter(img, null);
 692:     int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
 693:     setTexturePixels(nativePointer, pixels, width, height, width);
 694:       }
 695:     else if (paint instanceof GradientPaint)
 696:       {
 697:     GradientPaint gp = (GradientPaint) paint;
 698:     Point2D p1 = gp.getPoint1();
 699:     Point2D p2 = gp.getPoint2();
 700:     Color c1 = gp.getColor1();
 701:     Color c2 = gp.getColor2();
 702:     setGradient(nativePointer, p1.getX(), p1.getY(), p2.getX(), p2.getY(),
 703:                     c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha(),
 704:                     c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha(),
 705:                     gp.isCyclic());
 706:       }
 707:     else
 708:       throw new java.lang.UnsupportedOperationException();
 709:   }
 710: 
 711:   public Stroke getStroke()
 712:   {
 713:     return stroke;
 714:   }
 715: 
 716:   public void setStroke(Stroke st)
 717:   {
 718:     stroke = st;
 719:     if (stroke instanceof BasicStroke)
 720:       {
 721:     BasicStroke bs = (BasicStroke) stroke;
 722:     cairoSetLine(nativePointer, bs.getLineWidth(), bs.getEndCap(), 
 723:              bs.getLineJoin(), bs.getMiterLimit());
 724: 
 725:     float[] dashes = bs.getDashArray();
 726:     if (dashes != null)
 727:       {
 728:         double[] double_dashes = new double[dashes.length];
 729:         for (int i = 0; i < dashes.length; i++)
 730:           double_dashes[i] = dashes[i];
 731:         cairoSetDash(nativePointer, double_dashes, double_dashes.length,
 732:                      (double) bs.getDashPhase());
 733:       }
 734:     else
 735:       cairoSetDash(nativePointer, new double[0], 0, 0.0);
 736:       }
 737:   }
 738: 
 739:   public void setPaintMode()
 740:   {
 741:     setComposite(AlphaComposite.SrcOver);
 742:   }
 743: 
 744:   public void setXORMode(Color c)
 745:   {
 746:     // FIXME: implement
 747:   }
 748: 
 749:   public void setColor(Color c)
 750:   {
 751:     if (c == null)
 752:       c = Color.BLACK;
 753: 
 754:     fg = c;
 755:     paint = c;
 756:     updateColor();
 757:   }
 758:   
 759:   /**
 760:    * Set the current fg value as the cairo color.
 761:    */
 762:   void updateColor()
 763:   {
 764:     if (fg == null)
 765:       fg = Color.BLACK;
 766:     cairoSetRGBAColor(nativePointer, fg.getRed() / 255.0,
 767:                       fg.getGreen() / 255.0,fg.getBlue() / 255.0,
 768:                       fg.getAlpha() / 255.0);
 769:   }
 770: 
 771:   public Color getColor()
 772:   {
 773:     return fg;
 774:   }
 775: 
 776:   public void clipRect(int x, int y, int width, int height)
 777:   {
 778:     if (clip == null)
 779:       setClip(new Rectangle(x, y, width, height));
 780:     else if (clip instanceof Rectangle)
 781:       {
 782:         computeIntersection(x, y, width, height, (Rectangle) clip);
 783:         setClip(clip);
 784:       }
 785:     else
 786:       clip(new Rectangle(x, y, width, height));
 787:   }
 788: 
 789:   public Shape getClip()
 790:   {
 791:     if (clip == null)
 792:       return null;
 793:     else if (clip instanceof Rectangle2D)
 794:       return clip.getBounds2D(); //getClipInDevSpace();
 795:     else
 796:       {
 797:         GeneralPath p = new GeneralPath();
 798:         PathIterator pi = clip.getPathIterator(null);
 799:         p.append(pi, false);
 800:         return p;
 801:       }
 802:   }
 803: 
 804:   public Rectangle getClipBounds()
 805:   {
 806:     if (clip == null)
 807:       return null;
 808:     else
 809:       return clip.getBounds();
 810:   }
 811: 
 812:   protected Rectangle2D getClipInDevSpace()
 813:   {
 814:     Rectangle2D uclip = clip.getBounds2D();
 815:     if (transform == null)
 816:       return uclip;
 817:     else
 818:       {
 819:     Point2D pos = transform.transform(new Point2D.Double(uclip.getX(),
 820:                                                          uclip.getY()),
 821:                                       (Point2D) null);
 822:     Point2D extent = transform.deltaTransform(new Point2D.Double(uclip
 823:                                                                  .getWidth(),
 824:                                                                  uclip
 825:                                                                  .getHeight()),
 826:                                               (Point2D) null);
 827:     return new Rectangle2D.Double(pos.getX(), pos.getY(), extent.getX(),
 828:                                   extent.getY());
 829:       }
 830:   }
 831: 
 832:   public void setClip(int x, int y, int width, int height)
 833:   {
 834:     if( width < 0 || height < 0 )
 835:       return;
 836: 
 837:     setClip(new Rectangle2D.Double(x, y, width, height));
 838:   }
 839: 
 840:   public void setClip(Shape s)
 841:   {
 842:     // The first time the clip is set, save it as the original clip 
 843:     // to reset to on s == null. We can rely on this being non-null 
 844:     // because the constructor in subclasses is expected to set the 
 845:     // initial clip properly.
 846:     if( firstClip )
 847:       {
 848:     originalClip = s;
 849:     firstClip = false;
 850:       }
 851: 
 852:     clip = s;
 853:     cairoResetClip(nativePointer);
 854: 
 855:     if (clip != null)
 856:       {
 857:         cairoNewPath(nativePointer);
 858:         if (clip instanceof Rectangle2D)
 859:           {
 860:             Rectangle2D r = (Rectangle2D) clip;
 861:             cairoRectangle(nativePointer, r.getX(), r.getY(), r.getWidth(),
 862:                            r.getHeight());
 863:           }
 864:         else
 865:           walkPath(clip.getPathIterator(null), false);
 866:         
 867:         cairoClip(nativePointer);
 868:       }
 869:   }
 870: 
 871:   public void setBackground(Color c)
 872:   {
 873:     if (c == null)
 874:       c = Color.WHITE;
 875:     bg = c;
 876:   }
 877: 
 878:   public Color getBackground()
 879:   {
 880:     return bg;
 881:   }
 882: 
 883:   /**
 884:    * Return the current composite.
 885:    */
 886:   public Composite getComposite()
 887:   {
 888:     if (comp == null)
 889:       return AlphaComposite.SrcOver;
 890:     else
 891:       return comp;
 892:   }
 893: 
 894:   /**
 895:    * Sets the current composite context.
 896:    */
 897:   public void setComposite(Composite comp)
 898:   {
 899:     this.comp = comp;
 900: 
 901:     if (comp instanceof AlphaComposite)
 902:       {
 903:     AlphaComposite a = (AlphaComposite) comp;
 904:     cairoSetOperator(nativePointer, a.getRule());
 905:       }
 906:     else
 907:       {
 908:         // FIXME: this check is only required "if this Graphics2D
 909:         // context is drawing to a Component on the display screen".
 910:         SecurityManager sm = System.getSecurityManager();
 911:         if (sm != null)
 912:           sm.checkPermission(new AWTPermission("readDisplayPixels"));
 913: 
 914:         // FIXME: implement general Composite support
 915:         throw new java.lang.UnsupportedOperationException();
 916:       }
 917:   }
 918: 
 919:   ///////////////////////// DRAWING PRIMITIVES ///////////////////////////////////
 920: 
 921:   public void draw(Shape s)
 922:   {
 923:     if ((stroke != null && ! (stroke instanceof BasicStroke))
 924:         || (comp instanceof AlphaComposite && ((AlphaComposite) comp).getAlpha() != 1.0))
 925:       {
 926:         // Cairo doesn't support stroking with alpha, so we create the stroked
 927:         // shape and fill with alpha instead
 928:         fill(stroke.createStrokedShape(s));
 929:         return;
 930:       }
 931: 
 932:     createPath(s);
 933:     cairoStroke(nativePointer);
 934:   }
 935: 
 936:   public void fill(Shape s)
 937:   {
 938:     createPath(s);
 939: 
 940:     double alpha = 1.0;
 941:     if (comp instanceof AlphaComposite)
 942:       alpha = ((AlphaComposite) comp).getAlpha();
 943:     cairoFill(nativePointer, alpha);
 944:   }
 945: 
 946:   private void createPath(Shape s)
 947:   {
 948:     cairoNewPath(nativePointer);
 949: 
 950:     // Optimize rectangles, since there is a direct Cairo function
 951:     if (s instanceof Rectangle2D)
 952:       {
 953:         Rectangle2D r = (Rectangle2D) s;
 954:         cairoRectangle(nativePointer, shifted(r.getX(), shiftDrawCalls),
 955:                        shifted(r.getY(), shiftDrawCalls), r.getWidth(),
 956:                        r.getHeight());
 957:       }
 958: 
 959:     // We can optimize ellipses too; however we don't bother optimizing arcs:
 960:     // the iterator is fast enough (an ellipse requires 5 steps using the
 961:     // iterator, while most arcs are only 2-3)
 962:     else if (s instanceof Ellipse2D)
 963:       {
 964:         Ellipse2D e = (Ellipse2D) s;
 965: 
 966:         double radius = Math.min(e.getHeight(), e.getWidth()) / 2;
 967: 
 968:         // Cairo only draws circular shapes, but we can use a stretch to make
 969:         // them into ellipses
 970:         double xscale = 1, yscale = 1;
 971:         if (e.getHeight() != e.getWidth())
 972:           {
 973:             cairoSave(nativePointer);
 974: 
 975:             if (e.getHeight() < e.getWidth())
 976:               xscale = e.getWidth() / (radius * 2);
 977:             else
 978:               yscale = e.getHeight() / (radius * 2);
 979: 
 980:             if (xscale != 1 || yscale != 1)
 981:               cairoScale(nativePointer, xscale, yscale);
 982:           }
 983: 
 984:         cairoArc(nativePointer,
 985:                  shifted(e.getCenterX() / xscale, shiftDrawCalls),
 986:                  shifted(e.getCenterY() / yscale, shiftDrawCalls), radius, 0,
 987:                  Math.PI * 2);
 988: 
 989:         if (xscale != 1 || yscale != 1)
 990:           cairoRestore(nativePointer);
 991:       }
 992: 
 993:     // All other shapes are broken down and drawn in steps using the
 994:     // PathIterator
 995:     else
 996:       walkPath(s.getPathIterator(null), shiftDrawCalls);
 997:   }
 998: 
 999:   /**
1000:    * Note that the rest of the drawing methods go via fill() or draw() for the drawing,
1001:    * although subclasses may with to overload these methods where context-specific 
1002:    * optimizations are possible (e.g. bitmaps and fillRect(int, int, int, int)
1003:    */
1004: 
1005:   public void clearRect(int x, int y, int width, int height)
1006:   {
1007:     if (bg != null)
1008:       cairoSetRGBAColor(nativePointer, bg.getRed() / 255.0,
1009:                         bg.getGreen() / 255.0, bg.getBlue() / 255.0, 1.0);
1010:     fillRect(x, y, width, height);
1011:     updateColor();
1012:   }
1013: 
1014:   public void draw3DRect(int x, int y, int width, int height, boolean raised)
1015:   {
1016:     Stroke tmp = stroke;
1017:     setStroke(draw3DRectStroke);
1018:     super.draw3DRect(x, y, width, height, raised);
1019:     setStroke(tmp);
1020:   }
1021: 
1022:   public void drawArc(int x, int y, int width, int height, int startAngle,
1023:                       int arcAngle)
1024:   {
1025:     draw(new Arc2D.Double((double) x, (double) y, (double) width,
1026:                           (double) height, (double) startAngle,
1027:                           (double) arcAngle, Arc2D.OPEN));
1028:   }
1029: 
1030:   public void drawLine(int x1, int y1, int x2, int y2)
1031:   {
1032:     // The coordinates being pairwise identical means one wants
1033:     // to draw a single pixel. This is emulated by drawing
1034:     // a one pixel sized rectangle.
1035:     if (x1 == x2 && y1 == y2)
1036:       cairoFillRect(nativePointer, x1, y1, 1, 1);
1037:     else
1038:       cairoDrawLine(nativePointer, x1 + 0.5, y1 + 0.5, x2 + 0.5, y2 + 0.5);
1039:   }
1040: 
1041:   public void drawRect(int x, int y, int width, int height)
1042:   {
1043:     cairoDrawRect(nativePointer, shifted(x, shiftDrawCalls),
1044:                   shifted(y, shiftDrawCalls), width, height);
1045:   }
1046: 
1047:   public void fillArc(int x, int y, int width, int height, int startAngle,
1048:                       int arcAngle)
1049:   {
1050:     fill(new Arc2D.Double((double) x, (double) y, (double) width,
1051:                           (double) height, (double) startAngle,
1052:                           (double) arcAngle, Arc2D.OPEN));
1053:   }
1054: 
1055:   public void fillRect(int x, int y, int width, int height)
1056:   {
1057:     cairoFillRect(nativePointer, x, y, width, height);
1058:   }
1059: 
1060:   public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
1061:   {
1062:     fill(new Polygon(xPoints, yPoints, nPoints));
1063:   }
1064: 
1065:   public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
1066:   {
1067:     draw(new Polygon(xPoints, yPoints, nPoints));
1068:   }
1069: 
1070:   public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
1071:   {
1072:     draw(new Polygon(xPoints, yPoints, nPoints));
1073:   }
1074: 
1075:   public void drawOval(int x, int y, int width, int height)
1076:   {
1077:     drawArc(x, y, width, height, 0, 360);
1078:   }
1079: 
1080:   public void drawRoundRect(int x, int y, int width, int height, int arcWidth,
1081:                             int arcHeight)
1082:   {
1083:     draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
1084:   }
1085: 
1086:   public void fillOval(int x, int y, int width, int height)
1087:   {
1088:     fillArc(x, y, width, height, 0, 360);
1089:   }
1090: 
1091:   public void fillRoundRect(int x, int y, int width, int height, int arcWidth,
1092:                             int arcHeight)
1093:   {
1094:     fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
1095:   }
1096: 
1097:   /**
1098:    * CopyArea - performs clipping to the native surface as a convenience 
1099:    * (requires getRealBounds). Then calls copyAreaImpl.
1100:    */
1101:   public void copyArea(int ox, int oy, int owidth, int oheight, 
1102:                int odx, int ody)
1103:   {
1104:     Point2D pos = transform.transform(new Point2D.Double(ox, oy),
1105:                       (Point2D) null);
1106:     Point2D dim = transform.transform(new Point2D.Double(ox + owidth, 
1107:                              oy + oheight),
1108:                       (Point2D) null);
1109:     Point2D p2 = transform.transform(new Point2D.Double(ox + odx, oy + ody),
1110:                      (Point2D) null);
1111:     int x = (int)pos.getX();
1112:     int y = (int)pos.getY();
1113:     int width = (int)(dim.getX() - pos.getX());
1114:     int height = (int)(dim.getY() - pos.getY());
1115:     int dx = (int)(p2.getX() - pos.getX());
1116:     int dy = (int)(p2.getY() - pos.getY());
1117: 
1118:     Rectangle2D r = getRealBounds();
1119: 
1120:     if( width < 0 || height < 0 )
1121:       return;
1122:     // Return if outside the surface
1123:     if( x + dx > r.getWidth() || y + dy > r.getHeight() )
1124:       return;
1125: 
1126:     if( x + dx + width < r.getX() || y + dy + height < r.getY() )
1127:       return;
1128: 
1129:     // Clip edges if necessary 
1130:     if( x + dx < r.getX() ) // left
1131:       {
1132:     width = x + dx + width;
1133:     x = (int)r.getX() - dx;
1134:       }
1135: 
1136:     if( y + dy < r.getY() ) // top
1137:       {
1138:     height = y + dy + height;
1139:     y = (int)r.getY() - dy;
1140:       }
1141: 
1142:     if( x + dx + width >= r.getWidth() ) // right
1143:       width = (int)r.getWidth() - dx - x;
1144: 
1145:     if( y + dy + height >= r.getHeight() ) // bottom
1146:       height = (int)r.getHeight() - dy - y;
1147: 
1148:     copyAreaImpl(x, y, width, height, dx, dy);
1149:   }
1150: 
1151:   ///////////////////////// RENDERING HINTS ///////////////////////////////////
1152: 
1153:   /**
1154:    * FIXME- support better
1155:    */
1156:   public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
1157:   {
1158:     hints.put(hintKey, hintValue);
1159: 
1160:     if (hintKey.equals(RenderingHints.KEY_INTERPOLATION)
1161:         || hintKey.equals(RenderingHints.KEY_ALPHA_INTERPOLATION))
1162:       {
1163:     if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1164:       cairoSurfaceSetFilter(nativePointer, 0);
1165: 
1166:     else if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1167:       cairoSurfaceSetFilter(nativePointer, 1);
1168: 
1169:     else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1170:       cairoSurfaceSetFilter(nativePointer, 2);
1171: 
1172:     else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1173:       cairoSurfaceSetFilter(nativePointer, 3);
1174: 
1175:     else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1176:       cairoSurfaceSetFilter(nativePointer, 4);
1177:       }
1178: 
1179:     shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1180:       || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1181:   }
1182: 
1183:   public Object getRenderingHint(RenderingHints.Key hintKey)
1184:   {
1185:     return hints.get(hintKey);
1186:   }
1187: 
1188:   public void setRenderingHints(Map hints)
1189:   {
1190:     this.hints = new RenderingHints(getDefaultHints());
1191:     this.hints.add(new RenderingHints(hints));
1192: 
1193:     if (hints.containsKey(RenderingHints.KEY_INTERPOLATION))
1194:       {
1195:     if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
1196:       cairoSurfaceSetFilter(nativePointer, 0);
1197: 
1198:     else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
1199:       cairoSurfaceSetFilter(nativePointer, 1);
1200:       }
1201: 
1202:     if (hints.containsKey(RenderingHints.KEY_ALPHA_INTERPOLATION))
1203:       {
1204:     if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
1205:       cairoSurfaceSetFilter(nativePointer, 2);
1206: 
1207:     else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
1208:       cairoSurfaceSetFilter(nativePointer, 3);
1209: 
1210:     else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
1211:       cairoSurfaceSetFilter(nativePointer, 4);
1212:       }
1213: 
1214:     shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
1215:       || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
1216:   }
1217: 
1218:   public void addRenderingHints(Map hints)
1219:   {
1220:     this.hints.add(new RenderingHints(hints));
1221:   }
1222: 
1223:   public RenderingHints getRenderingHints()
1224:   {
1225:     return hints;
1226:   }
1227: 
1228:   ///////////////////////// IMAGE. METHODS ///////////////////////////////////
1229: 
1230:   protected boolean drawImage(Image img, AffineTransform xform,
1231:                             Color bgcolor, ImageObserver obs)
1232:   {
1233:     if (img == null)
1234:       return false;
1235: 
1236:     if (xform == null)
1237:       xform = new AffineTransform();
1238: 
1239:     // In this case, xform is an AffineTransform that transforms bounding
1240:     // box of the specified image from image space to user space. However
1241:     // when we pass this transform to cairo, cairo will use this transform
1242:     // to map "user coordinates" to "pixel" coordinates, which is the 
1243:     // other way around. Therefore to get the "user -> pixel" transform 
1244:     // that cairo wants from "image -> user" transform that we currently
1245:     // have, we will need to invert the transformation matrix.
1246:     AffineTransform invertedXform;
1247: 
1248:     try
1249:       {
1250:     invertedXform = xform.createInverse();
1251:       }
1252:     catch (NoninvertibleTransformException e)
1253:       {
1254:     throw new ImagingOpException("Unable to invert transform "
1255:                      + xform.toString());
1256:       }
1257: 
1258:     // Unrecognized image - convert to a BufferedImage
1259:     // Note - this can get us in trouble when the gdk lock is re-acquired.
1260:     // for example by VolatileImage. See ComponentGraphics for how we work
1261:     // around this.
1262:     
1263:     if( !(img instanceof BufferedImage) )
1264:       {
1265:     ImageProducer source = img.getSource();
1266:     if (source == null)
1267:       return false;
1268:     img = Toolkit.getDefaultToolkit().createImage(source);
1269:       }
1270: 
1271:     BufferedImage b = (BufferedImage) img;
1272:     DataBuffer db;
1273:     double[] i2u = new double[6];
1274:     int width = b.getWidth();
1275:     int height = b.getHeight();
1276: 
1277:     // If this BufferedImage has a BufferedImageGraphics object, 
1278:     // use the cached CairoSurface that BIG is drawing onto
1279:     
1280:     if( BufferedImageGraphics.bufferedImages.get( b ) != null )
1281:       db = (DataBuffer)BufferedImageGraphics.bufferedImages.get( b );
1282:     else
1283:       db = b.getRaster().getDataBuffer();
1284: 
1285:     invertedXform.getMatrix(i2u);
1286: 
1287:     double alpha = 1.0;
1288:     if (comp instanceof AlphaComposite)
1289:       alpha = ((AlphaComposite) comp).getAlpha();
1290: 
1291:     if(db instanceof CairoSurface)
1292:       {
1293:     ((CairoSurface)db).drawSurface(nativePointer, i2u, alpha);
1294:         updateColor();
1295:     return true;
1296:       }
1297:         
1298:     if( bgcolor != null )
1299:       {
1300:     // Fill a rectangle with the background color 
1301:     // to composite the image onto.
1302:     Paint oldPaint = paint;
1303:     AffineTransform oldTransform = transform;
1304:     setPaint( bgcolor );
1305:     setTransform( invertedXform );
1306:     fillRect(0, 0, width, height);
1307:     setTransform( oldTransform );
1308:     setPaint( oldPaint );
1309:       }
1310: 
1311:     int[] pixels = b.getRGB(0, 0, width, height, null, 0, width);
1312: 
1313:     drawPixels(nativePointer, pixels, width, height, width, i2u, alpha);
1314: 
1315:     // Cairo seems to lose the current color which must be restored.
1316:     updateColor();
1317:     return true;
1318:   }
1319: 
1320:   public void drawRenderedImage(RenderedImage image, AffineTransform xform)
1321:   {
1322:     drawRaster(image.getColorModel(), image.getData(), xform, null);
1323:   }
1324: 
1325:   public void drawRenderableImage(RenderableImage image, AffineTransform xform)
1326:   {
1327:     drawRenderedImage(image.createRendering(new RenderContext(xform)), xform);
1328:   }
1329: 
1330:   public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
1331:   {
1332:     return drawImage(img, xform, null, obs);
1333:   }
1334: 
1335:   public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y)
1336:   {
1337:     Image filtered = image;
1338:     if (op != null)
1339:       filtered = op.filter(image, null);
1340:     drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, null);
1341:   }
1342: 
1343:   public boolean drawImage(Image img, int x, int y, ImageObserver observer)
1344:   {
1345:     return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), null,
1346:                      observer);
1347:   }
1348: 
1349:   public boolean drawImage(Image img, int x, int y, Color bgcolor,
1350:                            ImageObserver observer)
1351:   {
1352:     return drawImage(img, x, y, img.getWidth(observer),
1353:                      img.getHeight(observer), bgcolor, observer);
1354:   }
1355: 
1356:   public boolean drawImage(Image img, int x, int y, int width, int height,
1357:                            Color bgcolor, ImageObserver observer)
1358:   {
1359:     double scaleX = width / (double) img.getWidth(observer);
1360:     double scaleY = height / (double) img.getHeight(observer);
1361:     if( scaleX == 0 || scaleY == 0 )
1362:       return true;
1363: 
1364:     return drawImage(img, new AffineTransform(scaleX, 0f, 0f, scaleY, x, y),
1365:                      bgcolor, observer);
1366:   }
1367: 
1368:   public boolean drawImage(Image img, int x, int y, int width, int height,
1369:                            ImageObserver observer)
1370:   {
1371:     return drawImage(img, x, y, width, height, null, observer);
1372:   }
1373: 
1374:   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1375:                            int sx1, int sy1, int sx2, int sy2, Color bgcolor,
1376:                            ImageObserver observer)
1377:   {
1378:     if (img == null)
1379:       return false;
1380: 
1381:     int sourceWidth = sx2 - sx1;
1382:     int sourceHeight = sy2 - sy1;
1383: 
1384:     int destWidth = dx2 - dx1;
1385:     int destHeight = dy2 - dy1;
1386: 
1387:     if(destWidth == 0 || destHeight == 0 || sourceWidth == 0 || 
1388:        sourceHeight == 0)
1389:       return true;
1390: 
1391:     double scaleX = destWidth / (double) sourceWidth;
1392:     double scaleY = destHeight / (double) sourceHeight;
1393: 
1394:     // FIXME: Avoid using an AT if possible here - it's at least twice as slow.
1395:     
1396:     Shape oldClip = getClip();
1397:     int cx, cy, cw, ch;
1398:     if( dx1 < dx2 ) 
1399:       { cx = dx1; cw = dx2 - dx1; }
1400:     else
1401:       { cx = dx2; cw = dx1 - dx2; }
1402:     if( dy1 < dy2 ) 
1403:       { cy = dy1; ch = dy2 - dy1; }
1404:     else
1405:       { cy = dy2; ch = dy1 - dy2; }
1406:     
1407:     clipRect( cx, cy, cw, ch );
1408: 
1409:     AffineTransform tx = new AffineTransform();
1410:     tx.translate( dx1 - sx1*scaleX, dy1 - sy1*scaleY );
1411:     tx.scale( scaleX, scaleY );
1412: 
1413:     boolean retval = drawImage(img, tx, bgcolor, observer);
1414:     setClip( oldClip );
1415:     return retval;
1416:   }
1417: 
1418:   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1419:                            int sx1, int sy1, int sx2, int sy2,
1420:                            ImageObserver observer)
1421:   {
1422:     return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer);
1423:   }
1424: 
1425:   ///////////////////////// TEXT METHODS ////////////////////////////////////
1426: 
1427:   public void drawString(String str, float x, float y)
1428:   {
1429:     if (str == null || str.length() == 0)
1430:       return;
1431:     (new TextLayout( str, getFont(), getFontRenderContext() )).
1432:       draw(this, x, y);
1433:   }
1434: 
1435:   public void drawString(String str, int x, int y)
1436:   {
1437:     drawString (str, (float) x, (float) y);
1438:   }
1439: 
1440:   public void drawString(AttributedCharacterIterator ci, int x, int y)
1441:   {
1442:     drawString (ci, (float) x, (float) y);
1443:   }
1444: 
1445:   public void drawGlyphVector(GlyphVector gv, float x, float y)
1446:   {
1447:     double alpha = 1.0;
1448: 
1449:     if( gv.getNumGlyphs() <= 0 )
1450:       return;
1451: 
1452:     if (comp instanceof AlphaComposite)
1453:       alpha = ((AlphaComposite) comp).getAlpha();
1454:     if (gv instanceof FreetypeGlyphVector && alpha == 1.0)
1455:       {
1456:         int n = gv.getNumGlyphs ();
1457:         int[] codes = gv.getGlyphCodes (0, n, null);
1458:         float[] positions = gv.getGlyphPositions (0, n, null);
1459: 
1460:         setFont (gv.getFont ());
1461:     synchronized( this.font ) 
1462:       { 
1463:         cairoDrawGlyphVector(nativePointer, (GdkFontPeer)getFont().getPeer(),
1464:                  x, y, n, codes, positions);
1465:       }
1466:       }
1467:     else
1468:       {
1469:         translate(x, y);
1470:         fill(gv.getOutline());
1471:         translate(-x, -y);
1472:       }
1473:   }
1474: 
1475:   public void drawString(AttributedCharacterIterator ci, float x, float y)
1476:   {
1477:     GlyphVector gv = getFont().createGlyphVector(getFontRenderContext(), ci);
1478:     drawGlyphVector(gv, x, y);
1479:   }
1480: 
1481:   /**
1482:    * Should perhaps be contexct dependent, but this is left for now as an 
1483:    * overloadable default implementation.
1484:    */
1485:   public FontRenderContext getFontRenderContext()
1486:   {
1487:     return new FontRenderContext(transform, true, true);
1488:   }
1489: 
1490:   // Until such time as pango is happy to talk directly to cairo, we
1491:   // actually need to redirect some calls from the GtkFontPeer and
1492:   // GtkFontMetrics into the drawing kit and ask cairo ourselves.
1493: 
1494:   public FontMetrics getFontMetrics()
1495:   {
1496:     return getFontMetrics(getFont());
1497:   }
1498: 
1499:   public FontMetrics getFontMetrics(Font f)
1500:   {
1501:     // the reason we go via the toolkit here is to try to get
1502:     // a cached object. the toolkit keeps such a cache.
1503:     return Toolkit.getDefaultToolkit().getFontMetrics(f);
1504:   }
1505: 
1506:   public void setFont(Font f)
1507:   {
1508:     // Sun's JDK does not throw NPEs, instead it leaves the current setting
1509:     // unchanged. So do we.
1510:     if (f == null)
1511:       return;
1512: 
1513:     if (f.getPeer() instanceof GdkFontPeer)
1514:       font = f;
1515:     else
1516:       font = 
1517:         ((ClasspathToolkit)(Toolkit.getDefaultToolkit()))
1518:         .getFont(f.getName(), f.getAttributes());    
1519:   }
1520: 
1521:   public Font getFont()
1522:   {
1523:     if (font == null)
1524:       return new Font("SansSerif", Font.PLAIN, 12);
1525:     return font;
1526:   }
1527: 
1528:   /////////////////////// MISC. PUBLIC METHODS /////////////////////////////////
1529: 
1530:   public boolean hit(Rectangle rect, Shape s, boolean onStroke)
1531:   {
1532:     if( onStroke )
1533:       {
1534:     Shape stroked = stroke.createStrokedShape( s );
1535:     return stroked.intersects( (double)rect.x, (double)rect.y, 
1536:                    (double)rect.width, (double)rect.height );
1537:       }
1538:     return s.intersects( (double)rect.x, (double)rect.y, 
1539:              (double)rect.width, (double)rect.height );
1540:   }
1541: 
1542:   public String toString()
1543:   {
1544:     return  (getClass().getName()
1545:              + "[font=" + getFont().toString()
1546:              + ",color=" + fg.toString()
1547:          + "]");
1548:   }
1549: 
1550:   ///////////////////////// PRIVATE METHODS ///////////////////////////////////
1551: 
1552:   /**
1553:    * All the drawImage() methods eventually get delegated here if the image
1554:    * is not a Cairo surface.
1555:    *
1556:    * @param bgcolor - if non-null draws the background color before 
1557:    * drawing the image.
1558:    */
1559:   private boolean drawRaster(ColorModel cm, Raster r,
1560:                              AffineTransform imageToUser, Color bgcolor)
1561:   {
1562:     if (r == null)
1563:       return false;
1564: 
1565:     SampleModel sm = r.getSampleModel();
1566:     DataBuffer db = r.getDataBuffer();
1567: 
1568:     if (db == null || sm == null)
1569:       return false;
1570: 
1571:     if (cm == null)
1572:       cm = ColorModel.getRGBdefault();
1573: 
1574:     double[] i2u = new double[6];
1575:     if (imageToUser != null)
1576:       imageToUser.getMatrix(i2u);
1577:     else
1578:       {
1579:     i2u[0] = 1;
1580:     i2u[1] = 0;
1581:     i2u[2] = 0;
1582:     i2u[3] = 1;
1583:     i2u[4] = 0;
1584:     i2u[5] = 0;
1585:       }
1586: 
1587:     int[] pixels = findSimpleIntegerArray(cm, r);
1588: 
1589:     if (pixels == null)
1590:       {
1591:     // FIXME: I don't think this code will work correctly with a non-RGB
1592:     // MultiPixelPackedSampleModel. Although this entire method should 
1593:     // probably be rewritten to better utilize Cairo's different supported
1594:     // data formats.
1595:     if (sm instanceof MultiPixelPackedSampleModel)
1596:       {
1597:         pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels);
1598:         for (int i = 0; i < pixels.length; i++)
1599:           pixels[i] = cm.getRGB(pixels[i]);
1600:       }
1601:     else
1602:       {
1603:         pixels = new int[r.getWidth() * r.getHeight()];
1604:         for (int i = 0; i < pixels.length; i++)
1605:           pixels[i] = cm.getRGB(db.getElem(i));
1606:       }
1607:       }
1608: 
1609:     // Change all transparent pixels in the image to the specified bgcolor,
1610:     // or (if there's no alpha) fill in an alpha channel so that it paints
1611:     // correctly.
1612:     if (cm.hasAlpha())
1613:       {
1614:     if (bgcolor != null && cm.hasAlpha())
1615:       for (int i = 0; i < pixels.length; i++)
1616:         {
1617:           if (cm.getAlpha(pixels[i]) == 0)
1618:         pixels[i] = bgcolor.getRGB();
1619:         }
1620:       }
1621:     else
1622:       for (int i = 0; i < pixels.length; i++)
1623:     pixels[i] |= 0xFF000000;
1624: 
1625:     double alpha = 1.0;
1626:     if (comp instanceof AlphaComposite)
1627:       alpha = ((AlphaComposite) comp).getAlpha();
1628:     drawPixels(nativePointer, pixels, r.getWidth(), r.getHeight(),
1629:                r.getWidth(), i2u, alpha);
1630: 
1631:     // Cairo seems to lose the current color which must be restored.
1632:     updateColor();
1633:     
1634:     return true;
1635:   }
1636: 
1637:   /**
1638:    * Shifts coordinates by 0.5.
1639:    */
1640:   private double shifted(double coord, boolean doShift)
1641:   {
1642:     if (doShift)
1643:       return Math.floor(coord) + 0.5;
1644:     else
1645:       return coord;
1646:   }
1647: 
1648:   /**
1649:    * Adds a pathIterator to the current Cairo path, also sets the cairo winding rule.
1650:    */
1651:   private void walkPath(PathIterator p, boolean doShift)
1652:   {
1653:     double x = 0;
1654:     double y = 0;
1655:     double[] coords = new double[6];
1656: 
1657:     cairoSetFillRule(nativePointer, p.getWindingRule());
1658:     for (; ! p.isDone(); p.next())
1659:       {
1660:     int seg = p.currentSegment(coords);
1661:     switch (seg)
1662:       {
1663:       case PathIterator.SEG_MOVETO:
1664:         x = shifted(coords[0], doShift);
1665:         y = shifted(coords[1], doShift);
1666:         cairoMoveTo(nativePointer, x, y);
1667:         break;
1668:       case PathIterator.SEG_LINETO:
1669:         x = shifted(coords[0], doShift);
1670:         y = shifted(coords[1], doShift);
1671:         cairoLineTo(nativePointer, x, y);
1672:         break;
1673:       case PathIterator.SEG_QUADTO:
1674:         // splitting a quadratic bezier into a cubic:
1675:         // see: http://pfaedit.sourceforge.net/bezier.html
1676:         double x1 = x + (2.0 / 3.0) * (shifted(coords[0], doShift) - x);
1677:         double y1 = y + (2.0 / 3.0) * (shifted(coords[1], doShift) - y);
1678: 
1679:         double x2 = x1 + (1.0 / 3.0) * (shifted(coords[2], doShift) - x);
1680:         double y2 = y1 + (1.0 / 3.0) * (shifted(coords[3], doShift) - y);
1681: 
1682:         x = shifted(coords[2], doShift);
1683:         y = shifted(coords[3], doShift);
1684:         cairoCurveTo(nativePointer, x1, y1, x2, y2, x, y);
1685:         break;
1686:       case PathIterator.SEG_CUBICTO:
1687:         x = shifted(coords[4], doShift);
1688:         y = shifted(coords[5], doShift);
1689:         cairoCurveTo(nativePointer, shifted(coords[0], doShift),
1690:                      shifted(coords[1], doShift),
1691:                      shifted(coords[2], doShift),
1692:                      shifted(coords[3], doShift), x, y);
1693:         break;
1694:       case PathIterator.SEG_CLOSE:
1695:         cairoClosePath(nativePointer);
1696:         break;
1697:       }
1698:       }
1699:   }
1700: 
1701:   /**
1702:    * Used by setRenderingHints()
1703:    */
1704:   private Map getDefaultHints()
1705:   {
1706:     HashMap defaultHints = new HashMap();
1707: 
1708:     defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
1709:                      RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
1710: 
1711:     defaultHints.put(RenderingHints.KEY_STROKE_CONTROL,
1712:                      RenderingHints.VALUE_STROKE_DEFAULT);
1713: 
1714:     defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS,
1715:                      RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
1716: 
1717:     defaultHints.put(RenderingHints.KEY_ANTIALIASING,
1718:                      RenderingHints.VALUE_ANTIALIAS_OFF);
1719: 
1720:     defaultHints.put(RenderingHints.KEY_RENDERING,
1721:                      RenderingHints.VALUE_RENDER_DEFAULT);
1722: 
1723:     return defaultHints;
1724:   }
1725: 
1726:   /**
1727:    * Used by drawRaster and GdkPixbufDecoder
1728:    */
1729:   public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster)
1730:   {
1731:     if (cm == null || raster == null)
1732:       return null;
1733: 
1734:     if (! cm.getColorSpace().isCS_sRGB())
1735:       return null;
1736: 
1737:     if (! (cm instanceof DirectColorModel))
1738:       return null;
1739: 
1740:     DirectColorModel dcm = (DirectColorModel) cm;
1741: 
1742:     if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00
1743:         || dcm.getBlueMask() != 0x000000FF)
1744:       return null;
1745: 
1746:     if (! (raster instanceof WritableRaster))
1747:       return null;
1748: 
1749:     if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT)
1750:       return null;
1751: 
1752:     if (! (raster.getDataBuffer() instanceof DataBufferInt))
1753:       return null;
1754: 
1755:     DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
1756: 
1757:     if (db.getNumBanks() != 1)
1758:       return null;
1759: 
1760:     // Finally, we have determined that this is a single bank, [A]RGB-int
1761:     // buffer in sRGB space. It's worth checking all this, because it means
1762:     // that cairo can paint directly into the data buffer, which is very
1763:     // fast compared to all the normal copying and converting.
1764: 
1765:     return db.getData();
1766:   }
1767: 
1768:   /**
1769:    * Helper method to transform the clip. This is called by the various
1770:    * transformation-manipulation methods to update the clip (which is in
1771:    * userspace) accordingly.
1772:    *
1773:    * The transform usually is the inverse transform that was applied to the
1774:    * graphics object.
1775:    *
1776:    * @param t the transform to apply to the clip
1777:    */
1778:   private void updateClip(AffineTransform t)
1779:   {
1780:     if (clip == null)
1781:       return;
1782: 
1783:     if (! (clip instanceof GeneralPath))
1784:       clip = new GeneralPath(clip);
1785: 
1786:     GeneralPath p = (GeneralPath) clip;
1787:     p.transform(t);
1788:   }
1789: 
1790:   private static Rectangle computeIntersection(int x, int y, int w, int h,
1791:                                                Rectangle rect)
1792:   {
1793:     int x2 = (int) rect.x;
1794:     int y2 = (int) rect.y;
1795:     int w2 = (int) rect.width;
1796:     int h2 = (int) rect.height;
1797: 
1798:     int dx = (x > x2) ? x : x2;
1799:     int dy = (y > y2) ? y : y2;
1800:     int dw = (x + w < x2 + w2) ? (x + w - dx) : (x2 + w2 - dx);
1801:     int dh = (y + h < y2 + h2) ? (y + h - dy) : (y2 + h2 - dy);
1802: 
1803:     if (dw >= 0 && dh >= 0)
1804:       rect.setBounds(dx, dy, dw, dh);
1805:     else
1806:       rect.setBounds(0, 0, 0, 0);
1807: 
1808:     return rect;
1809:   }
1810: }