View Javadoc

1   // Copyright 2000-2007, FreeHEP
2   package org.freehep.graphicsio;
3   
4   import java.awt.AlphaComposite;
5   import java.awt.BasicStroke;
6   import java.awt.Color;
7   import java.awt.Component;
8   import java.awt.Composite;
9   import java.awt.Dimension;
10  import java.awt.Font;
11  import java.awt.FontMetrics;
12  import java.awt.GradientPaint;
13  import java.awt.GraphicsConfiguration;
14  import java.awt.Image;
15  import java.awt.MediaTracker;
16  import java.awt.Paint;
17  import java.awt.Panel;
18  import java.awt.Rectangle;
19  import java.awt.RenderingHints;
20  import java.awt.Shape;
21  import java.awt.Stroke;
22  import java.awt.TexturePaint;
23  import java.awt.Toolkit;
24  import java.awt.font.FontRenderContext;
25  import java.awt.font.GlyphVector;
26  import java.awt.font.TextLayout;
27  import java.awt.geom.AffineTransform;
28  import java.awt.geom.Area;
29  import java.awt.geom.GeneralPath;
30  import java.awt.geom.NoninvertibleTransformException;
31  import java.awt.geom.Rectangle2D;
32  import java.awt.image.BufferedImage;
33  import java.awt.image.BufferedImageOp;
34  import java.awt.image.CropImageFilter;
35  import java.awt.image.FilteredImageSource;
36  import java.awt.image.ImageFilter;
37  import java.awt.image.ImageObserver;
38  import java.awt.image.RenderedImage;
39  import java.awt.image.renderable.RenderContext;
40  import java.awt.image.renderable.RenderableImage;
41  import java.io.IOException;
42  import java.text.AttributedCharacterIterator;
43  import java.util.Arrays;
44  import java.util.Map;
45  
46  import org.freehep.graphics2d.font.FontEncoder;
47  import org.freehep.graphics2d.font.FontUtilities;
48  import org.freehep.util.images.ImageUtilities;
49  
50  /**
51   * This class provides an abstract VectorGraphicsIO class for specific output
52   * drivers.
53   *
54   * @author Charles Loomis
55   * @author Mark Donszelmann
56   * @author Steffen Greiffenberg
57   * @version $Id: AbstractVectorGraphicsIO.java 12625 2007-06-08 21:43:44Z duns $
58   */
59  public abstract class AbstractVectorGraphicsIO extends VectorGraphicsIO {
60  
61      private static final String rootKey = AbstractVectorGraphicsIO.class
62              .getName();
63  
64      public static final String EMIT_WARNINGS = rootKey + ".EMIT_WARNINGS";
65  
66      public static final String TEXT_AS_SHAPES = rootKey + "." + FontConstants.TEXT_AS_SHAPES;
67  
68      public static final String EMIT_ERRORS = rootKey + ".EMIT_ERRORS";
69  
70      public static final String CLIP = rootKey+".CLIP";
71  
72      /*
73       * ================================================================================
74       * Table of Contents: ------------------ 1. Constructors & Factory Methods
75       * 2. Document Settings 3. Header, Trailer, Multipage & Comments 3.1 Header &
76       * Trailer 3.2 MultipageDocument methods 4. Create & Dispose 5. Drawing
77       * Methods 5.1. shapes (draw/fill) 5.1.1. lines, rectangles, round
78       * rectangles 5.1.2. polylines, polygons 5.1.3. ovals, arcs 5.1.4. shapes
79       * 5.2. Images 5.3. Strings 6. Transformations 7. Clipping 8. Graphics State /
80       * Settings 8.1. stroke/linewidth 8.2. paint/color 8.3. font 8.4. rendering
81       * hints 9. Auxiliary 10. Private/Utility Methods
82       * ================================================================================
83       */
84  
85      private Dimension size;
86  
87      private Component component;
88  
89      private boolean doRestoreOnDispose;
90  
91      private Rectangle deviceClip;
92  
93      /**
94       * Untransformed clipping Area defined by the user
95       */
96      private Area userClip;
97  
98      private AffineTransform currentTransform;
99      
100     // only for use in writeSetTransform to calculate the difference.
101 	private AffineTransform oldTransform = new AffineTransform();
102 
103     private Composite currentComposite;
104 
105     private Stroke currentStroke;
106 
107     private RenderingHints hints;
108 
109     /*
110      * ================================================================================
111      * 1. Constructors & Factory Methods
112      * ================================================================================
113      */
114 
115     /**
116      * Constructs a Graphics context with the following graphics state:
117      * <UL>
118      * <LI>Paint: black
119      * <LI>Font: Dailog, Plain, 12pt
120      * <LI>Stroke: Linewidth 1.0; No Dashing; Miter Join Style; Miter Limit 10;
121      * Square Endcaps;
122      * <LI>Transform: Identity
123      * <LI>Composite: AlphaComposite.SRC_OVER
124      * <LI>Clip: Rectangle(0, 0, size.width, size.height)
125      * </UL>
126      *
127      * @param size rectangle specifying the bounds of the image
128      * @param doRestoreOnDispose true if writeGraphicsRestore() should be called
129      *        when this graphics context is disposed of.
130      */
131     protected AbstractVectorGraphicsIO(Dimension size,
132             boolean doRestoreOnDispose) {
133         super();
134 
135         this.size = size;
136         this.component = null;
137         this.doRestoreOnDispose = doRestoreOnDispose;
138 
139         deviceClip = (size != null ? new Rectangle(0, 0, size.width,
140                 size.height) : null);
141         userClip = null;
142         currentTransform = new AffineTransform();
143         currentComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER);
144         currentStroke = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE,
145                 BasicStroke.JOIN_MITER, 10.0f, null, 0.0f);
146 
147         super.setColor(Color.BLACK);
148         super.setBackground(Color.BLACK);
149         super.setFont(new Font("Dialog", Font.PLAIN, 12));
150 
151         // Initialize the rendering hints.
152         hints = new RenderingHints(null);
153     }
154 
155     /**
156      * Constructs a Graphics context with the following graphics state:
157      * <UL>
158      * <LI>Paint: The color of the component.
159      * <LI>Font: The font of the component.
160      * <LI>Stroke: Linewidth 1.0; No Dashing; Miter Join Style; Miter Limit 10;
161      * Square Endcaps;
162      * <LI>Transform: The getDefaultTransform for the GraphicsConfiguration of
163      * the component.
164      * <LI>Composite: AlphaComposite.SRC_OVER
165      * <LI>Clip: The size of the component, Rectangle(0, 0, size.width,
166      * size.height)
167      * </UL>
168      *
169      * @param component to be used to initialize the values of the graphics
170      *        state
171      * @param doRestoreOnDispose true if writeGraphicsRestore() should be called
172      *        when this graphics context is disposed of.
173      */
174     protected AbstractVectorGraphicsIO(Component component,
175             boolean doRestoreOnDispose) {
176         super();
177 
178         this.size = component.getSize();
179         this.component = component;
180         this.doRestoreOnDispose = doRestoreOnDispose;
181 
182         deviceClip = (size != null ? new Rectangle(0, 0, size.width,
183                 size.height) : null);
184         userClip = null;
185         GraphicsConfiguration gc = component.getGraphicsConfiguration();
186         currentTransform = (gc != null) ? gc.getDefaultTransform()
187                 : new AffineTransform();
188         currentComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER);
189         currentStroke = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE,
190                 BasicStroke.JOIN_MITER, 10.0f, null, 0.0f);
191 
192         super.setFont(component.getFont());
193         super.setBackground(component.getBackground());
194         super.setColor(component.getForeground());
195 
196         // Initialize the rendering hints.
197         hints = new RenderingHints(null);
198     }
199 
200     /**
201      * Constructs a subgraphics context.
202      *
203      * @param graphics context to clone from
204      * @param doRestoreOnDispose true if writeGraphicsRestore() should be called
205      *        when this graphics context is disposed of.
206      */
207     protected AbstractVectorGraphicsIO(AbstractVectorGraphicsIO graphics,
208             boolean doRestoreOnDispose) {
209         super(graphics);
210         this.doRestoreOnDispose = doRestoreOnDispose;
211 
212         size = new Dimension(graphics.size);
213         component = graphics.component;
214 
215         deviceClip = new Rectangle(graphics.deviceClip);
216         userClip = (graphics.userClip != null) ? new Area(graphics.userClip)
217                 : null;
218         currentTransform = new AffineTransform(graphics.currentTransform);
219         currentComposite = graphics.currentComposite;
220         currentStroke = graphics.currentStroke;
221         hints = graphics.hints;
222     }
223 
224     /*
225      * ================================================================================ |
226      * 2. Document Settings
227      * ================================================================================
228      */
229     public Dimension getSize() {
230         return size;
231     }
232 
233     public Component getComponent() {
234         return component;
235     }
236 
237     /*
238      * ================================================================================ |
239      * 3. Header, Trailer, Multipage & Comments
240      * ================================================================================
241      */
242     /* 3.1 Header & Trailer */
243     public void startExport() {
244         try {
245             writeHeader();
246 
247             // delegate this to openPage if it is a MultiPage document
248             if (!(this instanceof MultiPageDocument)) {
249                 writeGraphicsState();
250                 writeBackground();
251             }
252         } catch (IOException e) {
253             handleException(e);
254         }
255     }
256 
257     public void endExport() {
258         try {
259             dispose();
260             writeTrailer();
261             closeStream();
262         } catch (IOException e) {
263             handleException(e);
264         }
265     }
266 
267     /**
268      * Called to write the header part of the output.
269      */
270     public abstract void writeHeader() throws IOException;
271 
272     /**
273      * Called to write the initial graphics state.
274      */
275     public void writeGraphicsState() throws IOException {
276 		writePaint(getPrintColor(getColor()));
277 
278 		writeSetTransform(getTransform());
279 
280 //		writeStroke(getStroke());
281 		
282         setClip(getClip());
283          
284         
285 		// Silly assignment, Font is written when String is drawed and "extra" writeFont does not exist
286 		// setFont(getFont());
287 
288 		// Silly assignment and "extra" writeComposite does not exist
289 		// setComposite(getComposite);
290     }
291 
292     public abstract void writeBackground() throws IOException;
293 
294     /**
295      * Called to write the trailing part of the output.
296      */
297     public abstract void writeTrailer() throws IOException;
298 
299     /**
300      * Called to close the stream you are writing to.
301      */
302     public abstract void closeStream() throws IOException;
303 
304     public void printComment(String comment) {
305         try {
306             writeComment(comment);
307         } catch (IOException e) {
308             handleException(e);
309         }
310     }
311 
312     /**
313      * Called to Write out a comment.
314      *
315      * @param comment to be written
316      */
317     public abstract void writeComment(String comment) throws IOException;
318 
319     /* 3.2 MultipageDocument methods */
320     protected void resetClip(Rectangle clip) {
321         deviceClip = clip;
322         userClip = null;
323     }
324 
325     /*
326      * ================================================================================
327      * 4. Create & Dispose
328      * ================================================================================
329      */
330     /**
331      * Disposes of the graphics context. If on creation restoreOnDispose was
332      * true, writeGraphicsRestore() will be called.
333      */
334     public void dispose() {
335         try {
336             // Swing sometimes calls dispose several times for a given
337             // graphics object. Ensure that the grestore is only written
338             // once if this happens.
339             if (doRestoreOnDispose) {
340                 writeGraphicsRestore();
341                 doRestoreOnDispose = false;
342             }
343         } catch (IOException e) {
344             handleException(e);
345         }
346     }
347 
348     /**
349      * Writes out the save of a graphics context for a later restore. Some
350      * implementations keep track of this by hand if the output format does not
351      * support it.
352      */
353     protected abstract void writeGraphicsSave() throws IOException;
354 
355     /**
356      * Writes out the restore of a graphics context. Some implementations keep
357      * track of this by hand if the output format does not support it.
358      */
359     protected abstract void writeGraphicsRestore() throws IOException;
360 
361     /*
362      * ================================================================================ |
363      * 5. Drawing Methods
364      * ================================================================================
365      */
366 
367     /* 5.3. Images */
368     public boolean drawImage(Image image, int x, int y, ImageObserver observer) {
369         int imageWidth = image.getWidth(observer);
370         int imageHeight = image.getHeight(observer);
371         return drawImage(image, x, y, x + imageWidth, y + imageHeight, 0, 0,
372                 imageWidth, imageHeight, null, observer);
373     }
374 
375     public boolean drawImage(Image image, int x, int y, int width, int height,
376             ImageObserver observer) {
377         int imageWidth = image.getWidth(observer);
378         int imageHeight = image.getHeight(observer);
379         return drawImage(image, x, y, x + width, y + height, 0, 0, imageWidth,
380                 imageHeight, null, observer);
381     }
382 
383     public boolean drawImage(Image image, int x, int y, int width, int height,
384             Color bgColor, ImageObserver observer) {
385         int imageWidth = image.getWidth(observer);
386         int imageHeight = image.getHeight(observer);
387         return drawImage(image, x, y, x + width, y + height, 0, 0, imageWidth,
388                 imageHeight, bgColor, observer);
389     }
390 
391     public boolean drawImage(Image image, int x, int y, Color bgColor,
392             ImageObserver observer) {
393         int imageWidth = image.getWidth(observer);
394         int imageHeight = image.getHeight(observer);
395         return drawImage(image, x, y, x + imageWidth, y + imageHeight, 0, 0,
396                 imageWidth, imageHeight, bgColor, observer);
397     }
398 
399     public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2,
400             int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
401         return drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null,
402                 observer);
403     }
404 
405     public boolean drawImage(Image image, AffineTransform xform,
406             ImageObserver observer) {
407         drawRenderedImage(ImageUtilities.createRenderedImage(image, observer,
408                 null), xform);
409         return true;
410     }
411 
412     public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) {
413         drawImage(op.filter(img, null), x, y, null);
414     }
415 
416     // NOTE: not tested yet!!!
417     public void drawRenderableImage(RenderableImage image, AffineTransform xform) {
418         drawRenderedImage(image.createRendering(new RenderContext(
419                 new AffineTransform(), getRenderingHints())), xform);
420     }
421 
422     /**
423      * Draw and resizes (transparent) image. Calls writeImage(...).
424      *
425      * @param image image to be drawn
426      * @param dx1 destination image bounds
427      * @param dy1 destination image bounds
428      * @param dx2 destination image bounds
429      * @param dy2 destination image bounds
430      * @param sx1 source image bounds
431      * @param sy1 source image bounds
432      * @param sx2 source image bounds
433      * @param sy2 source image bounds
434      * @param bgColor background color
435      * @param observer for updates if image still incomplete
436      * @return true if successful
437      */
438     public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2,
439             int sx1, int sy1, int sx2, int sy2, Color bgColor,
440             ImageObserver observer) {
441         try {
442             int srcX = Math.min(sx1, sx2);
443             int srcY = Math.min(sy1, sy2);
444             int srcWidth = Math.abs(sx2 - sx1);
445             int srcHeight = Math.abs(sy2 - sy1);
446             int width = Math.abs(dx2 - dx1);
447             int height = Math.abs(dy2 - dy1);
448 
449             if ((srcX != 0) || (srcY != 0)
450                     || (srcWidth != image.getWidth(observer))
451                     || (srcHeight != image.getHeight(observer))) {
452                 // crop the source image
453                 ImageFilter crop = new CropImageFilter(srcX, srcY, srcWidth,
454                         srcHeight);
455                 image = Toolkit.getDefaultToolkit().createImage(
456                         new FilteredImageSource(image.getSource(), crop));
457                 MediaTracker mediaTracker = new MediaTracker(new Panel());
458                 mediaTracker.addImage(image, 0);
459                 try {
460                     mediaTracker.waitForAll();
461                 } catch (InterruptedException e) {
462                     handleException(e);
463                 }
464             }
465 
466             boolean flipHorizontal = (dx2 < dx1) ^ (sx2 < sx1); // src flipped
467                                                                 // and not dest
468                                                                 // flipped or
469                                                                 // vice versa
470             boolean flipVertical = (dy2 < dy1) ^ (sy2 < sy1); // <=> source
471                                                                 // flipped XOR
472                                                                 // dest flipped
473 
474             double tx = (flipHorizontal) ? (double) dx2 : (double) dx1;
475             double ty = (flipVertical) ? (double) dy2 : (double) dy1;
476 
477             double sx = (double) width / srcWidth;
478             sx = flipHorizontal ? -1 * sx : sx;
479             double sy = (double) height / srcHeight;
480             sy = flipVertical ? -1 * sy : sy;
481 
482             writeImage(ImageUtilities.createRenderedImage(image, observer,
483                     bgColor), new AffineTransform(sx, 0, 0, sy, tx, ty),
484                     bgColor);
485             return true;
486         } catch (IOException e) {
487             handleException(e);
488             return false;
489         }
490     }
491 
492     /*
493      * // first use the original orientation int clippingWidth = Math.abs(sx2 -
494      * sx1); int clippingHeight = Math.abs(sy2 - sy1); int sulX = Math.min(sx1,
495      * sx2); int sulY = Math.min(sy1, sy2); Image background = null; if (bgColor !=
496      * null) { // Draw the image on the background color // maybe we could crop
497      * it and fill the transparent pixels in one go // by means of a filter.
498      * background = new BufferedImage(clippingWidth, clippingHeight,
499      * BufferedImage.TYPE_INT_ARGB); Graphics bgGfx = background.getGraphics();
500      * bgGfx.drawImage(image, 0, 0, clippingWidth, clippingWidth, sulX, sulY,
501      * sulX+clippingWidth, sulY+clippingHeight, getPrintColor(bgColor),
502      * observer); } else { // crop the source image ImageFilter crop = new
503      * CropImageFilter(sulX, sulY, clippingWidth, clippingHeight); background =
504      * Toolkit.getDefaultToolkit().createImage(new
505      * FilteredImageSource(image.getSource(), crop)); MediaTracker mediaTracker =
506      * new MediaTracker(new Panel()); mediaTracker.addImage(background, 0); try {
507      * mediaTracker.waitForAll(); } catch (InterruptedException e) {
508      * handleException(e); } } // now flip the image if necessary boolean
509      * flipHorizontal = (dx2<dx1) ^ (sx2<sx1); // src flipped and not dest
510      * flipped or vice versa boolean flipVertical = (dy2<dy1) ^ (sy2<sy1); //
511      * <=> source flipped XOR dest flipped int destWidth = Math.abs(dx2-dx1);
512      * int destHeight = Math.abs(dy2-dy1); try { return writeImage(background,
513      * flipHorizontal ? dx2 : dx1, flipVertical ? dy2 : dy1, flipHorizontal ?
514      * -destWidth : destWidth, flipVertical ? -destHeight : destHeight, (bgColor ==
515      * null), observer); } catch (IOException e) { return false; } }
516      */
517     /**
518      * Draws a rendered image using a transform.
519      *
520      * @param image to be drawn
521      * @param xform transform to be used on the image
522      */
523     public void drawRenderedImage(RenderedImage image, AffineTransform xform) {
524         try {
525             writeImage(image, xform, null);
526         } catch (Exception e) {
527             handleException(e);
528         }
529     }
530 
531     protected abstract void writeImage(RenderedImage image,
532             AffineTransform xform, Color bkg) throws IOException;
533 
534     /**
535      * Clears rectangle by painting it with the backgroundColor.
536      *
537      * @param x rectangle to be cleared.
538      * @param y rectangle to be cleared.
539      * @param width rectangle to be cleared.
540      * @param height rectangle to be cleared.
541      */
542     public void clearRect(double x, double y, double width, double height) {
543         Paint temp = getPaint();
544         setPaint(getBackground());
545         fillRect(x, y, width, height);
546         setPaint(temp);
547     }
548 
549     /**
550      * Draws the string at (x, y). If TEXT_AS_SHAPES is set
551      * {@link #drawGlyphVector(java.awt.font.GlyphVector, float, float)} is used, otherwise
552      * {@link #writeString(String, double, double)} for a more direct output of the string.
553      *
554      * @param string
555      * @param x
556      * @param y
557      */
558     public void drawString(String string, double x, double y) {
559         // something to draw?
560         if (string == null || string.equals("")) {
561             return;
562         }
563 
564         // draw strings directly?
565         if (isProperty(TEXT_AS_SHAPES)) {
566         	
567         	Font font = getFont();
568         	
569             // NOTE, see FVG-199, createGlyphVector does not seem to create the proper glyphcodes
570             // for either ZapfDingbats or Symbol. We use our own encoding which seems to work...
571         	String fontName = font.getName();
572         	if (fontName.equals("Symbol") || fontName.equals("ZapfDingbats")) {
573         		string = FontEncoder.getEncodedString(string, fontName);
574         		// use a standard font, not Symbol.
575         		font = new Font("Serif", font.getStyle(), font.getSize());
576         	}
577         	
578         	// create glyph
579             GlyphVector gv = font.createGlyphVector(getFontRenderContext(), string);
580 
581             // draw it
582             drawGlyphVector(gv, (float) x, (float) y);
583         } else {
584             // write string directly
585             try {
586                 writeString(string, x, y);
587             } catch (IOException e) {
588                 handleException(e);
589             }
590         }
591     }
592 
593     protected abstract void writeString(String string, double x, double y)
594             throws IOException;
595 
596     /**
597      * Use the transformation of the glyphvector and draw it
598      *
599      * @param g
600      * @param x
601      * @param y
602      */
603     public void drawGlyphVector(GlyphVector g, float x, float y) {
604         fill(g.getOutline(x, y));
605     }
606 
607     public void drawString(AttributedCharacterIterator iterator, float x,
608             float y) {
609 
610         // TextLayout draws the iterator as glyph vector
611         // thats why we use it only in the case of TEXT_AS_SHAPES,
612         // otherwise tagged strings are always written as glyphs
613         if (isProperty(TEXT_AS_SHAPES)) {
614             // draws all attributes
615             TextLayout tl = new TextLayout(iterator, getFontRenderContext());
616             tl.draw(this, x, y);
617         } else {
618             // reset to that font at the end
619             Font font = getFont();
620 
621             // initial attributes, we us TextAttribute.equals() rather
622             // than Font.equals() because using Font.equals() we do
623             // not get a 'false' if underline etc. is changed
624             Map/*<TextAttribute, ?>*/ attributes = FontUtilities.getAttributes(font);
625 
626             // stores all characters which are written with the same font
627             // if font is changed the buffer will be written and cleared
628             // after it
629             StringBuffer sb = new StringBuffer();
630 
631             for (char c = iterator.first();
632                  c != AttributedCharacterIterator.DONE;
633                  c = iterator.next()) {
634 
635                 // append c if font is not changed
636                 if (attributes.equals(iterator.getAttributes())) {
637                     sb.append(c);
638 
639                 } else {
640                     // TextLayout does not like 0 length strings
641                     if (sb.length() > 0) {
642                         // draw sb if font is changed
643                         drawString(sb.toString(), x, y);
644     
645                         // change the x offset for the next drawing
646                         // FIXME: change y offset for vertical text
647                         TextLayout tl = new TextLayout(
648                             sb.toString(),
649                             attributes,
650                             getFontRenderContext());
651     
652                         // calculate real width
653                         x = x + Math.max(
654                             tl.getAdvance(),
655                             (float)tl.getBounds().getWidth());
656                     }
657                     
658                     // empty sb
659                     sb = new StringBuffer();
660                     sb.append(c);
661 
662                     // change the font
663                     attributes = iterator.getAttributes();
664                     setFont(new Font(attributes));
665                 }
666             }
667 
668             // draw the rest
669             if (sb.length() > 0) {
670                 drawString(sb.toString(), x, y);
671             }
672 
673             // use the old font for the next string drawing
674             setFont(font);
675         }
676     }
677 
678     /*
679      * ================================================================================ |
680      * 6. Transformations
681      * ================================================================================
682      */
683     /**
684      * Get the current transform.
685      *
686      * @return current transform
687      */
688     public AffineTransform getTransform() {
689         return new AffineTransform(currentTransform);
690     }
691 
692     /**
693      * Set the current transform. Calls writeSetTransform(Transform).
694      *
695      * @param transform to be set
696      */
697     public void setTransform(AffineTransform transform) {
698         // Fix for FREEHEP-569
699     	oldTransform.setTransform(currentTransform);
700         currentTransform.setTransform(transform);
701         try {
702             writeSetTransform(transform);
703         } catch (IOException e) {
704             handleException(e);
705         }
706     }
707 
708     /**
709      * Transforms the current transform. Calls writeTransform(Transform)
710      *
711      * @param transform to be applied
712      */
713     public void transform(AffineTransform transform) {
714         currentTransform.concatenate(transform);
715         try {
716             writeTransform(transform);
717         } catch (IOException e) {
718             handleException(e);
719         }
720     }
721 
722     /**
723      * Translates the current transform. Calls writeTransform(Transform)
724      *
725      * @param x amount by which to translate
726      * @param y amount by which to translate
727      */
728     public void translate(double x, double y) {
729         currentTransform.translate(x, y);
730         try {
731             writeTransform(new AffineTransform(1, 0, 0, 1, x, y));
732         } catch (IOException e) {
733             handleException(e);
734         }
735     }
736 
737     /**
738      * Rotate the current transform over the Z-axis. Calls
739      * writeTransform(Transform). Rotating with a positive angle theta rotates
740      * points on the positive x axis toward the positive y axis.
741      *
742      * @param theta radians over which to rotate
743      */
744     public void rotate(double theta) {
745         currentTransform.rotate(theta);
746         try {
747             writeTransform(new AffineTransform(Math.cos(theta),
748                     Math.sin(theta), -Math.sin(theta), Math.cos(theta), 0, 0));
749         } catch (IOException e) {
750             handleException(e);
751         }
752     }
753 
754     /**
755      * Scales the current transform. Calls writeTransform(Transform).
756      *
757      * @param sx amount used for scaling
758      * @param sy amount used for scaling
759      */
760     public void scale(double sx, double sy) {
761         currentTransform.scale(sx, sy);
762         try {
763             writeTransform(new AffineTransform(sx, 0, 0, sy, 0, 0));
764         } catch (IOException e) {
765             handleException(e);
766         }
767     }
768 
769     /**
770      * Shears the current transform. Calls writeTransform(Transform).
771      *
772      * @param shx amount for shearing
773      * @param shy amount for shearing
774      */
775     public void shear(double shx, double shy) {
776         currentTransform.shear(shx, shy);
777         try {
778             writeTransform(new AffineTransform(1, shy, shx, 1, 0, 0));
779         } catch (IOException e) {
780             handleException(e);
781         }
782     }
783 
784     /**
785      * Writes out the transform as it needs to be concatenated to the internal
786      * transform of the output format. If there is no implementation of an
787      * internal transform, then this method needs to do nothing, BUT all
788      * coordinates need to be transformed by the currentTransform before being
789      * written out.
790      *
791      * @param transform to be written
792      */
793     protected abstract void writeTransform(AffineTransform transform)
794             throws IOException;
795 
796     /**
797      * Clears any existing transformation and sets the a new one.
798      * The default implementation calls writeTransform using the
799      * inverted affine transform to calculate it.
800 s     *
801      * @param transform to be written
802      */
803     protected void writeSetTransform(AffineTransform transform) throws IOException {
804     	try {
805 	    	AffineTransform deltaTransform = new AffineTransform(transform);
806 	        deltaTransform.concatenate(oldTransform.createInverse());
807 	    	writeTransform(deltaTransform);
808     	} catch (NoninvertibleTransformException e) {
809     		// ignored...
810     		System.err.println("Warning: (ignored) Could not invert matrix: "+oldTransform);
811     	}
812     }
813 
814     /*
815      * ================================================================================ |
816      * 7. Clipping
817      * ================================================================================
818      */
819 
820     /**
821      * Gets the current clip in form of a Shape (Rectangle).
822      *
823      * @return current clip
824      */
825     public Shape getClip() {
826         return (userClip != null) ? new Area(untransformShape(userClip)) : null;
827     }
828 
829     /**
830      * Gets the current clip in form of a Rectangle.
831      *
832      * @return current clip
833      */
834     public Rectangle getClipBounds() {
835         Shape clip = getClip();
836         return (clip != null) ? getClip().getBounds() : null;
837     }
838 
839     /**
840      * Gets the current clip in form of a Rectangle.
841      *
842      * @return current clip
843      */
844     public Rectangle getClipBounds(Rectangle r) {
845         Rectangle bounds = getClipBounds();
846         if (bounds != null)
847             r.setBounds(bounds);
848         return r;
849     }
850 
851     /**
852      * Clips rectangle. Calls clip(Rectangle).
853      *
854      * @param x rectangle for clipping
855      * @param y rectangle for clipping
856      * @param width rectangle for clipping
857      * @param height rectangle for clipping
858      */
859     public void clipRect(int x, int y, int width, int height) {
860         clip(new Rectangle(x, y, width, height));
861     }
862 
863     /**
864      * Clips rectangle. Calls clip(Rectangle2D).
865      *
866      * @param x rectangle for clipping
867      * @param y rectangle for clipping
868      * @param width rectangle for clipping
869      * @param height rectangle for clipping
870      */
871     public void clipRect(double x, double y, double width, double height) {
872         clip(new Rectangle2D.Double(x, y, width, height));
873     }
874 
875     /**
876      * Clips rectangle. Calls clip(Rectangle).
877      *
878      * @param x rectangle for clipping
879      * @param y rectangle for clipping
880      * @param width rectangle for clipping
881      * @param height rectangle for clipping
882      */
883     public void setClip(int x, int y, int width, int height) {
884         setClip(new Rectangle(x, y, width, height));
885     }
886 
887     /**
888      * Clips rectangle. Calls clip(Rectangle2D).
889      *
890      * @param x rectangle for clipping
891      * @param y rectangle for clipping
892      * @param width rectangle for clipping
893      * @param height rectangle for clipping
894      */
895     public void setClip(double x, double y, double width, double height) {
896         setClip(new Rectangle2D.Double(x, y, width, height));
897     }
898 
899     /**
900      * Clips shape. Clears userClip and calls clip(Shape).
901      *
902      * @param s used for clipping
903      */
904     public void setClip(Shape s) {
905 
906         Shape ts = transformShape(s);
907         userClip = (ts != null) ? new Area(ts) : null;
908 
909         try {
910             writeSetClip(s);
911         } catch (IOException e) {
912             handleException(e);
913         }
914     }
915 
916     /**
917      * Clips using given shape. Dispatches to writeClip(Rectangle),
918      * writeClip(Rectangle2D) or writeClip(Shape).
919      *
920      * @param s used for clipping
921      */
922     public void clip(Shape s) {
923         Shape ts = transformShape(s);
924         if (userClip != null) {
925             if (ts != null) {
926                 userClip.intersect(new Area(ts));
927             } else {
928                 userClip = null;
929             }
930         } else {
931             userClip = (ts != null) ? new Area(ts) : null;
932         }
933 
934         try {
935 	    writeClip(s);
936         } catch (IOException e) {
937             handleException(e);
938         }
939     }
940 
941     /**
942      * Write out Shape clip.
943      *
944      * @param shape to be used for clipping
945      */
946     protected abstract void writeClip(Shape shape) throws IOException;
947 
948     /**
949      * Write out Shape clip.
950      *
951      * @param shape to be used for clipping
952      */
953     protected abstract void writeSetClip(Shape shape) throws IOException;
954 
955     /*
956      * ================================================================================ |
957      * 8. Graphics State
958      * ================================================================================
959      */
960     /* 8.1. stroke/linewidth */
961     /**
962      * Get the current stroke.
963      *
964      * @return current stroke
965      */
966     public Stroke getStroke() {
967         return currentStroke;
968     }
969 
970     /**
971      * Sets the current stroke. Calls writeStroke if stroke is unequal to the
972      * current stroke.
973      *
974      * @param stroke to be set
975      */
976     public void setStroke(Stroke stroke) {
977         if (stroke.equals(currentStroke)) {
978             return;
979         }
980         try {
981             writeStroke(stroke);
982         } catch (IOException e) {
983             handleException(e);
984         }
985         currentStroke = stroke;
986     }
987 
988     /**
989      * Writes the current stroke. If stroke is an instance of BasicStroke it
990      * will call writeWidth, writeCap, writeJoin, writeMiterLimit and writeDash,
991      * if any were different than the current stroke.
992      */
993     protected void writeStroke(Stroke stroke) throws IOException {
994         if (stroke instanceof BasicStroke) {
995             BasicStroke ns = (BasicStroke) stroke;
996 
997             // get the current values for comparison if available,
998             // otherwise set them to -1="undefined"
999             int currentCap = -1, currentJoin = -1;
1000             float currentWidth = -1, currentLimit = -1, currentDashPhase = -1;
1001             float[] currentDashArray = null;
1002             if ((currentStroke != null)
1003                     && (currentStroke instanceof BasicStroke)) {
1004                 BasicStroke cs = (BasicStroke) currentStroke;
1005                 currentCap = cs.getEndCap();
1006                 currentJoin = cs.getLineJoin();
1007                 currentWidth = cs.getLineWidth();
1008                 currentLimit = cs.getMiterLimit();
1009                 currentDashArray = cs.getDashArray();
1010                 currentDashPhase = cs.getDashPhase();
1011             }
1012 
1013             // Check the linewidth.
1014             float width = ns.getLineWidth();
1015             if (currentWidth != width) {
1016                 writeWidth(width);
1017             }
1018 
1019             // Check the line caps.
1020             int cap = ns.getEndCap();
1021             if (currentCap != cap) {
1022                 writeCap(cap);
1023             }
1024 
1025             // Check the line joins.
1026             int join = ns.getLineJoin();
1027             if (currentJoin != join) {
1028                 writeJoin(join);
1029             }
1030 
1031             // Check the miter limit and validity of value
1032             float limit = ns.getMiterLimit();
1033             if ((currentLimit != limit) && (limit >= 1.0f)) {
1034                 writeMiterLimit(limit);
1035             }
1036 
1037             // Check to see if there are differences in the phase or dash
1038             if(!Arrays.equals(currentDashArray, ns.getDashArray()) ||
1039                (currentDashPhase != ns.getDashPhase())) {
1040 
1041                 // write the dashing parameters
1042                 if (ns.getDashArray() != null) {
1043                     writeDash(ns.getDashArray(), ns.getDashPhase());
1044                 } else {
1045                     writeDash(new float[0], ns.getDashPhase());
1046                 }
1047             }
1048         }
1049     }
1050 
1051     /**
1052      * Writes out the width of the stroke.
1053      *
1054      * @param width of the stroke
1055      */
1056     protected void writeWidth(float width) throws IOException {
1057         writeWarning(getClass() + ": writeWidth() not implemented.");
1058     }
1059 
1060     /**
1061      * Writes out the cap of the stroke.
1062      *
1063      * @param cap of the stroke
1064      */
1065     protected void writeCap(int cap) throws IOException {
1066         writeWarning(getClass() + ": writeCap() not implemented.");
1067     }
1068 
1069     /**
1070      * Writes out the join of the stroke.
1071      *
1072      * @param join of the stroke
1073      */
1074     protected void writeJoin(int join) throws IOException {
1075         writeWarning(getClass() + ": writeJoin() not implemented.");
1076     }
1077 
1078     /**
1079      * Writes out the miter limit of the stroke.
1080      *
1081      * @param limit miter limit of the stroke
1082      */
1083     protected void writeMiterLimit(float limit) throws IOException {
1084         writeWarning(getClass() + ": writeMiterLimit() not implemented.");
1085     }
1086 
1087     /**
1088      * Writes out the dash of the stroke.
1089      *
1090      * @param dash dash pattern, empty array is solid line
1091      * @param phase of the dash pattern
1092      */
1093     protected void writeDash(float[] dash, float phase) throws IOException {
1094         // for backward compatibility
1095         double[] dd = new double[dash.length];
1096         for (int i = 0; i < dash.length; i++) {
1097             dd[i] = dash[i];
1098         }
1099         writeDash(dd, (double)phase);
1100     }
1101 
1102     /**
1103      * Writes out the dash of the stroke.
1104 
1105      * @deprecated use writeDash(float[], float)
1106      * @param dash dash pattern, empty array is solid line
1107      * @param phase of the dash pattern
1108      */
1109     protected void writeDash(double[] dash, double phase) throws IOException {
1110         writeWarning(getClass() + ": writeDash() not implemented.");
1111     }
1112 
1113     /* 8.2 Paint */
1114     public void setColor(Color color) {
1115     	if (color == null) return;
1116     	
1117         if (color.equals(getColor()))
1118             return;
1119 
1120         try {
1121             super.setColor(color);
1122             writePaint(getPrintColor(color));
1123         } catch (IOException e) {
1124             handleException(e);
1125         }
1126     }
1127 
1128     /**
1129      * Sets the current paint. Dispatches to writePaint(Color),
1130      * writePaint(GradientPaint), writePaint(TexturePaint paint) or
1131      * writePaint(Paint). In the case paint is a Color the current color is also
1132      * changed.
1133      *
1134      * @param paint to be set
1135      */
1136     public void setPaint(Paint paint) {
1137     	if (paint == null) return;
1138     	
1139         if (paint.equals(getPaint()))
1140             return;
1141 
1142         try {
1143             if (paint instanceof Color) {
1144                 setColor((Color) paint);
1145             } else if (paint instanceof GradientPaint) {
1146                 super.setPaint(paint);
1147                 writePaint((GradientPaint) paint);
1148             } else if (paint instanceof TexturePaint) {
1149                 super.setPaint(paint);
1150                 writePaint((TexturePaint) paint);
1151             } else {
1152                 super.setPaint(paint);
1153                 writePaint(paint);
1154             }
1155         } catch (IOException e) {
1156             handleException(e);
1157         }
1158     }
1159 
1160     /**
1161      * Writes out paint as the given color.
1162      *
1163      * @param color to be written
1164      */
1165     protected abstract void writePaint(Color color) throws IOException;
1166 
1167     /**
1168      * Writes out paint as the given gradient.
1169      *
1170      * @param paint to be written
1171      */
1172     protected abstract void writePaint(GradientPaint paint) throws IOException;
1173 
1174     /**
1175      * Writes out paint as the given texture.
1176      *
1177      * @param paint to be written
1178      */
1179     protected abstract void writePaint(TexturePaint paint) throws IOException;
1180 
1181     /**
1182      * Writes out paint.
1183      *
1184      * @param paint to be written
1185      */
1186     protected abstract void writePaint(Paint paint) throws IOException;
1187 
1188     /* 8.3. font */
1189     /**
1190      * Gets the current font render context. This returns an standard
1191      * FontRenderContext with anti-aliasing and uses
1192      * fractional metrics.
1193      *
1194      * @return current font render context
1195      */
1196     public FontRenderContext getFontRenderContext() {
1197         // NOTE: not sure?
1198         // Fixed for VG-285
1199         return new FontRenderContext(new AffineTransform(1, 0, 0, 1, 0, 0),
1200                 true, true);
1201     }
1202 
1203     /**
1204      * Gets the fontmetrics.
1205      *
1206      * @deprecated
1207      * @param font to be used for retrieving fontmetrics
1208      * @return fontmetrics for given font
1209      */
1210     public FontMetrics getFontMetrics(Font font) {
1211         return Toolkit.getDefaultToolkit().getFontMetrics(font);
1212     }
1213 
1214     /* 8.4. rendering hints */
1215     /**
1216      * Gets a copy of the rendering hints.
1217      *
1218      * @return clone of table of rendering hints.
1219      */
1220     public RenderingHints getRenderingHints() {
1221         return (RenderingHints) hints.clone();
1222     }
1223 
1224     /**
1225      * Adds to table of rendering hints.
1226      *
1227      * @param hints table to be added
1228      */
1229     public void addRenderingHints(Map hints) {
1230         hints.putAll(hints);
1231     }
1232 
1233     /**
1234      * Sets table of rendering hints.
1235      *
1236      * @param hints table to be set
1237      */
1238     public void setRenderingHints(Map hints) {
1239         hints.clear();
1240         hints.putAll(hints);
1241     }
1242 
1243     /**
1244      * Gets a given rendering hint.
1245      *
1246      * @param key hint key
1247      * @return hint associated to key
1248      */
1249     public Object getRenderingHint(RenderingHints.Key key) {
1250         return hints.get(key);
1251     }
1252 
1253     /**
1254      * Sets a given rendering hint.
1255      *
1256      * @param key hint key
1257      * @param hint to be associated with key
1258      */
1259     public void setRenderingHint(RenderingHints.Key key, Object hint) {
1260         // extra protection, failed on under MacOS X 10.2.6, jdk 1.4.1_01-39/14
1261         if ((key == null) || (hint == null))
1262             return;
1263         hints.put(key, hint);
1264     }
1265 
1266     /**
1267      * Sets the current font.
1268      *
1269      * @param font to be set
1270      */
1271     public void setFont(Font font) {
1272     	if (font == null) return;
1273     	
1274         // FIXME: maybe add delayed setting
1275         super.setFont(font);
1276 
1277         // write the font
1278         try {
1279             writeFont(font);
1280         } catch (IOException e) {
1281             handleException(e);
1282         }
1283     }
1284 
1285     /**
1286      * Writes the font
1287      *
1288      * @param font to be written
1289      */
1290     protected abstract void writeFont(Font font) throws IOException;
1291 
1292     /*
1293 	 * ================================================================================ |
1294 	 * 9. Auxiliary
1295 	 * ================================================================================
1296 	 */
1297     /**
1298      * Gets current composite.
1299      *
1300      * @return current composite
1301      */
1302     public Composite getComposite() {
1303         return currentComposite;
1304     }
1305 
1306     /**
1307      * Sets current composite.
1308      *
1309      * @param composite to be set
1310      */
1311     public void setComposite(Composite composite) {
1312         currentComposite = composite;
1313     }
1314 
1315     /**
1316      * Handles an exception which has been caught. Dispatches exception to
1317      * writeWarning for UnsupportedOperationExceptions and writeError for others
1318      *
1319      * @param exception to be handled
1320      */
1321     protected void handleException(Exception exception) {
1322         if (exception instanceof UnsupportedOperationException) {
1323             writeWarning(exception);
1324         } else {
1325             writeError(exception);
1326         }
1327     }
1328 
1329     /**
1330      * Writes out a warning, by default to System.err.
1331      *
1332      * @param exception warning to be written
1333      */
1334     protected void writeWarning(Exception exception) {
1335         writeWarning(exception.getMessage());
1336     }
1337 
1338     /**
1339      * Writes out a warning, by default to System.err.
1340      *
1341      * @param warning to be written
1342      */
1343     protected void writeWarning(String warning) {
1344         if (isProperty(EMIT_WARNINGS)) {
1345             System.err.println(warning);
1346         }
1347     }
1348 
1349     /**
1350      * Writes out an error, by default the stack trace is printed.
1351      *
1352      * @param exception error to be reported
1353      */
1354     protected void writeError(Exception exception) {
1355         throw new RuntimeException(exception);
1356         // FIXME decide what we should do
1357         /*
1358          * if (isProperty(EMIT_ERRORS)) { System.err.println(exception);
1359          * exception.printStackTrace(System.err); }
1360          */
1361     }
1362 
1363     protected Shape createShape(double[] xPoints, double[] yPoints,
1364             int nPoints, boolean close) {
1365         GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
1366         if (nPoints > 0) {
1367             path.moveTo((float) xPoints[0], (float) yPoints[0]);
1368             for (int i = 1; i < nPoints; i++) {
1369                 path.lineTo((float) xPoints[i], (float) yPoints[i]);
1370             }
1371             if (close)
1372                 path.closePath();
1373         }
1374         return path;
1375     }
1376 
1377     private Shape transformShape(AffineTransform at, Shape s) {
1378         if (s == null)
1379             return null;
1380         return at.createTransformedShape(s);
1381     }
1382 
1383     private Shape transformShape(Shape s) {
1384         return transformShape(getTransform(), s);
1385     }
1386 
1387     private Shape untransformShape(Shape s) {
1388         if (s == null)
1389             return null;
1390         try {
1391             return transformShape(getTransform().createInverse(), s);
1392         } catch (NoninvertibleTransformException e) {
1393             return null;
1394         }
1395     }
1396 
1397     /**
1398      * Draws an overline for the text at (x, y). The method is usesefull for
1399      * drivers that do not support overlines by itself.
1400      *
1401      * @param text text for width calulation
1402      * @param font font for width calulation
1403      * @param x position of text
1404      * @param y position of text
1405      */
1406     protected void overLine(String text, Font font, float x, float y) {
1407         TextLayout layout = new TextLayout(text, font, getFontRenderContext());
1408         float width = Math.max(
1409             layout.getAdvance(),
1410             (float) layout.getBounds().getWidth());
1411 
1412         GeneralPath path = new GeneralPath();
1413         path.moveTo(x, y + (float) layout.getBounds().getY() - layout.getAscent());
1414         path.lineTo(x + width, y + (float) layout.getBounds().getY() - layout.getAscent() - layout.getAscent());
1415         draw(path);
1416     }
1417 }