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