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