JAL-1517 fix copyright for 2.8.2
[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 java.io.*;
24 import java.text.*;
25 import java.util.*;
26
27 import java.awt.*;
28 import java.awt.font.*;
29 import java.awt.geom.*;
30 import java.awt.image.*;
31 import java.awt.image.renderable.*;
32
33 /**
34  * EpsGraphics2D is suitable for creating high quality EPS graphics for use in
35  * documents and papers, and can be used just like a standard Graphics2D object.
36  * <p>
37  * Many Java programs use Graphics2D to draw stuff on the screen, and while it
38  * is easy to save the output as a png or jpeg file, it is a little harder to
39  * export it as an EPS for including in a document or paper.
40  * <p>
41  * This class makes the whole process extremely easy, because you can use it as
42  * if it's a Graphics2D object. The only difference is that all of the
43  * implemented methods create EPS output, which means the diagrams you draw can
44  * be resized without leading to any of the jagged edges you may see when
45  * resizing pixel-based images, such as jpeg and png files.
46  * <p>
47  * Example usage:
48  * <p>
49  * 
50  * <pre>
51  * Graphics2D g = new EpsGraphics2D();
52  * g.setColor(Color.black);
53  * 
54  * // Line thickness 2.
55  * g.setStroke(new BasicStroke(2.0f));
56  * 
57  * // Draw a line.
58  * g.drawLine(10, 10, 50, 10);
59  * 
60  * // Fill a rectangle in blue
61  * g.setColor(Color.blue);
62  * g.fillRect(10, 0, 20, 20);
63  * 
64  * // Get the EPS output.
65  * String output = g.toString();
66  * </pre>
67  * 
68  * <p>
69  * You do not need to worry about the size of the canvas when drawing on a
70  * EpsGraphics2D object. The bounding box of the EPS document will automatically
71  * resize to accomodate new items that you draw.
72  * <p>
73  * Not all methods are implemented yet. Those that are not are clearly labelled.
74  * <p>
75  * Copyright Paul Mutton, <a
76  * href="http://www.jibble.org/">http://www.jibble.org/</a>
77  * 
78  */
79 public class EpsGraphics2D extends java.awt.Graphics2D
80 {
81
82   public static final String VERSION = "0.8.8";
83
84   /**
85    * Constructs a new EPS document that is initially empty and can be drawn on
86    * like a Graphics2D object. The EPS document is stored in memory.
87    */
88   public EpsGraphics2D()
89   {
90     this("Untitled");
91   }
92
93   /**
94    * Constructs a new EPS document that is initially empty and can be drawn on
95    * like a Graphics2D object. The EPS document is stored in memory.
96    */
97   public EpsGraphics2D(String title)
98   {
99     _document = new EpsDocument(title);
100     _backgroundColor = Color.white;
101     _clip = null;
102     _transform = new AffineTransform();
103     _clipTransform = new AffineTransform();
104     _accurateTextMode = true;
105     setColor(Color.black);
106     setPaint(Color.black);
107     setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
108     setFont(Font.decode(null));
109     setStroke(new BasicStroke());
110   }
111
112   /**
113    * Constructs a new EPS document that is initially empty and can be drawn on
114    * like a Graphics2D object. The EPS document is written to the file as it
115    * goes, which reduces memory usage. The bounding box of the document is fixed
116    * and specified at construction time by minX,minY,maxX,maxY. The file is
117    * flushed and closed when the close() method is called.
118    */
119   public EpsGraphics2D(String title, File file, int minX, int minY,
120           int maxX, int maxY) throws IOException
121   {
122     this(title, new FileOutputStream(file), minX, minY, maxX, maxY);
123   }
124
125   /**
126    * Constructs a new EPS document that is initially empty and can be drawn on
127    * like a Graphics2D object. The EPS document is written to the output stream
128    * as it goes, which reduces memory usage. The bounding box of the document is
129    * fixed and specified at construction time by minX,minY,maxX,maxY. The output
130    * stream is flushed and closed when the close() method is called.
131    */
132   public EpsGraphics2D(String title, OutputStream outputStream, int minX,
133           int minY, int maxX, int maxY) throws IOException
134   {
135     this(title);
136     _document = new EpsDocument(title, outputStream, minX, minY, maxX, maxY);
137   }
138
139   /**
140    * Constructs a new EpsGraphics2D instance that is a copy of the supplied
141    * argument and points at the same EpsDocument.
142    */
143   protected EpsGraphics2D(EpsGraphics2D g)
144   {
145     _document = g._document;
146     _backgroundColor = g._backgroundColor;
147     _clip = g._clip;
148     _clipTransform = (AffineTransform) g._clipTransform.clone();
149     _transform = (AffineTransform) g._transform.clone();
150     _color = g._color;
151     _paint = g._paint;
152     _composite = g._composite;
153     _font = g._font;
154     _stroke = g._stroke;
155     _accurateTextMode = g._accurateTextMode;
156   }
157
158   /**
159    * This method is called to indicate that a particular method is not supported
160    * yet. The stack trace is printed to the standard output.
161    */
162   private void methodNotSupported()
163   {
164     EpsException e = new EpsException(
165             "Method not currently supported by EpsGraphics2D version "
166                     + 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("Unable to get inverse of matrix: "
999                 + _transform);
1000       }
1001     }
1002   }
1003
1004   /**
1005    * Sets the current clipping area to an arbitrary clip shape.
1006    */
1007   public void setClip(Shape clip)
1008   {
1009     if (clip != null)
1010     {
1011       if (_document.isClipSet())
1012       {
1013         append("grestore");
1014         append("gsave");
1015       }
1016       else
1017       {
1018         _document.setClipSet(true);
1019         append("gsave");
1020       }
1021       draw(clip, "clip");
1022       _clip = clip;
1023       _clipTransform = (AffineTransform) _transform.clone();
1024     }
1025     else
1026     {
1027       if (_document.isClipSet())
1028       {
1029         append("grestore");
1030         _document.setClipSet(false);
1031       }
1032       _clip = null;
1033     }
1034   }
1035
1036   /**
1037    * <b><i><font color="red">Not implemented</font></i></b> - performs no
1038    * action.
1039    */
1040   public void copyArea(int x, int y, int width, int height, int dx, int dy)
1041   {
1042     methodNotSupported();
1043   }
1044
1045   /**
1046    * Draws a straight line from (x1,y1) to (x2,y2).
1047    */
1048   public void drawLine(int x1, int y1, int x2, int y2)
1049   {
1050     Shape shape = new Line2D.Float(x1, y1, x2, y2);
1051     draw(shape);
1052   }
1053
1054   /**
1055    * Fills a rectangle with top-left corner placed at (x,y).
1056    */
1057   public void fillRect(int x, int y, int width, int height)
1058   {
1059     Shape shape = new Rectangle(x, y, width, height);
1060     draw(shape, "fill");
1061   }
1062
1063   /**
1064    * Draws a rectangle with top-left corner placed at (x,y).
1065    */
1066   public void drawRect(int x, int y, int width, int height)
1067   {
1068     Shape shape = new Rectangle(x, y, width, height);
1069     draw(shape);
1070   }
1071
1072   /**
1073    * Clears a rectangle with top-left corner placed at (x,y) using the current
1074    * background color.
1075    */
1076   public void clearRect(int x, int y, int width, int height)
1077   {
1078     Color originalColor = getColor();
1079
1080     setColor(getBackground());
1081     Shape shape = new Rectangle(x, y, width, height);
1082     draw(shape, "fill");
1083
1084     setColor(originalColor);
1085   }
1086
1087   /**
1088    * Draws a rounded rectangle.
1089    */
1090   public void drawRoundRect(int x, int y, int width, int height,
1091           int arcWidth, int arcHeight)
1092   {
1093     Shape shape = new RoundRectangle2D.Float(x, y, width, height, arcWidth,
1094             arcHeight);
1095     draw(shape);
1096   }
1097
1098   /**
1099    * Fills a rounded rectangle.
1100    */
1101   public void fillRoundRect(int x, int y, int width, int height,
1102           int arcWidth, int arcHeight)
1103   {
1104     Shape shape = new RoundRectangle2D.Float(x, y, width, height, arcWidth,
1105             arcHeight);
1106     draw(shape, "fill");
1107   }
1108
1109   /**
1110    * Draws an oval.
1111    */
1112   public void drawOval(int x, int y, int width, int height)
1113   {
1114     Shape shape = new Ellipse2D.Float(x, y, width, height);
1115     draw(shape);
1116   }
1117
1118   /**
1119    * Fills an oval.
1120    */
1121   public void fillOval(int x, int y, int width, int height)
1122   {
1123     Shape shape = new Ellipse2D.Float(x, y, width, height);
1124     draw(shape, "fill");
1125   }
1126
1127   /**
1128    * Draws an arc.
1129    */
1130   public void drawArc(int x, int y, int width, int height, int startAngle,
1131           int arcAngle)
1132   {
1133     Shape shape = new Arc2D.Float(x, y, width, height, startAngle,
1134             arcAngle, Arc2D.OPEN);
1135     draw(shape);
1136   }
1137
1138   /**
1139    * Fills an arc.
1140    */
1141   public void fillArc(int x, int y, int width, int height, int startAngle,
1142           int arcAngle)
1143   {
1144     Shape shape = new Arc2D.Float(x, y, width, height, startAngle,
1145             arcAngle, Arc2D.PIE);
1146     draw(shape, "fill");
1147   }
1148
1149   /**
1150    * Draws a polyline.
1151    */
1152   public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
1153   {
1154     if (nPoints > 0)
1155     {
1156       GeneralPath path = new GeneralPath();
1157       path.moveTo(xPoints[0], yPoints[0]);
1158       for (int i = 1; i < nPoints; i++)
1159       {
1160         path.lineTo(xPoints[i], yPoints[i]);
1161       }
1162       draw(path);
1163     }
1164   }
1165
1166   /**
1167    * Draws a polygon made with the specified points.
1168    */
1169   public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
1170   {
1171     Shape shape = new Polygon(xPoints, yPoints, nPoints);
1172     draw(shape);
1173   }
1174
1175   /**
1176    * Draws a polygon.
1177    */
1178   public void drawPolygon(Polygon p)
1179   {
1180     draw(p);
1181   }
1182
1183   /**
1184    * Fills a polygon made with the specified points.
1185    */
1186   public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
1187   {
1188     Shape shape = new Polygon(xPoints, yPoints, nPoints);
1189     draw(shape, "fill");
1190   }
1191
1192   /**
1193    * Fills a polygon.
1194    */
1195   public void fillPolygon(Polygon p)
1196   {
1197     draw(p, "fill");
1198   }
1199
1200   /**
1201    * Draws the specified characters, starting from (x,y)
1202    */
1203   public void drawChars(char[] data, int offset, int length, int x, int y)
1204   {
1205     String string = new String(data, offset, length);
1206     drawString(string, x, y);
1207   }
1208
1209   /**
1210    * Draws the specified bytes, starting from (x,y)
1211    */
1212   public void drawBytes(byte[] data, int offset, int length, int x, int y)
1213   {
1214     String string = new String(data, offset, length);
1215     drawString(string, x, y);
1216   }
1217
1218   /**
1219    * Draws an image.
1220    */
1221   public boolean drawImage(Image img, int x, int y, ImageObserver observer)
1222   {
1223     return drawImage(img, x, y, Color.white, observer);
1224   }
1225
1226   /**
1227    * Draws an image.
1228    */
1229   public boolean drawImage(Image img, int x, int y, int width, int height,
1230           ImageObserver observer)
1231   {
1232     return drawImage(img, x, y, width, height, Color.white, observer);
1233   }
1234
1235   /**
1236    * Draws an image.
1237    */
1238   public boolean drawImage(Image img, int x, int y, Color bgcolor,
1239           ImageObserver observer)
1240   {
1241     int width = img.getWidth(null);
1242     int height = img.getHeight(null);
1243     return drawImage(img, x, y, width, height, bgcolor, observer);
1244   }
1245
1246   /**
1247    * Draws an image.
1248    */
1249   public boolean drawImage(Image img, int x, int y, int width, int height,
1250           Color bgcolor, ImageObserver observer)
1251   {
1252     return drawImage(img, x, y, x + width, y + height, 0, 0, width, height,
1253             bgcolor, observer);
1254   }
1255
1256   /**
1257    * Draws an image.
1258    */
1259   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1260           int sx1, int sy1, int sx2, int sy2, ImageObserver observer)
1261   {
1262     return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2,
1263             Color.white, observer);
1264   }
1265
1266   /**
1267    * Draws an image.
1268    */
1269   public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
1270           int sx1, int sy1, int sx2, int sy2, Color bgcolor,
1271           ImageObserver observer)
1272   {
1273     if (dx1 >= dx2)
1274     {
1275       throw new IllegalArgumentException("dx1 >= dx2");
1276     }
1277     if (sx1 >= sx2)
1278     {
1279       throw new IllegalArgumentException("sx1 >= sx2");
1280     }
1281     if (dy1 >= dy2)
1282     {
1283       throw new IllegalArgumentException("dy1 >= dy2");
1284     }
1285     if (sy1 >= sy2)
1286     {
1287       throw new IllegalArgumentException("sy1 >= sy2");
1288     }
1289
1290     append("gsave");
1291
1292     int width = sx2 - sx1;
1293     int height = sy2 - sy1;
1294     int destWidth = dx2 - dx1;
1295     int destHeight = dy2 - dy1;
1296
1297     int[] pixels = new int[width * height];
1298     PixelGrabber pg = new PixelGrabber(img, sx1, sy1, sx2 - sx1, sy2 - sy1,
1299             pixels, 0, width);
1300     try
1301     {
1302       pg.grabPixels();
1303     } catch (InterruptedException e)
1304     {
1305       return false;
1306     }
1307
1308     AffineTransform matrix = new AffineTransform(_transform);
1309     matrix.translate(dx1, dy1);
1310     matrix.scale(destWidth / (double) width, destHeight / (double) height);
1311     double[] m = new double[6];
1312     try
1313     {
1314       matrix = matrix.createInverse();
1315     } catch (Exception e)
1316     {
1317       throw new EpsException("Unable to get inverse of matrix: " + matrix);
1318     }
1319     matrix.scale(1, -1);
1320     matrix.getMatrix(m);
1321     append(width + " " + height + " 8 [" + m[0] + " " + m[1] + " " + m[2]
1322             + " " + m[3] + " " + m[4] + " " + m[5] + "]");
1323     // Fill the background to update the bounding box.
1324     Color oldColor = getColor();
1325     setColor(getBackground());
1326     fillRect(dx1, dy1, destWidth, destHeight);
1327     setColor(oldColor);
1328     append("{currentfile 3 " + width
1329             + " mul string readhexstring pop} bind");
1330     append("false 3 colorimage");
1331     StringBuffer line = new StringBuffer();
1332     for (int y = 0; y < height; y++)
1333     {
1334       for (int x = 0; x < width; x++)
1335       {
1336         Color color = new Color(pixels[x + width * y]);
1337         line.append(toHexString(color.getRed())
1338                 + toHexString(color.getGreen())
1339                 + toHexString(color.getBlue()));
1340         if (line.length() > 64)
1341         {
1342           append(line.toString());
1343           line = new StringBuffer();
1344         }
1345       }
1346     }
1347     if (line.length() > 0)
1348     {
1349       append(line.toString());
1350     }
1351
1352     append("grestore");
1353
1354     return true;
1355   }
1356
1357   /**
1358    * Disposes of all resources used by this EpsGraphics2D object. If this is the
1359    * only remaining EpsGraphics2D instance pointing at a EpsDocument object,
1360    * then the EpsDocument object shall become eligible for garbage collection.
1361    */
1362   public void dispose()
1363   {
1364     _document = null;
1365   }
1366
1367   /**
1368    * Finalizes the object.
1369    */
1370   public void finalize()
1371   {
1372     super.finalize();
1373   }
1374
1375   /**
1376    * Returns the entire contents of the EPS document, complete with headers and
1377    * bounding box. The returned String is suitable for being written directly to
1378    * disk as an EPS file.
1379    */
1380   public String toString()
1381   {
1382     StringWriter writer = new StringWriter();
1383     try
1384     {
1385       _document.write(writer);
1386       _document.flush();
1387       _document.close();
1388     } catch (IOException e)
1389     {
1390       throw new EpsException(e.toString());
1391     }
1392     return writer.toString();
1393   }
1394
1395   /**
1396    * Returns true if the specified rectangular area might intersect the current
1397    * clipping area.
1398    */
1399   public boolean hitClip(int x, int y, int width, int height)
1400   {
1401     if (_clip == null)
1402     {
1403       return true;
1404     }
1405     Rectangle rect = new Rectangle(x, y, width, height);
1406     return hit(rect, _clip, true);
1407   }
1408
1409   /**
1410    * Returns the bounding rectangle of the current clipping area.
1411    */
1412   public Rectangle getClipBounds(Rectangle r)
1413   {
1414     if (_clip == null)
1415     {
1416       return r;
1417     }
1418     Rectangle rect = getClipBounds();
1419     r.setLocation((int) rect.getX(), (int) rect.getY());
1420     r.setSize((int) rect.getWidth(), (int) rect.getHeight());
1421     return r;
1422   }
1423
1424   private Color _color;
1425
1426   private Color _backgroundColor;
1427
1428   private Paint _paint;
1429
1430   private Composite _composite;
1431
1432   private BasicStroke _stroke;
1433
1434   private Font _font;
1435
1436   private Shape _clip;
1437
1438   private AffineTransform _clipTransform;
1439
1440   private AffineTransform _transform;
1441
1442   private boolean _accurateTextMode;
1443
1444   private EpsDocument _document;
1445
1446   private static FontRenderContext _fontRenderContext = new FontRenderContext(
1447           null, false, true);
1448 }