// Copyright 2000-2006, FreeHEP package org.freehep.graphics2d; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Insets; import java.awt.Paint; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Stroke; import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Arc2D; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.text.AttributedCharacterIterator; import java.util.Properties; import org.freehep.util.UserProperties; /** * This class implements all conversions from integer to double as well as a few * other convenience functions. It also handles the different drawSymbol and * fillSymbol methods and print colors. The drawing of framed strings is broken * down to lower level methods. * * @author Simon Fischer * @author Mark Donszelmann * @author Steffen Greiffenberg * @version $Id: AbstractVectorGraphics.java,v 1.4 2009-08-17 21:44:44 murkle * Exp $ */ public abstract class AbstractVectorGraphics extends VectorGraphics { private UserProperties properties; private String creator; final private boolean isDeviceIndependent = true; private SymbolShape cachedShape; private int colorMode; private Color backgroundColor; private Color currentColor; private Paint currentPaint; private Font currentFont; public AbstractVectorGraphics() { properties = new UserProperties(); creator = "FreeHEP Graphics2D Driver"; // isDeviceIndependent = false; cachedShape = new SymbolShape(); colorMode = PrintColor.COLOR; // all of these have to be set in the subclasses currentFont = null; backgroundColor = null; currentColor = null; currentPaint = null; } protected AbstractVectorGraphics(AbstractVectorGraphics graphics) { super(); properties = graphics.properties; creator = graphics.creator; // isDeviceIndependent = graphics.isDeviceIndependent; cachedShape = graphics.cachedShape; backgroundColor = graphics.backgroundColor; currentColor = graphics.currentColor; currentPaint = graphics.currentPaint; colorMode = graphics.colorMode; currentFont = graphics.currentFont; } @Override public void setProperties(Properties newProperties) { if (newProperties == null) { return; } properties.setProperties(newProperties); } @Override protected void initProperties(Properties defaults) { properties = new UserProperties(); properties.setProperties(defaults); } @Override protected Properties getProperties() { return properties; } @Override public String getProperty(String key) { return properties.getProperty(key); } @Override public Color getPropertyColor(String key) { return properties.getPropertyColor(key); } @Override public Rectangle getPropertyRectangle(String key) { return properties.getPropertyRectangle(key); } public Insets getPropertyInsets(String key) { return properties.getPropertyInsets(key); } @Override public Dimension getPropertyDimension(String key) { return properties.getPropertyDimension(key); } @Override public int getPropertyInt(String key) { return properties.getPropertyInt(key); } @Override public double getPropertyDouble(String key) { return properties.getPropertyDouble(key); } @Override public boolean isProperty(String key) { return properties.isProperty(key); } @Override public String getCreator() { return creator; } @Override public void setCreator(String creator) { if (creator != null) { this.creator = creator; } } @Override public boolean isDeviceIndependent() { return isDeviceIndependent; } @Override public void setDeviceIndependent(boolean isDeviceIndependent) { // this.isDeviceIndependent = isDeviceIndependent; } /** * Gets the current font. * * @return current font */ @Override public Font getFont() { return currentFont; } /** * Sets the current font. * * @param font * to be set */ @Override public void setFont(Font font) { if (font == null) { return; } // FIXME: maybe add delayed setting currentFont = font; } // public void drawSymbol(int x, int y, int size, int symbol) { // drawSymbol((double) x, (double) y, (double) size, symbol); // } // public void fillSymbol(int x, int y, int size, int symbol) { // fillSymbol((double) x, (double) y, (double) size, symbol); // } // // public void fillAndDrawSymbol(int x, int y, int size, int symbol, // Color fillColor) { // fillAndDrawSymbol((double) x, (double) y, (double) size, symbol, // fillColor); // } // public void drawSymbol(double x, double y, double size, int symbol) { // if (size <= 0) // return; // drawSymbol(this, x, y, size, symbol); // } // protected void drawSymbol(VectorGraphics g, double x, double y, // double size, int symbol) { // switch (symbol) { // case SYMBOL_VLINE: // case SYMBOL_STAR: // case SYMBOL_HLINE: // case SYMBOL_PLUS: // case SYMBOL_CROSS: // case SYMBOL_BOX: // case SYMBOL_UP_TRIANGLE: // case SYMBOL_DN_TRIANGLE: // case SYMBOL_DIAMOND: // cachedShape.create(symbol, x, y, size); // g.draw(cachedShape); // break; // // case SYMBOL_CIRCLE: { // double diameter = Math.max(1, size); // diameter += (diameter % 2); // g.drawOval(x - diameter / 2, y - diameter / 2, diameter, diameter); // break; // } // } // } // public void fillSymbol(double x, double y, double size, int symbol) { // if (size <= 0) // return; // fillSymbol(this, x, y, size, symbol); // } // // protected void fillSymbol(VectorGraphics g, double x, double y, // double size, int symbol) { // switch (symbol) { // case SYMBOL_VLINE: // case SYMBOL_STAR: // case SYMBOL_HLINE: // case SYMBOL_PLUS: // case SYMBOL_CROSS: // cachedShape.create(symbol, x, y, size); // g.draw(cachedShape); // break; // // case SYMBOL_BOX: // case SYMBOL_UP_TRIANGLE: // case SYMBOL_DN_TRIANGLE: // case SYMBOL_DIAMOND: // cachedShape.create(symbol, x, y, size); // g.fill(cachedShape); // break; // // case SYMBOL_CIRCLE: { // double diameter = Math.max(1, size); // diameter += (diameter % 2); // g.fillOval(x - diameter / 2, y - diameter / 2, diameter, diameter); // break; // } // } // } // public void fillAndDrawSymbol(double x, double y, double size, int // symbol, // Color fillColor) { // Color color = getColor(); // setColor(fillColor); // fillSymbol(x, y, size, symbol); // setColor(color); // drawSymbol(x, y, size, symbol); // } // public void fillAndDraw(Shape s, Color fillColor) { // Color color = getColor(); // setColor(fillColor); // fill(s); // setColor(color); // draw(s); // } // --------------------------------------------------------- // -------------------- WRAPPER METHODS -------------------- // -------------------- int -> double -------------------- // needs a bias in some cases // --------------------------------------------------------- private static final double bias = 0.5; @Override public void clearRect(int x, int y, int width, int height) { clearRect(x + bias, y + bias, width, height); } @Override public void drawLine(int x1, int y1, int x2, int y2) { drawLine(x1 + bias, y1 + bias, x2 + bias, y2 + bias); } @Override public void drawRect(int x, int y, int width, int height) { drawRect(x + bias, y + bias, width, height); } @Override public void fillRect(int x, int y, int width, int height) { fillRect((double) x, (double) y, (double) width, (double) height); } @Override public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { drawArc(x + bias, y + bias, width, height, startAngle, arcAngle); } @Override public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) { fillArc((double) x, (double) y, (double) width, (double) height, (double) startAngle, (double) arcAngle); } @Override public void drawOval(int x, int y, int width, int height) { drawOval(x + bias, y + bias, width, height); } @Override public void fillOval(int x, int y, int width, int height) { fillOval((double) x, (double) y, (double) width, (double) height); } @Override public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { drawRoundRect(x + bias, y + bias, width, height, arcWidth, arcHeight); } @Override public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { fillRoundRect((double) x, (double) y, (double) width, (double) height, (double) arcWidth, (double) arcHeight); } @Override public void translate(int x, int y) { translate((double) x, (double) y); } /*-------------------------------------------------------------------------------- | 8.1. stroke/linewidth *--------------------------------------------------------------------------------*/ @Override public void setLineWidth(int width) { setLineWidth((double) width); } @Override public void setLineWidth(double width) { Stroke stroke = getStroke(); if (stroke instanceof BasicStroke) { BasicStroke cs = (BasicStroke) stroke; if (cs.getLineWidth() != width) { stroke = new BasicStroke((float) width, cs.getEndCap(), cs.getLineJoin(), cs.getMiterLimit(), cs.getDashArray(), cs.getDashPhase()); setStroke(stroke); } } else { stroke = new BasicStroke((float) width); setStroke(stroke); } } @Override public void drawString(String str, int x, int y) { drawString(str, (double) x, (double) y); } @Override public void drawString(String s, float x, float y) { drawString(s, (double) x, (double) y); } @Override public void drawString(AttributedCharacterIterator iterator, int x, int y) { drawString(iterator, (float) x, (float) y); } /** * Draws frame and banner for a TextLayout, which is used for calculation * auf ajustment * * @param tl * TextLayout for frame calculation * @param x * coordinate to draw string * @param y * coordinate to draw string * @param horizontal * alignment of the text * @param vertical * alignment of the text * @param framed * true if text is surrounded by a frame * @param frameColor * color of the frame * @param frameWidth * witdh of the frame * @param banner * true if the frame is filled by a banner * @param bannerColor * color of the banner * @return Offset for the string inside the frame */ private Point2D drawFrameAndBanner(TextLayout tl, double x, double y, int horizontal, int vertical, boolean framed, Color frameColor, double frameWidth, boolean banner, Color bannerColor) { // calculate string bounds for alignment Rectangle2D bounds = tl.getBounds(); // calculate real bounds bounds.setRect(bounds.getX(), bounds.getY(), // care for Italic fonts too Math.max(tl.getAdvance(), bounds.getWidth()), bounds.getHeight()); // add x and y AffineTransform at = AffineTransform.getTranslateInstance(x, y); // horizontal alignment if (horizontal == TEXT_RIGHT) { at.translate(-bounds.getWidth(), 0); } else if (horizontal == TEXT_CENTER) { at.translate(-bounds.getWidth() / 2, 0); } // vertical alignment if (vertical == TEXT_BASELINE) { // no translation needed } else if (vertical == TEXT_TOP) { at.translate(0, -bounds.getY()); } else if (vertical == TEXT_CENTER) { // the following adds supersript ascent too, // so it does not work // at.translate(0, tl.getAscent() / 2); // this is nearly the same at.translate(0, tl.getDescent()); } else if (vertical == TEXT_BOTTOM) { at.translate(0, -bounds.getHeight() - bounds.getY()); } // transform the bounds bounds = at.createTransformedShape(bounds).getBounds2D(); // create the result with the same transformation Point2D result = at.transform(new Point2D.Double(0, 0), new Point2D.Double()); // space between string and border double adjustment = (getFont().getSize2D() * 2) / 10; // add the adjustment bounds.setRect(bounds.getX() - adjustment, bounds.getY() - adjustment, bounds.getWidth() + 2 * adjustment, bounds.getHeight() + 2 * adjustment); if (banner) { Paint paint = getPaint(); setColor(bannerColor); fill(bounds); setPaint(paint); } if (framed) { Paint paint = getPaint(); Stroke stroke = getStroke(); setColor(frameColor); setLineWidth(frameWidth); draw(bounds); setPaint(paint); setStroke(stroke); } return result; } /** * Draws frame, banner and aligned text inside * * @param str * text to be drawn * @param x * coordinate to draw string * @param y * coordinate to draw string * @param horizontal * alignment of the text * @param vertical * alignment of the text * @param framed * true if text is surrounded by a frame * @param frameColor * color of the frame * @param frameWidth * witdh of the frame * @param banner * true if the frame is filled by a banner * @param bannerColor * color of the banner */ @Override public void drawString(String str, double x, double y, int horizontal, int vertical, boolean framed, Color frameColor, double frameWidth, boolean banner, Color bannerColor) { // change the x offset for the next drawing // FIXME: change y offset for vertical text TextLayout tl = new TextLayout(str, getFont().getAttributes(), getFontRenderContext()); // draw the frame Point2D offset = drawFrameAndBanner(tl, x, y, horizontal, vertical, framed, frameColor, frameWidth, banner, bannerColor); // draw the string drawString(str, offset.getX(), offset.getY()); } /** * Draws the tagged string parsed by a {@link TagHandler} and adds a border * specified by the parameters * * @param str * Tagged text to be drawn * @param x * coordinate to draw string * @param y * coordinate to draw string * @param horizontal * alignment of the text * @param vertical * alignment of the text * @param framed * true if text is surrounded by a frame * @param frameColor * color of the frame * @param frameWidth * witdh of the frame * @param banner * true if the frame is filled by a banner * @param bannerColor * color of the banner */ @Override public void drawString(TagString str, double x, double y, int horizontal, int vertical, boolean framed, Color frameColor, double frameWidth, boolean banner, Color bannerColor) { GenericTagHandler tagHandler = new GenericTagHandler(this); TextLayout tl = tagHandler.createTextLayout(str, getFont().getSize2D() / 7.5); // draw the frame Point2D offset = drawFrameAndBanner(tl, x, y, horizontal, vertical, framed, frameColor, frameWidth, banner, bannerColor); // FIXME: not quite clear why correction is needed // see {@link GenericTagHandler#superscriptCorrection tagHandler.print(str, offset.getX(), offset.getY(), getFont().getSize2D() / 7.5); } // ------------------ other wrapper methods ---------------- @Override public void drawString(String str, double x, double y, int horizontal, int vertical) { drawString(str, x, y, horizontal, vertical, false, null, 0, false, null); } @Override public void drawString(TagString str, double x, double y) { drawString(str, x, y, TEXT_LEFT, TEXT_BASELINE); } @Override public void drawString(TagString str, double x, double y, int horizontal, int vertical) { drawString(str, x, y, horizontal, vertical, false, null, 0, false, null); } /* 8.2. paint/color */ @Override public int getColorMode() { return colorMode; } @Override public void setColorMode(int colorMode) { this.colorMode = colorMode; } /** * Gets the background color. * * @return background color */ @Override public Color getBackground() { return backgroundColor; } /** * Sets the background color. * * @param color * background color to be set */ @Override public void setBackground(Color color) { backgroundColor = color; } /** * Sets the current color and the current paint. Calls writePaint(Color). * * @param color * to be set */ @Override public void setColor(Color color) { if (color == null) { return; } currentColor = color; currentPaint = color; } /** * Gets the current color. * * @return the current color */ @Override public Color getColor() { return currentColor; } /** * Sets the current paint. * * @param paint * to be set */ @Override public void setPaint(Paint paint) { if (paint == null) { return; } if (!(paint instanceof Color)) { currentColor = null; } currentPaint = paint; } /** * Gets the current paint. * * @return paint current paint */ @Override public Paint getPaint() { return currentPaint; } /** * Returns a printColor created from the original printColor, based on the * ColorMode. If you run getPrintColor on it again you still get the same * color, so that it does not matter if you convert it more than once. */ protected Color getPrintColor(Color color) { // shortcut if mode is COLOR for speed if (colorMode == PrintColor.COLOR) { return color; } // otherwise... PrintColor printColor = PrintColor.createPrintColor(color); return printColor.getColor(colorMode); } @Override public void rotate(double theta, double x, double y) { translate(x, y); rotate(theta); translate(-x, -y); } @Override public void drawArc(double x, double y, double width, double height, double startAngle, double arcAngle) { draw(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN)); } @Override public void drawLine(double x1, double y1, double x2, double y2) { draw(new Line2D.Double(x1, y1, x2, y2)); } @Override public void drawOval(double x, double y, double width, double height) { draw(new Ellipse2D.Double(x, y, width, height)); } @Override public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { draw(createShape(xPoints, yPoints, nPoints, false, true)); } @Override public void drawPolyline(double[] xPoints, double[] yPoints, int nPoints) { draw(createShape(xPoints, yPoints, nPoints, false)); } @Override public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { draw(createShape(xPoints, yPoints, nPoints, true, true)); } @Override public void drawPolygon(double[] xPoints, double[] yPoints, int nPoints) { draw(createShape(xPoints, yPoints, nPoints, true)); } @Override public void drawRect(double x, double y, double width, double height) { draw(new Rectangle2D.Double(x, y, width, height)); } @Override public void drawRoundRect(double x, double y, double width, double height, double arcWidth, double arcHeight) { draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight)); } @Override public void fillArc(double x, double y, double width, double height, double startAngle, double arcAngle) { fill(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.PIE)); } @Override public void fillOval(double x, double y, double width, double height) { fill(new Ellipse2D.Double(x, y, width, height)); } @Override public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { fill(createShape(xPoints, yPoints, nPoints, true, false)); } @Override public void fillPolygon(double[] xPoints, double[] yPoints, int nPoints) { fill(createShape(xPoints, yPoints, nPoints, true)); } @Override public void fillRect(double x, double y, double width, double height) { fill(new Rectangle2D.Double(x, y, width, height)); } @Override public void fillRoundRect(double x, double y, double width, double height, double arcWidth, double arcHeight) { fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight)); } /** * Creates a polyline/polygon shape from a set of points. Needs to be * defined in subclass because its implementations could be device specific * * @param xPoints * X coordinates of the polyline. * @param yPoints * Y coordinates of the polyline. * @param nPoints * number of points of the polyline. * @param close * is shape closed */ protected abstract Shape createShape(double[] xPoints, double[] yPoints, int nPoints, boolean close); /** * Creates a polyline/polygon shape from a set of points. Needs a bias! * * @param xPoints * X coordinates of the polyline. * @param yPoints * Y coordinates of the polyline. * @param nPoints * number of points of the polyline. * @param close * is shape closed */ protected Shape createShape(int[] xPoints, int[] yPoints, int nPoints, boolean close, boolean biased) { float offset = biased ? (float) bias : 0.0f; GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); if (nPoints > 0) { path.moveTo(xPoints[0] + offset, yPoints[0] + offset); int lastX = xPoints[0]; int lastY = yPoints[0]; if (close && (Math.abs(xPoints[nPoints - 1] - lastX) < 1) && (Math.abs(yPoints[nPoints - 1] - lastY) < 1)) { nPoints--; } for (int i = 1; i < nPoints; i++) { if ((Math.abs(xPoints[i] - lastX) > 1) || (Math.abs(yPoints[i] - lastY) > 1)) { path.lineTo(xPoints[i] + offset, yPoints[i] + offset); lastX = xPoints[i]; lastY = yPoints[i]; } } if (close) { path.closePath(); } } return path; } }