/*
*
*
* Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.perseus.model;
import com.sun.perseus.j2d.GraphicsProperties;
import com.sun.perseus.j2d.RenderGraphics;
import com.sun.perseus.util.SVGConstants;
import org.w3c.dom.DOMException;
import org.w3c.dom.svg.SVGPath;
import org.w3c.dom.svg.SVGRect;
import com.sun.perseus.j2d.Box;
import com.sun.perseus.j2d.Transform;
import com.sun.perseus.j2d.Path;
import com.sun.perseus.j2d.PathSupport;
/**
* A <tt>ShapesNode</tt> is an <tt>AbstractShapeNode</tt>
* which draws a <tt>java.awt.geom.GeneralPath</tt>.
* <br />
* Note that the <tt>paint</tt> method throws a <tt>NullPointerException</tt>
* when called before setting the <tt>shape</tt> property to a non-null
* value.
* <br />
*
* @version $Id: ShapeNode.java,v 1.17 2006/06/29 10:47:34 ln156897 Exp $
*/
public class ShapeNode
extends AbstractShapeNode {
/**
* EMPTY_PATH is used initially by the ShapeNode class.
* This constant is used so that a new ShapeNode, right upon creation,
* may be rendered. The only place where the path is mutated is in
* the animation code. But the animation can only work if the
* path has real data. Therefore, it is safe to use this final static
* constant to initialize all ShapeNode instances (and avoid instantiating
* Path objects that are always tossed away later on).
*/
static final Path EMPTY_PATH = new Path();
/**
* The d attribute is required on <path>
*/
static final String[] PATH_REQUIRED_TRAITS
= {SVGConstants.SVG_D_ATTRIBUTE};
/**
* The points attribute is required on <polygon> and <polyline>
*/
static final String[] POLY_ALL_REQUIRED_TRAITS
= {SVGConstants.SVG_POINTS_ATTRIBUTE};
/**
* The Path painted by this node. Should _never_ be set to null.
*/
protected Path path = EMPTY_PATH;
/**
* The path's local name. One of
* SVGConstants.SVG_PATH_TAG, SVGConstants.SVG_POLYGON_TAG,
* or SVGConstants.SVG_POLYLINE_TAG.
*/
protected String localName;
/**
* Constructor.
*
* @param ownerDocument this element's owner <code>DocumentNode</code>
*/
public ShapeNode(final DocumentNode ownerDocument) {
this(ownerDocument, SVGConstants.SVG_PATH_TAG);
// The ShapeNode initially has an empty path, so that is reflected in
// the canRenderState.
canRenderState |= CAN_RENDER_EMPTY_PATH_BIT;
}
/**
* Constructs a new ShapeNode to represent an path, polyline,
* or polygon (depending on the localName value).
*
* @param ownerDocument this element's owner <code>DocumentNode</code>
* @param localName the element's local name. One of
* SVGConstants.SVG_PATH_TAG, SVGConstants.SVG_POLYGON_TAG,
* or SVGConstants.SVG_POLYLINE_TAG.
*/
public ShapeNode(final DocumentNode ownerDocument,
final String localName) {
super(ownerDocument);
if (SVGConstants.SVG_POLYLINE_TAG == localName
||
SVGConstants.SVG_POLYGON_TAG == localName
||
SVGConstants.SVG_PATH_TAG == localName) {
this.localName = localName;
} else {
throw new IllegalArgumentException();
}
}
/**
* @return the SVGConstants.SVG_PATH_TAG value
*/
public String getLocalName() {
return localName;
}
/**
* Used by <code>DocumentNode</code> to create a new instance from
* a prototype <code>SVG</code>.
*
* @param doc the <code>DocumentNode</code> for which a new node is
* should be created.
* @return a new <code>SVG</code> for the requested document.
*/
public ElementNode newInstance(final DocumentNode doc) {
return new ShapeNode(doc, localName);
}
/**
* @return the Path drawn by this node
*/
public Path getShape() {
return path;
}
/**
* @param newPath the new path for this node
*/
public void setPath(final Path newPath) {
if (equal(newPath, path)) {
return;
}
modifyingNode();
renderingDirty();
if (newPath != null) {
this.path = newPath;
} else {
this.path = new Path();
}
computeCanRenderEmptyPathBit(this.path);
modifiedNode();
}
/**
* @param rg the RenderGraphics on which to fill the shape.
*/
public void fillShape(final RenderGraphics rg) {
rg.fill(path);
}
/**
* @param rg the RenderGraphics on which to draw the shape.
*/
public void drawShape(final RenderGraphics rg) {
rg.draw(path);
}
/**
* @param x the hit point coordinate along the x-axis, in user space.
* @param y the hit point coordinate along the y-axis, in user space.
* @param fillRule the fillRule to apply when testing for containment.
* @return true if the hit point is contained within the shape.
*/
public boolean contains(final float x, final float y, final int fillRule) {
return PathSupport.isHit(path, fillRule, x, y);
}
/**
* Returns the stroked shape, using the given stroke properties.
*
* @param gp the <code>GraphicsProperties</code> defining the rendering
* context.
* @return the shape's stroked path.
*/
Object getStrokedPath(final GraphicsProperties gp) {
return PathSupport.getStrokedPath(path, gp);
}
/**
* ShapeNode handles the 'd' trait.
*
* @param traitName the name of the trait which the element may support.
* @return true if this element supports the given trait in one of the
* trait accessor methods.
*/
boolean supportsTrait(final String traitName) {
if (SVGConstants.SVG_PATH_TAG == localName) {
if (SVGConstants.SVG_D_ATTRIBUTE == traitName) {
return true;
}
} else if (SVGConstants.SVG_POINTS_ATTRIBUTE == traitName) {
// For polygon and polyline, the points trait is
// supported.
return true;
}
return super.supportsTrait(traitName);
}
/**
* @return an array of traits that are required by this element.
*/
public String[] getRequiredTraits() {
if (SVGConstants.SVG_PATH_TAG == localName) {
return PATH_REQUIRED_TRAITS;
} else {
return POLY_ALL_REQUIRED_TRAITS;
}
}
/**
* ShapeNode handles the 'd' trait.
*
* @param name the trait name (e.g., "d")
* @return the trait's value as a string (e.g., "M0,0L50,20Z")
*
* @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
* trait is not supported on this element or null.
* @throws DOMException with error code TYPE_MISMATCH_ERR if requested
* trait's computed value cannot be converted to a String (SVG Tiny only).
*/
public String getTraitImpl(final String name)
throws DOMException {
if (SVGConstants.SVG_PATH_TAG == localName) {
if (SVGConstants.SVG_D_ATTRIBUTE == name) {
return path.toString();
}
} else if (SVGConstants.SVG_POINTS_ATTRIBUTE == name) {
return path.toPointsString();
}
return super.getTraitImpl(name);
}
/**
* ShapeNode handles the 'd' trait.
*
* @param name the trait's name (e.g, "d")
* @return the trait's SVGPath value.
*
* @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
* trait is not supported on this element or null.
* @throws DOMException with error code TYPE_MISMATCH_ERR if requested
* trait's computed value cannot be converted to {@link
* org.w3c.dom.svg.SVGPath SVGPath}
* @throws SecurityException if the application does not have the necessary
* privilege rights to access this (SVG) content.
*/
SVGPath getPathTraitImpl(final String name)
throws DOMException {
if (SVGConstants.SVG_PATH_TAG.equals(localName)
&& SVGConstants.SVG_D_ATTRIBUTE.equals(name)) {
return new Path(path);
} else {
return super.getPathTraitImpl(name);
}
}
/**
* @param traitName the trait name.
*/
TraitAnim createTraitAnimImpl(final String traitName) {
if (SVGConstants.SVG_D_ATTRIBUTE == traitName
||
SVGConstants.SVG_POINTS_ATTRIBUTE == traitName) {
return new FloatTraitAnim(this, traitName, TRAIT_TYPE_SVG_PATH);
} else {
return super.createTraitAnimImpl(traitName);
}
}
/**
* Set the trait value as float.
*
* @param name the trait's name.
* @param value the trait's value.
*
* @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
* trait is not supported on this element.
* @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
* trait's value cannot be specified as a float
* @throws DOMException with error code INVALID_ACCESS_ERR if the input
* value is an invalid value for the given trait.
*/
void setFloatArrayTrait(final String name, final float[][] value)
throws DOMException {
if (SVGConstants.SVG_D_ATTRIBUTE == name
||
SVGConstants.SVG_POINTS_ATTRIBUTE == name) {
if (!path.equals(value)) {
modifyingNode();
renderingDirty();
path.setData(value[0]);
modifiedNode();
}
} else {
super.setFloatArrayTrait(name, value);
}
}
/**
* Validates the input trait value.
*
* @param traitName the name of the trait to be validated.
* @param value the value to be validated
* @param reqNamespaceURI the namespace of the element requesting
* validation.
* @param reqLocalName the local name of the element requesting validation.
* @param reqTraitNamespace the namespace of the trait which has the values
* value on the requesting element.
* @param reqTraitName the name of the trait which has the values value on
* the requesting element.
* @throws DOMException with error code INVALID_ACCESS_ERR if the input
* value is incompatible with the given trait.
*/
public float[][] validateFloatArrayTrait(
final String traitName,
final String value,
final String reqNamespaceURI,
final String reqLocalName,
final String reqTraitNamespace,
final String reqTraitName) throws DOMException {
if (SVGConstants.SVG_D_ATTRIBUTE == traitName) {
Path path = parsePathTrait(traitName, value);
return toAnimatedFloatArray(path);
} else if (SVGConstants.SVG_POINTS_ATTRIBUTE == traitName) {
Path path = parsePointsTrait(traitName, value);
if (SVGConstants.SVG_POLYGON_TAG == localName) {
path.close();
}
return toAnimatedFloatArray(path);
} else {
return super.validateFloatArrayTrait(traitName,
value,
reqNamespaceURI,
reqLocalName,
reqTraitNamespace,
reqTraitName);
}
}
/**
* ShapeNode handles the 'd' trait if it is a path and the
* 'points' trait if it is a polygon or a polyline.
*
* @param name the trait's name (e.g, "d")
* @param value the trait's new value (e.g., "M0, 0 L50, 50 Z"), using the
* SVG path syntax.
* @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
* trait is not supported on this element or null.
* @throws DOMException with error code INVALID_ACCESS_ERR if the input
* value is an invalid value for the given trait or null.
* @throws DOMException with error code NO_MODIFICATION_ALLOWED_ERR: if
* attempt is made to change readonly trait.
*/
public void setTraitImpl(final String name, final String value)
throws DOMException {
if (SVGConstants.SVG_PATH_TAG == localName) {
if (SVGConstants.SVG_D_ATTRIBUTE == name) {
setPath(parsePathTrait(name, value));
} else {
super.setTraitImpl(name, value);
}
} else if (SVGConstants.SVG_POINTS_ATTRIBUTE == name) {
setPath(parsePointsTrait(name, value));
// As per the specification, section 9.7 of the SVG 1.1 spec.,
// we need to close the path if we are dealing with a polygon.
// Only do so if the document does not have a delayed exception
// which means this element was loading and an error was detected
// in the points data.
if (ownerDocument.getDelayedException() == null) {
if (SVGConstants.SVG_POLYGON_TAG == localName) {
path.close();
}
}
} else {
super.setTraitImpl(name, value);
}
}
/**
* @param name the name of the trait to convert.
* @param value the float trait value to convert.
*/
String toStringTrait(final String name, final float[][] value) {
if (SVGConstants.SVG_PATH_TAG.equals(localName)
&&
SVGConstants.SVG_D_ATTRIBUTE.equals(name)) {
return path.toString(value[0]);
} else if (SVGConstants.SVG_POINTS_ATTRIBUTE.equals(name)) {
return path.toPointsString(value[0]);
} else {
return super.toStringTrait(name, value);
}
}
/**
* ShapeNode handles the 'd' trait.
*
* @param name the trait's name (e.g., "d")
* @param value the trait's new SVGPath value.
*
* @throws DOMException with error code NOT_SUPPORTED_ERROR if the requested
* trait is not supported on this element or null.
* @throws DOMException with error code TYPE_MISMATCH_ERR if the requested
* trait's value cannot be specified as an {@link org.w3c.dom.svg.SVGPath
* SVGPath}
* @throws DOMException with error code INVALID_ACCESS_ERR if the input
* value is an invalid value for the given trait or null. SVGPath is
* invalid if it does not begin with a MOVE_TO segment.
*/
void setPathTraitImpl(final String name, final SVGPath path)
throws DOMException {
// Note that here, we use equals because the strings
// have not been interned.
if (SVGConstants.SVG_PATH_TAG.equals(localName)
&&
SVGConstants.SVG_D_ATTRIBUTE.equals(name)) {
if (path.getNumberOfSegments() > 0
&&
path.getSegment(0) != SVGPath.MOVE_TO) {
// The first command _must_ be a moveTo.
// However, note than an empty path is accepted.
throw illegalTraitValue(name, path.toString());
}
setPath(new Path((Path) path));
} else {
super.setPathTraitImpl(name, path);
}
}
/**
* @param bbox the bounding box to which this node's bounding box should be
* appended. That bounding box is in the target coordinate space. It
* may be null, in which case this node should create a new one.
* @param t the transform from the node coordinate system to the coordinate
* system into which the bounds should be computed.
* @return the bounding box of this node, in the target coordinate space,
*/
Box addNodeBBox(final Box bbox,
final Transform t) {
return addShapeBBox(bbox, path, t);
}
}
|