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