// Copyright 2000-2007, FreeHEP package org.xmind.org.freehep.graphicsio; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Composite; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.GradientPaint; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.MediaTracker; import java.awt.Paint; import java.awt.Panel; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; import java.awt.TexturePaint; import java.awt.Toolkit; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.GeneralPath; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.CropImageFilter; import java.awt.image.FilteredImageSource; import java.awt.image.ImageFilter; import java.awt.image.ImageObserver; import java.awt.image.RenderedImage; import java.awt.image.renderable.RenderContext; import java.awt.image.renderable.RenderableImage; import java.io.IOException; import java.text.AttributedCharacterIterator; import java.text.AttributedCharacterIterator.Attribute; import java.util.Map; import org.xmind.org.freehep.graphics2d.font.FontEncoder; import org.xmind.org.freehep.graphics2d.font.FontUtilities; import org.xmind.org.freehep.util.images.ImageUtilities; /** * This class provides an abstract VectorGraphicsIO class for specific output * drivers. * * @author Charles Loomis * @author Mark Donszelmann * @author Steffen Greiffenberg * @author Jason Wong */ public abstract class AbstractVectorGraphicsIO extends VectorGraphicsIO { private static final String rootKey = AbstractVectorGraphicsIO.class .getName(); public static final String EMIT_WARNINGS = rootKey + ".EMIT_WARNINGS"; //$NON-NLS-1$ public static final String TEXT_AS_SHAPES = rootKey + "." //$NON-NLS-1$ + FontConstants.TEXT_AS_SHAPES; public static final String EMIT_ERRORS = rootKey + ".EMIT_ERRORS"; //$NON-NLS-1$ public static final String CLIP = rootKey + ".CLIP"; //$NON-NLS-1$ /* * ========================================================================== * ====== Table of Contents: ------------------ 1. Constructors & Factory * Methods 2. Document Settings 3. Header, Trailer, Multipage & Comments 3.1 * Header & Trailer 3.2 MultipageDocument methods 4. Create & Dispose 5. * Drawing Methods 5.1. shapes (draw/fill) 5.1.1. lines, rectangles, round * rectangles 5.1.2. polylines, polygons 5.1.3. ovals, arcs 5.1.4. shapes * 5.2. Images 5.3. Strings 6. Transformations 7. Clipping 8. Graphics State * / Settings 8.1. stroke/linewidth 8.2. paint/color 8.3. font 8.4. * rendering hints 9. Auxiliary 10. Private/Utility Methods * ================== * ============================================================== */ private Dimension size; private Component component; private boolean doRestoreOnDispose; private Rectangle deviceClip; /** * Untransformed clipping Area defined by the user */ private Area userClip; private AffineTransform currentTransform; // only for use in writeSetTransform to calculate the difference. private AffineTransform oldTransform = new AffineTransform(); private Composite currentComposite; private Stroke currentStroke; private RenderingHints hints; /* * ========================================================================== * ====== 1. Constructors & Factory Methods * ================================== * ============================================== */ /** * Constructs a Graphics context with the following graphics state: * <UL> * <LI>Paint: black * <LI>Font: Dailog, Plain, 12pt * <LI>Stroke: Linewidth 1.0; No Dashing; Miter Join Style; Miter Limit 10; * Square Endcaps; * <LI>Transform: Identity * <LI>Composite: AlphaComposite.SRC_OVER * <LI>Clip: Rectangle(0, 0, size.width, size.height) * </UL> * * @param size * rectangle specifying the bounds of the image * @param doRestoreOnDispose * true if writeGraphicsRestore() should be called when this * graphics context is disposed of. */ protected AbstractVectorGraphicsIO(Dimension size, boolean doRestoreOnDispose) { super(); this.size = size; this.component = null; this.doRestoreOnDispose = doRestoreOnDispose; deviceClip = (size != null ? new Rectangle(0, 0, size.width, size.height) : null); userClip = null; currentTransform = new AffineTransform(); currentComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER); currentStroke = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, null, 0.0f); super.setColor(Color.BLACK); super.setBackground(Color.BLACK); super.setFont(new Font("Dialog", Font.PLAIN, 12)); //$NON-NLS-1$ // Initialize the rendering hints. hints = new RenderingHints(null); } /** * Constructs a Graphics context with the following graphics state: * <UL> * <LI>Paint: The color of the component. * <LI>Font: The font of the component. * <LI>Stroke: Linewidth 1.0; No Dashing; Miter Join Style; Miter Limit 10; * Square Endcaps; * <LI>Transform: The getDefaultTransform for the GraphicsConfiguration of * the component. * <LI>Composite: AlphaComposite.SRC_OVER * <LI>Clip: The size of the component, Rectangle(0, 0, size.width, * size.height) * </UL> * * @param component * to be used to initialize the values of the graphics state * @param doRestoreOnDispose * true if writeGraphicsRestore() should be called when this * graphics context is disposed of. */ protected AbstractVectorGraphicsIO(Component component, boolean doRestoreOnDispose) { super(); this.size = component.getSize(); this.component = component; this.doRestoreOnDispose = doRestoreOnDispose; deviceClip = (size != null ? new Rectangle(0, 0, size.width, size.height) : null); userClip = null; GraphicsConfiguration gc = component.getGraphicsConfiguration(); currentTransform = (gc != null) ? gc.getDefaultTransform() : new AffineTransform(); currentComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER); currentStroke = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, null, 0.0f); super.setFont(component.getFont()); super.setBackground(component.getBackground()); super.setColor(component.getForeground()); // Initialize the rendering hints. hints = new RenderingHints(null); } /** * Constructs a subgraphics context. * * @param graphics * context to clone from * @param doRestoreOnDispose * true if writeGraphicsRestore() should be called when this * graphics context is disposed of. */ protected AbstractVectorGraphicsIO(AbstractVectorGraphicsIO graphics, boolean doRestoreOnDispose) { super(graphics); this.doRestoreOnDispose = doRestoreOnDispose; size = new Dimension(graphics.size); component = graphics.component; deviceClip = new Rectangle(graphics.deviceClip); userClip = (graphics.userClip != null) ? new Area(graphics.userClip) : null; currentTransform = new AffineTransform(graphics.currentTransform); currentComposite = graphics.currentComposite; currentStroke = graphics.currentStroke; hints = graphics.hints; } /* * ========================================================================== * ====== | 2. Document Settings * ============================================ * ==================================== */ public Dimension getSize() { return size; } public Component getComponent() { return component; } /* * ========================================================================== * ====== | 3. Header, Trailer, Multipage & Comments * ======================== * ======================================================== */ /* 3.1 Header & Trailer */ @Override public void startExport() { try { writeHeader(); // delegate this to openPage if it is a MultiPage document if (!(this instanceof MultiPageDocument)) { writeGraphicsState(); writeBackground(); } } catch (IOException e) { handleException(e); } } @Override public void endExport() { try { dispose(); writeTrailer(); closeStream(); } catch (IOException e) { handleException(e); } } /** * Called to write the header part of the output. */ public abstract void writeHeader() throws IOException; /** * Called to write the initial graphics state. */ public void writeGraphicsState() throws IOException { writePaint(getPrintColor(getColor())); writeSetTransform(getTransform()); // writeStroke(getStroke()); setClip(getClip()); // Silly assignment, Font is written when String is drawed and "extra" // writeFont does not exist // setFont(getFont()); // Silly assignment and "extra" writeComposite does not exist // setComposite(getComposite); } public abstract void writeBackground() throws IOException; /** * Called to write the trailing part of the output. */ public abstract void writeTrailer() throws IOException; /** * Called to close the stream you are writing to. */ public abstract void closeStream() throws IOException; public void printComment(String comment) { try { writeComment(comment); } catch (IOException e) { handleException(e); } } /** * Called to Write out a comment. * * @param comment * to be written */ public abstract void writeComment(String comment) throws IOException; /* 3.2 MultipageDocument methods */ protected void resetClip(Rectangle clip) { deviceClip = clip; userClip = null; } /* * ========================================================================== * ====== 4. Create & Dispose * ================================================ * ================================ */ /** * Disposes of the graphics context. If on creation restoreOnDispose was * true, writeGraphicsRestore() will be called. */ public void dispose() { try { // Swing sometimes calls dispose several times for a given // graphics object. Ensure that the grestore is only written // once if this happens. if (doRestoreOnDispose) { writeGraphicsRestore(); doRestoreOnDispose = false; } } catch (IOException e) { handleException(e); } } /** * Writes out the save of a graphics context for a later restore. Some * implementations keep track of this by hand if the output format does not * support it. */ protected abstract void writeGraphicsSave() throws IOException; /** * Writes out the restore of a graphics context. Some implementations keep * track of this by hand if the output format does not support it. */ protected abstract void writeGraphicsRestore() throws IOException; /* * ========================================================================== * ====== | 5. Drawing Methods * ============================================== * ================================== */ /* 5.3. Images */ public boolean drawImage(Image image, int x, int y, ImageObserver observer) { int imageWidth = image.getWidth(observer); int imageHeight = image.getHeight(observer); return drawImage(image, x, y, x + imageWidth, y + imageHeight, 0, 0, imageWidth, imageHeight, null, observer); } public boolean drawImage(Image image, int x, int y, int width, int height, ImageObserver observer) { int imageWidth = image.getWidth(observer); int imageHeight = image.getHeight(observer); return drawImage(image, x, y, x + width, y + height, 0, 0, imageWidth, imageHeight, null, observer); } public boolean drawImage(Image image, int x, int y, int width, int height, Color bgColor, ImageObserver observer) { int imageWidth = image.getWidth(observer); int imageHeight = image.getHeight(observer); return drawImage(image, x, y, x + width, y + height, 0, 0, imageWidth, imageHeight, bgColor, observer); } public boolean drawImage(Image image, int x, int y, Color bgColor, ImageObserver observer) { int imageWidth = image.getWidth(observer); int imageHeight = image.getHeight(observer); return drawImage(image, x, y, x + imageWidth, y + imageHeight, 0, 0, imageWidth, imageHeight, bgColor, observer); } public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { return drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer); } public boolean drawImage(Image image, AffineTransform xform, ImageObserver observer) { drawRenderedImage( ImageUtilities.createRenderedImage(image, observer, null), xform); return true; } public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { drawImage(op.filter(img, null), x, y, null); } // NOTE: not tested yet!!! public void drawRenderableImage(RenderableImage image, AffineTransform xform) { drawRenderedImage(image.createRendering(new RenderContext( new AffineTransform(), getRenderingHints())), xform); } /** * Draw and resizes (transparent) image. Calls writeImage(...). * * @param image * image to be drawn * @param dx1 * destination image bounds * @param dy1 * destination image bounds * @param dx2 * destination image bounds * @param dy2 * destination image bounds * @param sx1 * source image bounds * @param sy1 * source image bounds * @param sx2 * source image bounds * @param sy2 * source image bounds * @param bgColor * background color * @param observer * for updates if image still incomplete * @return true if successful */ public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgColor, ImageObserver observer) { try { int srcX = Math.min(sx1, sx2); int srcY = Math.min(sy1, sy2); int srcWidth = Math.abs(sx2 - sx1); int srcHeight = Math.abs(sy2 - sy1); int width = Math.abs(dx2 - dx1); int height = Math.abs(dy2 - dy1); if ((srcX != 0) || (srcY != 0) || (srcWidth != image.getWidth(observer)) || (srcHeight != image.getHeight(observer))) { // crop the source image ImageFilter crop = new CropImageFilter(srcX, srcY, srcWidth, srcHeight); image = Toolkit.getDefaultToolkit().createImage( new FilteredImageSource(image.getSource(), crop)); MediaTracker mediaTracker = new MediaTracker(new Panel()); mediaTracker.addImage(image, 0); try { mediaTracker.waitForAll(); } catch (InterruptedException e) { handleException(e); } } boolean flipHorizontal = (dx2 < dx1) ^ (sx2 < sx1); // src flipped // and not dest // flipped or // vice versa boolean flipVertical = (dy2 < dy1) ^ (sy2 < sy1); // <=> source // flipped XOR // dest flipped double tx = (flipHorizontal) ? (double) dx2 : (double) dx1; double ty = (flipVertical) ? (double) dy2 : (double) dy1; double sx = (double) width / srcWidth; sx = flipHorizontal ? -1 * sx : sx; double sy = (double) height / srcHeight; sy = flipVertical ? -1 * sy : sy; writeImage(ImageUtilities.createRenderedImage(image, observer, bgColor), new AffineTransform(sx, 0, 0, sy, tx, ty), bgColor); return true; } catch (IOException e) { handleException(e); return false; } } /* * // first use the original orientation int clippingWidth = Math.abs(sx2 - * sx1); int clippingHeight = Math.abs(sy2 - sy1); int sulX = Math.min(sx1, * sx2); int sulY = Math.min(sy1, sy2); Image background = null; if (bgColor * != null) { // Draw the image on the background color // maybe we could * crop it and fill the transparent pixels in one go // by means of a * filter. background = new BufferedImage(clippingWidth, clippingHeight, * BufferedImage.TYPE_INT_ARGB); Graphics bgGfx = background.getGraphics(); * bgGfx.drawImage(image, 0, 0, clippingWidth, clippingWidth, sulX, sulY, * sulX+clippingWidth, sulY+clippingHeight, getPrintColor(bgColor), * observer); } else { // crop the source image ImageFilter crop = new * CropImageFilter(sulX, sulY, clippingWidth, clippingHeight); background = * Toolkit.getDefaultToolkit().createImage(new * FilteredImageSource(image.getSource(), crop)); MediaTracker mediaTracker * = new MediaTracker(new Panel()); mediaTracker.addImage(background, 0); * try { mediaTracker.waitForAll(); } catch (InterruptedException e) { * handleException(e); } } // now flip the image if necessary boolean * flipHorizontal = (dx2<dx1) ^ (sx2<sx1); // src flipped and not dest * flipped or vice versa boolean flipVertical = (dy2<dy1) ^ (sy2<sy1); // * <=> source flipped XOR dest flipped int destWidth = Math.abs(dx2-dx1); * int destHeight = Math.abs(dy2-dy1); try { return writeImage(background, * flipHorizontal ? dx2 : dx1, flipVertical ? dy2 : dy1, flipHorizontal ? * -destWidth : destWidth, flipVertical ? -destHeight : destHeight, (bgColor * == null), observer); } catch (IOException e) { return false; } } */ /** * Draws a rendered image using a transform. * * @param image * to be drawn * @param xform * transform to be used on the image */ public void drawRenderedImage(RenderedImage image, AffineTransform xform) { try { writeImage(image, xform, null); } catch (Exception e) { handleException(e); } } protected abstract void writeImage(RenderedImage image, AffineTransform xform, Color bkg) throws IOException; /** * Clears rectangle by painting it with the backgroundColor. * * @param x * rectangle to be cleared. * @param y * rectangle to be cleared. * @param width * rectangle to be cleared. * @param height * rectangle to be cleared. */ public void clearRect(double x, double y, double width, double height) { Paint temp = getPaint(); setPaint(getBackground()); fillRect(x, y, width, height); setPaint(temp); } /** * Draws the string at (x, y). If TEXT_AS_SHAPES is set * {@link #drawGlyphVector(java.awt.font.GlyphVector, float, float)} is * used, otherwise {@link #writeString(String, double, double)} for a more * direct output of the string. * * @param string * @param x * @param y */ public void drawString(String string, double x, double y) { // something to draw? if (string == null || string.equals("")) { //$NON-NLS-1$ return; } // draw strings directly? if (isProperty(TEXT_AS_SHAPES)) { Font font = getFont(); // NOTE, see FVG-199, createGlyphVector does not seem to create the // proper glyphcodes // for either ZapfDingbats or Symbol. We use our own encoding which // seems to work... String fontName = font.getName(); if (fontName.equals("Symbol") || fontName.equals("ZapfDingbats")) { //$NON-NLS-1$ //$NON-NLS-2$ string = FontEncoder.getEncodedString(string, fontName); // use a standard font, not Symbol. font = new Font("Serif", font.getStyle(), font.getSize()); //$NON-NLS-1$ } // create glyph GlyphVector gv = font.createGlyphVector(getFontRenderContext(), string); // draw it drawGlyphVector(gv, (float) x, (float) y); } else { // write string directly try { writeString(string, x, y); } catch (IOException e) { handleException(e); } } } protected abstract void writeString(String string, double x, double y) throws IOException; /** * Use the transformation of the glyphvector and draw it * * @param g * @param x * @param y */ public void drawGlyphVector(GlyphVector g, float x, float y) { fill(g.getOutline(x, y)); } public void drawString(AttributedCharacterIterator iterator, float x, float y) { // TextLayout draws the iterator as glyph vector // thats why we use it only in the case of TEXT_AS_SHAPES, // otherwise tagged strings are always written as glyphs if (isProperty(TEXT_AS_SHAPES)) { // draws all attributes TextLayout tl = new TextLayout(iterator, getFontRenderContext()); tl.draw(this, x, y); } else { // reset to that font at the end Font font = getFont(); // initial attributes, we us TextAttribute.equals() rather // than Font.equals() because using Font.equals() we do // not get a 'false' if underline etc. is changed Map<? extends Attribute, Object> attributes = FontUtilities .getAttributes(font); // stores all characters which are written with the same font // if font is changed the buffer will be written and cleared // after it StringBuffer sb = new StringBuffer(); for (char c = iterator.first(); c != AttributedCharacterIterator.DONE; c = iterator .next()) { // append c if font is not changed if (attributes.equals(iterator.getAttributes())) { sb.append(c); } else { // TextLayout does not like 0 length strings if (sb.length() > 0) { // draw sb if font is changed drawString(sb.toString(), x, y); // change the x offset for the next drawing // FIXME: change y offset for vertical text TextLayout tl = new TextLayout(sb.toString(), attributes, getFontRenderContext()); // calculate real width x = x + Math.max(tl.getAdvance(), (float) tl .getBounds().getWidth()); } // empty sb sb = new StringBuffer(); sb.append(c); // change the font attributes = iterator.getAttributes(); setFont(new Font(attributes)); } } // draw the rest if (sb.length() > 0) { drawString(sb.toString(), x, y); } // use the old font for the next string drawing setFont(font); } } /* * ========================================================================== * ====== | 6. Transformations * ============================================== * ================================== */ /** * Get the current transform. * * @return current transform */ public AffineTransform getTransform() { return new AffineTransform(currentTransform); } /** * Set the current transform. Calls writeSetTransform(Transform). * * @param transform * to be set */ public void setTransform(AffineTransform transform) { // Fix for FREEHEP-569 oldTransform.setTransform(currentTransform); currentTransform.setTransform(transform); try { writeSetTransform(transform); } catch (IOException e) { handleException(e); } } /** * Transforms the current transform. Calls writeTransform(Transform) * * @param transform * to be applied */ public void transform(AffineTransform transform) { currentTransform.concatenate(transform); try { writeTransform(transform); } catch (IOException e) { handleException(e); } } /** * Translates the current transform. Calls writeTransform(Transform) * * @param x * amount by which to translate * @param y * amount by which to translate */ public void translate(double x, double y) { currentTransform.translate(x, y); try { writeTransform(new AffineTransform(1, 0, 0, 1, x, y)); } catch (IOException e) { handleException(e); } } /** * Rotate the current transform over the Z-axis. Calls * writeTransform(Transform). Rotating with a positive angle theta rotates * points on the positive x axis toward the positive y axis. * * @param theta * radians over which to rotate */ public void rotate(double theta) { currentTransform.rotate(theta); try { writeTransform(new AffineTransform(Math.cos(theta), Math.sin(theta), -Math.sin(theta), Math.cos(theta), 0, 0)); } catch (IOException e) { handleException(e); } } /** * Scales the current transform. Calls writeTransform(Transform). * * @param sx * amount used for scaling * @param sy * amount used for scaling */ public void scale(double sx, double sy) { currentTransform.scale(sx, sy); try { writeTransform(new AffineTransform(sx, 0, 0, sy, 0, 0)); } catch (IOException e) { handleException(e); } } /** * Shears the current transform. Calls writeTransform(Transform). * * @param shx * amount for shearing * @param shy * amount for shearing */ public void shear(double shx, double shy) { currentTransform.shear(shx, shy); try { writeTransform(new AffineTransform(1, shy, shx, 1, 0, 0)); } catch (IOException e) { handleException(e); } } /** * Writes out the transform as it needs to be concatenated to the internal * transform of the output format. If there is no implementation of an * internal transform, then this method needs to do nothing, BUT all * coordinates need to be transformed by the currentTransform before being * written out. * * @param transform * to be written */ protected abstract void writeTransform(AffineTransform transform) throws IOException; /** * Clears any existing transformation and sets the a new one. The default * implementation calls writeTransform using the inverted affine transform * to calculate it. s * * * @param transform * to be written */ protected void writeSetTransform(AffineTransform transform) throws IOException { try { AffineTransform deltaTransform = new AffineTransform(transform); deltaTransform.concatenate(oldTransform.createInverse()); writeTransform(deltaTransform); } catch (NoninvertibleTransformException e) { // ignored... System.err.println("Warning: (ignored) Could not invert matrix: " //$NON-NLS-1$ + oldTransform); } } /* * ========================================================================== * ====== | 7. Clipping * ====================================================== * ========================== */ /** * Gets the current clip in form of a Shape (Rectangle). * * @return current clip */ public Shape getClip() { return (userClip != null) ? new Area(untransformShape(userClip)) : null; } /** * Gets the current clip in form of a Rectangle. * * @return current clip */ public Rectangle getClipBounds() { Shape clip = getClip(); return (clip != null) ? getClip().getBounds() : null; } /** * Gets the current clip in form of a Rectangle. * * @return current clip */ public Rectangle getClipBounds(Rectangle r) { Rectangle bounds = getClipBounds(); if (bounds != null) r.setBounds(bounds); return r; } /** * Clips rectangle. Calls clip(Rectangle). * * @param x * rectangle for clipping * @param y * rectangle for clipping * @param width * rectangle for clipping * @param height * rectangle for clipping */ public void clipRect(int x, int y, int width, int height) { clip(new Rectangle(x, y, width, height)); } /** * Clips rectangle. Calls clip(Rectangle2D). * * @param x * rectangle for clipping * @param y * rectangle for clipping * @param width * rectangle for clipping * @param height * rectangle for clipping */ public void clipRect(double x, double y, double width, double height) { clip(new Rectangle2D.Double(x, y, width, height)); } /** * Clips rectangle. Calls clip(Rectangle). * * @param x * rectangle for clipping * @param y * rectangle for clipping * @param width * rectangle for clipping * @param height * rectangle for clipping */ public void setClip(int x, int y, int width, int height) { setClip(new Rectangle(x, y, width, height)); } /** * Clips rectangle. Calls clip(Rectangle2D). * * @param x * rectangle for clipping * @param y * rectangle for clipping * @param width * rectangle for clipping * @param height * rectangle for clipping */ public void setClip(double x, double y, double width, double height) { setClip(new Rectangle2D.Double(x, y, width, height)); } /** * Clips shape. Clears userClip and calls clip(Shape). * * @param s * used for clipping */ public void setClip(Shape s) { Shape ts = transformShape(s); userClip = (ts != null) ? new Area(ts) : null; try { writeSetClip(s); } catch (IOException e) { handleException(e); } } /** * Clips using given shape. Dispatches to writeClip(Rectangle), * writeClip(Rectangle2D) or writeClip(Shape). * * @param s * used for clipping */ public void clip(Shape s) { Shape ts = transformShape(s); if (userClip != null) { if (ts != null) { userClip.intersect(new Area(ts)); } else { userClip = null; } } else { userClip = (ts != null) ? new Area(ts) : null; } try { writeClip(s); } catch (IOException e) { handleException(e); } } /** * Write out Shape clip. * * @param shape * to be used for clipping */ protected abstract void writeClip(Shape shape) throws IOException; /** * Write out Shape clip. * * @param shape * to be used for clipping */ protected abstract void writeSetClip(Shape shape) throws IOException; /* * ========================================================================== * ====== | 8. Graphics State * ================================================ * ================================ */ /* 8.1. stroke/linewidth */ /** * Get the current stroke. * * @return current stroke */ public Stroke getStroke() { return currentStroke; } /** * Sets the current stroke. Calls writeStroke if stroke is unequal to the * current stroke. * * @param stroke * to be set */ public void setStroke(Stroke stroke) { if (stroke.equals(currentStroke)) { return; } try { writeStroke(stroke); } catch (IOException e) { handleException(e); } currentStroke = stroke; } /** * Writes the current stroke. If stroke is an instance of BasicStroke it * will call writeWidth, writeCap, writeJoin, writeMiterLimit and writeDash, * if any were different than the current stroke. */ protected void writeStroke(Stroke stroke) throws IOException { if (stroke instanceof BasicStroke) { BasicStroke ns = (BasicStroke) stroke; // get the current values for comparison if available, // otherwise set them to -1="undefined" int currentCap = -1, currentJoin = -1; float currentWidth = -1, currentLimit = -1, currentDashPhase = -1; float[] currentDashArray = null; if ((currentStroke != null) && (currentStroke instanceof BasicStroke)) { BasicStroke cs = (BasicStroke) currentStroke; currentCap = cs.getEndCap(); currentJoin = cs.getLineJoin(); currentWidth = cs.getLineWidth(); currentLimit = cs.getMiterLimit(); currentDashArray = cs.getDashArray(); currentDashPhase = cs.getDashPhase(); } // Check the linewidth. float width = ns.getLineWidth(); // if (currentWidth != width) { writeWidth(width); // } // Check the line caps. int cap = ns.getEndCap(); if (currentCap != cap) { writeCap(cap); } // Check the line joins. int join = ns.getLineJoin(); if (currentJoin != join) { writeJoin(join); } // Check the miter limit and validity of value float limit = ns.getMiterLimit(); if ((currentLimit != limit) && (limit >= 1.0f)) { writeMiterLimit(limit); } // Check to see if there are differences in the phase or dash // if (!Arrays.equals(currentDashArray, ns.getDashArray()) // || (currentDashPhase != ns.getDashPhase())) { // write the dashing parameters if (ns.getDashArray() != null) { writeDash(ns.getDashArray(), ns.getDashPhase()); } else { writeDash(new float[0], ns.getDashPhase()); } // } } } /** * Writes out the width of the stroke. * * @param width * of the stroke */ protected void writeWidth(float width) throws IOException { writeWarning(getClass() + ": writeWidth() not implemented."); //$NON-NLS-1$ } /** * Writes out the cap of the stroke. * * @param cap * of the stroke */ protected void writeCap(int cap) throws IOException { writeWarning(getClass() + ": writeCap() not implemented."); //$NON-NLS-1$ } /** * Writes out the join of the stroke. * * @param join * of the stroke */ protected void writeJoin(int join) throws IOException { writeWarning(getClass() + ": writeJoin() not implemented."); //$NON-NLS-1$ } /** * Writes out the miter limit of the stroke. * * @param limit * miter limit of the stroke */ protected void writeMiterLimit(float limit) throws IOException { writeWarning(getClass() + ": writeMiterLimit() not implemented."); //$NON-NLS-1$ } /** * Writes out the dash of the stroke. * * @param dash * dash pattern, empty array is solid line * @param phase * of the dash pattern */ protected void writeDash(float[] dash, float phase) throws IOException { // for backward compatibility double[] dd = new double[dash.length]; for (int i = 0; i < dash.length; i++) { dd[i] = dash[i]; } writeDash(dd, (double) phase); } /** * Writes out the dash of the stroke. * * @deprecated use writeDash(float[], float) * @param dash * dash pattern, empty array is solid line * @param phase * of the dash pattern */ protected void writeDash(double[] dash, double phase) throws IOException { writeWarning(getClass() + ": writeDash() not implemented."); //$NON-NLS-1$ } /* 8.2 Paint */ public void setColor(Color color) { if (color == null) return; // if (color.equals(getColor())) // return; try { super.setColor(color); writePaint(getPrintColor(color)); } catch (IOException e) { handleException(e); } } /** * Sets the current paint. Dispatches to writePaint(Color), * writePaint(GradientPaint), writePaint(TexturePaint paint) or * writePaint(Paint). In the case paint is a Color the current color is also * changed. * * @param paint * to be set */ public void setPaint(Paint paint) { if (paint == null) return; // if (paint.equals(getPaint())) // return; try { if (paint instanceof Color) { setColor((Color) paint); } else if (paint instanceof GradientPaint) { super.setPaint(paint); writePaint((GradientPaint) paint); } else if (paint instanceof TexturePaint) { super.setPaint(paint); writePaint((TexturePaint) paint); } else { super.setPaint(paint); writePaint(paint); } } catch (IOException e) { handleException(e); } } /** * Writes out paint as the given color. * * @param color * to be written */ protected abstract void writePaint(Color color) throws IOException; /** * Writes out paint as the given gradient. * * @param paint * to be written */ protected abstract void writePaint(GradientPaint paint) throws IOException; /** * Writes out paint as the given texture. * * @param paint * to be written */ protected abstract void writePaint(TexturePaint paint) throws IOException; /** * Writes out paint. * * @param paint * to be written */ protected abstract void writePaint(Paint paint) throws IOException; /* 8.3. font */ /** * Gets the current font render context. This returns an standard * FontRenderContext with anti-aliasing and uses fractional metrics. * * @return current font render context */ public FontRenderContext getFontRenderContext() { // NOTE: not sure? // Fixed for VG-285 return new FontRenderContext(new AffineTransform(1, 0, 0, 1, 0, 0), true, true); } /** * Gets the fontmetrics. * * @deprecated * @param font * to be used for retrieving fontmetrics * @return fontmetrics for given font */ public FontMetrics getFontMetrics(Font font) { return Toolkit.getDefaultToolkit().getFontMetrics(font); } /* 8.4. rendering hints */ /** * Gets a copy of the rendering hints. * * @return clone of table of rendering hints. */ public RenderingHints getRenderingHints() { return (RenderingHints) hints.clone(); } /** * Adds to table of rendering hints. * * @param hints * table to be added */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void addRenderingHints(Map hints) { hints.putAll(hints); } /** * Sets table of rendering hints. * * @param hints * table to be set */ @SuppressWarnings("rawtypes") public void setRenderingHints(Map hints) { this.hints.clear(); if (hints instanceof RenderingHints) { RenderingHints renderingHints = (RenderingHints) hints; this.hints.putAll((Map) renderingHints.clone()); } else { this.hints.putAll(hints); } } /** * Gets a given rendering hint. * * @param key * hint key * @return hint associated to key */ public Object getRenderingHint(RenderingHints.Key key) { return hints.get(key); } /** * Sets a given rendering hint. * * @param key * hint key * @param hint * to be associated with key */ public void setRenderingHint(RenderingHints.Key key, Object hint) { // extra protection, failed on under MacOS X 10.2.6, jdk 1.4.1_01-39/14 if ((key == null) || (hint == null)) return; hints.put(key, hint); } /** * Sets the current font. * * @param font * to be set */ public void setFont(Font font) { if (font == null) return; // FIXME: maybe add delayed setting super.setFont(font); // write the font try { writeFont(font); } catch (IOException e) { handleException(e); } } /** * Writes the font * * @param font * to be written */ protected abstract void writeFont(Font font) throws IOException; /* * ========================================================================== * ====== | 9. Auxiliary * ==================================================== * ============================ */ /** * Gets current composite. * * @return current composite */ public Composite getComposite() { return currentComposite; } /** * Sets current composite. * * @param composite * to be set */ public void setComposite(Composite composite) { currentComposite = composite; } /** * Handles an exception which has been caught. Dispatches exception to * writeWarning for UnsupportedOperationExceptions and writeError for others * * @param exception * to be handled */ protected void handleException(Exception exception) { if (exception instanceof UnsupportedOperationException) { writeWarning(exception); } else { writeError(exception); } } /** * Writes out a warning, by default to System.err. * * @param exception * warning to be written */ protected void writeWarning(Exception exception) { writeWarning(exception.getMessage()); } /** * Writes out a warning, by default to System.err. * * @param warning * to be written */ protected void writeWarning(String warning) { if (isProperty(EMIT_WARNINGS)) { System.err.println(warning); } } /** * Writes out an error, by default the stack trace is printed. * * @param exception * error to be reported */ protected void writeError(Exception exception) { throw new RuntimeException(exception); // FIXME decide what we should do /* * if (isProperty(EMIT_ERRORS)) { System.err.println(exception); * exception.printStackTrace(System.err); } */ } protected Shape createShape(double[] xPoints, double[] yPoints, int nPoints, boolean close) { GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); if (nPoints > 0) { path.moveTo((float) xPoints[0], (float) yPoints[0]); for (int i = 1; i < nPoints; i++) { path.lineTo((float) xPoints[i], (float) yPoints[i]); } if (close) path.closePath(); } return path; } private Shape transformShape(AffineTransform at, Shape s) { if (s == null) return null; return at.createTransformedShape(s); } private Shape transformShape(Shape s) { return transformShape(getTransform(), s); } private Shape untransformShape(Shape s) { if (s == null) return null; try { return transformShape(getTransform().createInverse(), s); } catch (NoninvertibleTransformException e) { return null; } } /** * Draws an overline for the text at (x, y). The method is usesefull for * drivers that do not support overlines by itself. * * @param text * text for width calulation * @param font * font for width calulation * @param x * position of text * @param y * position of text */ protected void overLine(String text, Font font, float x, float y) { TextLayout layout = new TextLayout(text, font, getFontRenderContext()); float width = Math.max(layout.getAdvance(), (float) layout.getBounds() .getWidth()); GeneralPath path = new GeneralPath(); path.moveTo(x, y + (float) layout.getBounds().getY() - layout.getAscent()); path.lineTo(x + width, y + (float) layout.getBounds().getY() - layout.getAscent() - layout.getAscent()); draw(path); } }