// Copyright 2000-2007, 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.Area;
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.graphics2d.font.FontUtilities;
import org.freehep.graphicsbase.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: freehep-graphics2d/src/main/java/org/freehep/graphics2d/AbstractVectorGraphics.java 59372df5e0d9 2007/02/06 21:11:19 duns $
*/
public abstract class AbstractVectorGraphics extends VectorGraphics {
private UserProperties properties;
private String creator;
private boolean isDeviceIndependent;
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;
}
public void setProperties(Properties newProperties) {
if (newProperties == null)
return;
properties.setProperties(newProperties);
}
protected void initProperties(Properties defaults) {
properties = new UserProperties();
properties.setProperties(defaults);
}
protected Properties getProperties() {
return properties;
}
public String getProperty(String key) {
return properties.getProperty(key);
}
public Color getPropertyColor(String key) {
return properties.getPropertyColor(key);
}
public Rectangle getPropertyRectangle(String key) {
return properties.getPropertyRectangle(key);
}
public Insets getPropertyInsets(String key) {
return properties.getPropertyInsets(key);
}
public Dimension getPropertyDimension(String key) {
return properties.getPropertyDimension(key);
}
public int getPropertyInt(String key) {
return properties.getPropertyInt(key);
}
public double getPropertyDouble(String key) {
return properties.getPropertyDouble(key);
}
public boolean isProperty(String key) {
return properties.isProperty(key);
}
public String getCreator() {
return creator;
}
public void setCreator(String creator) {
if (creator != null) {
this.creator = creator;
}
}
public boolean isDeviceIndependent() {
return isDeviceIndependent;
}
public void setDeviceIndependent(boolean isDeviceIndependent) {
this.isDeviceIndependent = isDeviceIndependent;
}
/**
* Gets the current font.
*
* @return current font
*/
public Font getFont() {
return currentFont;
}
/**
* Sets the current font.
*
* @param font to be set
*/
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;
public void clearRect(int x, int y, int width, int height) {
clearRect((double) x + bias, (double) y + bias, (double) width, (double) height);
}
public void drawLine(int x1, int y1, int x2, int y2) {
drawLine((double) x1 + bias, (double) y1 + bias, (double) x2 + bias, (double) y2 + bias);
}
public void drawRect(int x, int y, int width, int height) {
drawRect((double) x + bias, (double) y + bias, (double) width, (double) height);
}
public void fillRect(int x, int y, int width, int height) {
fillRect((double) x, (double) y, (double) width, (double) height);
}
public void drawArc(int x, int y, int width, int height, int startAngle,
int arcAngle) {
drawArc((double) x + bias, (double) y + bias, (double) width, (double) height,
(double) startAngle, (double) arcAngle);
}
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);
}
public void drawOval(int x, int y, int width, int height) {
drawOval((double) x + bias, (double) y + bias, (double) width, (double) height);
}
public void fillOval(int x, int y, int width, int height) {
fillOval((double) x, (double) y, (double) width, (double) height);
}
public void drawRoundRect(int x, int y, int width, int height,
int arcWidth, int arcHeight) {
drawRoundRect((double)x + bias, (double)y + bias, (double) width, (double) height, (double) arcWidth,
(double) arcHeight);
}
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);
}
public void translate(int x, int y) {
translate((double) x, (double) y);
}
/*--------------------------------------------------------------------------------
| 8.1. stroke/linewidth
*--------------------------------------------------------------------------------*/
public void setLineWidth(int width) {
setLineWidth((double) width);
}
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);
}
}
public void drawString(String str, int x, int y) {
drawString(str, (double) x, (double) y);
}
public void drawString(String s, float x, float y) {
drawString(s, (double) x, (double) y);
}
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
*/
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,
FontUtilities.getAttributes(getFont()),
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
*/
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 ----------------
public void drawString(String str, double x, double y, int horizontal,
int vertical) {
drawString(str, x, y, horizontal, vertical, false, null, 0, false, null);
}
public void drawString(TagString str, double x, double y) {
drawString(str, x, y, TEXT_LEFT, TEXT_BASELINE);
}
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 */
public int getColorMode() {
return colorMode;
}
public void setColorMode(int colorMode) {
this.colorMode = colorMode;
}
/**
* Gets the background color.
*
* @return background color
*/
public Color getBackground() {
return backgroundColor;
}
/**
* Sets the background color.
*
* @param color background color to be set
*/
public void setBackground(Color color) {
backgroundColor = color;
}
/**
* Sets the current color and the current paint. Calls writePaint(Color).
*
* @param color to be set
*/
public void setColor(Color color) {
if (color == null) return;
currentColor = color;
currentPaint = color;
}
/**
* Gets the current color.
*
* @return the current color
*/
public Color getColor() {
return currentColor;
}
/**
* Sets the current paint.
*
* @param paint to be set
*/
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
*/
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);
}
public void rotate(double theta, double x, double y) {
translate(x, y);
rotate(theta);
translate(-x, -y);
}
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));
}
public void drawLine(double x1, double y1, double x2, double y2) {
draw(new Line2D.Double(x1, y1, x2, y2));
}
public void drawOval(double x, double y, double width, double height) {
draw(new Ellipse2D.Double(x, y, width, height));
}
public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
draw(createShape(xPoints, yPoints, nPoints, false, true));
}
public void drawPolyline(double[] xPoints, double[] yPoints, int nPoints) {
draw(createShape(xPoints, yPoints, nPoints, false));
}
public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
draw(createShape(xPoints, yPoints, nPoints, true, true));
}
public void drawPolygon(double[] xPoints, double[] yPoints, int nPoints) {
draw(createShape(xPoints, yPoints, nPoints, true));
}
public void drawRect(double x, double y, double width, double height) {
draw(new Rectangle2D.Double(x, y, width, height));
}
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));
}
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));
}
public void fillOval(double x, double y, double width, double height) {
fill(new Ellipse2D.Double(x, y, width, height));
}
public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
fill(createShape(xPoints, yPoints, nPoints, true, false));
}
public void fillPolygon(double[] xPoints, double[] yPoints, int nPoints) {
fill(createShape(xPoints, yPoints, nPoints, true));
}
public void fillRect(double x, double y, double width, double height) {
fill(new Rectangle2D.Double(x, y, width, height));
}
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;
}
/**
* Checks whether or not the specified <code>Shape</code> intersects
* the specified {@link Rectangle}, which is in device
* space.
*
* @param rect the area in device space to check for a hit
* @param s the <code>Shape</code> to check for a hit
* @param onStroke flag used to choose between testing the stroked or the filled shape.
* @see java.awt.Graphics2D#hit(Rectangle, Shape, boolean)
*/
public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
if (onStroke && getStroke() != null) {
s = getStroke().createStrokedShape(s);
}
if (getTransform() != null) {
s = getTransform().createTransformedShape(s);
}
Area area = new Area(s);
if (getClip() != null) {
area.intersect(new Area(getClip()));
}
return area.intersects(rect);
}
}