OSDN Git Service

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