Clover icon

Coverage Report

  1. Project Clover database Mon Nov 11 2024 17:27:16 GMT
  2. Package org.jibble.epsgraphics

File EpsGraphics2D.java

 

Coverage histogram

../../../img/srcFileCovDistChart4.png
48% of files have more coverage

Code metrics

100
387
96
1
1,584
1,015
152
0.39
4.03
96
1.58

Classes

Class Line # Actions
EpsGraphics2D 121 387 152
0.3687821636.9%
 

Contributing tests

No tests hitting this source file were found.

Source view

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  0 toggle public EpsGraphics2D()
132    {
133  0 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  10 toggle public EpsGraphics2D(String title)
141    {
142  10 _document = new EpsDocument(title);
143  10 _backgroundColor = Color.white;
144  10 _clip = null;
145  10 _transform = new AffineTransform();
146  10 _clipTransform = new AffineTransform();
147  10 _accurateTextMode = true;
148  10 setColor(Color.black);
149  10 setPaint(Color.black);
150  10 setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
151  10 setFont(Font.decode(null));
152  10 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  0 toggle public EpsGraphics2D(String title, File file, int minX, int minY,
163    int maxX, int maxY) throws IOException
164    {
165  0 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  10 toggle public EpsGraphics2D(String title, OutputStream outputStream, int minX,
176    int minY, int maxX, int maxY) throws IOException
177    {
178  10 this(title);
179  10 _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  20 toggle protected EpsGraphics2D(EpsGraphics2D g)
188    {
189  20 _document = g._document;
190  20 _backgroundColor = g._backgroundColor;
191  20 _clip = g._clip;
192  20 _clipTransform = (AffineTransform) g._clipTransform.clone();
193  20 _transform = (AffineTransform) g._transform.clone();
194  20 _color = g._color;
195  20 _paint = g._paint;
196  20 _composite = g._composite;
197  20 _font = g._font;
198  20 _stroke = g._stroke;
199  20 _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  0 toggle private void methodNotSupported()
207    {
208  0 EpsException e = new EpsException(MessageManager.formatMessage(
209    "exception.eps_method_not_supported", new String[]
210    { VERSION }));
211  0 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  10 toggle public void setAccurateTextMode(boolean b)
230    {
231  10 _accurateTextMode = b;
232    }
233   
234    /**
235    * Returns whether accurate text mode is being used.
236    */
 
237  32580 toggle public boolean getAccurateTextMode()
238    {
239  32580 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  20 toggle public void flush() throws IOException
247    {
248  20 _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  10 toggle @Override
257    public void close() throws IOException
258    {
259  10 flush();
260  10 _document.close();
261    }
262   
263    /**
264    * Appends a line to the EpsDocument.
265    */
 
266  263640 toggle private void append(String line)
267    {
268  263640 _document.append(this, line);
269    }
270   
271    /**
272    * Returns the point after it has been transformed by the transformation.
273    */
 
274  32580 toggle private Point2D transform(float x, float y)
275    {
276  32580 Point2D result = new Point2D.Float(x, y);
277  32580 result = _transform.transform(result, result);
278  32580 result.setLocation(result.getX(), -result.getY());
279  32580 return result;
280    }
281   
282    /**
283    * Appends the commands required to draw a shape on the EPS document.
284    */
 
285  6970 toggle private void draw(Shape s, String action)
286    {
287   
288  6970 if (s != null)
289    {
290   
291    // Rectangle2D userBounds = s.getBounds2D();
292  6970 if (!_transform.isIdentity())
293    {
294  6950 s = _transform.createTransformedShape(s);
295    }
296   
297    // Update the bounds.
298  6970 if (!action.equals("clip"))
299    {
300  6970 Rectangle2D shapeBounds = s.getBounds2D();
301  6970 Rectangle2D visibleBounds = shapeBounds;
302  6970 if (_clip != null)
303    {
304  0 Rectangle2D clipBounds = _clip.getBounds2D();
305  0 visibleBounds = shapeBounds.createIntersection(clipBounds);
306    }
307  6970 float lineRadius = _stroke.getLineWidth() / 2;
308  6970 float minX = (float) visibleBounds.getMinX() - lineRadius;
309  6970 float minY = (float) visibleBounds.getMinY() - lineRadius;
310  6970 float maxX = (float) visibleBounds.getMaxX() + lineRadius;
311  6970 float maxY = (float) visibleBounds.getMaxY() + lineRadius;
312  6970 _document.updateBounds(minX, -minY);
313  6970 _document.updateBounds(maxX, -maxY);
314    }
315   
316  6970 append("newpath");
317  6970 int type = 0;
318  6970 float[] coords = new float[6];
319  6970 PathIterator it = s.getPathIterator(null);
320  6970 float x0 = 0;
321  6970 float y0 = 0;
322  6970 int count = 0;
323  47390 while (!it.isDone())
324    {
325  40420 type = it.currentSegment(coords);
326  40420 float x1 = coords[0];
327  40420 float y1 = -coords[1];
328  40420 float x2 = coords[2];
329  40420 float y2 = -coords[3];
330  40420 float x3 = coords[4];
331  40420 float y3 = -coords[5];
332   
333  40420 if (type == PathIterator.SEG_CLOSE)
334    {
335  6620 append("closepath");
336  6620 count++;
337    }
338  33800 else if (type == PathIterator.SEG_CUBICTO)
339    {
340  0 append(x1 + " " + y1 + " " + x2 + " " + y2 + " " + x3 + " " + y3
341    + " curveto");
342  0 count++;
343  0 x0 = x3;
344  0 y0 = y3;
345    }
346  33800 else if (type == PathIterator.SEG_LINETO)
347    {
348  26830 append(x1 + " " + y1 + " lineto");
349  26830 count++;
350  26830 x0 = x1;
351  26830 y0 = y1;
352    }
353  6970 else if (type == PathIterator.SEG_MOVETO)
354    {
355  6970 append(x1 + " " + y1 + " moveto");
356  6970 count++;
357  6970 x0 = x1;
358  6970 y0 = y1;
359    }
360  0 else if (type == PathIterator.SEG_QUADTO)
361    {
362    // Convert the quad curve into a cubic.
363  0 float _x1 = x0 + 2 / 3f * (x1 - x0);
364  0 float _y1 = y0 + 2 / 3f * (y1 - y0);
365  0 float _x2 = x1 + 1 / 3f * (x2 - x1);
366  0 float _y2 = y1 + 1 / 3f * (y2 - y1);
367  0 float _x3 = x2;
368  0 float _y3 = y2;
369  0 append(_x1 + " " + _y1 + " " + _x2 + " " + _y2 + " " + _x3 + " "
370    + _y3 + " curveto");
371  0 count++;
372  0 x0 = _x3;
373  0 y0 = _y3;
374    }
375  0 else if (type == PathIterator.WIND_EVEN_ODD)
376    {
377    // Ignore.
378    }
379  0 else if (type == PathIterator.WIND_NON_ZERO)
380    {
381    // Ignore.
382    }
383  40420 it.next();
384    }
385  6970 append(action);
386  6970 append("newpath");
387    }
388    }
389   
390    /**
391    * Returns a hex string that always contains two characters.
392    */
 
393  0 toggle private String toHexString(int n)
394    {
395  0 String result = Integer.toString(n, 16);
396  0 while (result.length() < 2)
397    {
398  0 result = "0" + result;
399    }
400  0 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  0 toggle @Override
410    public void draw3DRect(int x, int y, int width, int height,
411    boolean raised)
412    {
413  0 Color originalColor = getColor();
414  0 Stroke originalStroke = getStroke();
415   
416  0 setStroke(new BasicStroke(1.0f));
417   
418  0 if (raised)
419    {
420  0 setColor(originalColor.brighter());
421    }
422    else
423    {
424  0 setColor(originalColor.darker());
425    }
426   
427  0 drawLine(x, y, x + width, y);
428  0 drawLine(x, y, x, y + height);
429   
430  0 if (raised)
431    {
432  0 setColor(originalColor.darker());
433    }
434    else
435    {
436  0 setColor(originalColor.brighter());
437    }
438   
439  0 drawLine(x + width, y + height, x, y + height);
440  0 drawLine(x + width, y + height, x + width, y);
441   
442  0 setColor(originalColor);
443  0 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  0 toggle @Override
451    public void fill3DRect(int x, int y, int width, int height,
452    boolean raised)
453    {
454  0 Color originalColor = getColor();
455   
456  0 if (raised)
457    {
458  0 setColor(originalColor.brighter());
459    }
460    else
461    {
462  0 setColor(originalColor.darker());
463    }
464  0 draw(new Rectangle(x, y, width, height), "fill");
465  0 setColor(originalColor);
466  0 draw3DRect(x, y, width, height, raised);
467    }
468   
469    /**
470    * Draws a Shape on the EPS document.
471    */
 
472  350 toggle @Override
473    public void draw(Shape s)
474    {
475  350 draw(s, "stroke");
476    }
477   
478    /**
479    * Draws an Image on the EPS document.
480    */
 
481  0 toggle @Override
482    public boolean drawImage(Image img, AffineTransform xform,
483    ImageObserver obs)
484    {
485  0 AffineTransform at = getTransform();
486  0 transform(xform);
487  0 boolean st = drawImage(img, 0, 0, obs);
488  0 setTransform(at);
489  0 return st;
490    }
491   
492    /**
493    * Draws a BufferedImage on the EPS document.
494    */
 
495  0 toggle @Override
496    public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y)
497    {
498  0 BufferedImage img1 = op.filter(img, null);
499  0 drawImage(img1, new AffineTransform(1f, 0f, 0f, 1f, x, y), null);
500    }
501   
502    /**
503    * Draws a RenderedImage on the EPS document.
504    */
 
505  0 toggle @Override
506    public void drawRenderedImage(RenderedImage img, AffineTransform xform)
507    {
508  0 Hashtable properties = new Hashtable();
509  0 String[] names = img.getPropertyNames();
510  0 for (int i = 0; i < names.length; i++)
511    {
512  0 properties.put(names[i], img.getProperty(names[i]));
513    }
514   
515  0 ColorModel cm = img.getColorModel();
516  0 WritableRaster wr = img.copyData(null);
517  0 BufferedImage img1 = new BufferedImage(cm, wr,
518    cm.isAlphaPremultiplied(), properties);
519  0 AffineTransform at = AffineTransform.getTranslateInstance(img.getMinX(),
520    img.getMinY());
521  0 at.preConcatenate(xform);
522  0 drawImage(img1, at, null);
523    }
524   
525    /**
526    * Draws a RenderableImage by invoking its createDefaultRendering method.
527    */
 
528  0 toggle @Override
529    public void drawRenderableImage(RenderableImage img,
530    AffineTransform xform)
531    {
532  0 drawRenderedImage(img.createDefaultRendering(), xform);
533    }
534   
535    /**
536    * Draws a string at (x,y)
537    */
 
538  32580 toggle @Override
539    public void drawString(String str, int x, int y)
540    {
541  32580 drawString(str, (float) x, (float) y);
542    }
543   
544    /**
545    * Draws a string at (x,y)
546    */
 
547  32580 toggle @Override
548    public void drawString(String s, float x, float y)
549    {
550  32580 if (s != null && s.length() > 0)
551    {
552  32580 AttributedString as = new AttributedString(s);
553  32580 as.addAttribute(TextAttribute.FONT, getFont());
554  32580 drawString(as.getIterator(), x, y);
555    }
556    }
557   
558    /**
559    * Draws the characters of an AttributedCharacterIterator, starting from
560    * (x,y).
561    */
 
562  0 toggle @Override
563    public void drawString(AttributedCharacterIterator iterator, int x, int y)
564    {
565  0 drawString(iterator, (float) x, (float) y);
566    }
567   
568    /**
569    * Draws the characters of an AttributedCharacterIterator, starting from
570    * (x,y).
571    */
 
572  32580 toggle @Override
573    public void drawString(AttributedCharacterIterator iterator, float x,
574    float y)
575    {
576  32580 if (getAccurateTextMode())
577    {
578  0 TextLayout layout = new TextLayout(iterator, getFontRenderContext());
579  0 Shape shape = layout
580    .getOutline(AffineTransform.getTranslateInstance(x, y));
581  0 draw(shape, "fill");
582    }
583    else
584    {
585  32580 append("newpath");
586  32580 Point2D location = transform(x, y);
587  32580 append(location.getX() + " " + location.getY() + " moveto");
588  32580 StringBuffer buffer = new StringBuffer();
589  32580 for (char ch = iterator
590  67950 .first(); ch != CharacterIterator.DONE; ch = iterator.next())
591    {
592  35370 if (ch == '(' || ch == ')')
593    {
594  0 buffer.append('\\');
595    }
596  35370 buffer.append(ch);
597    }
598  32580 append("(" + buffer.toString() + ") show");
599    }
600    }
601   
602    /**
603    * Draws a GlyphVector at (x,y)
604    */
 
605  0 toggle @Override
606    public void drawGlyphVector(GlyphVector g, float x, float y)
607    {
608  0 Shape shape = g.getOutline(x, y);
609  0 draw(shape, "fill");
610    }
611   
612    /**
613    * Fills a Shape on the EPS document.
614    */
 
615  0 toggle @Override
616    public void fill(Shape s)
617    {
618  0 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  0 toggle @Override
626    public boolean hit(Rectangle rect, Shape s, boolean onStroke)
627    {
628  0 return s.intersects(rect);
629    }
630   
631    /**
632    * Returns the device configuration associated with this EpsGraphics2D object.
633    */
 
634  0 toggle @Override
635    public GraphicsConfiguration getDeviceConfiguration()
636    {
637  0 GraphicsConfiguration gc = null;
638  0 GraphicsEnvironment ge = GraphicsEnvironment
639    .getLocalGraphicsEnvironment();
640  0 GraphicsDevice[] gds = ge.getScreenDevices();
641  0 for (int i = 0; i < gds.length; i++)
642    {
643  0 GraphicsDevice gd = gds[i];
644  0 GraphicsConfiguration[] gcs = gd.getConfigurations();
645  0 if (gcs.length > 0)
646    {
647  0 return gcs[0];
648    }
649    }
650  0 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  10 toggle @Override
658    public void setComposite(Composite comp)
659    {
660  10 _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  10 toggle @Override
668    public void setPaint(Paint paint)
669    {
670  10 _paint = paint;
671  10 if (paint instanceof Color)
672    {
673  10 setColor((Color) paint);
674    }
675    }
676   
677    /**
678    * Sets the stroke. Only accepts BasicStroke objects (or subclasses of
679    * BasicStroke).
680    */
 
681  9540 toggle @Override
682    public void setStroke(Stroke s)
683    {
684  9540 if (s instanceof BasicStroke)
685    {
686  9540 _stroke = (BasicStroke) s;
687   
688  9540 append(_stroke.getLineWidth() + " setlinewidth");
689  9540 float miterLimit = _stroke.getMiterLimit();
690  9540 if (miterLimit < 1.0f)
691    {
692  0 miterLimit = 1;
693    }
694  9540 append(miterLimit + " setmiterlimit");
695  9540 append(_stroke.getLineJoin() + " setlinejoin");
696  9540 append(_stroke.getEndCap() + " setlinecap");
697   
698  9540 StringBuffer dashes = new StringBuffer();
699  9540 dashes.append("[ ");
700  9540 float[] dashArray = _stroke.getDashArray();
701  9540 if (dashArray != null)
702    {
703  0 for (int i = 0; i < dashArray.length; i++)
704    {
705  0 dashes.append((dashArray[i]) + " ");
706    }
707    }
708  9540 dashes.append("]");
709  9540 append(dashes.toString() + " 0 setdash");
710    }
711    }
712   
713    /**
714    * Sets a rendering hint. These are not used by EpsGraphics2D.
715    */
 
716  30 toggle @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  0 toggle @Override
727    public Object getRenderingHint(RenderingHints.Key hintKey)
728    {
729  0 return null;
730    }
731   
732    /**
733    * Sets the rendering hints. These are ignored by EpsGraphics2D.
734    */
 
735  0 toggle @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  0 toggle @Override
745    public void addRenderingHints(Map hints)
746    {
747    // Do nothing.
748    }
749   
750    /**
751    * Returns the preferences for the rendering algorithms.
752    */
 
753  0 toggle @Override
754    public RenderingHints getRenderingHints()
755    {
756  0 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  9530 toggle @Override
764    public void translate(int x, int y)
765    {
766  9530 translate((double) x, (double) y);
767    }
768   
769    /**
770    * Concatenates the current EpsGraphics2D Transformation with a translation
771    * transform.
772    */
 
773  9530 toggle @Override
774    public void translate(double tx, double ty)
775    {
776  9530 transform(AffineTransform.getTranslateInstance(tx, ty));
777    }
778   
779    /**
780    * Concatenates the current EpsGraphics2D Transform with a rotation transform.
781    */
 
782  0 toggle @Override
783    public void rotate(double theta)
784    {
785  0 rotate(theta, 0, 0);
786    }
787   
788    /**
789    * Concatenates the current EpsGraphics2D Transform with a translated rotation
790    * transform.
791    */
 
792  0 toggle @Override
793    public void rotate(double theta, double x, double y)
794    {
795  0 transform(AffineTransform.getRotateInstance(theta, x, y));
796    }
797   
798    /**
799    * Concatenates the current EpsGraphics2D Transform with a scaling
800    * transformation.
801    */
 
802  0 toggle @Override
803    public void scale(double sx, double sy)
804    {
805  0 transform(AffineTransform.getScaleInstance(sx, sy));
806    }
807   
808    /**
809    * Concatenates the current EpsGraphics2D Transform with a shearing transform.
810    */
 
811  0 toggle @Override
812    public void shear(double shx, double shy)
813    {
814  0 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  9530 toggle @Override
822    public void transform(AffineTransform Tx)
823    {
824  9530 _transform.concatenate(Tx);
825  9530 setTransform(getTransform());
826    }
827   
828    /**
829    * Sets the AffineTransform to be used by this EpsGraphics2D.
830    */
 
831  9530 toggle @Override
832    public void setTransform(AffineTransform Tx)
833    {
834  9530 if (Tx == null)
835    {
836  0 _transform = new AffineTransform();
837    }
838    else
839    {
840  9530 _transform = new AffineTransform(Tx);
841    }
842    // Need to update the stroke and font so they know the scale changed
843  9530 setStroke(getStroke());
844  9530 setFont(getFont());
845    }
846   
847    /**
848    * Gets the AffineTransform used by this EpsGraphics2D.
849    */
 
850  9530 toggle @Override
851    public AffineTransform getTransform()
852    {
853  9530 return new AffineTransform(_transform);
854    }
855   
856    /**
857    * Returns the current Paint of the EpsGraphics2D object.
858    */
 
859  40 toggle @Override
860    public Paint getPaint()
861    {
862  40 return _paint;
863    }
864   
865    /**
866    * returns the current Composite of the EpsGraphics2D object.
867    */
 
868  80 toggle @Override
869    public Composite getComposite()
870    {
871  80 return _composite;
872    }
873   
874    /**
875    * Sets the background color to be used by the clearRect method.
876    */
 
877  0 toggle @Override
878    public void setBackground(Color color)
879    {
880  0 if (color == null)
881    {
882  0 color = Color.black;
883    }
884  0 _backgroundColor = color;
885    }
886   
887    /**
888    * Gets the background color that is used by the clearRect method.
889    */
 
890  40 toggle @Override
891    public Color getBackground()
892    {
893  40 return _backgroundColor;
894    }
895   
896    /**
897    * Returns the Stroke currently used. Guaranteed to be an instance of
898    * BasicStroke.
899    */
 
900  9570 toggle @Override
901    public Stroke getStroke()
902    {
903  9570 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  0 toggle @Override
911    public void clip(Shape s)
912    {
913  0 if (_clip == null)
914    {
915  0 setClip(s);
916    }
917    else
918    {
919  0 Area area = new Area(_clip);
920  0 area.intersect(new Area(s));
921  0 setClip(area);
922    }
923    }
924   
925    /**
926    * Returns the FontRenderContext.
927    */
 
928  20 toggle @Override
929    public FontRenderContext getFontRenderContext()
930    {
931  20 return _fontRenderContext;
932    }
933   
934    // ///////////// Graphics methods ///////////////////////
935   
936    /**
937    * Returns a new Graphics object that is identical to this EpsGraphics2D.
938    */
 
939  20 toggle @Override
940    public Graphics create()
941    {
942  20 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  0 toggle @Override
950    public Graphics create(int x, int y, int width, int height)
951    {
952  0 Graphics g = create();
953  0 g.translate(x, y);
954  0 g.clipRect(0, 0, width, height);
955  0 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  40 toggle @Override
963    public Color getColor()
964    {
965  40 return _color;
966    }
967   
968    /**
969    * Sets the Color to be used when drawing all future shapes, text, etc.
970    */
 
971  39300 toggle @Override
972    public void setColor(Color c)
973    {
974  39300 if (c == null)
975    {
976  0 c = Color.black;
977    }
978  39300 _color = c;
979  39300 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  0 toggle @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  0 toggle @Override
998    public void setXORMode(Color c1)
999    {
1000  0 methodNotSupported();
1001    }
1002   
1003    /**
1004    * Returns the Font currently being used.
1005    */
 
1006  42280 toggle @Override
1007    public Font getFont()
1008    {
1009  42280 return _font;
1010    }
1011   
1012    /**
1013    * Sets the Font to be used in future text.
1014    */
 
1015  17570 toggle @Override
1016    public void setFont(Font font)
1017    {
1018  17570 if (font == null)
1019    {
1020  0 font = Font.decode(null);
1021    }
1022  17570 _font = font;
1023  17570 append("/" + _font.getPSName() + " findfont " + (_font.getSize())
1024    + " scalefont setfont");
1025    }
1026   
1027    /**
1028    * Gets the font metrics of the current font.
1029    */
 
1030  30 toggle @Override
1031    public FontMetrics getFontMetrics()
1032    {
1033  30 return getFontMetrics(getFont());
1034    }
1035   
1036    /**
1037    * Gets the font metrics for the specified font.
1038    */
 
1039  60 toggle @Override
1040    public FontMetrics getFontMetrics(Font f)
1041    {
1042  60 BufferedImage image = new BufferedImage(1, 1,
1043    BufferedImage.TYPE_INT_RGB);
1044  60 Graphics g = image.getGraphics();
1045  60 return g.getFontMetrics(f);
1046    }
1047   
1048    /**
1049    * Returns the bounding rectangle of the current clipping area.
1050    */
 
1051  0 toggle @Override
1052    public Rectangle getClipBounds()
1053    {
1054  0 if (_clip == null)
1055    {
1056  0 return null;
1057    }
1058  0 Rectangle rect = getClip().getBounds();
1059  0 return rect;
1060    }
1061   
1062    /**
1063    * Intersects the current clip with the specified rectangle.
1064    */
 
1065  0 toggle @Override
1066    public void clipRect(int x, int y, int width, int height)
1067    {
1068  0 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  0 toggle @Override
1075    public void setClip(int x, int y, int width, int height)
1076    {
1077  0 setClip(new Rectangle(x, y, width, height));
1078    }
1079   
1080    /**
1081    * Gets the current clipping area.
1082    */
 
1083  40 toggle @Override
1084    public Shape getClip()
1085    {
1086  40 if (_clip == null)
1087    {
1088  40 return null;
1089    }
1090    else
1091    {
1092  0 try
1093    {
1094  0 AffineTransform t = _transform.createInverse();
1095  0 t.concatenate(_clipTransform);
1096  0 return t.createTransformedShape(_clip);
1097    } catch (Exception e)
1098    {
1099  0 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  0 toggle @Override
1110    public void setClip(Shape clip)
1111    {
1112  0 if (clip != null)
1113    {
1114  0 if (_document.isClipSet())
1115    {
1116  0 append("grestore");
1117  0 append("gsave");
1118    }
1119    else
1120    {
1121  0 _document.setClipSet(true);
1122  0 append("gsave");
1123    }
1124  0 draw(clip, "clip");
1125  0 _clip = clip;
1126  0 _clipTransform = (AffineTransform) _transform.clone();
1127    }
1128    else
1129    {
1130  0 if (_document.isClipSet())
1131    {
1132  0 append("grestore");
1133  0 _document.setClipSet(false);
1134    }
1135  0 _clip = null;
1136    }
1137    }
1138   
1139    /**
1140    * <b><i><font color="red">Not implemented</font></i></b> - performs no
1141    * action.
1142    */
 
1143  0 toggle @Override
1144    public void copyArea(int x, int y, int width, int height, int dx, int dy)
1145    {
1146  0 methodNotSupported();
1147    }
1148   
1149    /**
1150    * Draws a straight line from (x1,y1) to (x2,y2).
1151    */
 
1152  350 toggle @Override
1153    public void drawLine(int x1, int y1, int x2, int y2)
1154    {
1155  350 Shape shape = new Line2D.Float(x1, y1, x2, y2);
1156  350 draw(shape);
1157    }
1158   
1159    /**
1160    * Fills a rectangle with top-left corner placed at (x,y).
1161    */
 
1162  6620 toggle @Override
1163    public void fillRect(int x, int y, int width, int height)
1164    {
1165  6620 Shape shape = new Rectangle(x, y, width, height);
1166  6620 draw(shape, "fill");
1167    }
1168   
1169    /**
1170    * Draws a rectangle with top-left corner placed at (x,y).
1171    */
 
1172  0 toggle @Override
1173    public void drawRect(int x, int y, int width, int height)
1174    {
1175  0 Shape shape = new Rectangle(x, y, width, height);
1176  0 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  0 toggle @Override
1184    public void clearRect(int x, int y, int width, int height)
1185    {
1186  0 Color originalColor = getColor();
1187   
1188  0 setColor(getBackground());
1189  0 Shape shape = new Rectangle(x, y, width, height);
1190  0 draw(shape, "fill");
1191   
1192  0 setColor(originalColor);
1193    }
1194   
1195    /**
1196    * Draws a rounded rectangle.
1197    */
 
1198  0 toggle @Override
1199    public void drawRoundRect(int x, int y, int width, int height,
1200    int arcWidth, int arcHeight)
1201    {
1202  0 Shape shape = new RoundRectangle2D.Float(x, y, width, height, arcWidth,
1203    arcHeight);
1204  0 draw(shape);
1205    }
1206   
1207    /**
1208    * Fills a rounded rectangle.
1209    */
 
1210  0 toggle @Override
1211    public void fillRoundRect(int x, int y, int width, int height,
1212    int arcWidth, int arcHeight)
1213    {
1214  0 Shape shape = new RoundRectangle2D.Float(x, y, width, height, arcWidth,
1215    arcHeight);
1216  0 draw(shape, "fill");
1217    }
1218   
1219    /**
1220    * Draws an oval.
1221    */
 
1222  0 toggle @Override
1223    public void drawOval(int x, int y, int width, int height)
1224    {
1225  0 Shape shape = new Ellipse2D.Float(x, y, width, height);
1226  0 draw(shape);
1227    }
1228   
1229    /**
1230    * Fills an oval.
1231    */
 
1232  0 toggle @Override
1233    public void fillOval(int x, int y, int width, int height)
1234    {
1235  0 Shape shape = new Ellipse2D.Float(x, y, width, height);
1236  0 draw(shape, "fill");
1237    }
1238   
1239    /**
1240    * Draws an arc.
1241    */
 
1242  0 toggle @Override
1243    public void drawArc(int x, int y, int width, int height, int startAngle,
1244    int arcAngle)
1245    {
1246  0 Shape shape = new Arc2D.Float(x, y, width, height, startAngle, arcAngle,
1247    Arc2D.OPEN);
1248  0 draw(shape);
1249    }
1250   
1251    /**
1252    * Fills an arc.
1253    */
 
1254  0 toggle @Override
1255    public void fillArc(int x, int y, int width, int height, int startAngle,
1256    int arcAngle)
1257    {
1258  0 Shape shape = new Arc2D.Float(x, y, width, height, startAngle, arcAngle,
1259    Arc2D.PIE);
1260  0 draw(shape, "fill");
1261    }
1262   
1263    /**
1264    * Draws a polyline.
1265    */
 
1266  0 toggle @Override
1267    public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
1268    {
1269  0 if (nPoints > 0)
1270    {
1271  0 GeneralPath path = new GeneralPath();
1272  0 path.moveTo(xPoints[0], yPoints[0]);
1273  0 for (int i = 1; i < nPoints; i++)
1274    {
1275  0 path.lineTo(xPoints[i], yPoints[i]);
1276    }
1277  0 draw(path);
1278    }
1279    }
1280   
1281    /**
1282    * Draws a polygon made with the specified points.
1283    */
 
1284  0 toggle @Override
1285    public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
1286    {
1287  0 Shape shape = new Polygon(xPoints, yPoints, nPoints);
1288  0 draw(shape);
1289    }
1290   
1291    /**
1292    * Draws a polygon.
1293    */
 
1294  0 toggle @Override
1295    public void drawPolygon(Polygon p)
1296    {
1297  0 draw(p);
1298    }
1299   
1300    /**
1301    * Fills a polygon made with the specified points.
1302    */
 
1303  0 toggle @Override
1304    public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
1305    {
1306  0 Shape shape = new Polygon(xPoints, yPoints, nPoints);
1307  0 draw(shape, "fill");
1308    }
1309   
1310    /**
1311    * Fills a polygon.
1312    */
 
1313  0 toggle @Override
1314    public void fillPolygon(Polygon p)
1315    {
1316  0 draw(p, "fill");
1317    }
1318   
1319    /**
1320    * Draws the specified characters, starting from (x,y)
1321    */
 
1322  3980 toggle @Override
1323    public void drawChars(char[] data, int offset, int length, int x, int y)
1324    {
1325  3980 String string = new String(data, offset, length);
1326  3980 drawString(string, x, y);
1327    }
1328   
1329    /**
1330    * Draws the specified bytes, starting from (x,y)
1331    */
 
1332  0 toggle @Override
1333    public void drawBytes(byte[] data, int offset, int length, int x, int y)
1334    {
1335  0 String string = new String(data, offset, length);
1336  0 drawString(string, x, y);
1337    }
1338   
1339    /**
1340    * Draws an image.
1341    */
 
1342  0 toggle @Override
1343    public boolean drawImage(Image img, int x, int y, ImageObserver observer)
1344    {
1345  0 return drawImage(img, x, y, Color.white, observer);
1346    }
1347   
1348    /**
1349    * Draws an image.
1350    */
 
1351  0 toggle @Override
1352    public boolean drawImage(Image img, int x, int y, int width, int height,
1353    ImageObserver observer)
1354    {
1355  0 return drawImage(img, x, y, width, height, Color.white, observer);
1356    }
1357   
1358    /**
1359    * Draws an image.
1360    */
 
1361  0 toggle @Override
1362    public boolean drawImage(Image img, int x, int y, Color bgcolor,
1363    ImageObserver observer)
1364    {
1365  0 int width = img.getWidth(null);
1366  0 int height = img.getHeight(null);
1367  0 return drawImage(img, x, y, width, height, bgcolor, observer);
1368    }
1369   
1370    /**
1371    * Draws an image.
1372    */
 
1373  0 toggle @Override
1374    public boolean drawImage(Image img, int x, int y, int width, int height,
1375    Color bgcolor, ImageObserver observer)
1376    {
1377  0 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  0 toggle @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  0 return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2,
1389    Color.white, observer);
1390    }
1391   
1392    /**
1393    * Draws an image.
1394    */
 
1395  0 toggle @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  0 if (dx1 >= dx2)
1401    {
1402  0 throw new IllegalArgumentException("dx1 >= dx2");
1403    }
1404  0 if (sx1 >= sx2)
1405    {
1406  0 throw new IllegalArgumentException("sx1 >= sx2");
1407    }
1408  0 if (dy1 >= dy2)
1409    {
1410  0 throw new IllegalArgumentException("dy1 >= dy2");
1411    }
1412  0 if (sy1 >= sy2)
1413    {
1414  0 throw new IllegalArgumentException("sy1 >= sy2");
1415    }
1416   
1417  0 append("gsave");
1418   
1419  0 int width = sx2 - sx1;
1420  0 int height = sy2 - sy1;
1421  0 int destWidth = dx2 - dx1;
1422  0 int destHeight = dy2 - dy1;
1423   
1424  0 int[] pixels = new int[width * height];
1425  0 PixelGrabber pg = new PixelGrabber(img, sx1, sy1, sx2 - sx1, sy2 - sy1,
1426    pixels, 0, width);
1427  0 try
1428    {
1429  0 pg.grabPixels();
1430    } catch (InterruptedException e)
1431    {
1432  0 return false;
1433    }
1434   
1435  0 AffineTransform matrix = new AffineTransform(_transform);
1436  0 matrix.translate(dx1, dy1);
1437  0 matrix.scale(destWidth / (double) width, destHeight / (double) height);
1438  0 double[] m = new double[6];
1439  0 try
1440    {
1441  0 matrix = matrix.createInverse();
1442    } catch (Exception e)
1443    {
1444  0 throw new EpsException(MessageManager.formatMessage(
1445    "exception.eps_unable_to_get_inverse_matrix", new String[]
1446    { matrix.toString() }));
1447    }
1448  0 matrix.scale(1, -1);
1449  0 matrix.getMatrix(m);
1450  0 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  0 Color oldColor = getColor();
1454  0 setColor(getBackground());
1455  0 fillRect(dx1, dy1, destWidth, destHeight);
1456  0 setColor(oldColor);
1457  0 append("{currentfile 3 " + width
1458    + " mul string readhexstring pop} bind");
1459  0 append("false 3 colorimage");
1460  0 StringBuffer line = new StringBuffer();
1461  0 for (int y = 0; y < height; y++)
1462    {
1463  0 for (int x = 0; x < width; x++)
1464    {
1465  0 Color color = new Color(pixels[x + width * y]);
1466  0 line.append(
1467    toHexString(color.getRed()) + toHexString(color.getGreen())
1468    + toHexString(color.getBlue()));
1469  0 if (line.length() > 64)
1470    {
1471  0 append(line.toString());
1472  0 line = new StringBuffer();
1473    }
1474    }
1475    }
1476  0 if (line.length() > 0)
1477    {
1478  0 append(line.toString());
1479    }
1480   
1481  0 append("grestore");
1482   
1483  0 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  12 toggle @Override
1492    public void dispose()
1493    {
1494  12 _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  0 toggle @Override
1514    public String toString()
1515    {
1516  0 StringWriter writer = new StringWriter();
1517  0 try
1518    {
1519  0 _document.write(writer);
1520  0 _document.flush();
1521  0 _document.close();
1522    } catch (IOException e)
1523    {
1524  0 throw new EpsException(e.toString());
1525    }
1526  0 return writer.toString();
1527    }
1528   
1529    /**
1530    * Returns true if the specified rectangular area might intersect the current
1531    * clipping area.
1532    */
 
1533  0 toggle @Override
1534    public boolean hitClip(int x, int y, int width, int height)
1535    {
1536  0 if (_clip == null)
1537    {
1538  0 return true;
1539    }
1540  0 Rectangle rect = new Rectangle(x, y, width, height);
1541  0 return hit(rect, _clip, true);
1542    }
1543   
1544    /**
1545    * Returns the bounding rectangle of the current clipping area.
1546    */
 
1547  0 toggle @Override
1548    public Rectangle getClipBounds(Rectangle r)
1549    {
1550  0 if (_clip == null)
1551    {
1552  0 return r;
1553    }
1554  0 Rectangle rect = getClipBounds();
1555  0 r.setLocation((int) rect.getX(), (int) rect.getY());
1556  0 r.setSize((int) rect.getWidth(), (int) rect.getHeight());
1557  0 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    }