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 }