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