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