FileDocCategorySizeDatePackage
ImageNode.javaAPI DocphoneME MR2 API (J2ME)34617Wed May 02 18:00:34 BST 2007com.sun.perseus.model

ImageNode.java

/*
 *
 *
 * 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.Box;
import com.sun.perseus.j2d.GraphicsProperties;
import com.sun.perseus.j2d.PaintTarget;
import com.sun.perseus.j2d.RasterImage;
import com.sun.perseus.j2d.RenderGraphics;
import com.sun.perseus.j2d.Tile;
import com.sun.perseus.j2d.Transform;

import com.sun.perseus.util.SVGConstants;
import com.sun.perseus.util.RunnableQueue;
import com.sun.perseus.util.RunnableQueue.RunnableHandler;

import org.w3c.dom.DOMException;


/**
 * This class models the SVG Tiny <code><image></code>.
 *
 * A value of 0 on the width and height attributes disables the 
 * rendering of the Image object. Negative width or height values
 * on the <code>ImageNode</code> are illegal.
 *
 * If the referenced <code>Image</code> is null, this node will draw
 * a gray box using the current fill value
 *
 * @version $Id: ImageNode.java,v 1.17 2006/06/29 10:47:32 ln156897 Exp $
 */
public class ImageNode extends AbstractRenderingNode 
        implements RasterImageConsumer {
    /**
     * width and height are required on <image>
     */
    static final String[] REQUIRED_TRAITS
        = {SVGConstants.SVG_WIDTH_ATTRIBUTE,
           SVGConstants.SVG_HEIGHT_ATTRIBUTE};

    /**
     * xlink:href is required on <image>
     */
    static final String[][] REQUIRED_TRAITS_NS
        = { {SVGConstants.XLINK_NAMESPACE_URI, 
             SVGConstants.SVG_HREF_ATTRIBUTE} };

    /**
     * The viewport defined by the image element
     */
    protected float x, y, width, height;

    /**
     * The painted Image. The image is built from the 
     * href attribute.
     */
    protected RasterImage image;

    /**
     * Controls whether the image has been loaded or
     * not.
     */
    protected boolean imageLoaded;

    /**
     * The reference to the image data. This can be using 
     * base64 PNG or JPEG encoding or an absolute or relative
     * URI.
     */
    protected String href;

    /**
     * The absolute URI is computed from the href and the
     * node's base URI.
     */
    protected String absoluteURI;

    /**
     * Controls whether the image is aligned in the viewport
     * or if it is just scaled to the viewport.
     */
    protected int align = StructureNode.ALIGN_XMIDYMID;

    /**
     * Constant used to identify broken URI values
     */
    protected static final String BROKEN_URI = "broken uri";

    // =========================================================================
    // Image specific
    // =========================================================================

    /**
     * Constructor.
     *
     * @param ownerDocument this element's owner <code>DocumentNode</code>
     */
    public ImageNode(final DocumentNode ownerDocument) {
        super(ownerDocument);
        image = ownerDocument.getImageLoader().getLoadingImage();
        
        // Initially, the image's width and height are zero, so we
        // set the corresponding bits accordingly.
        canRenderState |= CAN_RENDER_ZERO_WIDTH_BIT;
        canRenderState |= CAN_RENDER_ZERO_HEIGHT_BIT;
    }

    /**
     * @return the SVGConstants.SVG_IMAGE_TAG value
     */
    public String getLocalName() {
        return SVGConstants.SVG_IMAGE_TAG;
    }


    /**
     * Used by <code>DocumentNode</code> to create a new instance from
     * a prototype <code>ImageNode</code>.
     *
     * @param doc the <code>DocumentNode</code> for which a new node is
     *        should be created.
     * @return a new <code>ImageNode</code> for the requested document.
     */
    public ElementNode newInstance(final DocumentNode doc) {
        return new ImageNode(doc);
    }

    /**
     * Computes the rendering tile for the given set of GraphicsProperties.
     *
     * @param tile the Tile instance whose bounds should be set.
     * @param t the Transform to the requested tile space, from this node's user
     * space.
     * @param gp the <code>GraphicsProperties</code> for which the tile
     *        should be computed.
     * @return the screen bounding box when this node is rendered with the 
     * given render context.
     */
    protected void computeRenderingTile(final Tile tile, 
                                        final Transform t,
                                        final GraphicsProperties gp) {
        tile.snapBox(addNodeBBox(null, t));
    }

    /**
     * @param newX the new position of this image's upper left
     *        corner on the x-axis
     */
    public void setX(final float newX) {
        if (newX == x) {
            return;
        }
        modifyingNode();
        renderingDirty();
        this.x = newX;
        modifiedNode();
    }

    /**
     * @param newY the new postion of this image's upper left 
     *        corner on the y-axis
     */
    public void setY(final float newY) {
        if (newY == y) {
            return;
        }
        modifyingNode();
        renderingDirty();
        this.y = newY;
        modifiedNode();
    }

    /**
     * @return this image's upper left corner position on the x-axis
     */
    public float getX() {
        return x;
    }

    /**
     * @return this image's upper left corner position on the y-axis
     */
    public float getY() {
        return y;
    }

    /**
     * @param newWidth this node's new width. Should be strictly positive
     */
    public void setWidth(final float newWidth) {
        if (newWidth < 0) {
            throw new IllegalArgumentException();
        }

        if (newWidth == width) {
            return;
        }

        modifyingNode();
        renderingDirty();
        this.width = newWidth;
        computeCanRenderWidthBit(width);
        modifiedNode();
    }

    /**
     * @return this node's width
     */
    public float getWidth() {
        return width;
    }

    /**
     * @param newHeight the new height for this image's node
     *        Should be strictly positive.
     */
    public void setHeight(final float newHeight) {
        if (newHeight < 0) {
            throw new IllegalArgumentException();
        }

        if (newHeight == height) {
            return;
        }

        modifyingNode();
        renderingDirty();
        this.height = newHeight;
        computeCanRenderHeightBit(height);
        modifiedNode();
    }

    /**
     * @return this image's height
     */
    public float getHeight() {
        return height;
    }

    /**
     * Sets this node's parent but does not generate change 
     * events.
     * 
     * @param newParent the node's new parent node.
     */
    protected void setParentQuiet(final ModelNode newParent) {
        super.setParentQuiet(newParent);

        // Reset the image if this node has a different parent that
        // impacts the computation of the absoluteURI
        if (href != null && !href.equals(absoluteURI)) {
            willNeedImage();
        }
    }

    /**
     * @param newHref the new reference for this node's image.
     * @throws IllegalArgumentException if the href cannot be resolved
     *         into an absoluteURI and paintNeedsLoad is set to true.
     */
    public void setHref(final String newHref) {
        if (newHref == null) {
            throw new IllegalArgumentException();
        }

        if (equal(newHref, href)) {
            return;
        }

        modifyingNode();
        renderingDirty();
        this.href = newHref;
        this.image = ownerDocument.getImageLoader().getLoadingImage();
        ownerDocument.getImageLoader().removeRasterImageConsumer(absoluteURI, 
                                                                 this);
        this.absoluteURI = null;
        this.imageLoaded = false;

        // Only declare the new URI to load if this node is
        // part of the document tree and if its href is set 
        // to a non-null value.
        if (isInDocumentTree() && href != null) {
            willNeedImage();
        }
        modifiedNode();
    }

    /**
     * Implementation. Declares to the ImageLoader that an image will be 
     * needed.
     */
    void willNeedImage() {
        imageLoaded = false;
        image = ownerDocument.getImageLoader().getLoadingImage();

        absoluteURI
            = ownerDocument.getImageLoader().resolveURI(href, getURIBase());
        
        if (absoluteURI == null) {
            if (paintNeedsLoad) {
                throw new IllegalArgumentException();
            } else {
                absoluteURI = BROKEN_URI;
                imageLoaded = true;
                image = ownerDocument.getImageLoader().getBrokenImage();
                return;
            }
        } else {
            ownerDocument.getImageLoader().addRasterImageConsumer(absoluteURI, 
                                                                  this);
            ownerDocument.getImageLoader().needsURI(absoluteURI);
        }
    }

    /**
     * @return this node's image reference. This should be a URI
     */
    public String getHref() {
        return href;
    }

    /**
     * @return this node's <tt>Image</tt> resource. May be null
     *         in case no uri has been set or if loading the 
     *         image data failed.
     */
    public RasterImage getImage() {
        return image;
    }

    /**
     * Sets the node's image if the computed absolute URI
     * is equal to the input uri.
     *
     * @param image the new node image. Should not be null.
     * @param uri the uri <code>image</code> corresponds to. This is
     *        passed in case the node's uri changed between a call
     *        to the <code>ImageLoader</code>'s <code>needsURI</code>
     *        and a call to <code>setImage</code>.
     */
    public void setImage(final RasterImage image, final String uri) {
        // This is done to handle situations where the href/parent
        // or other change occur while the image is loading.
        if (absoluteURI != null && absoluteURI.equals(uri)) {
            if (this.image != image) {
                modifyingNode();
                this.image = image;
                modifiedNode();
            }
        }
    }

    /**
     * @return the associated RunnableQueue. If not null, setImage should
     *         be called from within the runnable queue.
     */
    public RunnableQueue getUpdateQueue() {
        return ownerDocument.getUpdateQueue();
    }

    /**
     * @return the associated RunnableHandler, in case the associated 
     *         RunnableQueue is not null.
     */
    public RunnableHandler getRunnableHandler() {
        return ownerDocument.getRunnableHandler();
    }

    /**
     * @param newAlign one of StructureNode.ALIGN_NONE or 
     *        StructureNode.ALIGN_XMIDYMID
     */
    public void setAlign(final int newAlign) {
        if (newAlign == align) {
            return;
        }
        modifyingNode();
        this.align = newAlign;
        modifiedNode();
    }

    /**
     * @return the image's align property
     * @see StructureNode#ALIGN_NONE
     * @see StructureNode#ALIGN_XMIDYMID
     */
    public int getAlign() {
        return align;
    }


    /**
     * Paints this node into the input RenderGraphics. 
     *
     * @param rg this node is painted into this <tt>RenderGraphics</tt>
     * @param gp the <code>GraphicsProperties</code> controlling the operation's
     *        rendering
     * @param pt the <code>PaintTarget</code> for the paint operation.
     * @param txf the <code>Transform</code> from user space to device space for
     *        the paint operation.
     */
    void paintRendered(final RenderGraphics rg,
                       final GraphicsProperties gp,
                       final PaintTarget pt,
                       final Transform tx) {
        if (!gp.getVisibility()) {
            return;
        }

        rg.setTransform(tx);
	rg.setOpacity(getOpacity());

        if (!imageLoaded) {
            loadImage();
        }

        //
        // Scale the image so that it fits into width/height
        // and is centered
        //
        int iw = image.getWidth();
        int ih = image.getHeight();

        if (align == StructureNode.ALIGN_NONE) {
            rg.drawImage(image, x, y, width, height);
        } else {
            float ws = width / iw;
            float hs = height / ih;
            float is = ws;
            if (hs < ws) {
                is = hs;
            }

            float oh = ih * is;
            float ow = iw * is;
            float dx = (width - ow) / 2;
            float dy = (height - oh) / 2;

            rg.drawImage(image, (x + dx), (y + dy), ow, oh);
        }
    }
    
    /**
     * Implementation. Loads the image through the ownerDocument's 
     * ImageLoader. If this image's paintNeedsLoad is true, this
     * will block until the image data has been retrieved. Otherwise,
     * a request to asynchronously load the image is placed.
     */
    public void loadImage() {
        // Note that if absoluteURI is null, it is because either href is null
        // or this node is not hooked into the tree. In these cases, we stick
        // to the loadingImage
        if (absoluteURI != null) {
            if (paintNeedsLoad) {
                image = ownerDocument.getImageLoader()
                    .getImageAndWait(absoluteURI);
                if (image == ownerDocument.getImageLoader().getBrokenImage()) {
                    throw new IllegalStateException();
                }
            } else {
                ownerDocument.getImageLoader().getImageLater(absoluteURI, this);
            }
        }
        imageLoaded = true;
    }

    /**
     * An <code>ImageNode</code> has something to render 
     *
     * @return true
     */
    public boolean hasNodeRendering() {
        return true;
    }

    /**
     * Returns true if this node is hit by the input point. The input point
     * is in viewport space. 
     *  
     * @param pt the x/y coordinate. Should never be null and be
     *        of size two. If not, the behavior is unspecified.
     *        The x/y coordinate is in viewport space.
     *
     * @return true if the node is hit by the input point. 
     * @see #nodeHitAt
     */
    protected boolean isHitVP(float[] pt) {
        if (!getVisibility()) {
                return false;
        }
        getInverseTransformState().transformPoint(pt, ownerDocument.upt);
        pt = ownerDocument.upt;
        return (pt[0] > x && pt[0] < (x + width)
                && pt[1] > y && pt[1] < (y + height));
    }

    /**
     * Returns true if the proxy node is hit by the input point. The input point
     * is in viewport space.
     *  
     * @param pt the x/y coordinate. Should never be null and be
     *        of size two. If not, the behavior is unspecified.
     *        The x/y coordinate is in viewport space.
     * @param proxy the tested <code>ElementNodeProxy</code>.
     * @return true if the node is hit by the input point. 
     * @see #nodeHitAt
     */
    protected boolean isProxyHitVP(float[] pt, 
                                   AbstractRenderingNodeProxy proxy) {
        proxy.getInverseTransformState().transformPoint(pt, ownerDocument.upt);
        pt = ownerDocument.upt;
        return (pt[0] > x && pt[0] < (x + width)
                && pt[1] > y && pt[1] < (y + height));
    }

    /**
     * Supported traits: x, y, width, height
     *
     * @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_X_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_Y_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_WIDTH_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_HEIGHT_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE == traitName) {
            return true;
        } else {
            return super.supportsTrait(traitName);
        }
    }

    /**
     * @return an array of traits that are required by this element.
     */
    public String[] getRequiredTraits() {
        return REQUIRED_TRAITS;
    }

    /**
     * @return an array of namespaceURI, localName trait pairs required by
     *         this element.
     */
    public String[][] getRequiredTraitsNS() {
        return REQUIRED_TRAITS_NS;
    }

    /**
     * Supported traits: xlink:href
     *
     * @param namespaceURI the trait's namespace.
     * @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 supportsTraitNS(final String namespaceURI,
                            final String traitName) {
        if (SVGConstants.XLINK_NAMESPACE_URI == namespaceURI
            &&
            SVGConstants.SVG_HREF_ATTRIBUTE == traitName) {
            return true;
        } else {
            return super.supportsTraitNS(namespaceURI, traitName);
        }
    }

    /**
     * ImageNode handles x, y, width and height traits.
     * Other traits are handled by the super class.
     *
     * @param name the requested trait's name.
     * @return the requested trait's 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 a String (SVG Tiny only).
     */
    public String getTraitImpl(final String name)
        throws DOMException {
        if (SVGConstants.SVG_X_ATTRIBUTE == name) {
            return Float.toString(getX());
        } else if (SVGConstants.SVG_Y_ATTRIBUTE == name) {
            return Float.toString(getY());
        } else if (SVGConstants.SVG_WIDTH_ATTRIBUTE == name) {
            return Float.toString(getWidth());
        } else if (SVGConstants.SVG_HEIGHT_ATTRIBUTE == name) {
            return Float.toString(getHeight());
        } else if (SVGConstants.SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE == name) {
            return alignToStringTrait(align);
        } else {
            return super.getTraitImpl(name);
        }
    }

    /**
     * ImageNode handles the xlink href attribute
     *
     * @param namespaceURI the requested trait's namespace URI.
     * @param name the requested trait's local name (i.e., un-prefixed, as 
     *        "href")
     * @return the requested trait's string 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 a String (SVG Tiny
     *         only).
     * @throws SecurityException if the application does not have the necessary
     *         privilege rights to access this (SVG) content.
     */
    String getTraitNSImpl(String namespaceURI, String name)
        throws DOMException {
        if (SVGConstants.XLINK_NAMESPACE_URI == namespaceURI
            &&
            SVGConstants.SVG_HREF_ATTRIBUTE == name) {
            if (href == null) {
                return "";
            } 
            return href;
        } else {
            return super.getTraitNSImpl(namespaceURI, name);
        }
    }

    /**
     * ImageNode handles x, y, width and height traits.
     * Other attributes are handled by the super class.
     *
     * @param name the requested trait name.
     * @param the requested trait's floating point 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 a float
     * @throws SecurityException if the application does not have the necessary
     * privilege rights to access this (SVG) content.
     */
    float getFloatTraitImpl(final String name)
        throws DOMException {
        if (SVGConstants.SVG_X_ATTRIBUTE == name) {
            return getX();
        } else if (SVGConstants.SVG_Y_ATTRIBUTE == name) {
            return getY();
        } else if (SVGConstants.SVG_WIDTH_ATTRIBUTE == name) {
            return getWidth();
        } else if (SVGConstants.SVG_HEIGHT_ATTRIBUTE == name) {
            return getHeight();
        } else {
            return super.getFloatTraitImpl(name);
        }
    }

    /**
     * @param traitName the trait name.
     */
    TraitAnim createTraitAnimImpl(final String traitName) {
        if (SVGConstants.SVG_X_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_Y_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_WIDTH_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_HEIGHT_ATTRIBUTE == traitName) {
            return new FloatTraitAnim(this, traitName, TRAIT_TYPE_FLOAT);
        } else if (SVGConstants.SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE 
                == traitName) {
            return new StringTraitAnim(this, NULL_NS, traitName);
        } else {
            return super.createTraitAnimImpl(traitName);
        }
    }

    /**
     * @param traitName the trait name.
     * @param traitNamespace the trait's namespace. Should not be null.
     */
    TraitAnim createTraitAnimNSImpl(final String traitNamespace, 
                                    final String traitName) {
        if (traitNamespace == SVGConstants.XLINK_NAMESPACE_URI
            &&
            traitName == SVGConstants.SVG_HREF_ATTRIBUTE) {
            return new StringTraitAnim(this, traitNamespace, traitName);
        }

        return super.createTraitAnimNSImpl(traitNamespace, 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_X_ATTRIBUTE == name) {
            setX(value[0][0]);
        } else if (SVGConstants.SVG_Y_ATTRIBUTE == name) {
            setY(value[0][0]);
        } else if (SVGConstants.SVG_WIDTH_ATTRIBUTE == name) {
            checkPositive(name, value[0][0]);
            setWidth(value[0][0]);
        } else if (SVGConstants.SVG_HEIGHT_ATTRIBUTE == name) {
            checkPositive(name, value[0][0]);
            setHeight(value[0][0]);
        } 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_X_ATTRIBUTE == traitName
            ||
            SVGConstants.SVG_Y_ATTRIBUTE == traitName) {
            return new float[][]{{parseFloatTrait(traitName, value)}};
        } else if (SVGConstants.SVG_WIDTH_ATTRIBUTE == traitName
                   ||
                   SVGConstants.SVG_HEIGHT_ATTRIBUTE == traitName) {
            return new float[][] {{parsePositiveFloatTrait(traitName, value)}};
        } else {
            return super.validateFloatArrayTrait(traitName,
                                                 value,
                                                 reqNamespaceURI,
                                                 reqLocalName,
                                                 reqTraitNamespace,
                                                 reqTraitName);
        }
    }

    /**
     * Validates the input trait value.
     *
     * @param namespaceURI the trait's namespace URI.
     * @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.
     */
    String validateTraitNS(final String namespaceURI,
                           final String traitName,
                           final String value,
                           final String reqNamespaceURI,
                           final String reqLocalName,
                           final String reqTraitNamespace,
                           final String reqTraitName) throws DOMException {
        if (namespaceURI != null) {
            return super.validateTraitNS(namespaceURI,
                                         traitName,
                                         value,
                                         reqNamespaceURI,
                                         reqLocalName,
                                         reqTraitNamespace,
                                         reqTraitName);
        }

        if (SVGConstants.SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE
                .equals(traitName)) {
            if (!SVGConstants
                .SVG_SVG_PRESERVE_ASPECT_RATIO_DEFAULT_VALUE.equals(value)
                &&
                !SVGConstants.SVG_NONE_VALUE.equals(value)) {
                throw illegalTraitValue(traitName, value);
            }
            return value;
        } else {
            return super.validateTraitNS(namespaceURI,
                                         traitName,
                                         value,
                                         reqNamespaceURI,
                                         reqLocalName,
                                         reqTraitNamespace,
                                         reqTraitName);
        }
    }

    /**
     * ImageNode handles x, y, rx, ry, width and height traits.
     * Other traits are handled by the super class.
     *
     * @param name the trait's name.
     * @param value the new trait string 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 a String
     * @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_X_ATTRIBUTE == name) {
            setX(parseFloatTrait(name, value));
        } else if (SVGConstants.SVG_Y_ATTRIBUTE == name) {
            setY(parseFloatTrait(name, value));
        } else if (SVGConstants.SVG_WIDTH_ATTRIBUTE == name) {
            setWidth(parsePositiveFloatTrait(name, value));
        } else if (SVGConstants.SVG_HEIGHT_ATTRIBUTE == name) {
            setHeight(parsePositiveFloatTrait(name, value));
        } else if (SVGConstants
                   .SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE.equals(name)) {
            if (SVGConstants
                .SVG_SVG_PRESERVE_ASPECT_RATIO_DEFAULT_VALUE.equals(value)) {
                setAlign(StructureNode.ALIGN_XMIDYMID);
            } else if (SVGConstants.SVG_NONE_VALUE.equals(value)) {
                setAlign(StructureNode.ALIGN_NONE);
            } else {
                throw illegalTraitValue(name, value);
            }
        } else {
            super.setTraitImpl(name, value);
        }
    }

    /**
     * ImageNode supports the xlink:href trait.
     *
     * @param namespaceURI the trait's namespace.
     * @param name the trait's local name (un-prefixed, e.g., "href");
     * @param value the new trait value (e.g., "http://www.sun.com/mypng.png")
     *
     * @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 a String
     * @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.
     * @throws SecurityException if the application does not have the necessary
     * privilege rights to access this (SVG) content.
     */
    public void setTraitNSImpl(final String namespaceURI, 
                               final String name, 
                               final String value)
        throws DOMException {
        try {
            if (SVGConstants.XLINK_NAMESPACE_URI == namespaceURI
                &&
                SVGConstants.SVG_HREF_ATTRIBUTE == name) {
                setHref(value);
            } else {
                super.setTraitNSImpl(namespaceURI, name, value);
            }
        } catch (IllegalArgumentException iae) {
            throw illegalTraitValue(name, value);
        }
    }

    /**
     * ImageNode handles x, y, rx, ry, width and height traits.
     * Other traits are handled by the super class.
     *
     * @param name the trait's name.
     * @param value the new trait's floating point 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.
     * @throws SecurityException if the application does not have the necessary
     * privilege rights to access this (SVG) content.
     */
    public void setFloatTraitImpl(final String name, final float value)
        throws DOMException {
        try {
            if (SVGConstants.SVG_X_ATTRIBUTE == name) {
                setX(value);
            } else if (SVGConstants.SVG_Y_ATTRIBUTE == name) {
                setY(value);
            } else if (SVGConstants.SVG_WIDTH_ATTRIBUTE == name) {
                setWidth(value);
            } else if (SVGConstants.SVG_HEIGHT_ATTRIBUTE == name) {
                setHeight(value);
            } else {
                super.setFloatTraitImpl(name, value);
            }
        } catch (IllegalArgumentException iae) {
            throw illegalTraitValue(name, Float.toString(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_X_ATTRIBUTE == name
            ||
            SVGConstants.SVG_Y_ATTRIBUTE == name
            ||
            SVGConstants.SVG_WIDTH_ATTRIBUTE == name
            ||
            SVGConstants.SVG_HEIGHT_ATTRIBUTE == name) {
            return Float.toString(value[0][0]);
        } else {
            return super.toStringTrait(name, value);
        }
    }

    /**
     * @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(Box bbox, 
                    final Transform t) {
        return addTransformedBBox(bbox, x, y, width, height, t);
    }
}