eps output facility
authoramwaterhouse <Andrew Waterhouse>
Fri, 18 Mar 2005 16:14:47 +0000 (16:14 +0000)
committeramwaterhouse <Andrew Waterhouse>
Fri, 18 Mar 2005 16:14:47 +0000 (16:14 +0000)
src/org/jibble/epsgraphics/EpsDocument.java [new file with mode: 0755]
src/org/jibble/epsgraphics/EpsException.java [new file with mode: 0755]
src/org/jibble/epsgraphics/EpsGraphics2D.java [new file with mode: 0755]

diff --git a/src/org/jibble/epsgraphics/EpsDocument.java b/src/org/jibble/epsgraphics/EpsDocument.java
new file mode 100755 (executable)
index 0000000..cae9835
--- /dev/null
@@ -0,0 +1,235 @@
+/* \r
+Copyright Paul James Mutton, 2001-2004, http://www.jibble.org/\r
+\r
+This file is part of EpsGraphics2D.\r
+\r
+This software is dual-licensed, allowing you to choose between the GNU\r
+General Public License (GPL) and the www.jibble.org Commercial License.\r
+Since the GPL may be too restrictive for use in a proprietary application,\r
+a commercial license is also provided. Full license information can be\r
+found at http://www.jibble.org/licenses/\r
+\r
+$Author$\r
+$Id$\r
+\r
+*/\r
+\r
+package org.jibble.epsgraphics;\r
+\r
+import java.util.*;\r
+import java.io.*;\r
+\r
+\r
+/**\r
+ * This represents an EPS document. Several EpsGraphics2D objects may point\r
+ * to the same EpsDocument.\r
+ *  <p>\r
+ * Copyright Paul Mutton,\r
+ *           <a href="http://www.jibble.org/">http://www.jibble.org/</a>\r
+ * \r
+ */\r
+public class EpsDocument {\r
+    \r
+    \r
+    /**\r
+     * Constructs an empty EpsDevice.\r
+     */\r
+    public EpsDocument(String title) {\r
+        _title = title;\r
+        minX = Float.POSITIVE_INFINITY;\r
+        minY = Float.POSITIVE_INFINITY;\r
+        maxX = Float.NEGATIVE_INFINITY;\r
+        maxY = Float.NEGATIVE_INFINITY;\r
+        _stringWriter = new StringWriter();\r
+        _bufferedWriter = new BufferedWriter(_stringWriter);\r
+    }\r
+    \r
+    /**\r
+     * Constructs an empty EpsDevice that writes directly to a file.\r
+     * Bounds must be set before use.\r
+     */\r
+    public EpsDocument(String title, OutputStream outputStream, int minX, int minY, int maxX, int maxY) throws IOException {\r
+        _title = title;\r
+        this.minX = minX;\r
+        this.minY = minY;\r
+        this.maxX = maxX;\r
+        this.maxY = maxY;\r
+        _bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));\r
+        write(_bufferedWriter);\r
+    }\r
+    \r
+    \r
+    /**\r
+     * Returns the title of the EPS document.\r
+     */\r
+    public synchronized String getTitle() {\r
+        return _title;\r
+    }\r
+    \r
+    \r
+    /**\r
+     * Updates the bounds of the current EPS document.\r
+     */\r
+    public synchronized void updateBounds(double x, double y) {\r
+        if (x > maxX) {\r
+            maxX = (float) x;\r
+        }\r
+        if (x < minX) {\r
+            minX = (float) x;\r
+        }\r
+        if (y > maxY) {\r
+            maxY = (float) y;\r
+        }\r
+        if (y < minY) {\r
+            minY = (float) y;\r
+        }\r
+    }\r
+    \r
+    \r
+    /**\r
+     * Appends a line to the EpsDocument.  A new line character is added\r
+     * to the end of the line when it is added.\r
+     */\r
+    public synchronized void append(EpsGraphics2D g, String line) {\r
+        if (_lastG == null) {\r
+            _lastG = g;\r
+        }\r
+        else if (g != _lastG) {\r
+            EpsGraphics2D lastG = _lastG;\r
+            _lastG = g;\r
+            // We are being drawn on with a different EpsGraphics2D context.\r
+            // We may need to update the clip, etc from this new context.\r
+            if (g.getClip() != lastG.getClip()) {\r
+                g.setClip(g.getClip());\r
+            }\r
+            if (!g.getColor().equals(lastG.getColor())) {\r
+                g.setColor(g.getColor());\r
+            }\r
+            if (!g.getBackground().equals(lastG.getBackground())) {\r
+                g.setBackground(g.getBackground());\r
+            }\r
+            // We don't need this, as this only affects the stroke and font,\r
+            // which are dealt with separately later on.\r
+            //if (!g.getTransform().equals(lastG.getTransform())) {\r
+            //    g.setTransform(g.getTransform());\r
+            //}\r
+            if (!g.getPaint().equals(lastG.getPaint())) {\r
+                g.setPaint(g.getPaint());\r
+            }\r
+            if (!g.getComposite().equals(lastG.getComposite())) {\r
+                g.setComposite(g.getComposite());\r
+            }\r
+            if (!g.getComposite().equals(lastG.getComposite())) {\r
+                g.setComposite(g.getComposite());\r
+            }\r
+            if (!g.getFont().equals(lastG.getFont())) {\r
+                g.setFont(g.getFont());\r
+            }\r
+            if (!g.getStroke().equals(lastG.getStroke())) {\r
+                g.setStroke(g.getStroke());\r
+            }\r
+        }\r
+        _lastG = g;\r
+        \r
+        try {\r
+            _bufferedWriter.write(line + "\n");\r
+        }\r
+        catch (IOException e) {\r
+            throw new EpsException("Could not write to the output file: " + e);\r
+        }\r
+    }\r
+    \r
+    \r
+    /**\r
+     * Outputs the contents of the EPS document to the specified\r
+     * Writer, complete with headers and bounding box.\r
+     */\r
+    public synchronized void write(Writer writer) throws IOException {\r
+        float offsetX = -minX;\r
+        float offsetY = -minY;\r
+        \r
+        writer.write("%!PS-Adobe-3.0 EPSF-3.0\n");\r
+        writer.write("%%Creator: EpsGraphics2D " + EpsGraphics2D.VERSION + " by Paul Mutton, http://www.jibble.org/\n");\r
+        writer.write("%%Title: " + _title + "\n");\r
+        writer.write("%%CreationDate: " + new Date() + "\n");\r
+        writer.write("%%BoundingBox: 0 0 " + ((int) Math.ceil(maxX + offsetX)) + " " + ((int) Math.ceil(maxY + offsetY)) + "\n");\r
+        writer.write("%%DocumentData: Clean7Bit\n");\r
+        writer.write("%%DocumentProcessColors: Black\n");\r
+        writer.write("%%ColorUsage: Color\n");\r
+        writer.write("%%Origin: 0 0\n");\r
+        writer.write("%%Pages: 1\n");\r
+        writer.write("%%Page: 1 1\n");\r
+        writer.write("%%EndComments\n\n");\r
+        \r
+        writer.write("gsave\n");\r
+        \r
+        if (_stringWriter != null) {\r
+            writer.write(offsetX + " " + (offsetY) + " translate\n");\r
+            \r
+            _bufferedWriter.flush();\r
+            StringBuffer buffer = _stringWriter.getBuffer();\r
+            for (int i = 0; i < buffer.length(); i++) {\r
+                writer.write(buffer.charAt(i));\r
+            }\r
+            \r
+            writeFooter(writer);\r
+        }\r
+        else {\r
+            writer.write(offsetX + " " + ((maxY - minY) - offsetY) + " translate\n");\r
+        }\r
+        \r
+        writer.flush();\r
+    }\r
+    \r
+    \r
+    private void writeFooter(Writer writer) throws IOException {\r
+        writer.write("grestore\n");\r
+        if (isClipSet()) {\r
+            writer.write("grestore\n");\r
+        }\r
+        writer.write("showpage\n");\r
+        writer.write("\n");\r
+        writer.write("%%EOF");\r
+        writer.flush();\r
+    }\r
+    \r
+    \r
+    public synchronized void flush() throws IOException {\r
+        _bufferedWriter.flush();\r
+    }\r
+    \r
+    public synchronized void close() throws IOException {\r
+        if (_stringWriter == null) {\r
+            writeFooter(_bufferedWriter);\r
+            _bufferedWriter.flush();\r
+            _bufferedWriter.close();\r
+        }\r
+    }\r
+    \r
+    \r
+    public boolean isClipSet() {\r
+        return _isClipSet;\r
+    }\r
+    \r
+    public void setClipSet(boolean isClipSet) {\r
+        _isClipSet = isClipSet;\r
+    }\r
+    \r
+    \r
+    private float minX;\r
+    private float minY;\r
+    private float maxX;\r
+    private float maxY;\r
+    \r
+    private boolean _isClipSet = false;\r
+\r
+    private String _title;\r
+    private StringWriter _stringWriter;\r
+    private BufferedWriter _bufferedWriter = null;\r
+    \r
+    // We need to remember which was the last EpsGraphics2D object to use\r
+    // us, as we need to replace the clipping region if another EpsGraphics2D\r
+    // object tries to use us.\r
+    private EpsGraphics2D _lastG = null;\r
+    \r
+}
\ No newline at end of file
diff --git a/src/org/jibble/epsgraphics/EpsException.java b/src/org/jibble/epsgraphics/EpsException.java
new file mode 100755 (executable)
index 0000000..3f0611f
--- /dev/null
@@ -0,0 +1,25 @@
+/* \r
+Copyright Paul James Mutton, 2001-2004, http://www.jibble.org/\r
+\r
+This file is part of EpsGraphics2D.\r
+\r
+This software is dual-licensed, allowing you to choose between the GNU\r
+General Public License (GPL) and the www.jibble.org Commercial License.\r
+Since the GPL may be too restrictive for use in a proprietary application,\r
+a commercial license is also provided. Full license information can be\r
+found at http://www.jibble.org/licenses/\r
+\r
+$Author$\r
+$Id$\r
+\r
+*/\r
+\r
+package org.jibble.epsgraphics;\r
+\r
+public class EpsException extends RuntimeException {\r
+    \r
+    public EpsException(String message) {\r
+        super(message);\r
+    }\r
+    \r
+}
\ No newline at end of file
diff --git a/src/org/jibble/epsgraphics/EpsGraphics2D.java b/src/org/jibble/epsgraphics/EpsGraphics2D.java
new file mode 100755 (executable)
index 0000000..25ad886
--- /dev/null
@@ -0,0 +1,1344 @@
+/*\r
+Copyright Paul James Mutton, 2001-2004, http://www.jibble.org/\r
+\r
+This file is part of EpsGraphics2D.\r
+\r
+This software is dual-licensed, allowing you to choose between the GNU\r
+General Public License (GPL) and the www.jibble.org Commercial License.\r
+Since the GPL may be too restrictive for use in a proprietary application,\r
+a commercial license is also provided. Full license information can be\r
+found at http://www.jibble.org/licenses/\r
+\r
+$Author$\r
+$Id$\r
+\r
+*/\r
+\r
+package org.jibble.epsgraphics;\r
+\r
+import java.awt.*;\r
+import java.awt.image.*;\r
+import java.awt.image.renderable.*;\r
+import java.awt.geom.*;\r
+import java.io.*;\r
+import java.text.*;\r
+import java.awt.font.*;\r
+import java.util.*;\r
+\r
+\r
+/**\r
+ * EpsGraphics2D is suitable for creating high quality EPS graphics for\r
+ * use in documents and papers, and can be used just like a standard\r
+ * Graphics2D object.\r
+ *  <p>\r
+ * Many Java programs use Graphics2D to draw stuff on the screen, and while\r
+ * it is easy to save the output as a png or jpeg file, it is a little\r
+ * harder to export it as an EPS for including in a document or paper.\r
+ *  <p>\r
+ * This class makes the whole process extremely easy, because you can use\r
+ * it as if it's a Graphics2D object.  The only difference is that all of\r
+ * the implemented methods create EPS output, which means the diagrams you\r
+ * draw can be resized without leading to any of the jagged edges you may\r
+ * see when resizing pixel-based images, such as jpeg and png files.\r
+ *  <p>\r
+ *   Example usage:\r
+ *  <p>\r
+ * <pre>    Graphics2D g = new EpsGraphics2D();\r
+ *    g.setColor(Color.black);\r
+ *\r
+ *    // Line thickness 2.\r
+ *    g.setStroke(new BasicStroke(2.0f));\r
+ *\r
+ *    // Draw a line.\r
+ *    g.drawLine(10, 10, 50, 10);\r
+ *\r
+ *    // Fill a rectangle in blue\r
+ *    g.setColor(Color.blue);\r
+ *    g.fillRect(10, 0, 20, 20);\r
+ *\r
+ *    // Get the EPS output.\r
+ *    String output = g.toString();</pre>\r
+ *  <p>\r
+ * You do not need to worry about the size of the canvas when drawing on a\r
+ * EpsGraphics2D object.  The bounding box of the EPS document will\r
+ * automatically resize to accomodate new items that you draw.\r
+ *  <p>\r
+ * Not all methods are implemented yet.  Those that are not are clearly\r
+ * labelled.\r
+ *  <p>\r
+ * Copyright Paul Mutton,\r
+ *           <a href="http://www.jibble.org/">http://www.jibble.org/</a>\r
+ *\r
+ */\r
+public class EpsGraphics2D extends java.awt.Graphics2D {\r
+\r
+\r
+    public static final String VERSION = "0.8.8";\r
+\r
+\r
+    /**\r
+     * Constructs a new EPS document that is initially empty and can be\r
+     * drawn on like a Graphics2D object.  The EPS document is stored in\r
+     * memory.\r
+     */\r
+    public EpsGraphics2D() {\r
+        this("Untitled");\r
+    }\r
+\r
+\r
+    /**\r
+     * Constructs a new EPS document that is initially empty and can be\r
+     * drawn on like a Graphics2D object.  The EPS document is stored in\r
+     * memory.\r
+     */\r
+    public EpsGraphics2D(String title) {\r
+        _document = new EpsDocument(title);\r
+        _backgroundColor = Color.white;\r
+        _clip = null;\r
+        _transform = new AffineTransform();\r
+        _clipTransform = new AffineTransform();\r
+        _accurateTextMode = true;\r
+        setColor(Color.black);\r
+        setPaint(Color.black);\r
+        setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));\r
+        setFont(Font.decode(null));\r
+        setStroke(new BasicStroke());\r
+    }\r
+\r
+\r
+    /**\r
+     * Constructs a new EPS document that is initially empty and can be\r
+     * drawn on like a Graphics2D object. The EPS document is written to\r
+     * the file as it goes, which reduces memory usage. The bounding box of\r
+     * the document is fixed and specified at construction time by\r
+     * minX,minY,maxX,maxY. The file is flushed and closed when the close()\r
+     * method is called.\r
+     */\r
+    public EpsGraphics2D(String title, File file, int minX, int minY, int maxX, int maxY) throws IOException {\r
+        this(title, new FileOutputStream(file), minX, minY, maxX, maxY);\r
+    }\r
+\r
+\r
+    /**\r
+     * Constructs a new EPS document that is initially empty and can be\r
+     * drawn on like a Graphics2D object. The EPS document is written to\r
+     * the output stream as it goes, which reduces memory usage. The\r
+     * bounding box of the document is fixed and specified at construction\r
+     * time by minX,minY,maxX,maxY. The output stream is flushed and closed\r
+     * when the close() method is called.\r
+     */\r
+    public EpsGraphics2D(String title, OutputStream outputStream, int minX, int minY, int maxX, int maxY) throws IOException {\r
+        this(title);\r
+        _document = new EpsDocument(title, outputStream, minX, minY, maxX, maxY);\r
+    }\r
+\r
+\r
+    /**\r
+     * Constructs a new EpsGraphics2D instance that is a copy of the\r
+     * supplied argument and points at the same EpsDocument.\r
+     */\r
+    protected EpsGraphics2D(EpsGraphics2D g) {\r
+        _document = g._document;\r
+        _backgroundColor = g._backgroundColor;\r
+        _clip = g._clip;\r
+        _clipTransform = (AffineTransform) g._clipTransform.clone();\r
+        _transform = (AffineTransform) g._transform.clone();\r
+        _color = g._color;\r
+        _paint = g._paint;\r
+        _composite = g._composite;\r
+        _font = g._font;\r
+        _stroke = g._stroke;\r
+        _accurateTextMode = g._accurateTextMode;\r
+    }\r
+\r
+\r
+    /**\r
+     * This method is called to indicate that a particular method is not\r
+     * supported yet.  The stack trace is printed to the standard output.\r
+     */\r
+    private void methodNotSupported() {\r
+        EpsException e = new EpsException("Method not currently supported by EpsGraphics2D version " + VERSION);\r
+        e.printStackTrace(System.err);\r
+    }\r
+\r
+\r
+    /////////////// Specialist methods ///////////////////////\r
+\r
+\r
+    /**\r
+     * Sets whether to use accurate text mode when rendering text in EPS.\r
+     * This is enabled (true) by default. When accurate text mode is used,\r
+     * all text will be rendered in EPS to appear exactly the same as it\r
+     * would do when drawn with a Graphics2D context. With accurate text\r
+     * mode enabled, it is not necessary for the EPS viewer to have the\r
+     * required font installed.\r
+     * <p>\r
+     * Turning off accurate text mode will require the EPS viewer to have\r
+     * the necessary fonts installed. If you are using a lot of text, you\r
+     * will find that this significantly reduces the file size of your EPS\r
+     * documents.  AffineTransforms can only affect the starting point of\r
+     * text using this simpler text mode - all text will be horizontal.\r
+     */\r
+    public void setAccurateTextMode(boolean b) {\r
+        _accurateTextMode = b;\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns whether accurate text mode is being used.\r
+     */\r
+    public boolean getAccurateTextMode() {\r
+        return _accurateTextMode;\r
+    }\r
+\r
+\r
+    /**\r
+     * Flushes the buffered contents of this EPS document to the underlying\r
+     * OutputStream it is being written to.\r
+     */\r
+    public void flush() throws IOException {\r
+        _document.flush();\r
+    }\r
+\r
+\r
+    /**\r
+     * Closes the EPS file being output to the underlying OutputStream.\r
+     * The OutputStream is automatically flushed before being closed.\r
+     * If you forget to do this, the file may be incomplete.\r
+     */\r
+    public void close() throws IOException {\r
+        flush();\r
+        _document.close();\r
+    }\r
+\r
+\r
+    /**\r
+     * Appends a line to the EpsDocument.\r
+     */\r
+    private void append(String line) {\r
+        _document.append(this, line);\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns the point after it has been transformed by the transformation.\r
+     */\r
+    private Point2D transform(float x, float y) {\r
+        Point2D result = new Point2D.Float(x, y);\r
+        result = _transform.transform(result, result);\r
+        result.setLocation(result.getX(), -result.getY());\r
+        return result;\r
+    }\r
+\r
+\r
+    /**\r
+     * Appends the commands required to draw a shape on the EPS document.\r
+     */\r
+    private void draw(Shape s, String action) {\r
+\r
+        if (s != null) {\r
+\r
+            Rectangle2D userBounds = s.getBounds2D();\r
+            if (!_transform.isIdentity()) {\r
+                s = _transform.createTransformedShape(s);\r
+            }\r
+\r
+            // Update the bounds.\r
+            if (!action.equals("clip")) {\r
+                Rectangle2D shapeBounds = s.getBounds2D();\r
+                Rectangle2D visibleBounds = shapeBounds;\r
+                if (_clip != null) {\r
+                    Rectangle2D clipBounds = _clip.getBounds2D();\r
+                    visibleBounds = shapeBounds.createIntersection(clipBounds);\r
+                }\r
+                float lineRadius = _stroke.getLineWidth() / 2;\r
+                float minX = (float) visibleBounds.getMinX() - lineRadius;\r
+                float minY = (float) visibleBounds.getMinY() - lineRadius;\r
+                float maxX = (float) visibleBounds.getMaxX() + lineRadius;\r
+                float maxY = (float) visibleBounds.getMaxY() + lineRadius;\r
+                _document.updateBounds(minX, -minY);\r
+                _document.updateBounds(maxX, -maxY);\r
+            }\r
+\r
+            append("newpath");\r
+            int type = 0;\r
+            float[] coords = new float[6];\r
+            PathIterator it = s.getPathIterator(null);\r
+            float x0 = 0;\r
+            float y0 = 0;\r
+            int count = 0;\r
+            while (!it.isDone()) {\r
+                type = it.currentSegment(coords);\r
+                float x1 = coords[0];\r
+                float y1 = -coords[1];\r
+                float x2 = coords[2];\r
+                float y2 = -coords[3];\r
+                float x3 = coords[4];\r
+                float y3 = -coords[5];\r
+\r
+                if (type == PathIterator.SEG_CLOSE) {\r
+                    append("closepath");\r
+                    count++;\r
+                }\r
+                else if (type == PathIterator.SEG_CUBICTO) {\r
+                    append(x1 + " " + y1 + " " + x2 + " " + y2 + " " + x3 + " " + y3 + " curveto");\r
+                    count++;\r
+                    x0 = x3;\r
+                    y0 = y3;\r
+                }\r
+                else if (type == PathIterator.SEG_LINETO) {\r
+                    append(x1 + " " + y1 + " lineto");\r
+                    count++;\r
+                    x0 = x1;\r
+                    y0 = y1;\r
+                }\r
+                else if (type == PathIterator.SEG_MOVETO) {\r
+                    append(x1 + " " + y1 + " moveto");\r
+                    count++;\r
+                    x0 = x1;\r
+                    y0 = y1;\r
+                }\r
+                else if (type == PathIterator.SEG_QUADTO) {\r
+                    // Convert the quad curve into a cubic.\r
+                    float _x1 = x0 + 2 / 3f * (x1 - x0);\r
+                    float _y1 = y0 + 2 / 3f * (y1 - y0);\r
+                    float _x2 = x1 + 1 / 3f * (x2 - x1);\r
+                    float _y2 = y1 + 1 / 3f * (y2 - y1);\r
+                    float _x3 = x2;\r
+                    float _y3 = y2;\r
+                    append(_x1 + " " + _y1 + " " + _x2 + " " + _y2 + " " + _x3 + " " + _y3 + " curveto");\r
+                    count++;\r
+                    x0 = _x3;\r
+                    y0 = _y3;\r
+                }\r
+                else if (type == PathIterator.WIND_EVEN_ODD) {\r
+                    // Ignore.\r
+                }\r
+                else if (type == PathIterator.WIND_NON_ZERO) {\r
+                    // Ignore.\r
+                }\r
+                it.next();\r
+            }\r
+            append(action);\r
+            append("newpath");\r
+        }\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns a hex string that always contains two characters.\r
+     */\r
+    private String toHexString(int n) {\r
+        String result = Integer.toString(n, 16);\r
+        while (result.length() < 2) {\r
+            result = "0" + result;\r
+        }\r
+        return result;\r
+    }\r
+\r
+\r
+    /////////////// Graphics2D methods ///////////////////////\r
+\r
+\r
+    /**\r
+     * Draws a 3D rectangle outline.  If it is raised, light appears to come\r
+     * from the top left.\r
+     */\r
+    public void draw3DRect(int x, int y, int width, int height, boolean raised) {\r
+        Color originalColor = getColor();\r
+        Stroke originalStroke = getStroke();\r
+\r
+        setStroke(new BasicStroke(1.0f));\r
+\r
+        if (raised) {\r
+            setColor(originalColor.brighter());\r
+        }\r
+        else {\r
+            setColor(originalColor.darker());\r
+        }\r
+\r
+        drawLine(x, y, x + width, y);\r
+        drawLine(x, y, x, y + height);\r
+\r
+        if (raised) {\r
+            setColor(originalColor.darker());\r
+        }\r
+        else {\r
+            setColor(originalColor.brighter());\r
+        }\r
+\r
+        drawLine(x + width, y + height, x, y + height);\r
+        drawLine(x + width, y + height, x + width, y);\r
+\r
+        setColor(originalColor);\r
+        setStroke(originalStroke);\r
+    }\r
+\r
+\r
+    /**\r
+     * Fills a 3D rectangle.  If raised, it has bright fill and light appears\r
+     * to come from the top left.\r
+     */\r
+    public void fill3DRect(int x, int y, int width, int height, boolean raised) {\r
+        Color originalColor = getColor();\r
+\r
+        if (raised) {\r
+            setColor(originalColor.brighter());\r
+        }\r
+        else {\r
+            setColor(originalColor.darker());\r
+        }\r
+        draw(new Rectangle(x, y, width, height), "fill");\r
+        setColor(originalColor);\r
+        draw3DRect(x, y, width, height, raised);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a Shape on the EPS document.\r
+     */\r
+    public void draw(Shape s) {\r
+        draw(s, "stroke");\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws an Image on the EPS document.\r
+     */\r
+    public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) {\r
+        AffineTransform at = getTransform();\r
+        transform(xform);\r
+        boolean st = drawImage(img, 0, 0, obs);\r
+        setTransform(at);\r
+        return st;\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a BufferedImage on the EPS document.\r
+     */\r
+    public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {\r
+        BufferedImage img1 = op.filter(img, null);\r
+        drawImage(img1, new AffineTransform(1f, 0f, 0f, 1f, x, y), null);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a RenderedImage on the EPS document.\r
+     */\r
+    public void drawRenderedImage(RenderedImage img, AffineTransform xform) {\r
+        Hashtable properties = new Hashtable();\r
+        String[] names = img.getPropertyNames();\r
+        for (int i = 0; i < names.length; i++) {\r
+            properties.put(names[i], img.getProperty(names[i]));\r
+        }\r
+\r
+        ColorModel cm = img.getColorModel();\r
+        WritableRaster wr = img.copyData(null);\r
+        BufferedImage img1 = new BufferedImage(cm, wr, cm.isAlphaPremultiplied(), properties);\r
+        AffineTransform at = AffineTransform.getTranslateInstance(img.getMinX(), img.getMinY());\r
+        at.preConcatenate(xform);\r
+        drawImage(img1, at, null);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a RenderableImage by invoking its createDefaultRendering method.\r
+     */\r
+    public void drawRenderableImage(RenderableImage img, AffineTransform xform) {\r
+        drawRenderedImage(img.createDefaultRendering(), xform);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a string at (x,y)\r
+     */\r
+    public void drawString(String str, int x, int y) {\r
+        drawString(str, (float) x, (float) y);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a string at (x,y)\r
+     */\r
+    public void drawString(String s, float x, float y) {\r
+        if (s != null && s.length() > 0) {\r
+            AttributedString as = new AttributedString(s);\r
+            as.addAttribute(TextAttribute.FONT, getFont());\r
+            drawString(as.getIterator(), x, y);\r
+        }\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws the characters of an AttributedCharacterIterator, starting from\r
+     * (x,y).\r
+     */\r
+    public void drawString(AttributedCharacterIterator iterator, int x, int y) {\r
+        drawString(iterator, (float) x, (float) y);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws the characters of an AttributedCharacterIterator, starting from\r
+     * (x,y).\r
+     */\r
+    public void drawString(AttributedCharacterIterator iterator, float x, float y) {\r
+        if (getAccurateTextMode()) {\r
+            TextLayout layout = new TextLayout(iterator, getFontRenderContext());\r
+            Shape shape = layout.getOutline(AffineTransform.getTranslateInstance(x, y));\r
+            draw(shape, "fill");\r
+        }\r
+        else {\r
+            append("newpath");\r
+            Point2D location = transform(x, y);\r
+            append(location.getX() + " " + location.getY() + " moveto");\r
+            StringBuffer buffer = new StringBuffer();\r
+            for (char ch = iterator.first(); ch != CharacterIterator.DONE; ch = iterator.next()) {\r
+                if (ch == '(' || ch == ')') {\r
+                    buffer.append('\\');\r
+                }\r
+                buffer.append(ch);\r
+            }\r
+            append("(" + buffer.toString() + ") show");\r
+        }\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a GlyphVector at (x,y)\r
+     */\r
+    public void drawGlyphVector(GlyphVector g, float x, float y) {\r
+        Shape shape = g.getOutline(x, y);\r
+        draw(shape, "fill");\r
+    }\r
+\r
+\r
+    /**\r
+     * Fills a Shape on the EPS document.\r
+     */\r
+    public void fill(Shape s) {\r
+        draw(s, "fill");\r
+    }\r
+\r
+\r
+    /**\r
+     * Checks whether or not the specified Shape intersects the specified\r
+     * Rectangle, which is in device space.\r
+     */\r
+    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {\r
+        return s.intersects(rect);\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns the device configuration associated with this EpsGraphics2D\r
+     * object.\r
+     */\r
+    public GraphicsConfiguration getDeviceConfiguration() {\r
+        GraphicsConfiguration gc = null;\r
+        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();\r
+        GraphicsDevice[] gds = ge.getScreenDevices();\r
+        for (int i = 0; i < gds.length; i++) {\r
+            GraphicsDevice gd = gds[i];\r
+            GraphicsConfiguration[] gcs = gd.getConfigurations();\r
+            if (gcs.length > 0) {\r
+                return gcs[0];\r
+            }\r
+        }\r
+        return gc;\r
+    }\r
+\r
+\r
+    /**\r
+     * Sets the Composite to be used by this EpsGraphics2D.  EpsGraphics2D\r
+     * does not make use of these.\r
+     */\r
+    public void setComposite(Composite comp) {\r
+        _composite = comp;\r
+    }\r
+\r
+\r
+    /**\r
+     * Sets the Paint attribute for the EpsGraphics2D object.  Only Paint\r
+     * objects of type Color are respected by EpsGraphics2D.\r
+     */\r
+    public void setPaint(Paint paint) {\r
+        _paint = paint;\r
+        if (paint instanceof Color) {\r
+            setColor((Color) paint);\r
+        }\r
+    }\r
+\r
+\r
+    /**\r
+     * Sets the stroke.  Only accepts BasicStroke objects (or subclasses of\r
+     * BasicStroke).\r
+     */\r
+    public void setStroke(Stroke s) {\r
+        if (s instanceof BasicStroke) {\r
+            _stroke = (BasicStroke) s;\r
+\r
+            append(_stroke.getLineWidth() + " setlinewidth");\r
+            float miterLimit = _stroke.getMiterLimit();\r
+            if (miterLimit < 1.0f) {\r
+                miterLimit = 1;\r
+            }\r
+            append(miterLimit + " setmiterlimit");\r
+            append(_stroke.getLineJoin() + " setlinejoin");\r
+            append(_stroke.getEndCap() + " setlinecap");\r
+\r
+            StringBuffer dashes = new StringBuffer();\r
+            dashes.append("[ ");\r
+            float[] dashArray = _stroke.getDashArray();\r
+            if (dashArray != null) {\r
+                for (int i = 0; i < dashArray.length; i++) {\r
+                    dashes.append((dashArray[i]) + " ");\r
+                }\r
+            }\r
+            dashes.append("]");\r
+            append(dashes.toString() + " 0 setdash");\r
+        }\r
+    }\r
+\r
+\r
+    /**\r
+     * Sets a rendering hint. These are not used by EpsGraphics2D.\r
+     */\r
+    public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) {\r
+        // Do nothing.\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns the value of a single preference for the rendering\r
+     * algorithms.  Rendering hints are not used by EpsGraphics2D.\r
+     */\r
+    public Object getRenderingHint(RenderingHints.Key hintKey) {\r
+        return null;\r
+    }\r
+\r
+\r
+    /**\r
+     * Sets the rendering hints.  These are ignored by EpsGraphics2D.\r
+     */\r
+    public void setRenderingHints(Map hints) {\r
+        // Do nothing.\r
+    }\r
+\r
+\r
+    /**\r
+     * Adds rendering hints.  These are ignored by EpsGraphics2D.\r
+     */\r
+    public void addRenderingHints(Map hints) {\r
+        // Do nothing.\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns the preferences for the rendering algorithms.\r
+     */\r
+    public RenderingHints getRenderingHints() {\r
+        return new RenderingHints(null);\r
+    }\r
+\r
+\r
+    /**\r
+     * Translates the origin of the EpsGraphics2D context to the point (x,y)\r
+     * in the current coordinate system.\r
+     */\r
+    public void translate(int x, int y) {\r
+        translate((double) x, (double) y);\r
+    }\r
+\r
+\r
+    /**\r
+     * Concatenates the current EpsGraphics2D Transformation with a\r
+     * translation transform.\r
+     */\r
+    public void translate(double tx, double ty) {\r
+        transform(AffineTransform.getTranslateInstance(tx, ty));\r
+    }\r
+\r
+\r
+    /**\r
+     * Concatenates the current EpsGraphics2D Transform with a rotation\r
+     * transform.\r
+     */\r
+    public void rotate(double theta) {\r
+        rotate(theta, 0, 0);\r
+    }\r
+\r
+\r
+    /**\r
+     * Concatenates the current EpsGraphics2D Transform with a translated\r
+     * rotation transform.\r
+     */\r
+    public void rotate(double theta, double x, double y) {\r
+        transform(AffineTransform.getRotateInstance(theta, x, y));\r
+    }\r
+\r
+\r
+    /**\r
+     * Concatenates the current EpsGraphics2D Transform with a scaling\r
+     * transformation.\r
+     */\r
+    public void scale(double sx, double sy) {\r
+        transform(AffineTransform.getScaleInstance(sx, sy));\r
+    }\r
+\r
+\r
+    /**\r
+     * Concatenates the current EpsGraphics2D Transform with a shearing\r
+     * transform.\r
+     */\r
+    public void shear(double shx, double shy) {\r
+        transform(AffineTransform.getShearInstance(shx, shy));\r
+    }\r
+\r
+\r
+    /**\r
+     * Composes an AffineTransform object with the Transform in this\r
+     * EpsGraphics2D according to the rule last-specified-first-applied.\r
+     */\r
+    public void transform(AffineTransform Tx) {\r
+        _transform.concatenate(Tx);\r
+        setTransform(getTransform());\r
+    }\r
+\r
+\r
+    /**\r
+     * Sets the AffineTransform to be used by this EpsGraphics2D.\r
+     */\r
+    public void setTransform(AffineTransform Tx) {\r
+        if (Tx == null) {\r
+            _transform = new AffineTransform();\r
+        }\r
+        else {\r
+            _transform = new AffineTransform(Tx);\r
+        }\r
+        // Need to update the stroke and font so they know the scale changed\r
+        setStroke(getStroke());\r
+        setFont(getFont());\r
+    }\r
+\r
+\r
+    /**\r
+     * Gets the AffineTransform used by this EpsGraphics2D.\r
+     */\r
+    public AffineTransform getTransform() {\r
+        return new AffineTransform(_transform);\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns the current Paint of the EpsGraphics2D object.\r
+     */\r
+    public Paint getPaint() {\r
+        return _paint;\r
+    }\r
+\r
+\r
+    /**\r
+     * returns the current Composite of the EpsGraphics2D object.\r
+     */\r
+    public Composite getComposite() {\r
+        return _composite;\r
+    }\r
+\r
+\r
+    /**\r
+     * Sets the background color to be used by the clearRect method.\r
+     */\r
+    public void setBackground(Color color) {\r
+        if (color == null) {\r
+            color = Color.black;\r
+        }\r
+        _backgroundColor = color;\r
+    }\r
+\r
+\r
+    /**\r
+     * Gets the background color that is used by the clearRect method.\r
+     */\r
+    public Color getBackground() {\r
+        return _backgroundColor;\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns the Stroke currently used.  Guaranteed to be an instance of\r
+     * BasicStroke.\r
+     */\r
+    public Stroke getStroke() {\r
+        return _stroke;\r
+    }\r
+\r
+\r
+    /**\r
+     * Intersects the current clip with the interior of the specified Shape\r
+     * and sets the clip to the resulting intersection.\r
+     */\r
+    public void clip(Shape s) {\r
+        if (_clip == null) {\r
+            setClip(s);\r
+        }\r
+        else {\r
+            Area area = new Area(_clip);\r
+            area.intersect(new Area(s));\r
+            setClip(area);\r
+        }\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns the FontRenderContext.\r
+     */\r
+    public FontRenderContext getFontRenderContext() {\r
+        return _fontRenderContext;\r
+    }\r
+\r
+\r
+    /////////////// Graphics methods ///////////////////////\r
+\r
+\r
+    /**\r
+     * Returns a new Graphics object that is identical to this EpsGraphics2D.\r
+     */\r
+    public Graphics create() {\r
+        return new EpsGraphics2D(this);\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns an EpsGraphics2D object based on this\r
+     * Graphics object, but with a new translation and clip\r
+     * area.\r
+     */\r
+    public Graphics create(int x, int y, int width, int height) {\r
+        Graphics g = create();\r
+        g.translate(x, y);\r
+        g.clipRect(0, 0, width, height);\r
+        return g;\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns the current Color.  This will be a default value (black)\r
+     * until it is changed using the setColor method.\r
+     */\r
+    public Color getColor() {\r
+        return _color;\r
+    }\r
+\r
+\r
+    /**\r
+     * Sets the Color to be used when drawing all future shapes, text, etc.\r
+     */\r
+    public void setColor(Color c) {\r
+        if (c == null) {\r
+            c = Color.black;\r
+        }\r
+        _color = c;\r
+        append((c.getRed() / 255f) + " " + (c.getGreen() / 255f) + " " + (c.getBlue() / 255f) + " setrgbcolor");\r
+    }\r
+\r
+\r
+    /**\r
+     * Sets the paint mode of this EpsGraphics2D object to overwrite the\r
+     * destination EpsDocument with the current color.\r
+     */\r
+    public void setPaintMode() {\r
+        // Do nothing - paint mode is the only method supported anyway.\r
+    }\r
+\r
+\r
+    /**\r
+     * <b><i><font color="red">Not implemented</font></i></b> - performs no action.\r
+     */\r
+    public void setXORMode(Color c1) {\r
+        methodNotSupported();\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns the Font currently being used.\r
+     */\r
+    public Font getFont() {\r
+        return _font;\r
+    }\r
+\r
+\r
+    /**\r
+     * Sets the Font to be used in future text.\r
+     */\r
+    public void setFont(Font font) {\r
+        if (font == null) {\r
+            font = Font.decode(null);\r
+        }\r
+        _font = font;\r
+        append("/" + _font.getPSName() + " findfont " + ((int) _font.getSize()) + " scalefont setfont");\r
+    }\r
+\r
+\r
+    /**\r
+     * Gets the font metrics of the current font.\r
+     */\r
+    public FontMetrics getFontMetrics() {\r
+        return getFontMetrics(getFont());\r
+    }\r
+\r
+\r
+    /**\r
+     * Gets the font metrics for the specified font.\r
+     */\r
+    public FontMetrics getFontMetrics(Font f) {\r
+        BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);\r
+        Graphics g = image.getGraphics();\r
+        return g.getFontMetrics(f);\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns the bounding rectangle of the current clipping area.\r
+     */\r
+    public Rectangle getClipBounds() {\r
+        if (_clip == null) {\r
+            return null;\r
+        }\r
+        Rectangle rect = getClip().getBounds();\r
+        return rect;\r
+    }\r
+\r
+\r
+    /**\r
+     * Intersects the current clip with the specified rectangle.\r
+     */\r
+    public void clipRect(int x, int y, int width, int height) {\r
+        clip(new Rectangle(x, y, width, height));\r
+    }\r
+\r
+\r
+    /**\r
+     * Sets the current clip to the rectangle specified by the given\r
+     * coordinates.\r
+     */\r
+    public void setClip(int x, int y, int width, int height) {\r
+        setClip(new Rectangle(x, y, width, height));\r
+    }\r
+\r
+\r
+    /**\r
+     * Gets the current clipping area.\r
+     */\r
+    public Shape getClip() {\r
+        if (_clip == null) {\r
+            return null;\r
+        }\r
+        else {\r
+            try {\r
+                AffineTransform t = _transform.createInverse();\r
+                t.concatenate(_clipTransform);\r
+                return t.createTransformedShape(_clip);\r
+            }\r
+            catch (Exception e) {\r
+                throw new EpsException("Unable to get inverse of matrix: " + _transform);\r
+            }\r
+        }\r
+    }\r
+\r
+\r
+    /**\r
+     * Sets the current clipping area to an arbitrary clip shape.\r
+     */\r
+    public void setClip(Shape clip) {\r
+        if (clip != null) {\r
+            if (_document.isClipSet()) {\r
+                append("grestore");\r
+                append("gsave");\r
+            }\r
+            else {\r
+                _document.setClipSet(true);\r
+                append("gsave");\r
+            }\r
+            draw(clip, "clip");\r
+            _clip = clip;\r
+            _clipTransform = (AffineTransform) _transform.clone();\r
+        }\r
+        else {\r
+            if (_document.isClipSet()) {\r
+                append("grestore");\r
+                _document.setClipSet(false);\r
+            }\r
+            _clip = null;\r
+        }\r
+    }\r
+\r
+\r
+    /**\r
+     * <b><i><font color="red">Not implemented</font></i></b> - performs no action.\r
+     */\r
+    public void copyArea(int x, int y, int width, int height, int dx, int dy) {\r
+        methodNotSupported();\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a straight line from (x1,y1) to (x2,y2).\r
+     */\r
+    public void drawLine(int x1, int y1, int x2, int y2) {\r
+        Shape shape = new Line2D.Float(x1, y1, x2, y2);\r
+        draw(shape);\r
+    }\r
+\r
+\r
+    /**\r
+     * Fills a rectangle with top-left corner placed at (x,y).\r
+     */\r
+    public void fillRect(int x, int y, int width, int height) {\r
+        Shape shape = new Rectangle(x, y, width, height);\r
+        draw(shape, "fill");\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a rectangle with top-left corner placed at (x,y).\r
+     */\r
+    public void drawRect(int x, int y, int width, int height) {\r
+        Shape shape = new Rectangle(x, y, width, height);\r
+        draw(shape);\r
+    }\r
+\r
+\r
+    /**\r
+     * Clears a rectangle with top-left corner placed at (x,y) using the\r
+     * current background color.\r
+     */\r
+    public void clearRect(int x, int y, int width, int height) {\r
+        Color originalColor = getColor();\r
+\r
+        setColor(getBackground());\r
+        Shape shape = new Rectangle(x, y, width, height);\r
+        draw(shape, "fill");\r
+\r
+        setColor(originalColor);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a rounded rectangle.\r
+     */\r
+    public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {\r
+        Shape shape = new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight);\r
+        draw(shape);\r
+    }\r
+\r
+\r
+    /**\r
+     * Fills a rounded rectangle.\r
+     */\r
+    public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {\r
+        Shape shape = new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight);\r
+        draw(shape, "fill");\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws an oval.\r
+     */\r
+    public void drawOval(int x, int y, int width, int height) {\r
+        Shape shape = new Ellipse2D.Float(x, y, width, height);\r
+        draw(shape);\r
+    }\r
+\r
+\r
+    /**\r
+     * Fills an oval.\r
+     */\r
+    public void fillOval(int x, int y, int width, int height) {\r
+        Shape shape = new Ellipse2D.Float(x, y, width, height);\r
+        draw(shape, "fill");\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws an arc.\r
+     */\r
+    public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) {\r
+        Shape shape = new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN);\r
+        draw(shape);\r
+    }\r
+\r
+\r
+    /**\r
+     * Fills an arc.\r
+     */\r
+    public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) {\r
+        Shape shape = new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.PIE);\r
+        draw(shape, "fill");\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a polyline.\r
+     */\r
+    public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {\r
+        if (nPoints > 0) {\r
+            GeneralPath path = new GeneralPath();\r
+            path.moveTo(xPoints[0], yPoints[0]);\r
+            for (int i = 1; i < nPoints; i++) {\r
+                path.lineTo(xPoints[i], yPoints[i]);\r
+            }\r
+            draw(path);\r
+        }\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a polygon made with the specified points.\r
+     */\r
+    public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {\r
+        Shape shape = new Polygon(xPoints, yPoints, nPoints);\r
+        draw(shape);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws a polygon.\r
+     */\r
+    public void drawPolygon(Polygon p) {\r
+        draw(p);\r
+    }\r
+\r
+\r
+    /**\r
+     * Fills a polygon made with the specified points.\r
+     */\r
+    public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {\r
+        Shape shape = new Polygon(xPoints, yPoints, nPoints);\r
+        draw(shape, "fill");\r
+    }\r
+\r
+\r
+    /**\r
+     * Fills a polygon.\r
+     */\r
+    public void fillPolygon(Polygon p) {\r
+        draw(p, "fill");\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws the specified characters, starting from (x,y)\r
+     */\r
+    public void drawChars(char[] data, int offset, int length, int x, int y) {\r
+        String string = new String(data, offset, length);\r
+        drawString(string, x, y);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws the specified bytes, starting from (x,y)\r
+     */\r
+    public void drawBytes(byte[] data, int offset, int length, int x, int y) {\r
+        String string = new String(data, offset, length);\r
+        drawString(string, x, y);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws an image.\r
+     */\r
+    public boolean drawImage(Image img, int x, int y, ImageObserver observer) {\r
+        return drawImage(img, x, y, Color.white, observer);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws an image.\r
+     */\r
+    public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) {\r
+        return drawImage(img, x, y, width, height, Color.white, observer);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws an image.\r
+     */\r
+    public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) {\r
+        int width = img.getWidth(null);\r
+        int height = img.getHeight(null);\r
+        return drawImage(img, x, y, width, height, bgcolor, observer);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws an image.\r
+     */\r
+    public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) {\r
+        return drawImage(img, x, y, x + width, y + height, 0, 0, width, height, bgcolor, observer);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws an image.\r
+     */\r
+    public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {\r
+        return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, Color.white, observer);\r
+    }\r
+\r
+\r
+    /**\r
+     * Draws an image.\r
+     */\r
+    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) {\r
+        if (dx1 >= dx2) {\r
+            throw new IllegalArgumentException("dx1 >= dx2");\r
+        }\r
+        if (sx1 >= sx2) {\r
+            throw new IllegalArgumentException("sx1 >= sx2");\r
+        }\r
+        if (dy1 >= dy2) {\r
+            throw new IllegalArgumentException("dy1 >= dy2");\r
+        }\r
+        if (sy1 >= sy2) {\r
+            throw new IllegalArgumentException("sy1 >= sy2");\r
+        }\r
+\r
+        append("gsave");\r
+\r
+        int width = sx2 - sx1;\r
+        int height = sy2 - sy1;\r
+        int destWidth = dx2 - dx1;\r
+        int destHeight = dy2 - dy1;\r
+\r
+        int[] pixels = new int[width * height];\r
+        PixelGrabber pg = new PixelGrabber(img, sx1, sy1, sx2 - sx1, sy2 - sy1, pixels, 0, width);\r
+        try {\r
+            pg.grabPixels();\r
+        }\r
+        catch(InterruptedException e) {\r
+            return false;\r
+        }\r
+\r
+        AffineTransform matrix = new AffineTransform(_transform);\r
+        matrix.translate(dx1, dy1);\r
+        matrix.scale(destWidth / (double) width, destHeight / (double) height);\r
+        double[] m = new double[6];\r
+        try {\r
+            matrix = matrix.createInverse();\r
+        }\r
+        catch (Exception e) {\r
+            throw new EpsException("Unable to get inverse of matrix: " + matrix);\r
+        }\r
+        matrix.scale(1, -1);\r
+        matrix.getMatrix(m);\r
+        append(width + " " + height + " 8 [" + m[0] + " " + m[1] + " " + m[2] + " " +  m[3] + " " + m[4] + " " + m[5] + "]");\r
+        // Fill the background to update the bounding box.\r
+        Color oldColor = getColor();\r
+        setColor(getBackground());\r
+        fillRect(dx1, dy1, destWidth, destHeight);\r
+        setColor(oldColor);\r
+        append("{currentfile 3 " + width + " mul string readhexstring pop} bind");\r
+        append("false 3 colorimage");\r
+        StringBuffer line = new StringBuffer();\r
+        for (int y = 0; y < height; y++) {\r
+            for (int x = 0; x < width; x++) {\r
+                Color color = new Color(pixels[x + width * y]);\r
+                line.append(toHexString(color.getRed()) + toHexString(color.getGreen()) + toHexString(color.getBlue()));\r
+                if (line.length() > 64) {\r
+                    append(line.toString());\r
+                    line = new StringBuffer();\r
+                }\r
+            }\r
+        }\r
+        if (line.length() > 0) {\r
+            append(line.toString());\r
+        }\r
+\r
+        append("grestore");\r
+\r
+        return true;\r
+    }\r
+\r
+\r
+    /**\r
+     * Disposes of all resources used by this EpsGraphics2D object.\r
+     * If this is the only remaining EpsGraphics2D instance pointing at\r
+     * a EpsDocument object, then the EpsDocument object shall become\r
+     * eligible for garbage collection.\r
+     */\r
+    public void dispose() {\r
+        _document = null;\r
+    }\r
+\r
+\r
+    /**\r
+     * Finalizes the object.\r
+     */\r
+    public void finalize() {\r
+        super.finalize();\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns the entire contents of the EPS document, complete with\r
+     * headers and bounding box.  The returned String is suitable for\r
+     * being written directly to disk as an EPS file.\r
+     */\r
+    public String toString() {\r
+        StringWriter writer = new StringWriter();\r
+        try {\r
+            _document.write(writer);\r
+            _document.flush();\r
+            _document.close();\r
+        }\r
+        catch (IOException e) {\r
+            throw new EpsException(e.toString());\r
+        }\r
+        return writer.toString();\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns true if the specified rectangular area might intersect the\r
+     * current clipping area.\r
+     */\r
+    public boolean hitClip(int x, int y, int width, int height) {\r
+        if (_clip == null) {\r
+            return true;\r
+        }\r
+        Rectangle rect = new Rectangle(x, y, width, height);\r
+        return hit(rect, _clip, true);\r
+    }\r
+\r
+\r
+    /**\r
+     * Returns the bounding rectangle of the current clipping area.\r
+     */\r
+    public Rectangle getClipBounds(Rectangle r) {\r
+        if (_clip == null) {\r
+            return r;\r
+        }\r
+        Rectangle rect = getClipBounds();\r
+        r.setLocation((int) rect.getX(), (int) rect.getY());\r
+        r.setSize((int) rect.getWidth(), (int) rect.getHeight());\r
+        return r;\r
+    }\r
+\r
+\r
+    private Color _color;\r
+    private Color _backgroundColor;\r
+    private Paint _paint;\r
+    private Composite _composite;\r
+    private BasicStroke _stroke;\r
+    private Font _font;\r
+    private Shape _clip;\r
+    private AffineTransform _clipTransform;\r
+    private AffineTransform _transform;\r
+    private boolean _accurateTextMode;\r
+\r
+    private EpsDocument _document;\r
+\r
+    private static FontRenderContext _fontRenderContext = new FontRenderContext(null, false, true);\r
+}
\ No newline at end of file