// Copyright 2000-2007, FreeHEP
package 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.util.Arrays;
import java.util.Map;
import org.freehep.graphics2d.font.FontEncoder;
import org.freehep.graphics2d.font.FontUtilities;
import 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
* @version $Id: AbstractVectorGraphicsIO.java 12625 2007-06-08 21:43:44Z duns $
*/
public abstract class AbstractVectorGraphicsIO extends VectorGraphicsIO {
private static final String rootKey = AbstractVectorGraphicsIO.class
.getName();
public static final String EMIT_WARNINGS = rootKey + ".EMIT_WARNINGS";
public static final String TEXT_AS_SHAPES = rootKey + "." + FontConstants.TEXT_AS_SHAPES;
public static final String EMIT_ERRORS = rootKey + ".EMIT_ERRORS";
public static final String CLIP = rootKey+".CLIP";
/*
* ================================================================================
* 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:
*
* - Paint: black
*
- Font: Dailog, Plain, 12pt
*
- Stroke: Linewidth 1.0; No Dashing; Miter Join Style; Miter Limit 10;
* Square Endcaps;
*
- Transform: Identity
*
- Composite: AlphaComposite.SRC_OVER
*
- Clip: Rectangle(0, 0, size.width, size.height)
*
*
* @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));
// Initialize the rendering hints.
hints = new RenderingHints(null);
}
/**
* Constructs a Graphics context with the following graphics state:
*
* - Paint: The color of the component.
*
- Font: The font of the component.
*
- Stroke: Linewidth 1.0; No Dashing; Miter Join Style; Miter Limit 10;
* Square Endcaps;
*
- Transform: The getDefaultTransform for the GraphicsConfiguration of
* the component.
*
- Composite: AlphaComposite.SRC_OVER
*
- Clip: The size of the component, Rectangle(0, 0, size.width,
* size.height)
*
*
* @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 */
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);
}
}
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 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("")) {
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")) {
string = FontEncoder.getEncodedString(string, fontName);
// use a standard font, not Symbol.
font = new Font("Serif", font.getStyle(), font.getSize());
}
// 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/**/ 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: "+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.");
}
/**
* Writes out the cap of the stroke.
*
* @param cap of the stroke
*/
protected void writeCap(int cap) throws IOException {
writeWarning(getClass() + ": writeCap() not implemented.");
}
/**
* Writes out the join of the stroke.
*
* @param join of the stroke
*/
protected void writeJoin(int join) throws IOException {
writeWarning(getClass() + ": writeJoin() not implemented.");
}
/**
* 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.");
}
/**
* 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.");
}
/* 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
*/
public void addRenderingHints(Map hints) {
hints.putAll(hints);
}
/**
* Sets table of rendering hints.
*
* @param hints table to be set
*/
public void setRenderingHints(Map hints) {
hints.clear();
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);
}
}