mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			2177 lines
		
	
	
		
			63 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			2177 lines
		
	
	
		
			63 KiB
		
	
	
	
		
			Java
		
	
	
	
| /* CairoGraphics2D.java --
 | |
|    Copyright (C) 2006  Free Software Foundation, Inc.
 | |
| 
 | |
| This file is part of GNU Classpath.
 | |
| 
 | |
| GNU Classpath is free software; you can redistribute it and/or modify
 | |
| it under the terms of the GNU General Public License as published by
 | |
| the Free Software Foundation; either version 2, or (at your option)
 | |
| any later version.
 | |
| 
 | |
| GNU Classpath is distributed in the hope that it will be useful, but
 | |
| WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
| General Public License for more details.
 | |
| 
 | |
| You should have received a copy of the GNU General Public License
 | |
| along with GNU Classpath; see the file COPYING.  If not, write to the
 | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | |
| 02110-1301 USA.
 | |
| 
 | |
| Linking this library statically or dynamically with other modules is
 | |
| making a combined work based on this library.  Thus, the terms and
 | |
| conditions of the GNU General Public License cover the whole
 | |
| combination.
 | |
| 
 | |
| As a special exception, the copyright holders of this library give you
 | |
| permission to link this library with independent modules to produce an
 | |
| executable, regardless of the license terms of these independent
 | |
| modules, and to copy and distribute the resulting executable under
 | |
| terms of your choice, provided that you also meet, for each linked
 | |
| independent module, the terms and conditions of the license of that
 | |
| module.  An independent module is a module which is not derived from
 | |
| or based on this library.  If you modify this library, you may extend
 | |
| this exception to your version of the library, but you are not
 | |
| obligated to do so.  If you do not wish to do so, delete this
 | |
| exception statement from your version. */
 | |
| 
 | |
| 
 | |
| package gnu.java.awt.peer.gtk;
 | |
| 
 | |
| import gnu.classpath.Configuration;
 | |
| 
 | |
| import gnu.java.awt.ClasspathToolkit;
 | |
| 
 | |
| import java.awt.AWTPermission;
 | |
| import java.awt.AlphaComposite;
 | |
| import java.awt.BasicStroke;
 | |
| import java.awt.Color;
 | |
| import java.awt.Composite;
 | |
| import java.awt.CompositeContext;
 | |
| import java.awt.Font;
 | |
| import java.awt.FontMetrics;
 | |
| import java.awt.GradientPaint;
 | |
| import java.awt.Graphics;
 | |
| import java.awt.Graphics2D;
 | |
| import java.awt.GraphicsConfiguration;
 | |
| import java.awt.Image;
 | |
| import java.awt.Paint;
 | |
| import java.awt.PaintContext;
 | |
| import java.awt.Point;
 | |
| import java.awt.Polygon;
 | |
| import java.awt.Rectangle;
 | |
| import java.awt.RenderingHints;
 | |
| import java.awt.Shape;
 | |
| import java.awt.Stroke;
 | |
| import java.awt.TexturePaint;
 | |
| import java.awt.Toolkit;
 | |
| import java.awt.font.FontRenderContext;
 | |
| import java.awt.font.GlyphVector;
 | |
| import java.awt.font.TextLayout;
 | |
| import java.awt.geom.AffineTransform;
 | |
| import java.awt.geom.Arc2D;
 | |
| import java.awt.geom.Area;
 | |
| import java.awt.geom.Ellipse2D;
 | |
| import java.awt.geom.GeneralPath;
 | |
| import java.awt.geom.Line2D;
 | |
| import java.awt.geom.NoninvertibleTransformException;
 | |
| import java.awt.geom.PathIterator;
 | |
| import java.awt.geom.Point2D;
 | |
| import java.awt.geom.Rectangle2D;
 | |
| import java.awt.geom.RoundRectangle2D;
 | |
| import java.awt.image.AffineTransformOp;
 | |
| import java.awt.image.BufferedImage;
 | |
| import java.awt.image.BufferedImageOp;
 | |
| import java.awt.image.ColorModel;
 | |
| import java.awt.image.DataBuffer;
 | |
| import java.awt.image.DataBufferInt;
 | |
| import java.awt.image.DirectColorModel;
 | |
| import java.awt.image.ImageObserver;
 | |
| import java.awt.image.ImageProducer;
 | |
| import java.awt.image.ImagingOpException;
 | |
| import java.awt.image.MultiPixelPackedSampleModel;
 | |
| import java.awt.image.Raster;
 | |
| import java.awt.image.RenderedImage;
 | |
| import java.awt.image.SampleModel;
 | |
| import java.awt.image.WritableRaster;
 | |
| import java.awt.image.renderable.RenderContext;
 | |
| import java.awt.image.renderable.RenderableImage;
 | |
| import java.text.AttributedCharacterIterator;
 | |
| import java.util.HashMap;
 | |
| import java.util.Map;
 | |
| 
 | |
| /**
 | |
|  * This is an abstract implementation of Graphics2D on Cairo.
 | |
|  *
 | |
|  * It should be subclassed for different Cairo contexts.
 | |
|  *
 | |
|  * Note for subclassers: Apart from the constructor (see comments below),
 | |
|  * The following abstract methods must be implemented:
 | |
|  *
 | |
|  * Graphics create()
 | |
|  * GraphicsConfiguration getDeviceConfiguration()
 | |
|  * copyArea(int x, int y, int width, int height, int dx, int dy)
 | |
|  *
 | |
|  * Also, dispose() must be overloaded to free any native datastructures
 | |
|  * used by subclass and in addition call super.dispose() to free the
 | |
|  * native cairographics2d structure and cairo_t.
 | |
|  *
 | |
|  * @author Sven de Marothy
 | |
|  */
 | |
| public abstract class CairoGraphics2D extends Graphics2D
 | |
| {
 | |
|   static
 | |
|   {
 | |
|     if (true) // GCJ LOCAL
 | |
|       {
 | |
|         System.loadLibrary("gtkpeer");
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Important: This is a pointer to the native cairographics2d structure
 | |
|    *
 | |
|    * DO NOT CHANGE WITHOUT CHANGING NATIVE CODE.
 | |
|    */
 | |
|   long nativePointer;
 | |
| 
 | |
|   // Drawing state variables
 | |
|   /**
 | |
|    * The current paint
 | |
|    */
 | |
|   Paint paint;
 | |
|   boolean customPaint;
 | |
| 
 | |
|   /**
 | |
|    * The current stroke
 | |
|    */
 | |
|   Stroke stroke;
 | |
| 
 | |
|   /*
 | |
|    * Current foreground and background color.
 | |
|    */
 | |
|   Color fg, bg;
 | |
| 
 | |
|   /**
 | |
|    * Current clip shape.
 | |
|    */
 | |
|   Shape clip;
 | |
| 
 | |
|   /**
 | |
|    * Current transform.
 | |
|    */
 | |
|   AffineTransform transform;
 | |
| 
 | |
|   /**
 | |
|    * Current font.
 | |
|    */
 | |
|   Font font;
 | |
| 
 | |
|   /**
 | |
|    * The current compositing context, if any.
 | |
|    */
 | |
|   Composite comp;
 | |
|   CompositeContext compCtx;
 | |
| 
 | |
|   /**
 | |
|    * Rendering hint map.
 | |
|    */
 | |
|   private RenderingHints hints;
 | |
| 
 | |
|   /**
 | |
|    * Status of the anti-alias flag in cairo.
 | |
|    */
 | |
|   private boolean antialias = false;
 | |
|   private boolean ignoreAA = false;
 | |
| 
 | |
|   /**
 | |
|    * Some operations (drawing rather than filling) require that their
 | |
|    * coords be shifted to land on 0.5-pixel boundaries, in order to land on
 | |
|    * "middle of pixel" coordinates and light up complete pixels.
 | |
|    */
 | |
|   protected boolean shiftDrawCalls = false;
 | |
| 
 | |
|   /**
 | |
|    * Keep track if the first clip to be set, which is restored on setClip(null);
 | |
|    */
 | |
|   private boolean firstClip = true;
 | |
|   private Shape originalClip;
 | |
| 
 | |
|   /**
 | |
|    * Stroke used for 3DRects
 | |
|    */
 | |
|   private static BasicStroke draw3DRectStroke = new BasicStroke();
 | |
| 
 | |
|   static ColorModel rgb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF);
 | |
|   static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF,
 | |
|                                                   0xFF000000);
 | |
| 
 | |
|   /**
 | |
|    * Native constants for interpolation methods.
 | |
|    * Note, this corresponds to an enum in native/jni/gtk-peer/cairographics2d.h
 | |
|    */
 | |
|   public static final int INTERPOLATION_NEAREST         = 0,
 | |
|                           INTERPOLATION_BILINEAR        = 1,
 | |
|                           INTERPOLATION_BICUBIC         = 5,
 | |
|                           ALPHA_INTERPOLATION_SPEED     = 2,
 | |
|                           ALPHA_INTERPOLATION_QUALITY   = 3,
 | |
|                           ALPHA_INTERPOLATION_DEFAULT   = 4;
 | |
|   // TODO: Does ALPHA_INTERPOLATION really correspond to CAIRO_FILTER_FAST/BEST/GOOD?
 | |
| 
 | |
|   /**
 | |
|    * Constructor does nothing.
 | |
|    */
 | |
|   public CairoGraphics2D()
 | |
|   {
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets up the default values and allocates the native cairographics2d structure
 | |
|    * @param cairo_t_pointer a native pointer to a cairo_t of the context.
 | |
|    */
 | |
|   public void setup(long cairo_t_pointer)
 | |
|   {
 | |
|     nativePointer = init(cairo_t_pointer);
 | |
|     setRenderingHints(new RenderingHints(getDefaultHints()));
 | |
|     setFont(new Font("SansSerif", Font.PLAIN, 12));
 | |
|     setColor(Color.black);
 | |
|     setBackground(Color.white);
 | |
|     setPaint(Color.black);
 | |
|     setStroke(new BasicStroke());
 | |
|     setTransform(new AffineTransform());
 | |
|     cairoSetAntialias(nativePointer, antialias);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Same as above, but copies the state of another CairoGraphics2D.
 | |
|    */
 | |
|   public void copy(CairoGraphics2D g, long cairo_t_pointer)
 | |
|   {
 | |
|     nativePointer = init(cairo_t_pointer);
 | |
|     paint = g.paint;
 | |
|     stroke = g.stroke;
 | |
|     setRenderingHints(g.hints);
 | |
| 
 | |
|     Color foreground;
 | |
| 
 | |
|     if (g.fg.getAlpha() != -1)
 | |
|       foreground = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(),
 | |
|                              g.fg.getAlpha());
 | |
|     else
 | |
|       foreground = new Color(g.fg.getRGB());
 | |
| 
 | |
|     if (g.bg != null)
 | |
|       {
 | |
|         if (g.bg.getAlpha() != -1)
 | |
|           bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(),
 | |
|                          g.bg.getAlpha());
 | |
|         else
 | |
|           bg = new Color(g.bg.getRGB());
 | |
|       }
 | |
| 
 | |
|     firstClip = g.firstClip;
 | |
|     originalClip = g.originalClip;
 | |
|     clip = g.getClip();
 | |
| 
 | |
|     if (g.transform == null)
 | |
|       transform = null;
 | |
|     else
 | |
|       transform = new AffineTransform(g.transform);
 | |
| 
 | |
|     setFont(g.font);
 | |
|     setColor(foreground);
 | |
|     setBackground(bg);
 | |
|     setPaint(paint);
 | |
|     setStroke(stroke);
 | |
|     setTransformImpl(transform);
 | |
|     setClip(clip);
 | |
|     setComposite(comp);
 | |
| 
 | |
|     antialias = !g.antialias;
 | |
|     setAntialias(g.antialias);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Generic destructor - call the native dispose() method.
 | |
|    */
 | |
|   public void finalize()
 | |
|   {
 | |
|     dispose();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Disposes the native cairographics2d structure, including the
 | |
|    * cairo_t and any gradient stuff, if allocated.
 | |
|    * Subclasses should of course overload and call this if
 | |
|    * they have additional native structures.
 | |
|    */
 | |
|   public void dispose()
 | |
|   {
 | |
|     disposeNative(nativePointer);
 | |
|     nativePointer = 0;
 | |
|     if (compCtx != null)
 | |
|       compCtx.dispose();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Allocate the cairographics2d structure and set the cairo_t pointer in it.
 | |
|    * @param pointer - a cairo_t pointer, casted to a long.
 | |
|    */
 | |
|   protected native long init(long pointer);
 | |
| 
 | |
|   /**
 | |
|    * These are declared abstract as there may be context-specific issues.
 | |
|    */
 | |
|   public abstract Graphics create();
 | |
| 
 | |
|   public abstract GraphicsConfiguration getDeviceConfiguration();
 | |
| 
 | |
|   protected abstract void copyAreaImpl(int x, int y, int width, int height,
 | |
|                                        int dx, int dy);
 | |
| 
 | |
| 
 | |
|   /**
 | |
|    * Find the bounds of this graphics context, in device space.
 | |
|    *
 | |
|    * @return the bounds in device-space
 | |
|    */
 | |
|   protected abstract Rectangle2D getRealBounds();
 | |
| 
 | |
|   ////// Native Methods ////////////////////////////////////////////////////
 | |
| 
 | |
|   /**
 | |
|    * Dispose of allocate native resouces.
 | |
|    */
 | |
|   public native void disposeNative(long pointer);
 | |
| 
 | |
|   /**
 | |
|    * Draw pixels as an RGBA int matrix
 | |
|    * @param w - width
 | |
|    * @param h - height
 | |
|    * @param stride - stride of the array width
 | |
|    * @param i2u - affine transform array
 | |
|    */
 | |
|   protected native void drawPixels(long pointer, int[] pixels, int w, int h,
 | |
|                                  int stride, double[] i2u, double alpha,
 | |
|                                  int interpolation);
 | |
| 
 | |
|   protected native void setGradient(long pointer, double x1, double y1,
 | |
|                                   double x2, double y2,
 | |
|                                   int r1, int g1, int b1, int a1, int r2,
 | |
|                                   int g2, int b2, int a2, boolean cyclic);
 | |
| 
 | |
|   protected native void setPaintPixels(long pointer, int[] pixels, int w,
 | |
|                                      int h, int stride, boolean repeat,
 | |
|                                      int x, int y);
 | |
| 
 | |
|   /**
 | |
|    * Set the current transform matrix
 | |
|    */
 | |
|   protected native void cairoSetMatrix(long pointer, double[] m);
 | |
| 
 | |
|   /**
 | |
|    * Scaling method
 | |
|    */
 | |
|   protected native void cairoScale(long pointer, double x, double y);
 | |
| 
 | |
|   /**
 | |
|    * Set the compositing operator
 | |
|    */
 | |
|   protected native void cairoSetOperator(long pointer, int cairoOperator);
 | |
| 
 | |
|   /**
 | |
|    * Sets the current color in RGBA as a 0.0-1.0 double
 | |
|    */
 | |
|   protected native void cairoSetRGBAColor(long pointer, double red, double green,
 | |
|                                         double blue, double alpha);
 | |
| 
 | |
|   /**
 | |
|    * Sets the current winding rule in Cairo
 | |
|    */
 | |
|   protected native void cairoSetFillRule(long pointer, int cairoFillRule);
 | |
| 
 | |
|   /**
 | |
|    * Set the line style, cap, join and miter limit.
 | |
|    * Cap and join parameters are in the BasicStroke enumerations.
 | |
|    */
 | |
|   protected native void cairoSetLine(long pointer, double width, int cap,
 | |
|                                    int join, double miterLimit);
 | |
| 
 | |
|   /**
 | |
|    * Set the dash style
 | |
|    */
 | |
|   protected native void cairoSetDash(long pointer, double[] dashes, int ndash,
 | |
|                                    double offset);
 | |
| 
 | |
|   /*
 | |
|    * Draws a Glyph Vector
 | |
|    */
 | |
|   protected native void cairoDrawGlyphVector(long pointer, GdkFontPeer font,
 | |
|                                    float x, float y, int n,
 | |
|                                    int[] codes, float[] positions, long[] fontset);
 | |
| 
 | |
|   /**
 | |
|    * Set the font in cairo.
 | |
|    */
 | |
|   protected native void cairoSetFont(long pointer, GdkFontPeer font);
 | |
| 
 | |
|   /**
 | |
|    * Appends a rectangle to the current path
 | |
|    */
 | |
|   protected native void cairoRectangle(long pointer, double x, double y,
 | |
|                                      double width, double height);
 | |
| 
 | |
|   /**
 | |
|    * Appends an arc to the current path
 | |
|    */
 | |
|   protected native void cairoArc(long pointer, double x, double y,
 | |
|                                double radius, double angle1, double angle2);
 | |
| 
 | |
|   /**
 | |
|    * Save / restore a cairo path
 | |
|    */
 | |
|   protected native void cairoSave(long pointer);
 | |
|   protected native void cairoRestore(long pointer);
 | |
| 
 | |
|   /**
 | |
|    * New current path
 | |
|    */
 | |
|   protected native void cairoNewPath(long pointer);
 | |
| 
 | |
|   /**
 | |
|    * Close current path
 | |
|    */
 | |
|   protected native void cairoClosePath(long pointer);
 | |
| 
 | |
|   /** moveTo */
 | |
|   protected native void cairoMoveTo(long pointer, double x, double y);
 | |
| 
 | |
|   /** lineTo */
 | |
|   protected native void cairoLineTo(long pointer, double x, double y);
 | |
| 
 | |
|   /** Cubic curve-to */
 | |
|   protected native void cairoCurveTo(long pointer, double x1, double y1,
 | |
|                                    double x2, double y2,
 | |
|                                    double x3, double y3);
 | |
| 
 | |
|   /**
 | |
|    * Stroke current path
 | |
|    */
 | |
|   protected native void cairoStroke(long pointer);
 | |
| 
 | |
|   /**
 | |
|    * Fill current path
 | |
|    */
 | |
|   protected native void cairoFill(long pointer, double alpha);
 | |
| 
 | |
|   /**
 | |
|    * Clip current path
 | |
|    */
 | |
|   protected native void cairoClip(long pointer);
 | |
| 
 | |
|   /**
 | |
|    * Clear clip
 | |
|    */
 | |
|   protected native void cairoResetClip(long pointer);
 | |
| 
 | |
|   /**
 | |
|    * Set antialias.
 | |
|    */
 | |
|   protected native void cairoSetAntialias(long pointer, boolean aa);
 | |
| 
 | |
| 
 | |
|   ///////////////////////// TRANSFORMS ///////////////////////////////////
 | |
|   /**
 | |
|    * Set the current transform
 | |
|    */
 | |
|   public void setTransform(AffineTransform tx)
 | |
|   {
 | |
|     // Transform clip into target space using the old transform.
 | |
|     updateClip(transform);
 | |
| 
 | |
|     // Update the native transform.
 | |
|     setTransformImpl(tx);
 | |
| 
 | |
|     // Transform the clip back into user space using the inverse new transform.
 | |
|     try
 | |
|       {
 | |
|         updateClip(transform.createInverse());
 | |
|       }
 | |
|     catch (NoninvertibleTransformException ex)
 | |
|       {
 | |
|         // TODO: How can we deal properly with this?
 | |
|         ex.printStackTrace();
 | |
|       }
 | |
| 
 | |
|     if (clip != null)
 | |
|       setClip(clip);
 | |
|   }
 | |
| 
 | |
|   private void setTransformImpl(AffineTransform tx)
 | |
|   {
 | |
|     transform = tx;
 | |
|     if (transform != null)
 | |
|       {
 | |
|         double[] m = new double[6];
 | |
|         transform.getMatrix(m);
 | |
|         cairoSetMatrix(nativePointer, m);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   public void transform(AffineTransform tx)
 | |
|   {
 | |
|     if (transform == null)
 | |
|       transform = new AffineTransform(tx);
 | |
|     else
 | |
|       transform.concatenate(tx);
 | |
| 
 | |
|     if (clip != null)
 | |
|       {
 | |
|         try
 | |
|           {
 | |
|             AffineTransform clipTransform = tx.createInverse();
 | |
|             updateClip(clipTransform);
 | |
|           }
 | |
|         catch (NoninvertibleTransformException ex)
 | |
|           {
 | |
|             // TODO: How can we deal properly with this?
 | |
|             ex.printStackTrace();
 | |
|           }
 | |
|       }
 | |
| 
 | |
|     setTransformImpl(transform);
 | |
|   }
 | |
| 
 | |
|   public void rotate(double theta)
 | |
|   {
 | |
|     transform(AffineTransform.getRotateInstance(theta));
 | |
|   }
 | |
| 
 | |
|   public void rotate(double theta, double x, double y)
 | |
|   {
 | |
|     transform(AffineTransform.getRotateInstance(theta, x, y));
 | |
|   }
 | |
| 
 | |
|   public void scale(double sx, double sy)
 | |
|   {
 | |
|     transform(AffineTransform.getScaleInstance(sx, sy));
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Translate the system of the co-ordinates. As translation is a frequent
 | |
|    * operation, it is done in an optimised way, unlike scaling and rotating.
 | |
|    */
 | |
|   public void translate(double tx, double ty)
 | |
|   {
 | |
|     if (transform != null)
 | |
|       transform.translate(tx, ty);
 | |
|     else
 | |
|       transform = AffineTransform.getTranslateInstance(tx, ty);
 | |
| 
 | |
|     if (clip != null)
 | |
|       {
 | |
|         // FIXME: this should actuall try to transform the shape
 | |
|         // rather than degrade to bounds.
 | |
|         if (clip instanceof Rectangle2D)
 | |
|           {
 | |
|             Rectangle2D r = (Rectangle2D) clip;
 | |
|             r.setRect(r.getX() - tx, r.getY() - ty, r.getWidth(),
 | |
|                       r.getHeight());
 | |
|           }
 | |
|         else
 | |
|           {
 | |
|             AffineTransform clipTransform =
 | |
|               AffineTransform.getTranslateInstance(-tx, -ty);
 | |
|             updateClip(clipTransform);
 | |
|           }
 | |
|       }
 | |
| 
 | |
|     setTransformImpl(transform);
 | |
|   }
 | |
| 
 | |
|   public void translate(int x, int y)
 | |
|   {
 | |
|     translate((double) x, (double) y);
 | |
|   }
 | |
| 
 | |
|   public void shear(double shearX, double shearY)
 | |
|   {
 | |
|     transform(AffineTransform.getShearInstance(shearX, shearY));
 | |
|   }
 | |
| 
 | |
|   ///////////////////////// DRAWING STATE ///////////////////////////////////
 | |
| 
 | |
|   public void clip(Shape s)
 | |
|   {
 | |
|     // Do not touch clip when s == null.
 | |
|     if (s == null)
 | |
|       {
 | |
|         // The spec says this should clear the clip. The reference
 | |
|         // implementation throws a NullPointerException instead. I think,
 | |
|         // in this case we should conform to the specs, as it shouldn't
 | |
|         // affect compatibility.
 | |
|         setClip(null);
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|     // If the current clip is still null, initialize it.
 | |
|     if (clip == null)
 | |
|       {
 | |
|         clip = getRealBounds();
 | |
|       }
 | |
| 
 | |
|     // This is so common, let's optimize this.
 | |
|     if (clip instanceof Rectangle2D && s instanceof Rectangle2D)
 | |
|       {
 | |
|         Rectangle2D clipRect = (Rectangle2D) clip;
 | |
|         Rectangle2D r = (Rectangle2D) s;
 | |
|         Rectangle2D.intersect(clipRect, r, clipRect);
 | |
|         setClip(clipRect);
 | |
|       }
 | |
|    else
 | |
|      {
 | |
|        Area current;
 | |
|        if (clip instanceof Area)
 | |
|          current = (Area) clip;
 | |
|        else
 | |
|          current = new Area(clip);
 | |
| 
 | |
|        Area intersect;
 | |
|        if (s instanceof Area)
 | |
|          intersect = (Area) s;
 | |
|        else
 | |
|          intersect = new Area(s);
 | |
| 
 | |
|        current.intersect(intersect);
 | |
|        clip = current;
 | |
|        // Call setClip so that the native side gets notified.
 | |
|        setClip(clip);
 | |
|      }
 | |
|   }
 | |
| 
 | |
|   public Paint getPaint()
 | |
|   {
 | |
|     return paint;
 | |
|   }
 | |
| 
 | |
|   public AffineTransform getTransform()
 | |
|   {
 | |
|     return (AffineTransform) transform.clone();
 | |
|   }
 | |
| 
 | |
|   public void setPaint(Paint p)
 | |
|   {
 | |
|     if (p == null)
 | |
|       return;
 | |
| 
 | |
|     paint = p;
 | |
|     if (paint instanceof Color)
 | |
|       {
 | |
|         setColor((Color) paint);
 | |
|         customPaint = false;
 | |
|       }
 | |
| 
 | |
|     else if (paint instanceof TexturePaint)
 | |
|       {
 | |
|         TexturePaint tp = (TexturePaint) paint;
 | |
|         BufferedImage img = tp.getImage();
 | |
| 
 | |
|         // map the image to the anchor rectangle
 | |
|         int width = (int) tp.getAnchorRect().getWidth();
 | |
|         int height = (int) tp.getAnchorRect().getHeight();
 | |
| 
 | |
|         double scaleX = width / (double) img.getWidth();
 | |
|         double scaleY = height / (double) img.getHeight();
 | |
| 
 | |
|         AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
 | |
|         AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
 | |
|         BufferedImage texture = op.filter(img, null);
 | |
|         int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
 | |
|         setPaintPixels(nativePointer, pixels, width, height, width, true, 0, 0);
 | |
|         customPaint = false;
 | |
|       }
 | |
| 
 | |
|     else if (paint instanceof GradientPaint)
 | |
|       {
 | |
|         GradientPaint gp = (GradientPaint) paint;
 | |
|         Point2D p1 = gp.getPoint1();
 | |
|         Point2D p2 = gp.getPoint2();
 | |
|         Color c1 = gp.getColor1();
 | |
|         Color c2 = gp.getColor2();
 | |
|         setGradient(nativePointer, p1.getX(), p1.getY(), p2.getX(), p2.getY(),
 | |
|                     c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha(),
 | |
|                     c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha(),
 | |
|                     gp.isCyclic());
 | |
|         customPaint = false;
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         customPaint = true;
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets a custom paint
 | |
|    *
 | |
|    * @param bounds the bounding box, in user space
 | |
|    */
 | |
|   protected void setCustomPaint(Rectangle bounds)
 | |
|   {
 | |
|     if (paint instanceof Color || paint instanceof TexturePaint
 | |
|         || paint instanceof GradientPaint)
 | |
|       return;
 | |
| 
 | |
|     int userX = bounds.x;
 | |
|     int userY = bounds.y;
 | |
|     int userWidth = bounds.width;
 | |
|     int userHeight = bounds.height;
 | |
| 
 | |
|     // Find bounds in device space
 | |
|     Rectangle2D bounds2D = getTransformedBounds(bounds, transform);
 | |
|     int deviceX = (int)bounds2D.getX();
 | |
|     int deviceY = (int)bounds2D.getY();
 | |
|     int deviceWidth = (int)Math.ceil(bounds2D.getWidth());
 | |
|     int deviceHeight = (int)Math.ceil(bounds2D.getHeight());
 | |
| 
 | |
|     // Get raster of the paint background
 | |
|     PaintContext pc = paint.createContext(CairoSurface.cairoColorModel,
 | |
|                                           new Rectangle(deviceX, deviceY,
 | |
|                                                         deviceWidth,
 | |
|                                                         deviceHeight),
 | |
|                                           bounds,
 | |
|                                           transform, hints);
 | |
| 
 | |
|     Raster raster = pc.getRaster(deviceX, deviceY, deviceWidth,
 | |
|                                  deviceHeight);
 | |
| 
 | |
|     // Clear the transform matrix in Cairo, since the raster returned by the
 | |
|     // PaintContext is already in device-space
 | |
|     AffineTransform oldTx = new AffineTransform(transform);
 | |
|     setTransformImpl(new AffineTransform());
 | |
| 
 | |
|     // Set pixels in cairo, aligning the top-left of the background image
 | |
|     // to the top-left corner in device space
 | |
|     if (pc.getColorModel().equals(CairoSurface.cairoColorModel)
 | |
|         && raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT)
 | |
|       {
 | |
|         // Use a fast copy if the paint context can uses a Cairo-compatible
 | |
|         // color model
 | |
|         setPaintPixels(nativePointer,
 | |
|                        (int[])raster.getDataElements(0, 0, deviceWidth,
 | |
|                                                      deviceHeight, null),
 | |
|                        deviceWidth, deviceHeight, deviceWidth, false,
 | |
|                        deviceX, deviceY);
 | |
|       }
 | |
| 
 | |
|     else if (pc.getColorModel().equals(CairoSurface.cairoCM_opaque)
 | |
|             && raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT)
 | |
|       {
 | |
|         // We can also optimize if the context uses a similar color model
 | |
|         // but without an alpha channel; we just add the alpha
 | |
|         int[] pixels = (int[])raster.getDataElements(0, 0, deviceWidth,
 | |
|                                                      deviceHeight, null);
 | |
| 
 | |
|         for (int i = 0; i < pixels.length; i++)
 | |
|           pixels[i] = 0xff000000 | (pixels[i] & 0x00ffffff);
 | |
| 
 | |
|         setPaintPixels(nativePointer, pixels, deviceWidth, deviceHeight,
 | |
|                        deviceWidth, false, deviceX, deviceY);
 | |
|       }
 | |
| 
 | |
|     else
 | |
|       {
 | |
|         // Fall back on wrapping the raster in a BufferedImage, and
 | |
|         // use BufferedImage.getRGB() to do color-model conversion
 | |
|         WritableRaster wr = Raster.createWritableRaster(raster.getSampleModel(),
 | |
|                                                         new Point(raster.getMinX(),
 | |
|                                                                   raster.getMinY()));
 | |
|         wr.setRect(raster);
 | |
| 
 | |
|         BufferedImage img2 = new BufferedImage(pc.getColorModel(), wr,
 | |
|                                                pc.getColorModel().isAlphaPremultiplied(),
 | |
|                                                null);
 | |
| 
 | |
|         setPaintPixels(nativePointer,
 | |
|                        img2.getRGB(0, 0, deviceWidth, deviceHeight, null, 0,
 | |
|                                    deviceWidth),
 | |
|                        deviceWidth, deviceHeight, deviceWidth, false,
 | |
|                        deviceX, deviceY);
 | |
|       }
 | |
| 
 | |
|     // Restore transform
 | |
|     setTransformImpl(oldTx);
 | |
|   }
 | |
| 
 | |
|   public Stroke getStroke()
 | |
|   {
 | |
|     return stroke;
 | |
|   }
 | |
| 
 | |
|   public void setStroke(Stroke st)
 | |
|   {
 | |
|     stroke = st;
 | |
|     if (stroke instanceof BasicStroke)
 | |
|       {
 | |
|         BasicStroke bs = (BasicStroke) stroke;
 | |
|         cairoSetLine(nativePointer, bs.getLineWidth(), bs.getEndCap(),
 | |
|                      bs.getLineJoin(), bs.getMiterLimit());
 | |
| 
 | |
|         float[] dashes = bs.getDashArray();
 | |
|         if (dashes != null)
 | |
|           {
 | |
|             double[] double_dashes = new double[dashes.length];
 | |
|             for (int i = 0; i < dashes.length; i++)
 | |
|               double_dashes[i] = dashes[i];
 | |
| 
 | |
|             cairoSetDash(nativePointer, double_dashes, double_dashes.length,
 | |
|                          (double) bs.getDashPhase());
 | |
|           }
 | |
|         else
 | |
|           cairoSetDash(nativePointer, new double[0], 0, 0.0);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Utility method to find the bounds of a shape, including the stroke width.
 | |
|    *
 | |
|    * @param s the shape
 | |
|    * @return the bounds of the shape, including stroke width
 | |
|    */
 | |
|   protected Rectangle findStrokedBounds(Shape s)
 | |
|   {
 | |
|     Rectangle r = s.getBounds();
 | |
| 
 | |
|     if (stroke instanceof BasicStroke)
 | |
|       {
 | |
|         int strokeWidth = (int)Math.ceil(((BasicStroke)stroke).getLineWidth());
 | |
|         r.x -= strokeWidth / 2;
 | |
|         r.y -= strokeWidth / 2;
 | |
|         r.height += strokeWidth;
 | |
|         r.width += strokeWidth;
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         Shape s2 = stroke.createStrokedShape(s);
 | |
|         r = s2.getBounds();
 | |
|       }
 | |
| 
 | |
|     return r;
 | |
|   }
 | |
| 
 | |
|   public void setPaintMode()
 | |
|   {
 | |
|     setComposite(AlphaComposite.SrcOver);
 | |
|   }
 | |
| 
 | |
|   public void setXORMode(Color c)
 | |
|   {
 | |
|     // FIXME: implement
 | |
|   }
 | |
| 
 | |
|   public void setColor(Color c)
 | |
|   {
 | |
|     if (c == null)
 | |
|       c = Color.BLACK;
 | |
| 
 | |
|     fg = c;
 | |
|     paint = c;
 | |
|     updateColor();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Set the current fg value as the cairo color.
 | |
|    */
 | |
|   void updateColor()
 | |
|   {
 | |
|     if (fg == null)
 | |
|       fg = Color.BLACK;
 | |
| 
 | |
|     cairoSetRGBAColor(nativePointer, fg.getRed() / 255.0,
 | |
|                       fg.getGreen() / 255.0,fg.getBlue() / 255.0,
 | |
|                       fg.getAlpha() / 255.0);
 | |
|   }
 | |
| 
 | |
|   public Color getColor()
 | |
|   {
 | |
|     return fg;
 | |
|   }
 | |
| 
 | |
|   public void clipRect(int x, int y, int width, int height)
 | |
|   {
 | |
|     if (clip == null)
 | |
|       setClip(new Rectangle(x, y, width, height));
 | |
|     else if (clip instanceof Rectangle)
 | |
|       {
 | |
|         computeIntersection(x, y, width, height, (Rectangle) clip);
 | |
|         setClip(clip);
 | |
|       }
 | |
|     else
 | |
|       clip(new Rectangle(x, y, width, height));
 | |
|   }
 | |
| 
 | |
|   public Shape getClip()
 | |
|   {
 | |
|     if (clip == null)
 | |
|       return null;
 | |
|     else if (clip instanceof Rectangle2D)
 | |
|       return clip.getBounds2D(); //getClipInDevSpace();
 | |
|     else
 | |
|       {
 | |
|         GeneralPath p = new GeneralPath();
 | |
|         PathIterator pi = clip.getPathIterator(null);
 | |
|         p.append(pi, false);
 | |
|         return p;
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   public Rectangle getClipBounds()
 | |
|   {
 | |
|     if (clip == null)
 | |
|       return null;
 | |
|     else
 | |
|       return clip.getBounds();
 | |
|   }
 | |
| 
 | |
|   protected Rectangle2D getClipInDevSpace()
 | |
|   {
 | |
|     Rectangle2D uclip = clip.getBounds2D();
 | |
|     if (transform == null)
 | |
|       return uclip;
 | |
|     else
 | |
|       return getTransformedBounds(clip.getBounds2D(), transform);
 | |
|   }
 | |
| 
 | |
|   public void setClip(int x, int y, int width, int height)
 | |
|   {
 | |
|     if( width < 0 || height < 0 )
 | |
|       return;
 | |
| 
 | |
|     setClip(new Rectangle2D.Double(x, y, width, height));
 | |
|   }
 | |
| 
 | |
|   public void setClip(Shape s)
 | |
|   {
 | |
|     // The first time the clip is set, save it as the original clip
 | |
|     // to reset to on s == null. We can rely on this being non-null
 | |
|     // because the constructor in subclasses is expected to set the
 | |
|     // initial clip properly.
 | |
|     if( firstClip )
 | |
|       {
 | |
|         originalClip = s;
 | |
|         firstClip = false;
 | |
|       }
 | |
| 
 | |
|     clip = s;
 | |
|     cairoResetClip(nativePointer);
 | |
| 
 | |
|     if (clip != null)
 | |
|       {
 | |
|         cairoNewPath(nativePointer);
 | |
|         if (clip instanceof Rectangle2D)
 | |
|           {
 | |
|             Rectangle2D r = (Rectangle2D) clip;
 | |
|             cairoRectangle(nativePointer, r.getX(), r.getY(), r.getWidth(),
 | |
|                            r.getHeight());
 | |
|           }
 | |
|         else
 | |
|           walkPath(clip.getPathIterator(null), false);
 | |
| 
 | |
|         cairoClip(nativePointer);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   public void setBackground(Color c)
 | |
|   {
 | |
|     if (c == null)
 | |
|       c = Color.WHITE;
 | |
|     bg = c;
 | |
|   }
 | |
| 
 | |
|   public Color getBackground()
 | |
|   {
 | |
|     return bg;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Return the current composite.
 | |
|    */
 | |
|   public Composite getComposite()
 | |
|   {
 | |
|     if (comp == null)
 | |
|       return AlphaComposite.SrcOver;
 | |
|     else
 | |
|       return comp;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets the current composite context.
 | |
|    */
 | |
|   public void setComposite(Composite comp)
 | |
|   {
 | |
|     if (this.comp == comp)
 | |
|       return;
 | |
| 
 | |
|     this.comp = comp;
 | |
|     if (compCtx != null)
 | |
|       compCtx.dispose();
 | |
|     compCtx = null;
 | |
| 
 | |
|     if (comp instanceof AlphaComposite)
 | |
|       {
 | |
|         AlphaComposite a = (AlphaComposite) comp;
 | |
|         cairoSetOperator(nativePointer, a.getRule());
 | |
|       }
 | |
| 
 | |
|     else
 | |
|       {
 | |
|         cairoSetOperator(nativePointer, AlphaComposite.SRC_OVER);
 | |
| 
 | |
|         if (comp != null)
 | |
|           {
 | |
|             // FIXME: this check is only required "if this Graphics2D
 | |
|             // context is drawing to a Component on the display screen".
 | |
|             SecurityManager sm = System.getSecurityManager();
 | |
|             if (sm != null)
 | |
|               sm.checkPermission(new AWTPermission("readDisplayPixels"));
 | |
| 
 | |
|             compCtx = comp.createContext(getBufferCM(), getNativeCM(), hints);
 | |
|           }
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Returns the Colour Model describing the native, raw image data for this
 | |
|    * specific peer.
 | |
|    *
 | |
|    * @return ColorModel the ColorModel of native data in this peer
 | |
|    */
 | |
|   protected abstract ColorModel getNativeCM();
 | |
| 
 | |
|   /**
 | |
|    * Returns the Color Model describing the buffer that this peer uses
 | |
|    * for custom composites.
 | |
|    *
 | |
|    * @return ColorModel the ColorModel of the composite buffer in this peer.
 | |
|    */
 | |
|   protected ColorModel getBufferCM()
 | |
|   {
 | |
|     // This may be overridden by some subclasses
 | |
|     return getNativeCM();
 | |
|   }
 | |
| 
 | |
|   ///////////////////////// DRAWING PRIMITIVES ///////////////////////////////////
 | |
| 
 | |
|   public void draw(Shape s)
 | |
|   {
 | |
|     if ((stroke != null && ! (stroke instanceof BasicStroke))
 | |
|         || (comp instanceof AlphaComposite && ((AlphaComposite) comp).getAlpha() != 1.0))
 | |
|       {
 | |
|         // Cairo doesn't support stroking with alpha, so we create the stroked
 | |
|         // shape and fill with alpha instead
 | |
|         fill(stroke.createStrokedShape(s));
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|     if (customPaint)
 | |
|       {
 | |
|         Rectangle r = findStrokedBounds(s);
 | |
|         setCustomPaint(r);
 | |
|       }
 | |
| 
 | |
|     setAntialias(!hints.get(RenderingHints.KEY_ANTIALIASING)
 | |
|                        .equals(RenderingHints.VALUE_ANTIALIAS_OFF));
 | |
|     createPath(s, true);
 | |
|     cairoStroke(nativePointer);
 | |
|   }
 | |
| 
 | |
|   public void fill(Shape s)
 | |
|   {
 | |
|     createPath(s, false);
 | |
| 
 | |
|     if (customPaint)
 | |
|       setCustomPaint(s.getBounds());
 | |
| 
 | |
|     setAntialias(!hints.get(RenderingHints.KEY_ANTIALIASING)
 | |
|                        .equals(RenderingHints.VALUE_ANTIALIAS_OFF));
 | |
|     double alpha = 1.0;
 | |
|     if (comp instanceof AlphaComposite)
 | |
|       alpha = ((AlphaComposite) comp).getAlpha();
 | |
|     cairoFill(nativePointer, alpha);
 | |
|   }
 | |
| 
 | |
|   private void createPath(Shape s, boolean isDraw)
 | |
|   {
 | |
|     cairoNewPath(nativePointer);
 | |
| 
 | |
|     // Optimize rectangles, since there is a direct Cairo function
 | |
|     if (s instanceof Rectangle2D)
 | |
|       {
 | |
|         Rectangle2D r = (Rectangle2D) s;
 | |
| 
 | |
|         // Pixels need to be shifted in draw operations to ensure that they
 | |
|         // light up entire pixels, but we also need to make sure the rectangle
 | |
|         // does not get distorted by this shifting operation
 | |
|         double x = shiftX(r.getX(),shiftDrawCalls && isDraw);
 | |
|         double y = shiftY(r.getY(), shiftDrawCalls && isDraw);
 | |
|         double w = Math.round(r.getWidth());
 | |
|         double h = Math.round(r.getHeight());
 | |
|         cairoRectangle(nativePointer, x, y, w, h);
 | |
|       }
 | |
| 
 | |
|     // Lines are easy too
 | |
|     else if (s instanceof Line2D)
 | |
|       {
 | |
|         Line2D l = (Line2D) s;
 | |
|         cairoMoveTo(nativePointer, shiftX(l.getX1(), shiftDrawCalls && isDraw),
 | |
|                   shiftY(l.getY1(), shiftDrawCalls && isDraw));
 | |
|         cairoLineTo(nativePointer, shiftX(l.getX2(), shiftDrawCalls && isDraw),
 | |
|                   shiftY(l.getY2(), shiftDrawCalls && isDraw));
 | |
|       }
 | |
| 
 | |
|     // We can optimize ellipses too; however we don't bother optimizing arcs:
 | |
|     // the iterator is fast enough (an ellipse requires 5 steps using the
 | |
|     // iterator, while most arcs are only 2-3)
 | |
|     else if (s instanceof Ellipse2D)
 | |
|       {
 | |
|         Ellipse2D e = (Ellipse2D) s;
 | |
| 
 | |
|         double radius = Math.min(e.getHeight(), e.getWidth()) / 2;
 | |
| 
 | |
|         // Cairo only draws circular shapes, but we can use a stretch to make
 | |
|         // them into ellipses
 | |
|         double xscale = 1, yscale = 1;
 | |
|         if (e.getHeight() != e.getWidth())
 | |
|           {
 | |
|             cairoSave(nativePointer);
 | |
| 
 | |
|             if (e.getHeight() < e.getWidth())
 | |
|               xscale = e.getWidth() / (radius * 2);
 | |
|             else
 | |
|               yscale = e.getHeight() / (radius * 2);
 | |
| 
 | |
|             if (xscale != 1 || yscale != 1)
 | |
|               cairoScale(nativePointer, xscale, yscale);
 | |
|           }
 | |
| 
 | |
|         cairoArc(nativePointer,
 | |
|                  shiftX(e.getCenterX() / xscale, shiftDrawCalls && isDraw),
 | |
|                  shiftY(e.getCenterY() / yscale, shiftDrawCalls && isDraw),
 | |
|                  radius, 0, Math.PI * 2);
 | |
| 
 | |
|         if (xscale != 1 || yscale != 1)
 | |
|           cairoRestore(nativePointer);
 | |
|       }
 | |
| 
 | |
|     // All other shapes are broken down and drawn in steps using the
 | |
|     // PathIterator
 | |
|     else
 | |
|       walkPath(s.getPathIterator(null), shiftDrawCalls && isDraw);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Note that the rest of the drawing methods go via fill() or draw() for the drawing,
 | |
|    * although subclasses may with to overload these methods where context-specific
 | |
|    * optimizations are possible (e.g. bitmaps and fillRect(int, int, int, int)
 | |
|    */
 | |
| 
 | |
|   public void clearRect(int x, int y, int width, int height)
 | |
|   {
 | |
|     if (bg != null)
 | |
|       cairoSetRGBAColor(nativePointer, bg.getRed() / 255.0,
 | |
|                         bg.getGreen() / 255.0, bg.getBlue() / 255.0,
 | |
|                         bg.getAlpha() / 255.0);
 | |
| 
 | |
|     Composite oldcomp = comp;
 | |
|     setComposite(AlphaComposite.Src);
 | |
|     fillRect(x, y, width, height);
 | |
| 
 | |
|     setComposite(oldcomp);
 | |
|     updateColor();
 | |
|   }
 | |
| 
 | |
|   public void draw3DRect(int x, int y, int width, int height, boolean raised)
 | |
|   {
 | |
|     Stroke tmp = stroke;
 | |
|     setStroke(draw3DRectStroke);
 | |
|     super.draw3DRect(x, y, width, height, raised);
 | |
|     setStroke(tmp);
 | |
|   }
 | |
| 
 | |
|   public void drawArc(int x, int y, int width, int height, int startAngle,
 | |
|                       int arcAngle)
 | |
|   {
 | |
|     draw(new Arc2D.Double((double) x, (double) y, (double) width,
 | |
|                           (double) height, (double) startAngle,
 | |
|                           (double) arcAngle, Arc2D.OPEN));
 | |
|   }
 | |
| 
 | |
|   public void drawLine(int x1, int y1, int x2, int y2)
 | |
|   {
 | |
|     // The coordinates being pairwise identical means one wants
 | |
|     // to draw a single pixel. This is emulated by drawing
 | |
|     // a one pixel sized rectangle.
 | |
|     if (x1 == x2 && y1 == y2)
 | |
|       fill(new Rectangle(x1, y1, 1, 1));
 | |
|     else
 | |
|       draw(new Line2D.Double(x1, y1, x2, y2));
 | |
|   }
 | |
| 
 | |
|   public void drawRect(int x, int y, int width, int height)
 | |
|   {
 | |
|     draw(new Rectangle(x, y, width, height));
 | |
|   }
 | |
| 
 | |
|   public void fillArc(int x, int y, int width, int height, int startAngle,
 | |
|                       int arcAngle)
 | |
|   {
 | |
|     fill(new Arc2D.Double((double) x, (double) y, (double) width,
 | |
|                           (double) height, (double) startAngle,
 | |
|                           (double) arcAngle, Arc2D.PIE));
 | |
|   }
 | |
| 
 | |
|   public void fillRect(int x, int y, int width, int height)
 | |
|   {
 | |
|     fill (new Rectangle(x, y, width, height));
 | |
|   }
 | |
| 
 | |
|   public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
 | |
|   {
 | |
|     fill(new Polygon(xPoints, yPoints, nPoints));
 | |
|   }
 | |
| 
 | |
|   public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
 | |
|   {
 | |
|     draw(new Polygon(xPoints, yPoints, nPoints));
 | |
|   }
 | |
| 
 | |
|   public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
 | |
|   {
 | |
|     for (int i = 1; i < nPoints; i++)
 | |
|       draw(new Line2D.Double(xPoints[i - 1], yPoints[i - 1],
 | |
|                              xPoints[i], yPoints[i]));
 | |
|   }
 | |
| 
 | |
|   public void drawOval(int x, int y, int width, int height)
 | |
|   {
 | |
|     drawArc(x, y, width, height, 0, 360);
 | |
|   }
 | |
| 
 | |
|   public void drawRoundRect(int x, int y, int width, int height, int arcWidth,
 | |
|                             int arcHeight)
 | |
|   {
 | |
|     draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
 | |
|   }
 | |
| 
 | |
|   public void fillOval(int x, int y, int width, int height)
 | |
|   {
 | |
|     fillArc(x, y, width, height, 0, 360);
 | |
|   }
 | |
| 
 | |
|   public void fillRoundRect(int x, int y, int width, int height, int arcWidth,
 | |
|                             int arcHeight)
 | |
|   {
 | |
|     fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * CopyArea - performs clipping to the native surface as a convenience
 | |
|    * (requires getRealBounds). Then calls copyAreaImpl.
 | |
|    */
 | |
|   public void copyArea(int ox, int oy, int owidth, int oheight,
 | |
|                        int odx, int ody)
 | |
|   {
 | |
|     // FIXME: does this handle a rotation transform properly?
 | |
|     // (the width/height might not be correct)
 | |
|     Point2D pos = transform.transform(new Point2D.Double(ox, oy),
 | |
|                                       (Point2D) null);
 | |
|     Point2D dim = transform.transform(new Point2D.Double(ox + owidth,
 | |
|                                                          oy + oheight),
 | |
|                                       (Point2D) null);
 | |
|     Point2D p2 = transform.transform(new Point2D.Double(ox + odx, oy + ody),
 | |
|                                      (Point2D) null);
 | |
|     int x = (int)pos.getX();
 | |
|     int y = (int)pos.getY();
 | |
|     int width = (int)(dim.getX() - pos.getX());
 | |
|     int height = (int)(dim.getY() - pos.getY());
 | |
|     int dx = (int)(p2.getX() - pos.getX());
 | |
|     int dy = (int)(p2.getY() - pos.getY());
 | |
| 
 | |
|     Rectangle2D r = getRealBounds();
 | |
| 
 | |
|     if( width <= 0 || height <= 0 )
 | |
|       return;
 | |
|     // Return if outside the surface
 | |
|     if( x + dx > r.getWidth() || y + dy > r.getHeight() )
 | |
|       return;
 | |
| 
 | |
|     if( x + dx + width < r.getX() || y + dy + height < r.getY() )
 | |
|       return;
 | |
| 
 | |
|     // Clip edges if necessary
 | |
|     if( x + dx < r.getX() ) // left
 | |
|       {
 | |
|         width = x + dx + width;
 | |
|         x = (int)r.getX() - dx;
 | |
|       }
 | |
| 
 | |
|     if( y + dy < r.getY() ) // top
 | |
|       {
 | |
|         height = y + dy + height;
 | |
|         y = (int)r.getY() - dy;
 | |
|       }
 | |
| 
 | |
|     if( x + dx + width >= r.getWidth() ) // right
 | |
|       width = (int)r.getWidth() - dx - x;
 | |
| 
 | |
|     if( y + dy + height >= r.getHeight() ) // bottom
 | |
|       height = (int)r.getHeight() - dy - y;
 | |
| 
 | |
|     copyAreaImpl(x, y, width, height, dx, dy);
 | |
|   }
 | |
| 
 | |
|   ///////////////////////// RENDERING HINTS ///////////////////////////////////
 | |
| 
 | |
|   public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
 | |
|   {
 | |
|     hints.put(hintKey, hintValue);
 | |
| 
 | |
|     shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
 | |
|       || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
 | |
|   }
 | |
| 
 | |
|   public Object getRenderingHint(RenderingHints.Key hintKey)
 | |
|   {
 | |
|     return hints.get(hintKey);
 | |
|   }
 | |
| 
 | |
|   public void setRenderingHints(Map<?,?> hints)
 | |
|   {
 | |
|     this.hints = new RenderingHints(getDefaultHints());
 | |
|     this.hints.putAll(hints);
 | |
| 
 | |
|     shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
 | |
|       || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
 | |
| 
 | |
|     if (compCtx != null)
 | |
|       {
 | |
|         compCtx.dispose();
 | |
|         compCtx = comp.createContext(getNativeCM(), getNativeCM(), this.hints);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   public void addRenderingHints(Map hints)
 | |
|   {
 | |
|     this.hints.putAll(hints);
 | |
|   }
 | |
| 
 | |
|   public RenderingHints getRenderingHints()
 | |
|   {
 | |
|     return hints;
 | |
|   }
 | |
| 
 | |
|   private int getInterpolation()
 | |
|   {
 | |
|     if (this.hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
 | |
|       return INTERPOLATION_NEAREST;
 | |
| 
 | |
|     else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
 | |
|       return INTERPOLATION_BILINEAR;
 | |
| 
 | |
|     else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BICUBIC))
 | |
|       return INTERPOLATION_BICUBIC;
 | |
| 
 | |
|     else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
 | |
|       return ALPHA_INTERPOLATION_SPEED;
 | |
| 
 | |
|     else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
 | |
|       return ALPHA_INTERPOLATION_QUALITY;
 | |
| 
 | |
|     else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
 | |
|       return ALPHA_INTERPOLATION_DEFAULT;
 | |
| 
 | |
|     // Do bilinear interpolation as default
 | |
|     return INTERPOLATION_BILINEAR;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Set antialias if needed.  If the ignoreAA flag is set, this method will
 | |
|    * return without doing anything.
 | |
|    *
 | |
|    * @param needAA RenderingHints.VALUE_ANTIALIAS_ON or RenderingHints.VALUE_ANTIALIAS_OFF
 | |
|    */
 | |
|   private void setAntialias(boolean needAA)
 | |
|   {
 | |
|     if (ignoreAA)
 | |
|       return;
 | |
| 
 | |
|     if (needAA != antialias)
 | |
|       {
 | |
|         antialias = !antialias;
 | |
|         cairoSetAntialias(nativePointer, antialias);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   ///////////////////////// IMAGE. METHODS ///////////////////////////////////
 | |
| 
 | |
|   protected boolean drawImage(Image img, AffineTransform xform,
 | |
|                             Color bgcolor, ImageObserver obs)
 | |
|   {
 | |
|     if (img == null)
 | |
|       return false;
 | |
| 
 | |
|     if (xform == null)
 | |
|       xform = new AffineTransform();
 | |
| 
 | |
|     // In this case, xform is an AffineTransform that transforms bounding
 | |
|     // box of the specified image from image space to user space. However
 | |
|     // when we pass this transform to cairo, cairo will use this transform
 | |
|     // to map "user coordinates" to "pixel" coordinates, which is the
 | |
|     // other way around. Therefore to get the "user -> pixel" transform
 | |
|     // that cairo wants from "image -> user" transform that we currently
 | |
|     // have, we will need to invert the transformation matrix.
 | |
|     AffineTransform invertedXform;
 | |
| 
 | |
|     try
 | |
|       {
 | |
|         invertedXform = xform.createInverse();
 | |
|       }
 | |
|     catch (NoninvertibleTransformException e)
 | |
|       {
 | |
|         throw new ImagingOpException("Unable to invert transform "
 | |
|                                      + xform.toString());
 | |
|       }
 | |
| 
 | |
|     // Unrecognized image - convert to a BufferedImage
 | |
|     // Note - this can get us in trouble when the gdk lock is re-acquired.
 | |
|     // for example by VolatileImage. See ComponentGraphics for how we work
 | |
|     // around this.
 | |
|     img = AsyncImage.realImage(img, obs);
 | |
|     if( !(img instanceof BufferedImage) )
 | |
|       {
 | |
|         ImageProducer source = img.getSource();
 | |
|         if (source == null)
 | |
|           return false;
 | |
|         img = Toolkit.getDefaultToolkit().createImage(source);
 | |
|       }
 | |
| 
 | |
|     BufferedImage b = (BufferedImage) img;
 | |
|     Raster raster;
 | |
|     double[] i2u = new double[6];
 | |
|     int width = b.getWidth();
 | |
|     int height = b.getHeight();
 | |
| 
 | |
|     // If this BufferedImage has a BufferedImageGraphics object,
 | |
|     // use the cached CairoSurface that BIG is drawing onto
 | |
| 
 | |
|     if( BufferedImageGraphics.bufferedImages.get( b ) != null )
 | |
|       raster = BufferedImageGraphics.bufferedImages.get( b );
 | |
|     else
 | |
|       raster = b.getRaster();
 | |
| 
 | |
|     invertedXform.getMatrix(i2u);
 | |
| 
 | |
|     double alpha = 1.0;
 | |
|     if (comp instanceof AlphaComposite)
 | |
|       alpha = ((AlphaComposite) comp).getAlpha();
 | |
| 
 | |
|     if(raster instanceof CairoSurface
 | |
|         && ((CairoSurface)raster).sharedBuffer == true)
 | |
|       {
 | |
|         drawCairoSurface((CairoSurface)raster, xform, alpha, getInterpolation());
 | |
|         updateColor();
 | |
|         return true;
 | |
|       }
 | |
| 
 | |
|     if( bgcolor != null )
 | |
|       {
 | |
|         Color oldColor = bg;
 | |
|         setBackground(bgcolor);
 | |
| 
 | |
|         Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height);
 | |
|         bounds = getTransformedBounds(bounds, xform);
 | |
| 
 | |
|         clearRect((int)bounds.getX(), (int)bounds.getY(),
 | |
|                   (int)bounds.getWidth(), (int)bounds.getHeight());
 | |
| 
 | |
|         setBackground(oldColor);
 | |
|       }
 | |
| 
 | |
|     int[] pixels = b.getRGB(0, 0, width, height, null, 0, width);
 | |
|     // FIXME: The above method returns data in the standard ARGB colorspace,
 | |
|     // meaning data should NOT be alpha pre-multiplied; however Cairo expects
 | |
|     // data to be premultiplied.
 | |
| 
 | |
|     cairoSave(nativePointer);
 | |
|     Rectangle2D bounds = new Rectangle2D.Double(0, 0, width, height);
 | |
|     bounds = getTransformedBounds(bounds, xform);
 | |
|     cairoRectangle(nativePointer, bounds.getX(), bounds.getY(),
 | |
|                    bounds.getWidth(), bounds.getHeight());
 | |
|     cairoClip(nativePointer);
 | |
| 
 | |
|     drawPixels(nativePointer, pixels, width, height, width, i2u, alpha,
 | |
|                getInterpolation());
 | |
| 
 | |
|     cairoRestore(nativePointer);
 | |
| 
 | |
|     // Cairo seems to lose the current color which must be restored.
 | |
|     updateColor();
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   public void drawRenderedImage(RenderedImage image, AffineTransform xform)
 | |
|   {
 | |
|     drawRaster(image.getColorModel(), image.getData(), xform, null);
 | |
|   }
 | |
| 
 | |
|   public void drawRenderableImage(RenderableImage image, AffineTransform xform)
 | |
|   {
 | |
|     drawRenderedImage(image.createRendering(new RenderContext(xform)), xform);
 | |
|   }
 | |
| 
 | |
|   public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
 | |
|   {
 | |
|     return drawImage(img, xform, null, obs);
 | |
|   }
 | |
| 
 | |
|   public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y)
 | |
|   {
 | |
|     Image filtered = image;
 | |
|     if (op != null)
 | |
|       filtered = op.filter(image, null);
 | |
|     drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, null);
 | |
|   }
 | |
| 
 | |
|   public boolean drawImage(Image img, int x, int y, ImageObserver observer)
 | |
|   {
 | |
|     return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), null,
 | |
|                      observer);
 | |
|   }
 | |
| 
 | |
|   public boolean drawImage(Image img, int x, int y, Color bgcolor,
 | |
|                            ImageObserver observer)
 | |
|   {
 | |
|     return drawImage(img, x, y, img.getWidth(observer),
 | |
|                      img.getHeight(observer), bgcolor, observer);
 | |
|   }
 | |
| 
 | |
|   public boolean drawImage(Image img, int x, int y, int width, int height,
 | |
|                            Color bgcolor, ImageObserver observer)
 | |
|   {
 | |
|     double scaleX = width / (double) img.getWidth(observer);
 | |
|     double scaleY = height / (double) img.getHeight(observer);
 | |
|     if( scaleX == 0 || scaleY == 0 )
 | |
|       return true;
 | |
| 
 | |
|     return drawImage(img, new AffineTransform(scaleX, 0f, 0f, scaleY, x, y),
 | |
|                      bgcolor, observer);
 | |
|   }
 | |
| 
 | |
|   public boolean drawImage(Image img, int x, int y, int width, int height,
 | |
|                            ImageObserver observer)
 | |
|   {
 | |
|     return drawImage(img, x, y, width, height, null, observer);
 | |
|   }
 | |
| 
 | |
|   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
 | |
|                            int sx1, int sy1, int sx2, int sy2, Color bgcolor,
 | |
|                            ImageObserver observer)
 | |
|   {
 | |
|     if (img == null)
 | |
|       return false;
 | |
| 
 | |
|     int sourceWidth = sx2 - sx1;
 | |
|     int sourceHeight = sy2 - sy1;
 | |
| 
 | |
|     int destWidth = dx2 - dx1;
 | |
|     int destHeight = dy2 - dy1;
 | |
| 
 | |
|     if(destWidth == 0 || destHeight == 0 || sourceWidth == 0 ||
 | |
|        sourceHeight == 0)
 | |
|       return true;
 | |
| 
 | |
|     double scaleX = destWidth / (double) sourceWidth;
 | |
|     double scaleY = destHeight / (double) sourceHeight;
 | |
| 
 | |
|     // FIXME: Avoid using an AT if possible here - it's at least twice as slow.
 | |
| 
 | |
|     Shape oldClip = getClip();
 | |
|     int cx, cy, cw, ch;
 | |
|     if( dx1 < dx2 )
 | |
|       { cx = dx1; cw = dx2 - dx1; }
 | |
|     else
 | |
|       { cx = dx2; cw = dx1 - dx2; }
 | |
|     if( dy1 < dy2 )
 | |
|       { cy = dy1; ch = dy2 - dy1; }
 | |
|     else
 | |
|       { cy = dy2; ch = dy1 - dy2; }
 | |
| 
 | |
|     clipRect( cx, cy, cw, ch );
 | |
| 
 | |
|     AffineTransform tx = new AffineTransform();
 | |
|     tx.translate( dx1 - sx1*scaleX, dy1 - sy1*scaleY );
 | |
|     tx.scale( scaleX, scaleY );
 | |
| 
 | |
|     boolean retval = drawImage(img, tx, bgcolor, observer);
 | |
|     setClip( oldClip );
 | |
|     return retval;
 | |
|   }
 | |
| 
 | |
|   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
 | |
|                            int sx1, int sy1, int sx2, int sy2,
 | |
|                            ImageObserver observer)
 | |
|   {
 | |
|     return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Optimized method for drawing a CairoSurface onto this graphics context.
 | |
|    *
 | |
|    * @param surface The surface to draw.
 | |
|    * @param tx The transformation matrix (cannot be null).
 | |
|    * @param alpha The alpha value to paint with ( 0 <= alpha <= 1).
 | |
|    * @param interpolation The interpolation type.
 | |
|    */
 | |
|   protected void drawCairoSurface(CairoSurface surface, AffineTransform tx,
 | |
|                                   double alpha, int interpolation)
 | |
|   {
 | |
|     // Find offset required if this surface is a sub-raster, and append offset
 | |
|     // to transformation.
 | |
|     if (surface.getSampleModelTranslateX() != 0
 | |
|         || surface.getSampleModelTranslateY() != 0)
 | |
|       {
 | |
|         Point2D origin = new Point2D.Double(0, 0);
 | |
|         Point2D offset = new Point2D.Double(surface.getSampleModelTranslateX(),
 | |
|                                             surface.getSampleModelTranslateY());
 | |
| 
 | |
|         tx.transform(origin, origin);
 | |
|         tx.transform(offset, offset);
 | |
| 
 | |
|         tx.translate(offset.getX() - origin.getX(),
 | |
|                      offset.getY() - origin.getY());
 | |
|       }
 | |
| 
 | |
|     // Find dimensions of this surface relative to the root parent surface
 | |
|     Rectangle bounds = new Rectangle(-surface.getSampleModelTranslateX(),
 | |
|                                      -surface.getSampleModelTranslateY(),
 | |
|                                      surface.width, surface.height);
 | |
| 
 | |
|     // Clip to the translated image
 | |
|     //   We use direct cairo methods to avoid the overhead of maintaining a
 | |
|     //   java copy of the clip, since we will be reverting it immediately
 | |
|     //   after drawing
 | |
|     Shape newBounds = tx.createTransformedShape(bounds);
 | |
|     cairoSave(nativePointer);
 | |
|     walkPath(newBounds.getPathIterator(null), false);
 | |
|     cairoClip(nativePointer);
 | |
| 
 | |
|     // Draw the surface
 | |
|     try
 | |
|     {
 | |
|       double[] i2u = new double[6];
 | |
|       tx.createInverse().getMatrix(i2u);
 | |
|       surface.nativeDrawSurface(surface.surfacePointer, nativePointer, i2u,
 | |
|                                 alpha, interpolation);
 | |
|     }
 | |
|     catch (NoninvertibleTransformException ex)
 | |
|     {
 | |
|       // This should never happen(?), so we don't need to do anything here.
 | |
|       ;
 | |
|     }
 | |
| 
 | |
|     // Restore clip
 | |
|     cairoRestore(nativePointer);
 | |
|   }
 | |
| 
 | |
| 
 | |
|   ///////////////////////// TEXT METHODS ////////////////////////////////////
 | |
| 
 | |
|   public void drawString(String str, float x, float y)
 | |
|   {
 | |
|     if (str == null || str.length() == 0)
 | |
|       return;
 | |
|     GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer();
 | |
|     TextLayout tl = (TextLayout) fontPeer.textLayoutCache.get(str);
 | |
|     if (tl == null)
 | |
|       {
 | |
|         tl = new TextLayout( str, getFont(), getFontRenderContext() );
 | |
|         fontPeer.textLayoutCache.put(str, tl);
 | |
|       }
 | |
| 
 | |
|     // Set antialias to text_antialiasing, and set the ignoreAA flag so that
 | |
|     // the setting doesn't get overridden in a draw() or fill() call.
 | |
|     setAntialias(!hints.get(RenderingHints.KEY_TEXT_ANTIALIASING)
 | |
|                        .equals(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
 | |
|     ignoreAA = true;
 | |
| 
 | |
|     tl.draw(this, x, y);
 | |
|     ignoreAA = false;
 | |
|   }
 | |
| 
 | |
|   public void drawString(String str, int x, int y)
 | |
|   {
 | |
|     drawString (str, (float) x, (float) y);
 | |
|   }
 | |
| 
 | |
|   public void drawString(AttributedCharacterIterator ci, int x, int y)
 | |
|   {
 | |
|     drawString (ci, (float) x, (float) y);
 | |
|   }
 | |
| 
 | |
|   public void drawGlyphVector(GlyphVector gv, float x, float y)
 | |
|   {
 | |
|     double alpha = 1.0;
 | |
| 
 | |
|     if( gv.getNumGlyphs() <= 0 )
 | |
|       return;
 | |
| 
 | |
|     if (customPaint)
 | |
|       setCustomPaint(gv.getOutline().getBounds());
 | |
| 
 | |
|     if (comp instanceof AlphaComposite)
 | |
|       alpha = ((AlphaComposite) comp).getAlpha();
 | |
| 
 | |
|     setAntialias(!hints.get(RenderingHints.KEY_TEXT_ANTIALIASING)
 | |
|                        .equals(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF));
 | |
|     ignoreAA = true;
 | |
| 
 | |
|     if (gv instanceof FreetypeGlyphVector && alpha == 1.0
 | |
|         && !((FreetypeGlyphVector)gv).hasTransforms())
 | |
|       {
 | |
|         int n = gv.getNumGlyphs ();
 | |
|         int[] codes = gv.getGlyphCodes (0, n, null);
 | |
|         long[] fontset = ((FreetypeGlyphVector)gv).getGlyphFonts (0, n, null);
 | |
|         float[] positions = gv.getGlyphPositions (0, n, null);
 | |
| 
 | |
|         setFont (gv.getFont ());
 | |
|         GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer();
 | |
|         synchronized (fontPeer)
 | |
|           {
 | |
|             cairoDrawGlyphVector(nativePointer, fontPeer,
 | |
|                                  x, y, n, codes, positions, fontset);
 | |
|           }
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         translate(x, y);
 | |
|         fill(gv.getOutline());
 | |
|         translate(-x, -y);
 | |
|       }
 | |
| 
 | |
|     ignoreAA = false;
 | |
|   }
 | |
| 
 | |
|   public void drawString(AttributedCharacterIterator ci, float x, float y)
 | |
|   {
 | |
|     GlyphVector gv = getFont().createGlyphVector(getFontRenderContext(), ci);
 | |
|     drawGlyphVector(gv, x, y);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Should perhaps be contexct dependent, but this is left for now as an
 | |
|    * overloadable default implementation.
 | |
|    */
 | |
|   public FontRenderContext getFontRenderContext()
 | |
|   {
 | |
|     return new FontRenderContext(transform, true, true);
 | |
|   }
 | |
| 
 | |
|   // Until such time as pango is happy to talk directly to cairo, we
 | |
|   // actually need to redirect some calls from the GtkFontPeer and
 | |
|   // GtkFontMetrics into the drawing kit and ask cairo ourselves.
 | |
| 
 | |
|   public FontMetrics getFontMetrics()
 | |
|   {
 | |
|     return getFontMetrics(getFont());
 | |
|   }
 | |
| 
 | |
|   public FontMetrics getFontMetrics(Font f)
 | |
|   {
 | |
|     return ((GdkFontPeer) f.getPeer()).getFontMetrics(f);
 | |
|   }
 | |
| 
 | |
|   public void setFont(Font f)
 | |
|   {
 | |
|     // Sun's JDK does not throw NPEs, instead it leaves the current setting
 | |
|     // unchanged. So do we.
 | |
|     if (f == null)
 | |
|       return;
 | |
| 
 | |
|     if (f.getPeer() instanceof GdkFontPeer)
 | |
|       font = f;
 | |
|     else
 | |
|       font =
 | |
|         ((ClasspathToolkit)(Toolkit.getDefaultToolkit()))
 | |
|         .getFont(f.getName(), f.getAttributes());
 | |
| 
 | |
|     GdkFontPeer fontpeer = (GdkFontPeer) getFont().getPeer();
 | |
|     synchronized (fontpeer)
 | |
|       {
 | |
|         cairoSetFont(nativePointer, fontpeer);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   public Font getFont()
 | |
|   {
 | |
|     if (font == null)
 | |
|       return new Font("SansSerif", Font.PLAIN, 12);
 | |
|     return font;
 | |
|   }
 | |
| 
 | |
|   /////////////////////// MISC. PUBLIC METHODS /////////////////////////////////
 | |
| 
 | |
|   public boolean hit(Rectangle rect, Shape s, boolean onStroke)
 | |
|   {
 | |
|     if( onStroke )
 | |
|       {
 | |
|         Shape stroked = stroke.createStrokedShape( s );
 | |
|         return stroked.intersects( (double)rect.x, (double)rect.y,
 | |
|                                    (double)rect.width, (double)rect.height );
 | |
|       }
 | |
|     return s.intersects( (double)rect.x, (double)rect.y,
 | |
|                          (double)rect.width, (double)rect.height );
 | |
|   }
 | |
| 
 | |
|   public String toString()
 | |
|   {
 | |
|     return  (getClass().getName()
 | |
|              + "[font=" + getFont().toString()
 | |
|              + ",color=" + fg.toString()
 | |
|              + "]");
 | |
|   }
 | |
| 
 | |
|   ///////////////////////// PRIVATE METHODS ///////////////////////////////////
 | |
| 
 | |
|   /**
 | |
|    * All the drawImage() methods eventually get delegated here if the image
 | |
|    * is not a Cairo surface.
 | |
|    *
 | |
|    * @param bgcolor - if non-null draws the background color before
 | |
|    * drawing the image.
 | |
|    */
 | |
|   private boolean drawRaster(ColorModel cm, Raster r,
 | |
|                              AffineTransform imageToUser, Color bgcolor)
 | |
|   {
 | |
|     if (r == null)
 | |
|       return false;
 | |
| 
 | |
|     SampleModel sm = r.getSampleModel();
 | |
|     DataBuffer db = r.getDataBuffer();
 | |
| 
 | |
|     if (db == null || sm == null)
 | |
|       return false;
 | |
| 
 | |
|     if (cm == null)
 | |
|       cm = ColorModel.getRGBdefault();
 | |
| 
 | |
|     double[] i2u = new double[6];
 | |
|     if (imageToUser != null)
 | |
|       imageToUser.getMatrix(i2u);
 | |
|     else
 | |
|       {
 | |
|         i2u[0] = 1;
 | |
|         i2u[1] = 0;
 | |
|         i2u[2] = 0;
 | |
|         i2u[3] = 1;
 | |
|         i2u[4] = 0;
 | |
|         i2u[5] = 0;
 | |
|       }
 | |
| 
 | |
|     int[] pixels = findSimpleIntegerArray(cm, r);
 | |
| 
 | |
|     if (pixels == null)
 | |
|       {
 | |
|         // FIXME: I don't think this code will work correctly with a non-RGB
 | |
|         // MultiPixelPackedSampleModel. Although this entire method should
 | |
|         // probably be rewritten to better utilize Cairo's different supported
 | |
|         // data formats.
 | |
|         if (sm instanceof MultiPixelPackedSampleModel)
 | |
|           {
 | |
|             pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels);
 | |
|             for (int i = 0; i < pixels.length; i++)
 | |
|               pixels[i] = cm.getRGB(pixels[i]);
 | |
|           }
 | |
|         else
 | |
|           {
 | |
|             pixels = new int[r.getWidth() * r.getHeight()];
 | |
|             for (int i = 0; i < pixels.length; i++)
 | |
|               pixels[i] = cm.getRGB(db.getElem(i));
 | |
|           }
 | |
|       }
 | |
| 
 | |
|     // Change all transparent pixels in the image to the specified bgcolor,
 | |
|     // or (if there's no alpha) fill in an alpha channel so that it paints
 | |
|     // correctly.
 | |
|     if (cm.hasAlpha())
 | |
|       {
 | |
|         if (bgcolor != null && cm.hasAlpha())
 | |
|           for (int i = 0; i < pixels.length; i++)
 | |
|             {
 | |
|               if (cm.getAlpha(pixels[i]) == 0)
 | |
|                 pixels[i] = bgcolor.getRGB();
 | |
|             }
 | |
|       }
 | |
|     else
 | |
|       for (int i = 0; i < pixels.length; i++)
 | |
|         pixels[i] |= 0xFF000000;
 | |
| 
 | |
|     double alpha = 1.0;
 | |
|     if (comp instanceof AlphaComposite)
 | |
|       alpha = ((AlphaComposite) comp).getAlpha();
 | |
| 
 | |
|     drawPixels(nativePointer, pixels, r.getWidth(), r.getHeight(),
 | |
|                r.getWidth(), i2u, alpha, getInterpolation());
 | |
| 
 | |
|     // Cairo seems to lose the current color which must be restored.
 | |
|     updateColor();
 | |
| 
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Shifts an x-coordinate by 0.5 in device space.
 | |
|    */
 | |
|   private double shiftX(double coord, boolean doShift)
 | |
|   {
 | |
|     if (doShift)
 | |
|       {
 | |
|         double shift = 0.5;
 | |
|         if (!transform.isIdentity())
 | |
|           shift /= transform.getScaleX();
 | |
|         return (coord + shift);
 | |
|       }
 | |
|     else
 | |
|       return coord;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Shifts a y-coordinate by 0.5 in device space.
 | |
|    */
 | |
|   private double shiftY(double coord, boolean doShift)
 | |
|   {
 | |
|     if (doShift)
 | |
|       {
 | |
|         double shift = 0.5;
 | |
|         if (!transform.isIdentity())
 | |
|           shift /= transform.getScaleY();
 | |
|         return (coord + shift);
 | |
|       }
 | |
|     else
 | |
|       return coord;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Adds a pathIterator to the current Cairo path, also sets the cairo winding rule.
 | |
|    */
 | |
|   private void walkPath(PathIterator p, boolean doShift)
 | |
|   {
 | |
|     double x = 0;
 | |
|     double y = 0;
 | |
|     double[] coords = new double[6];
 | |
| 
 | |
|     cairoSetFillRule(nativePointer, p.getWindingRule());
 | |
|     for (; ! p.isDone(); p.next())
 | |
|       {
 | |
|         int seg = p.currentSegment(coords);
 | |
|         switch (seg)
 | |
|           {
 | |
|           case PathIterator.SEG_MOVETO:
 | |
|             x = shiftX(coords[0], doShift);
 | |
|             y = shiftY(coords[1], doShift);
 | |
|             cairoMoveTo(nativePointer, x, y);
 | |
|             break;
 | |
|           case PathIterator.SEG_LINETO:
 | |
|             x = shiftX(coords[0], doShift);
 | |
|             y = shiftY(coords[1], doShift);
 | |
|             cairoLineTo(nativePointer, x, y);
 | |
|             break;
 | |
|           case PathIterator.SEG_QUADTO:
 | |
|             // splitting a quadratic bezier into a cubic:
 | |
|             // see: http://pfaedit.sourceforge.net/bezier.html
 | |
|             double x1 = x + (2.0 / 3.0) * (shiftX(coords[0], doShift) - x);
 | |
|             double y1 = y + (2.0 / 3.0) * (shiftY(coords[1], doShift) - y);
 | |
| 
 | |
|             double x2 = x1 + (1.0 / 3.0) * (shiftX(coords[2], doShift) - x);
 | |
|             double y2 = y1 + (1.0 / 3.0) * (shiftY(coords[3], doShift) - y);
 | |
| 
 | |
|             x = shiftX(coords[2], doShift);
 | |
|             y = shiftY(coords[3], doShift);
 | |
|             cairoCurveTo(nativePointer, x1, y1, x2, y2, x, y);
 | |
|             break;
 | |
|           case PathIterator.SEG_CUBICTO:
 | |
|             x = shiftX(coords[4], doShift);
 | |
|             y = shiftY(coords[5], doShift);
 | |
|             cairoCurveTo(nativePointer, shiftX(coords[0], doShift),
 | |
|                          shiftY(coords[1], doShift),
 | |
|                          shiftX(coords[2], doShift),
 | |
|                          shiftY(coords[3], doShift), x, y);
 | |
|             break;
 | |
|           case PathIterator.SEG_CLOSE:
 | |
|             cairoClosePath(nativePointer);
 | |
|             break;
 | |
|           }
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Used by setRenderingHints()
 | |
|    */
 | |
|   private Map<RenderingHints.Key, Object> getDefaultHints()
 | |
|   {
 | |
|     HashMap<RenderingHints.Key, Object> defaultHints =
 | |
|       new HashMap<RenderingHints.Key, Object>();
 | |
| 
 | |
|     defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
 | |
|                      RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
 | |
| 
 | |
|     defaultHints.put(RenderingHints.KEY_STROKE_CONTROL,
 | |
|                      RenderingHints.VALUE_STROKE_DEFAULT);
 | |
| 
 | |
|     defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS,
 | |
|                      RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
 | |
| 
 | |
|     defaultHints.put(RenderingHints.KEY_ANTIALIASING,
 | |
|                      RenderingHints.VALUE_ANTIALIAS_OFF);
 | |
| 
 | |
|     defaultHints.put(RenderingHints.KEY_RENDERING,
 | |
|                      RenderingHints.VALUE_RENDER_DEFAULT);
 | |
| 
 | |
|     return defaultHints;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Used by drawRaster and GdkPixbufDecoder
 | |
|    */
 | |
|   public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster)
 | |
|   {
 | |
|     if (cm == null || raster == null)
 | |
|       return null;
 | |
| 
 | |
|     if (! cm.getColorSpace().isCS_sRGB())
 | |
|       return null;
 | |
| 
 | |
|     if (! (cm instanceof DirectColorModel))
 | |
|       return null;
 | |
| 
 | |
|     DirectColorModel dcm = (DirectColorModel) cm;
 | |
| 
 | |
|     if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00
 | |
|         || dcm.getBlueMask() != 0x000000FF)
 | |
|       return null;
 | |
| 
 | |
|     if (! (raster instanceof WritableRaster))
 | |
|       return null;
 | |
| 
 | |
|     if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT)
 | |
|       return null;
 | |
| 
 | |
|     if (! (raster.getDataBuffer() instanceof DataBufferInt))
 | |
|       return null;
 | |
| 
 | |
|     DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
 | |
| 
 | |
|     if (db.getNumBanks() != 1)
 | |
|       return null;
 | |
| 
 | |
|     // Finally, we have determined that this is a single bank, [A]RGB-int
 | |
|     // buffer in sRGB space. It's worth checking all this, because it means
 | |
|     // that cairo can paint directly into the data buffer, which is very
 | |
|     // fast compared to all the normal copying and converting.
 | |
| 
 | |
|     return db.getData();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Helper method to transform the clip. This is called by the various
 | |
|    * transformation-manipulation methods to update the clip (which is in
 | |
|    * userspace) accordingly.
 | |
|    *
 | |
|    * The transform usually is the inverse transform that was applied to the
 | |
|    * graphics object.
 | |
|    *
 | |
|    * @param t the transform to apply to the clip
 | |
|    */
 | |
|   private void updateClip(AffineTransform t)
 | |
|   {
 | |
|     if (clip == null)
 | |
|       return;
 | |
| 
 | |
|     // If the clip is a rectangle, and the transformation preserves the shape
 | |
|     // (translate/stretch only), then keep the clip as a rectangle
 | |
|     double[] matrix = new double[4];
 | |
|     t.getMatrix(matrix);
 | |
|     if (clip instanceof Rectangle2D && matrix[1] == 0 && matrix[2] == 0)
 | |
|       {
 | |
|         Rectangle2D rect = (Rectangle2D)clip;
 | |
|         double[] origin = new double[] {rect.getX(), rect.getY()};
 | |
|         double[] dimensions = new double[] {rect.getWidth(), rect.getHeight()};
 | |
|         t.transform(origin, 0, origin, 0, 1);
 | |
|         t.deltaTransform(dimensions, 0, dimensions, 0, 1);
 | |
|         rect.setRect(origin[0], origin[1], dimensions[0], dimensions[1]);
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         if (! (clip instanceof GeneralPath))
 | |
|           clip = new GeneralPath(clip);
 | |
| 
 | |
|         GeneralPath p = (GeneralPath) clip;
 | |
|         p.transform(t);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   private static Rectangle computeIntersection(int x, int y, int w, int h,
 | |
|                                                Rectangle rect)
 | |
|   {
 | |
|     int x2 = rect.x;
 | |
|     int y2 = rect.y;
 | |
|     int w2 = rect.width;
 | |
|     int h2 = rect.height;
 | |
| 
 | |
|     int dx = (x > x2) ? x : x2;
 | |
|     int dy = (y > y2) ? y : y2;
 | |
|     int dw = (x + w < x2 + w2) ? (x + w - dx) : (x2 + w2 - dx);
 | |
|     int dh = (y + h < y2 + h2) ? (y + h - dy) : (y2 + h2 - dy);
 | |
| 
 | |
|     if (dw >= 0 && dh >= 0)
 | |
|       rect.setBounds(dx, dy, dw, dh);
 | |
|     else
 | |
|       rect.setBounds(0, 0, 0, 0);
 | |
| 
 | |
|     return rect;
 | |
|   }
 | |
| 
 | |
|   static Rectangle2D getTransformedBounds(Rectangle2D bounds, AffineTransform tx)
 | |
|   {
 | |
|     double x1 = bounds.getX();
 | |
|     double x2 = bounds.getX() + bounds.getWidth();
 | |
|     double x3 = x1;
 | |
|     double x4 = x2;
 | |
|     double y1 = bounds.getY();
 | |
|     double y2 = y1;
 | |
|     double y3 = bounds.getY() + bounds.getHeight();
 | |
|     double y4 = y3;
 | |
| 
 | |
|     double[] points = new double[] {x1, y1, x2, y2, x3, y3, x4, y4};
 | |
|     tx.transform(points, 0, points, 0, 4);
 | |
| 
 | |
|     double minX = points[0];
 | |
|     double maxX = minX;
 | |
|     double minY = points[1];
 | |
|     double maxY = minY;
 | |
|     for (int i = 0; i < 8; i++)
 | |
|       {
 | |
|         if (points[i] < minX)
 | |
|           minX = points[i];
 | |
|         if (points[i] > maxX)
 | |
|           maxX = points[i];
 | |
|         i++;
 | |
| 
 | |
|         if (points[i] < minY)
 | |
|           minY = points[i];
 | |
|         if (points[i] > maxY)
 | |
|           maxY = points[i];
 | |
|       }
 | |
| 
 | |
|     return new Rectangle2D.Double(minX, minY, (maxX - minX), (maxY - minY));
 | |
|   }
 | |
| }
 |