OSDN Git Service

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