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