org.jhotdraw.samples.svg.figures.SVGPathFigure Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jhotdraw Show documentation
Show all versions of jhotdraw Show documentation
JHotDraw 7 with openTCS-specific modifications
The newest version!
/*
* @(#)SVGPathFigure.java
*
* Copyright (c) 1996-2010 by the original authors of JHotDraw and all its
* contributors. All rights reserved.
*
* You may not use, copy or modify this file, except in compliance with the
* license agreement you entered into with the copyright holders. For details
* see accompanying license terms.
*/
package org.jhotdraw.samples.svg.figures;
import edu.umd.cs.findbugs.annotations.Nullable;
import org.jhotdraw.draw.handle.TransformHandleKit;
import org.jhotdraw.draw.handle.Handle;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.util.*;
import javax.swing.*;
import javax.swing.undo.*;
import org.jhotdraw.draw.*;
import org.jhotdraw.geom.*;
import org.jhotdraw.samples.svg.*;
import org.jhotdraw.util.*;
import static org.jhotdraw.samples.svg.SVGAttributeKeys.*;
/**
* SVGPath is a composite Figure which contains one or more
* SVGBezierFigures as its children.
*
* @author Werner Randelshofer
* @version $Id: SVGPathFigure.java 718 2010-11-21 17:49:53Z rawcoder $
*/
public class SVGPathFigure extends AbstractAttributedCompositeFigure implements SVGFigure {
/**
* This cached path is used for drawing.
*/
@Nullable private transient Path2D.Double cachedPath;
// private transient Rectangle2D.Double cachedDrawingArea;
/**
* This is used to perform faster hit testing.
*/
@Nullable private transient Shape cachedHitShape;
private final static boolean DEBUG = false;
/** Creates a new instance. */
public SVGPathFigure() {
add(new SVGBezierFigure());
SVGAttributeKeys.setDefaults(this);
}
public SVGPathFigure(boolean isEmpty) {
if (!isEmpty) {
add(new SVGBezierFigure());
}
SVGAttributeKeys.setDefaults(this);
setConnectable(false);
}
@Override
public void draw(Graphics2D g) {
double opacity = get(OPACITY);
opacity = Math.min(Math.max(0d, opacity), 1d);
if (opacity != 0d) {
if (opacity != 1d) {
Rectangle2D.Double drawingArea = getDrawingArea();
Rectangle2D clipBounds = g.getClipBounds();
if (clipBounds != null) {
Rectangle2D.intersect(drawingArea, clipBounds, drawingArea);
}
if (!drawingArea.isEmpty()) {
BufferedImage buf = new BufferedImage(
Math.max(1, (int) ((2 + drawingArea.width) * g.getTransform().getScaleX())),
Math.max(1, (int) ((2 + drawingArea.height) * g.getTransform().getScaleY())),
BufferedImage.TYPE_INT_ARGB);
Graphics2D gr = buf.createGraphics();
gr.scale(g.getTransform().getScaleX(), g.getTransform().getScaleY());
gr.translate((int) -drawingArea.x, (int) -drawingArea.y);
gr.setRenderingHints(g.getRenderingHints());
drawFigure(gr);
gr.dispose();
Composite savedComposite = g.getComposite();
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) opacity));
g.drawImage(buf, (int) drawingArea.x, (int) drawingArea.y,
2 + (int) drawingArea.width, 2 + (int) drawingArea.height, null);
g.setComposite(savedComposite);
}
} else {
drawFigure(g);
}
}
}
@Override
public void drawFigure(Graphics2D g) {
AffineTransform savedTransform = null;
if (get(TRANSFORM) != null) {
savedTransform = g.getTransform();
g.transform(get(TRANSFORM));
}
Paint paint = SVGAttributeKeys.getFillPaint(this);
if (paint != null) {
g.setPaint(paint);
drawFill(g);
}
paint = SVGAttributeKeys.getStrokePaint(this);
if (paint != null) {
g.setPaint(paint);
g.setStroke(SVGAttributeKeys.getStroke(this));
drawStroke(g);
}
if (get(TRANSFORM) != null) {
g.setTransform(savedTransform);
}
}
@Override
protected void drawChildren(Graphics2D g) {
// empty
}
@Override
public void drawFill(Graphics2D g) {
g.fill(getPath());
}
@Override
public void drawStroke(Graphics2D g) {
g.draw(getPath());
}
@Override
protected void invalidate() {
super.invalidate();
cachedPath = null;
cachedDrawingArea = null;
cachedHitShape = null;
}
protected Path2D.Double getPath() {
if (cachedPath == null) {
cachedPath = new Path2D.Double();
cachedPath.setWindingRule(get(WINDING_RULE) == WindingRule.EVEN_ODD ? Path2D.Double.WIND_EVEN_ODD : Path2D.Double.WIND_NON_ZERO);
for (Figure child : getChildren()) {
SVGBezierFigure b = (SVGBezierFigure) child;
cachedPath.append(b.getBezierPath(), false);
}
}
return cachedPath;
}
protected Shape getHitShape() {
if (cachedHitShape == null) {
cachedHitShape = getPath();
if (get(FILL_COLOR) == null && get(FILL_GRADIENT) == null) {
cachedHitShape = SVGAttributeKeys.getHitStroke(this).createStrokedShape(cachedHitShape);
}
}
return cachedHitShape;
}
// int count;
@Override
public Rectangle2D.Double getDrawingArea() {
if (cachedDrawingArea == null) {
double strokeTotalWidth = Math.max(1d, AttributeKeys.getStrokeTotalWidth(this));
double width = strokeTotalWidth / 2d;
if (get(STROKE_JOIN) == BasicStroke.JOIN_MITER) {
width *= get(STROKE_MITER_LIMIT);
} else if (get(STROKE_CAP) != BasicStroke.CAP_BUTT) {
width += strokeTotalWidth * 2;
}
Shape gp = (Path2D.Double) getPath();
Rectangle2D strokeRect = new Rectangle2D.Double(0, 0, width, width);
AffineTransform tx = get(TRANSFORM);
if (tx != null) {
// We have to use the (rectangular) bounds of the path here,
// because we draw a rectangular handle over the shape of the figure
gp = tx.createTransformedShape(gp.getBounds2D());
strokeRect = tx.createTransformedShape(strokeRect).getBounds2D();
}
Rectangle2D rx = gp.getBounds2D();
Rectangle2D.Double r = (rx instanceof Rectangle2D.Double) ? (Rectangle2D.Double) rx : new Rectangle2D.Double(rx.getX(), rx.getY(), rx.getWidth(), rx.getHeight());
Geom.grow(r, strokeRect.getWidth(), strokeRect.getHeight());
cachedDrawingArea = r;
}
return (Rectangle2D.Double) cachedDrawingArea.clone();
}
@Override
public boolean contains(Point2D.Double p) {
getPath();
if (get(TRANSFORM) != null) {
try {
p = (Point2D.Double) get(TRANSFORM).inverseTransform(p, new Point2D.Double());
} catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
boolean isClosed = getChild(0).get(PATH_CLOSED);
if (isClosed && get(FILL_COLOR) == null && get(FILL_GRADIENT) == null) {
return getHitShape().contains(p);
}
/*
return cachedPath.contains(p2);
*/
double tolerance = Math.max(2f, AttributeKeys.getStrokeTotalWidth(this) / 2d);
if (isClosed || get(FILL_COLOR) != null || get(FILL_GRADIENT) != null) {
if (getPath().contains(p)) {
return true;
}
double grow = AttributeKeys.getPerpendicularHitGrowth(this) /** 2d*/
;
GrowStroke gs = new GrowStroke(grow,
(AttributeKeys.getStrokeTotalWidth(this)
* get(STROKE_MITER_LIMIT)));
if (gs.createStrokedShape(getPath()).contains(p)) {
return true;
} else {
if (isClosed) {
return false;
}
}
}
if (!isClosed) {
if (Shapes.outlineContains(getPath(), p, tolerance)) {
return true;
}
}
return false;
}
@Override
public void setBounds(Point2D.Double anchor, Point2D.Double lead) {
if (getChildCount() == 1 && ((SVGBezierFigure) getChild(0)).getNodeCount() <= 2) {
SVGBezierFigure b = (SVGBezierFigure) getChild(0);
b.setBounds(anchor, lead);
invalidate();
} else {
super.setBounds(anchor, lead);
}
}
@Override
public void transform(AffineTransform tx) {
if (get(TRANSFORM) != null
|| (tx.getType() & (AffineTransform.TYPE_TRANSLATION)) != tx.getType()) {
if (get(TRANSFORM) == null) {
TRANSFORM.setClone(this, tx);
} else {
AffineTransform t = TRANSFORM.getClone(this);
t.preConcatenate(tx);
set(TRANSFORM, t);
}
} else {
for (Figure f : getChildren()) {
f.transform(tx);
}
if (get(FILL_GRADIENT) != null
&& !get(FILL_GRADIENT).isRelativeToFigureBounds()) {
Gradient g = FILL_GRADIENT.getClone(this);
g.transform(tx);
set(FILL_GRADIENT, g);
}
if (get(STROKE_GRADIENT) != null
&& !get(STROKE_GRADIENT).isRelativeToFigureBounds()) {
Gradient g = STROKE_GRADIENT.getClone(this);
g.transform(tx);
set(STROKE_GRADIENT, g);
}
}
invalidate();
}
@SuppressWarnings("unchecked")
@Override
public void restoreTransformTo(Object geometry) {
invalidate();
Object[] restoreData = (Object[]) geometry;
ArrayList