Merge remote-tracking branch 'origin/develop' into
[jalview.git] / src / org / jibble / epsgraphics / EpsGraphics2D.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package org.jibble.epsgraphics;
22
23 import jalview.util.MessageManager;
24
25 import java.io.*;
26 import java.text.*;
27 import java.util.*;
28
29 import java.awt.*;
30 import java.awt.font.*;
31 import java.awt.geom.*;
32 import java.awt.image.*;
33 import java.awt.image.renderable.*;
34
35 /**
36  * EpsGraphics2D is suitable for creating high quality EPS graphics for use in
37  * documents and papers, and can be used just like a standard Graphics2D object.
38  * <p>
39  * Many Java programs use Graphics2D to draw stuff on the screen, and while it
40  * is easy to save the output as a png or jpeg file, it is a little harder to
41  * export it as an EPS for including in a document or paper.
42  * <p>
43  * This class makes the whole process extremely easy, because you can use it as
44  * if it's a Graphics2D object. The only difference is that all of the
45  * implemented methods create EPS output, which means the diagrams you draw can
46  * be resized without leading to any of the jagged edges you may see when
47  * resizing pixel-based images, such as jpeg and png files.
48  * <p>
49  * Example usage:
50  * <p>
51  * 
52  * <pre>
53  * Graphics2D g = new EpsGraphics2D();
54  * g.setColor(Color.black);
55  * 
56  * // Line thickness 2.
57  * g.setStroke(new BasicStroke(2.0f));
58  * 
59  * // Draw a line.
60  * g.drawLine(10, 10, 50, 10);
61  * 
62  * // Fill a rectangle in blue
63  * g.setColor(Color.blue);
64  * g.fillRect(10, 0, 20, 20);
65  * 
66  * // Get the EPS output.
67  * String output = g.toString();
68  * </pre>
69  * 
70  * <p>
71  * You do not need to worry about the size of the canvas when drawing on a
72  * EpsGraphics2D object. The bounding box of the EPS document will automatically
73  * resize to accomodate new items that you draw.
74  * <p>
75  * Not all methods are implemented yet. Those that are not are clearly labelled.
76  * <p>
77  * Copyright Paul Mutton, <a
78  * href="http://www.jibble.org/">http://www.jibble.org/</a>
79  * 
80  */
81 public class EpsGraphics2D extends java.awt.Graphics2D
82 {
83
84   public static final String VERSION = "0.8.8";
85
86   /**
87    * Constructs a new EPS document that is initially empty and can be drawn on
88    * like a Graphics2D object. The EPS document is stored in memory.
89    */
90   public EpsGraphics2D()
91   {
92     this("Untitled");
93   }
94
95   /**
96    * Constructs a new EPS document that is initially empty and can be drawn on
97    * like a Graphics2D object. The EPS document is stored in memory.
98    */
99   public EpsGraphics2D(String title)
100   {
101     _document = new EpsDocument(title);
102     _backgroundColor = Color.white;
103     _clip = null;
104     _transform = new AffineTransform();
105     _clipTransform = new AffineTransform();
106     _accurateTextMode = true;
107     setColor(Color.black);
108     setPaint(Color.black);
109     setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
110     setFont(Font.decode(null));
111     setStroke(new BasicStroke());
112   }
113
114   /**
115    * Constructs a new EPS document that is initially empty and can be drawn on
116    * like a Graphics2D object. The EPS document is written to the file as it
117    * goes, which reduces memory usage. The bounding box of the document is fixed
118    * and specified at construction time by minX,minY,maxX,maxY. The file is
119    * flushed and closed when the close() method is called.
120    */
121   public EpsGraphics2D(String title, File file, int minX, int minY,
122           int maxX, int maxY) throws IOException
123   {
124     this(title, new FileOutputStream(file), minX, minY, maxX, maxY);
125   }
126
127   /**
128    * Constructs a new EPS document that is initially empty and can be drawn on
129    * like a Graphics2D object. The EPS document is written to the output stream
130    * as it goes, which reduces memory usage. The bounding box of the document is
131    * fixed and specified at construction time by minX,minY,maxX,maxY. The output
132    * stream is flushed and closed when the close() method is called.
133    */
134   public EpsGraphics2D(String title, OutputStream outputStream, int minX,
135           int minY, int maxX, int maxY) throws IOException
136   {
137     this(title);
138     _document = new EpsDocument(title, outputStream, minX, minY, maxX, maxY);
139   }
140
141   /**
142    * Constructs a new EpsGraphics2D instance that is a copy of the supplied
143    * argument and points at the same EpsDocument.
144    */
145   protected EpsGraphics2D(EpsGraphics2D g)
146   {
147     _document = g._document;
148     _backgroundColor = g._backgroundColor;
149     _clip = g._clip;
150     _clipTransform = (AffineTransform) g._clipTransform.clone();
151     _transform = (AffineTransform) g._transform.clone();
152     _color = g._color;
153     _paint = g._paint;
154     _composite = g._composite;
155     _font = g._font;
156     _stroke = g._stroke;
157     _accurateTextMode = g._accurateTextMode;
158   }
159
160   /**
161    * This method is called to indicate that a particular method is not supported
162    * yet. The stack trace is printed to the standard output.
163    */
164   private void methodNotSupported()
165   {
166     EpsException e = new EpsException(MessageManager.formatMessage("exception.eps_method_not_supported", new String[]{VERSION}));
167     e.printStackTrace(System.err);
168   }
169
170   // ///////////// Specialist methods ///////////////////////
171
172   /**
173    * Sets whether to use accurate text mode when rendering text in EPS. This is
174    * enabled (true) by default. When accurate text mode is used, all text will
175    * be rendered in EPS to appear exactly the same as it would do when drawn
176    * with a Graphics2D context. With accurate text mode enabled, it is not
177    * necessary for the EPS viewer to have the required font installed.
178    * <p>
179    * Turning off accurate text mode will require the EPS viewer to have the
180    * necessary fonts installed. If you are using a lot of text, you will find
181    * that this significantly reduces the file size of your EPS documents.
182    * AffineTransforms can only affect the starting point of text using this
183    * simpler text mode - all text will be horizontal.
184    */
185   public void setAccurateTextMode(boolean b)
186   {
187     _accurateTextMode = b;
188   }
189
190   /**
191    * Returns whether accurate text mode is being used.
192    */
193   public boolean getAccurateTextMode()
194   {
195     return _accurateTextMode;
196   }
197
198   /**
199    * Flushes the buffered contents of this EPS document to the underlying
200    * OutputStream it is being written to.
201    */
202   public void flush() throws IOException
203   {
204     _document.flush();
205   }
206
207   /**
208    * Closes the EPS file being output to the underlying OutputStream. The
209    * OutputStream is automatically flushed before being closed. If you forget to
210    * do this, the file may be incomplete.
211    */
212   public void close() throws IOException
213   {
214     flush();
215     _document.close();
216   }
217
218   /**
219    * Appends a line to the EpsDocument.
220    */
221   private void append(String line)
222   {
223     _document.append(this, line);
224   }
225
226   /**
227    * Returns the point after it has been transformed by the transformation.
228    */
229   private Point2D transform(float x, float y)
230   {
231     Point2D result = new Point2D.Float(x, y);
232     result = _transform.transform(result, result);
233     result.setLocation(result.getX(), -result.getY());
234     return result;
235   }
236
237   /**
238    * Appends the commands required to draw a shape on the EPS document.
239    */
240   private void draw(Shape s, String action)
241   {
242
243     if (s != null)
244     {
245
246       // Rectangle2D userBounds = s.getBounds2D();
247       if (!_transform.isIdentity())
248       {
249         s = _transform.createTransformedShape(s);
250       }
251
252       // Update the bounds.
253       if (!action.equals("clip"))
254       {
255         Rectangle2D shapeBounds = s.getBounds2D();
256         Rectangle2D visibleBounds = shapeBounds;
257         if (_clip != null)
258         {
259           Rectangle2D clipBounds = _clip.getBounds2D();
260           visibleBounds = shapeBounds.createIntersection(clipBounds);
261         }
262         float lineRadius = _stroke.getLineWidth() / 2;
263         float minX = (float) visibleBounds.getMinX() - lineRadius;
264         float minY = (float) visibleBounds.getMinY() - lineRadius;
265         float maxX = (float) visibleBounds.getMaxX() + lineRadius;
266         float maxY = (float) visibleBounds.getMaxY() + lineRadius;
267         _document.updateBounds(minX, -minY);
268         _document.updateBounds(maxX, -maxY);
269       }
270
271       append("newpath");
272       int type = 0;
273       float[] coords = new float[6];
274       PathIterator it = s.getPathIterator(null);
275       float x0 = 0;
276       float y0 = 0;
277       int count = 0;
278       while (!it.isDone())
279       {
280         type = it.currentSegment(coords);
281         float x1 = coords[0];
282         float y1 = -coords[1];
283         float x2 = coords[2];
284         float y2 = -coords[3];
285         float x3 = coords[4];
286         float y3 = -coords[5];
287
288         if (type == PathIterator.SEG_CLOSE)
289         {
290           append("closepath");
291           count++;
292         }
293         else if (type == PathIterator.SEG_CUBICTO)
294         {
295           append(x1 + " " + y1 + " " + x2 + " " + y2 + " " + x3 + " " + y3
296                   + " curveto");
297           count++;
298           x0 = x3;
299           y0 = y3;
300         }
301         else if (type == PathIterator.SEG_LINETO)
302         {
303           append(x1 + " " + y1 + " lineto");
304           count++;
305           x0 = x1;
306           y0 = y1;
307         }
308         else if (type == PathIterator.SEG_MOVETO)
309         {
310           append(x1 + " " + y1 + " moveto");
311           count++;
312           x0 = x1;
313           y0 = y1;
314         }
315         else if (type == PathIterator.SEG_QUADTO)
316         {
317           // Convert the quad curve into a cubic.
318           float _x1 = x0 + 2 / 3f * (x1 - x0);
319           float _y1 = y0 + 2 / 3f * (y1 - y0);
320           float _x2 = x1 + 1 / 3f * (x2 - x1);
321           float _y2 = y1 + 1 / 3f * (y2 - y1);
322           float _x3 = x2;
323           float _y3 = y2;
324           append(_x1 + " " + _y1 + " " + _x2 + " " + _y2 + " " + _x3 + " "
325                   + _y3 + " curveto");
326           count++;
327           x0 = _x3;
328           y0 = _y3;
329         }
330         else if (type == PathIterator.WIND_EVEN_ODD)
331         {
332           // Ignore.
333         }
334         else if (type == PathIterator.WIND_NON_ZERO)
335         {
336           // Ignore.
337         }
338         it.next();
339       }
340       append(action);
341       append("newpath");
342     }
343   }
344
345   /**
346    * Returns a hex string that always contains two characters.
347    */
348   private String toHexString(int n)
349   {
350     String result = Integer.toString(n, 16);
351     while (result.length() < 2)
352     {
353       result = "0" + result;
354     }
355     return result;
356   }
357
358   // ///////////// Graphics2D methods ///////////////////////
359
360   /**
361    * Draws a 3D rectangle outline. If it is raised, light appears to come from
362    * the top left.
363    */
364   public void draw3DRect(int x, int y, int width, int height, boolean raised)
365   {
366     Color originalColor = getColor();
367     Stroke originalStroke = getStroke();
368
369     setStroke(new BasicStroke(1.0f));
370
371     if (raised)
372     {
373       setColor(originalColor.brighter());
374     }
375     else
376     {
377       setColor(originalColor.darker());
378     }
379
380     drawLine(x, y, x + width, y);
381     drawLine(x, y, x, y + height);
382
383     if (raised)
384     {
385       setColor(originalColor.darker());
386     }
387     else
388     {
389       setColor(originalColor.brighter());
390     }
391
392     drawLine(x + width, y + height, x, y + height);
393     drawLine(x + width, y + height, x + width, y);
394
395     setColor(originalColor);
396     setStroke(originalStroke);
397   }
398
399   /**
400    * Fills a 3D rectangle. If raised, it has bright fill and light appears to
401    * come from the top left.
402    */
403   public void fill3DRect(int x, int y, int width, int height, boolean raised)
404   {
405     Color originalColor = getColor();
406
407     if (raised)
408     {
409       setColor(originalColor.brighter());
410     }
411     else
412     {
413       setColor(originalColor.darker());
414     }
415     draw(new Rectangle(x, y, width, height), "fill");
416     setColor(originalColor);
417     draw3DRect(x, y, width, height, raised);
418   }
419
420   /**
421    * Draws a Shape on the EPS document.
422    */
423   public void draw(Shape s)
424   {
425     draw(s, "stroke");
426   }
427
428   /**
429    * Draws an Image on the EPS document.
430    */
431   public boolean drawImage(Image img, AffineTransform xform,
432           ImageObserver obs)
433   {
434     AffineTransform at = getTransform();
435     transform(xform);
436     boolean st = drawImage(img, 0, 0, obs);
437     setTransform(at);
438     return st;
439   }
440
441   /**
442    * Draws a BufferedImage on the EPS document.
443    */
444   public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y)
445   {
446     BufferedImage img1 = op.filter(img, null);
447     drawImage(img1, new AffineTransform(1f, 0f, 0f, 1f, x, y), null);
448   }
449
450   /**
451    * Draws a RenderedImage on the EPS document.
452    */
453   public void drawRenderedImage(RenderedImage img, AffineTransform xform)
454   {
455     Hashtable properties = new Hashtable();
456     String[] names = img.getPropertyNames();
457     for (int i = 0; i < names.length; i++)
458     {
459       properties.put(names[i], img.getProperty(names[i]));
460     }
461
462     ColorModel cm = img.getColorModel();
463     WritableRaster wr = img.copyData(null);
464     BufferedImage img1 = new BufferedImage(cm, wr, cm
465             .isAlphaPremultiplied(), properties);
466     AffineTransform at = AffineTransform.getTranslateInstance(
467             img.getMinX(), img.getMinY());
468     at.preConcatenate(xform);
469     drawImage(img1, at, null);
470   }
471
472   /**
473    * Draws a RenderableImage by invoking its createDefaultRendering method.
474    */
475   public void drawRenderableImage(RenderableImage img, AffineTransform xform)
476   {
477     drawRenderedImage(img.createDefaultRendering(), xform);
478   }
479
480   /**
481    * Draws a string at (x,y)
482    */
483   public void drawString(String str, int x, int y)
484   {
485     drawString(str, (float) x, (float) y);
486   }
487
488   /**
489    * Draws a string at (x,y)
490    */
491   public void drawString(String s, float x, float y)
492   {
493     if (s != null && s.length() > 0)
494     {
495       AttributedString as = new AttributedString(s);
496       as.addAttribute(TextAttribute.FONT, getFont());
497       drawString(as.getIterator(), x, y);
498     }
499   }
500
501   /**
502    * Draws the characters of an AttributedCharacterIterator, starting from
503    * (x,y).
504    */
505   public void drawString(AttributedCharacterIterator iterator, int x, int y)
506   {
507     drawString(iterator, (float) x, (float) y);
508   }
509
510   /**
511    * Draws the characters of an AttributedCharacterIterator, starting from
512    * (x,y).
513    */
514   public void drawString(AttributedCharacterIterator iterator, float x,
515           float y)
516   {
517     if (getAccurateTextMode())
518     {
519       TextLayout layout = new TextLayout(iterator, getFontRenderContext());
520       Shape shape = layout.getOutline(AffineTransform.getTranslateInstance(
521               x, y));
522       draw(shape, "fill");
523     }
524     else
525     {
526       append("newpath");
527       Point2D location = transform(x, y);
528       append(location.getX() + " " + location.getY() + " moveto");
529       StringBuffer buffer = new StringBuffer();
530       for (char ch = iterator.first(); ch != CharacterIterator.DONE; ch = iterator
531               .next())
532       {
533         if (ch == '(' || ch == ')')
534         {
535           buffer.append('\\');
536         }
537         buffer.append(ch);
538       }
539       append("(" + buffer.toString() + ") show");
540     }
541   }
542
543   /**
544    * Draws a GlyphVector at (x,y)
545    */
546   public void drawGlyphVector(GlyphVector g, float x, float y)
547   {
548     Shape shape = g.getOutline(x, y);
549     draw(shape, "fill");
550   }
551
552   /**
553    * Fills a Shape on the EPS document.
554    */
555   public void fill(Shape s)
556   {
557     draw(s, "fill");
558   }
559
560   /**
561    * Checks whether or not the specified Shape intersects the specified
562    * Rectangle, which is in device space.
563    */
564   public boolean hit(Rectangle rect, Shape s, boolean onStroke)
565   {
566     return s.intersects(rect);
567   }
568
569   /**
570    * Returns the device configuration associated with this EpsGraphics2D object.
571    */
572   public GraphicsConfiguration getDeviceConfiguration()
573   {
574     GraphicsConfiguration gc = null;
575     GraphicsEnvironment ge = GraphicsEnvironment
576             .getLocalGraphicsEnvironment();
577     GraphicsDevice[] gds = ge.getScreenDevices();
578     for (int i = 0; i < gds.length; i++)
579     {
580       GraphicsDevice gd = gds[i];
581       GraphicsConfiguration[] gcs = gd.getConfigurations();
582       if (gcs.length > 0)
583       {
584         return gcs[0];
585       }
586     }
587     return gc;
588   }
589
590   /**
591    * Sets the Composite to be used by this EpsGraphics2D. EpsGraphics2D does not
592    * make use of these.
593    */
594   public void setComposite(Composite comp)
595   {
596     _composite = comp;
597   }
598
599   /**
600    * Sets the Paint attribute for the EpsGraphics2D object. Only Paint objects
601    * of type Color are respected by EpsGraphics2D.
602    */
603   public void setPaint(Paint paint)
604   {
605     _paint = paint;
606     if (paint instanceof Color)
607     {
608       setColor((Color) paint);
609     }
610   }
611
612   /**
613    * Sets the stroke. Only accepts BasicStroke objects (or subclasses of
614    * BasicStroke).
615    */
616   public void setStroke(Stroke s)
617   {
618     if (s instanceof BasicStroke)
619     {
620       _stroke = (BasicStroke) s;
621
622       append(_stroke.getLineWidth() + " setlinewidth");
623       float miterLimit = _stroke.getMiterLimit();
624       if (miterLimit < 1.0f)
625       {
626         miterLimit = 1;
627       }
628       append(miterLimit + " setmiterlimit");
629       append(_stroke.getLineJoin() + " setlinejoin");
630       append(_stroke.getEndCap() + " setlinecap");
631
632       StringBuffer dashes = new StringBuffer();
633       dashes.append("[ ");
634       float[] dashArray = _stroke.getDashArray();
635       if (dashArray != null)
636       {
637         for (int i = 0; i < dashArray.length; i++)
638         {
639           dashes.append((dashArray[i]) + " ");
640         }
641       }
642       dashes.append("]");
643       append(dashes.toString() + " 0 setdash");
644     }
645   }
646
647   /**
648    * Sets a rendering hint. These are not used by EpsGraphics2D.
649    */
650   public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
651   {
652     // Do nothing.
653   }
654
655   /**
656    * Returns the value of a single preference for the rendering algorithms.
657    * Rendering hints are not used by EpsGraphics2D.
658    */
659   public Object getRenderingHint(RenderingHints.Key hintKey)
660   {
661     return null;
662   }
663
664   /**
665    * Sets the rendering hints. These are ignored by EpsGraphics2D.
666    */
667   public void setRenderingHints(Map hints)
668   {
669     // Do nothing.
670   }
671
672   /**
673    * Adds rendering hints. These are ignored by EpsGraphics2D.
674    */
675   public void addRenderingHints(Map hints)
676   {
677     // Do nothing.
678   }
679
680   /**
681    * Returns the preferences for the rendering algorithms.
682    */
683   public RenderingHints getRenderingHints()
684   {
685     return new RenderingHints(null);
686   }
687
688   /**
689    * Translates the origin of the EpsGraphics2D context to the point (x,y) in
690    * the current coordinate system.
691    */
692   public void translate(int x, int y)
693   {
694     translate((double) x, (double) y);
695   }
696
697   /**
698    * Concatenates the current EpsGraphics2D Transformation with a translation
699    * transform.
700    */
701   public void translate(double tx, double ty)
702   {
703     transform(AffineTransform.getTranslateInstance(tx, ty));
704   }
705
706   /**
707    * Concatenates the current EpsGraphics2D Transform with a rotation transform.
708    */
709   public void rotate(double theta)
710   {
711     rotate(theta, 0, 0);
712   }
713
714   /**
715    * Concatenates the current EpsGraphics2D Transform with a translated rotation
716    * transform.
717    */
718   public void rotate(double theta, double x, double y)
719   {
720     transform(AffineTransform.getRotateInstance(theta, x, y));
721   }
722
723   /**
724    * Concatenates the current EpsGraphics2D Transform with a scaling
725    * transformation.
726    */
727   public void scale(double sx, double sy)
728   {
729     transform(AffineTransform.getScaleInstance(sx, sy));
730   }
731
732   /**
733    * Concatenates the current EpsGraphics2D Transform with a shearing transform.
734    */
735   public void shear(double shx, double shy)
736   {
737     transform(AffineTransform.getShearInstance(shx, shy));
738   }
739
740   /**
741    * Composes an AffineTransform object with the Transform in this EpsGraphics2D
742    * according to the rule last-specified-first-applied.
743    */
744   public void transform(AffineTransform Tx)
745   {
746     _transform.concatenate(Tx);
747     setTransform(getTransform());
748   }
749
750   /**
751    * Sets the AffineTransform to be used by this EpsGraphics2D.
752    */
753   public void setTransform(AffineTransform Tx)
754   {
755     if (Tx == null)
756     {
757       _transform = new AffineTransform();
758     }
759     else
760     {
761       _transform = new AffineTransform(Tx);
762     }
763     // Need to update the stroke and font so they know the scale changed
764     setStroke(getStroke());
765     setFont(getFont());
766   }
767
768   /**
769    * Gets the AffineTransform used by this EpsGraphics2D.
770    */
771   public AffineTransform getTransform()
772   {
773     return new AffineTransform(_transform);
774   }
775
776   /**
777    * Returns the current Paint of the EpsGraphics2D object.
778    */
779   public Paint getPaint()
780   {
781     return _paint;
782   }
783
784   /**
785    * returns the current Composite of the EpsGraphics2D object.
786    */
787   public Composite getComposite()
788   {
789     return _composite;
790   }
791
792   /**
793    * Sets the background color to be used by the clearRect method.
794    */
795   public void setBackground(Color color)
796   {
797     if (color == null)
798     {
799       color = Color.black;
800     }
801     _backgroundColor = color;
802   }
803
804   /**
805    * Gets the background color that is used by the clearRect method.
806    */
807   public Color getBackground()
808   {
809     return _backgroundColor;
810   }
811
812   /**
813    * Returns the Stroke currently used. Guaranteed to be an instance of
814    * BasicStroke.
815    */
816   public Stroke getStroke()
817   {
818     return _stroke;
819   }
820
821   /**
822    * Intersects the current clip with the interior of the specified Shape and
823    * sets the clip to the resulting intersection.
824    */
825   public void clip(Shape s)
826   {
827     if (_clip == null)
828     {
829       setClip(s);
830     }
831     else
832     {
833       Area area = new Area(_clip);
834       area.intersect(new Area(s));
835       setClip(area);
836     }
837   }
838
839   /**
840    * Returns the FontRenderContext.
841    */
842   public FontRenderContext getFontRenderContext()
843   {
844     return _fontRenderContext;
845   }
846
847   // ///////////// Graphics methods ///////////////////////
848
849   /**
850    * Returns a new Graphics object that is identical to this EpsGraphics2D.
851    */
852   public Graphics create()
853   {
854     return new EpsGraphics2D(this);
855   }
856
857   /**
858    * Returns an EpsGraphics2D object based on this Graphics object, but with a
859    * new translation and clip area.
860    */
861   public Graphics create(int x, int y, int width, int height)
862   {
863     Graphics g = create();
864     g.translate(x, y);
865     g.clipRect(0, 0, width, height);
866     return g;
867   }
868
869   /**
870    * Returns the current Color. This will be a default value (black) until it is
871    * changed using the setColor method.
872    */
873   public Color getColor()
874   {
875     return _color;
876   }
877
878   /**
879    * Sets the Color to be used when drawing all future shapes, text, etc.
880    */
881   public void setColor(Color c)
882   {
883     if (c == null)
884     {
885       c = Color.black;
886     }
887     _color = c;
888     append((c.getRed() / 255f) + " " + (c.getGreen() / 255f) + " "
889             + (c.getBlue() / 255f) + " setrgbcolor");
890   }
891
892   /**
893    * Sets the paint mode of this EpsGraphics2D object to overwrite the
894    * destination EpsDocument with the current color.
895    */
896   public void setPaintMode()
897   {
898     // Do nothing - paint mode is the only method supported anyway.
899   }
900
901   /**
902    * <b><i><font color="red">Not implemented</font></i></b> - performs no
903    * action.
904    */
905   public void setXORMode(Color c1)
906   {
907     methodNotSupported();
908   }
909
910   /**
911    * Returns the Font currently being used.
912    */
913   public Font getFont()
914   {
915     return _font;
916   }
917
918   /**
919    * Sets the Font to be used in future text.
920    */
921   public void setFont(Font font)
922   {
923     if (font == null)
924     {
925       font = Font.decode(null);
926     }
927     _font = font;
928     append("/" + _font.getPSName() + " findfont " + ((int) _font.getSize())
929             + " scalefont setfont");
930   }
931
932   /**
933    * Gets the font metrics of the current font.
934    */
935   public FontMetrics getFontMetrics()
936   {
937     return getFontMetrics(getFont());
938   }
939
940   /**
941    * Gets the font metrics for the specified font.
942    */
943   public FontMetrics getFontMetrics(Font f)
944   {
945     BufferedImage image = new BufferedImage(1, 1,
946             BufferedImage.TYPE_INT_RGB);
947     Graphics g = image.getGraphics();
948     return g.getFontMetrics(f);
949   }
950
951   /**
952    * Returns the bounding rectangle of the current clipping area.
953    */
954   public Rectangle getClipBounds()
955   {
956     if (_clip == null)
957     {
958       return null;
959     }
960     Rectangle rect = getClip().getBounds();
961     return rect;
962   }
963
964   /**
965    * Intersects the current clip with the specified rectangle.
966    */
967   public void clipRect(int x, int y, int width, int height)
968   {
969     clip(new Rectangle(x, y, width, height));
970   }
971
972   /**
973    * Sets the current clip to the rectangle specified by the given coordinates.
974    */
975   public void setClip(int x, int y, int width, int height)
976   {
977     setClip(new Rectangle(x, y, width, height));
978   }
979
980   /**
981    * Gets the current clipping area.
982    */
983   public Shape getClip()
984   {
985     if (_clip == null)
986     {
987       return null;
988     }
989     else
990     {
991       try
992       {
993         AffineTransform t = _transform.createInverse();
994         t.concatenate(_clipTransform);
995         return t.createTransformedShape(_clip);
996       } catch (Exception e)
997       {
998         throw new EpsException(MessageManager.formatMessage("exception.eps_unable_to_get_inverse_matrix", new String[]{_transform.toString()}));
999       }
1000     }
1001   }
1002
1003   /**
1004    * Sets the current clipping area to an arbitrary clip shape.
1005    */
1006   public void setClip(Shape clip)
1007   {
1008     if (clip != null)
1009     {
1010       if (_document.isClipSet())
1011       {
1012         append("grestore");
1013         append("gsave");
1014       }
1015       else
1016       {
1017         _document.setClipSet(true);
1018         append("gsave");
1019       }
1020       draw(clip, "clip");
1021       _clip = clip;
1022       _clipTransform = (AffineTransform) _transform.clone();
1023     }
1024     else
1025     {
1026       if (_document.isClipSet())
1027       {
1028         append("grestore");
1029         _document.setClipSet(false);
1030       }
1031       _clip = null;
1032     }
1033   }
1034
1035   /**
1036    * <b><i><font color="red">Not implemented</font></i></b> - performs no
1037    * action.
1038    */
1039   public void copyArea(int x, int y, int width, int height, int dx, int dy)
1040   {
1041     methodNotSupported();
1042   }
1043
1044   /**
1045    * Draws a straight line from (x1,y1) to (x2,y2).
1046    */
1047   public void drawLine(int x1, int y1, int x2, int y2)
1048   {
1049     Shape shape = new Line2D.Float(x1, y1, x2, y2);
1050     draw(shape);
1051   }
1052
1053   /**
1054    * Fills a rectangle with top-left corner placed at (x,y).
1055    */
1056   public void fillRect(int x, int y, int width, int height)
1057   {
1058     Shape shape = new Rectangle(x, y, width, height);
1059     draw(shape, "fill");
1060   }
1061
1062   /**
1063    * Draws a rectangle with top-left corner placed at (x,y).
1064    */
1065   public void drawRect(int x, int y, int width, int height)
1066   {
1067     Shape shape = new Rectangle(x, y, width, height);
1068     draw(shape);
1069   }
1070
1071   /**
1072    * Clears a rectangle with top-left corner placed at (x,y) using the current
1073    * background color.
1074    */
1075   public void clearRect(int x, int y, int width, int height)
1076   {
1077     Color originalColor = getColor();
1078
1079     setColor(getBackground());
1080     Shape shape = new Rectangle(x, y, width, height);
1081     draw(shape, "fill");
1082
1083     setColor(originalColor);
1084   }
1085
1086   /**
1087    * Draws a rounded rectangle.
1088    */
1089   public void drawRoundRect(int x, int y, int width, int height,
1090           int arcWidth, int arcHeight)
1091   {
1092     Shape shape = new RoundRectangle2D.Float(x, y, width, height, arcWidth,
1093             arcHeight);
1094     draw(shape);
1095   }
1096
1097   /**
1098    * Fills a rounded rectangle.
1099    */
1100   public void fillRoundRect(int x, int y, int width, int height,
1101           int arcWidth, int arcHeight)
1102   {
1103     Shape shape = new RoundRectangle2D.Float(x, y, width, height, arcWidth,
1104             arcHeight);
1105     draw(shape, "fill");
1106   }
1107
1108   /**
1109    * Draws an oval.
1110    */
1111   public void drawOval(int x, int y, int width, int height)
1112   {
1113     Shape shape = new Ellipse2D.Float(x, y, width, height);
1114     draw(shape);
1115   }
1116
1117   /**
1118    * Fills an oval.
1119    */
1120   public void fillOval(int x, int y, int width, int height)
1121   {
1122     Shape shape = new Ellipse2D.Float(x, y, width, height);
1123     draw(shape, "fill");
1124   }
1125
1126   /**
1127    * Draws an arc.
1128    */
1129   public void drawArc(int x, int y, int width, int height, int startAngle,
1130           int arcAngle)
1131   {
1132     Shape shape = new Arc2D.Float(x, y, width, height, startAngle,
1133             arcAngle, Arc2D.OPEN);
1134     draw(shape);
1135   }
1136
1137   /**
1138    * Fills an arc.
1139    */
1140   public void fillArc(int x, int y, int width, int height, int startAngle,
1141           int arcAngle)
1142   {
1143     Shape shape = new Arc2D.Float(x, y, width, height, startAngle,
1144             arcAngle, Arc2D.PIE);
1145     draw(shape, "fill");
1146   }
1147
1148   /**
1149    * Draws a polyline.
1150    */
1151   public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
1152   {
1153     if (nPoints > 0)
1154     {
1155       GeneralPath path = new GeneralPath();
1156       path.moveTo(xPoints[0], yPoints[0]);
1157       for (int i = 1; i < nPoints; i++)
1158       {
1159         path.lineTo(xPoints[i], yPoints[i]);
1160       }
1161       draw(path);
1162     }
1163   }
1164
1165   /**
1166    * Draws a polygon made with the specified points.
1167    */
1168   public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
1169   {
1170     Shape shape = new Polygon(xPoints, yPoints, nPoints);
1171     draw(shape);
1172   }
1173
1174   /**
1175    * Draws a polygon.
1176    */
1177   public void drawPolygon(Polygon p)
1178   {
1179     draw(p);
1180   }
1181
1182   /**
1183    * Fills a polygon made with the specified points.
1184    */
1185   public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
1186   {
1187     Shape shape = new Polygon(xPoints, yPoints, nPoints);
1188     draw(shape, "fill");
1189   }
1190
1191   /**
1192    * Fills a polygon.
1193    */
1194   public void fillPolygon(Polygon p)
1195   {
1196     draw(p, "fill");
1197   }
1198
1199   /**
1200    * Draws the specified characters, starting from (x,y)
1201    */
1202   public void drawChars(char[] data, int offset, int length, int x, int y)
1203   {
1204     String string = new String(data, offset, length);
1205     drawString(string, x, y);
1206   }
1207
1208   /**
1209    * Draws the specified bytes, starting from (x,y)
1210    */
1211   public void drawBytes(byte[] data, int offset, int length, int x, int y)
1212   {
1213     String string = new String(data, offset, length);
1214     drawString(string, x, y);
1215   }
1216
1217   /**
1218    * Draws an image.
1219    */
1220   public boolean drawImage(Image img, int x, int y, ImageObserver observer)
1221   {
1222     return drawImage(img, x, y, Color.white, observer);
1223   }
1224
1225   /**
1226    * Draws an image.
1227    */
1228   public boolean drawImage(Image img, int x, int y, int width, int height,
1229           ImageObserver observer)
1230   {
1231     return drawImage(img, x, y, width, height, Color.white, observer);
1232   }
1233
1234   /**
1235    * Draws an image.
1236    */
1237   public boolean drawImage(Image img, int x, int y, Color bgcolor,
1238           ImageObserver observer)
1239   {
1240     int width = img.getWidth(null);
1241     int height = img.getHeight(null);
1242     return drawImage(img, x, y, width, height, bgcolor, observer);
1243   }
1244
1245   /**
1246    * Draws an image.
1247    */
1248   public boolean drawImage(Image img, int x, int y, int width, int height,
1249           Color bgcolor, ImageObserver observer)
1250   {
1251     return drawImage(img, x, y, x + width, y + height, 0, 0, width, height,
1252             bgcolor, observer);
1253   }
1254
1255   /**
1256    * Draws an image.
1257    */
1258   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1259           int sx1, int sy1, int sx2, int sy2, ImageObserver observer)
1260   {
1261     return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2,
1262             Color.white, observer);
1263   }
1264
1265   /**
1266    * Draws an image.
1267    */
1268   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1269           int sx1, int sy1, int sx2, int sy2, Color bgcolor,
1270           ImageObserver observer)
1271   {
1272     if (dx1 >= dx2)
1273     {
1274       throw new IllegalArgumentException("dx1 >= dx2");
1275     }
1276     if (sx1 >= sx2)
1277     {
1278       throw new IllegalArgumentException("sx1 >= sx2");
1279     }
1280     if (dy1 >= dy2)
1281     {
1282       throw new IllegalArgumentException("dy1 >= dy2");
1283     }
1284     if (sy1 >= sy2)
1285     {
1286       throw new IllegalArgumentException("sy1 >= sy2");
1287     }
1288
1289     append("gsave");
1290
1291     int width = sx2 - sx1;
1292     int height = sy2 - sy1;
1293     int destWidth = dx2 - dx1;
1294     int destHeight = dy2 - dy1;
1295
1296     int[] pixels = new int[width * height];
1297     PixelGrabber pg = new PixelGrabber(img, sx1, sy1, sx2 - sx1, sy2 - sy1,
1298             pixels, 0, width);
1299     try
1300     {
1301       pg.grabPixels();
1302     } catch (InterruptedException e)
1303     {
1304       return false;
1305     }
1306
1307     AffineTransform matrix = new AffineTransform(_transform);
1308     matrix.translate(dx1, dy1);
1309     matrix.scale(destWidth / (double) width, destHeight / (double) height);
1310     double[] m = new double[6];
1311     try
1312     {
1313       matrix = matrix.createInverse();
1314     } catch (Exception e)
1315     {
1316         throw new EpsException(MessageManager.formatMessage("exception.eps_unable_to_get_inverse_matrix", new String[]{matrix.toString()}));
1317     }
1318     matrix.scale(1, -1);
1319     matrix.getMatrix(m);
1320     append(width + " " + height + " 8 [" + m[0] + " " + m[1] + " " + m[2]
1321             + " " + m[3] + " " + m[4] + " " + m[5] + "]");
1322     // Fill the background to update the bounding box.
1323     Color oldColor = getColor();
1324     setColor(getBackground());
1325     fillRect(dx1, dy1, destWidth, destHeight);
1326     setColor(oldColor);
1327     append("{currentfile 3 " + width
1328             + " mul string readhexstring pop} bind");
1329     append("false 3 colorimage");
1330     StringBuffer line = new StringBuffer();
1331     for (int y = 0; y < height; y++)
1332     {
1333       for (int x = 0; x < width; x++)
1334       {
1335         Color color = new Color(pixels[x + width * y]);
1336         line.append(toHexString(color.getRed())
1337                 + toHexString(color.getGreen())
1338                 + toHexString(color.getBlue()));
1339         if (line.length() > 64)
1340         {
1341           append(line.toString());
1342           line = new StringBuffer();
1343         }
1344       }
1345     }
1346     if (line.length() > 0)
1347     {
1348       append(line.toString());
1349     }
1350
1351     append("grestore");
1352
1353     return true;
1354   }
1355
1356   /**
1357    * Disposes of all resources used by this EpsGraphics2D object. If this is the
1358    * only remaining EpsGraphics2D instance pointing at a EpsDocument object,
1359    * then the EpsDocument object shall become eligible for garbage collection.
1360    */
1361   public void dispose()
1362   {
1363     _document = null;
1364   }
1365
1366   /**
1367    * Finalizes the object.
1368    */
1369   public void finalize()
1370   {
1371     super.finalize();
1372   }
1373
1374   /**
1375    * Returns the entire contents of the EPS document, complete with headers and
1376    * bounding box. The returned String is suitable for being written directly to
1377    * disk as an EPS file.
1378    */
1379   public String toString()
1380   {
1381     StringWriter writer = new StringWriter();
1382     try
1383     {
1384       _document.write(writer);
1385       _document.flush();
1386       _document.close();
1387     } catch (IOException e)
1388     {
1389       throw new EpsException(e.toString());
1390     }
1391     return writer.toString();
1392   }
1393
1394   /**
1395    * Returns true if the specified rectangular area might intersect the current
1396    * clipping area.
1397    */
1398   public boolean hitClip(int x, int y, int width, int height)
1399   {
1400     if (_clip == null)
1401     {
1402       return true;
1403     }
1404     Rectangle rect = new Rectangle(x, y, width, height);
1405     return hit(rect, _clip, true);
1406   }
1407
1408   /**
1409    * Returns the bounding rectangle of the current clipping area.
1410    */
1411   public Rectangle getClipBounds(Rectangle r)
1412   {
1413     if (_clip == null)
1414     {
1415       return r;
1416     }
1417     Rectangle rect = getClipBounds();
1418     r.setLocation((int) rect.getX(), (int) rect.getY());
1419     r.setSize((int) rect.getWidth(), (int) rect.getHeight());
1420     return r;
1421   }
1422
1423   private Color _color;
1424
1425   private Color _backgroundColor;
1426
1427   private Paint _paint;
1428
1429   private Composite _composite;
1430
1431   private BasicStroke _stroke;
1432
1433   private Font _font;
1434
1435   private Shape _clip;
1436
1437   private AffineTransform _clipTransform;
1438
1439   private AffineTransform _transform;
1440
1441   private boolean _accurateTextMode;
1442
1443   private EpsDocument _document;
1444
1445   private static FontRenderContext _fontRenderContext = new FontRenderContext(
1446           null, false, true);
1447 }