Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package org.jibble.epsgraphics

File EpsGraphics2D.java

 

Coverage histogram

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

Code metrics

100
387
96
1
1,579
1,010
152
0.39
4.03
96
1.58

Classes

Class Line # Actions
EpsGraphics2D 121 387 152
0.3156089231.6%
 

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