/* * $RCSfile: Use.java,v $ * * Copyright 1990-2009 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.util.SVGConstants; import org.w3c.dom.DOMException; import org.w3c.dom.svg.SVGMatrix; import org.w3c.dom.svg.SVGRect; import com.sun.perseus.j2d.Box; import com.sun.perseus.j2d.RenderGraphics; import com.sun.perseus.j2d.Transform; /** * Represents an SVG Tiny <code><use></code> element. * * @version $Id: Use.java,v 1.13 2006/06/29 10:47:36 ln156897 Exp $ */ public class Use extends Group implements IDRef { /** * xlink:href is required on <use> */ static final String[][] REQUIRED_TRAITS_NS = { {SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.SVG_HREF_ATTRIBUTE} }; /** * The x and y coordinates of the use element */ protected float x, y; /** * Expanded content tree: the root <code>ElementNodeProxy</code> */ protected ElementNodeProxy proxy; /** * The identifier of the referenced node */ protected String idRef; /** * The referenced element. */ protected ElementNode ref; /** * Constructor. * * @param ownerDocument this element's owner <code>DocumentNode</code> */ public Use(final DocumentNode ownerDocument) { super(ownerDocument); } /** * Clears the text layouts, if any exist. This is typically * called when the font selection has changed and nodes such * as <code>Text</code> should recompute their layouts. * This should recursively call clearLayouts on children * node or expanded content, if any. */ protected void clearLayouts() { clearLayouts(proxy); } /** * Returns the <code>ModelNode</code>, if any, hit by the * point at coordinate x/y. * * @param pt the x/y coordinate. Should never be null and be * of size two. If not, the behavior is unspecified. * The coordinates are in viewport space. * @return the <tt>ModelNode</tt> hit at the given point or null * if none was hit. */ public ModelNode nodeHitAt(final float[] pt) { // If a node does not render, it is never hit if (canRenderState != 0) { return null; } // Check for a hit on expanded content return nodeHitAt(getLastExpandedChild(), pt); } /** * Returns the <code>ModelNode</code>, if any, hit by the * point at coordinate x/y in the proxy tree starting at * proxy. * * @param pt the x/y coordinate. Should never be null and be * of size two. If not, the behavior is unspecified. * The coordinates are in viewport space. * @param proxy the root of the proxy tree to test. * @return the <tt>ModelNode</tt> hit at the given point or null * if none was hit. */ ModelNode proxyNodeHitAt(final float[] pt, final ElementNodeProxy proxy) { // If a node does not render, it is never hit if (canRenderState != 0) { return null; } // Check for a hit on expanded content return nodeHitAt(proxy.getLastExpandedChild(), pt); } /** * Called when the computed value of the given property has changed. * On a use element, this propagates only to expanded content. * * @param propertyIndex index for the property whose value has changed. * @param parentPropertyValue the value that children of this node should * now inherit. */ protected void propagatePropertyState(final int propertyIndex, final Object parentPropertyValue) { // Propagate to proxies. if (firstProxy != null) { ElementNodeProxy proxy = firstProxy; while (proxy != null) { ((CompositeGraphicsNodeProxy) proxy) .proxiedPropertyStateChange(propertyIndex, parentPropertyValue); proxy = proxy.nextProxy; } } // Propagate to expanded children. ModelNode node = getFirstExpandedChild(); while (node != null) { node.recomputePropertyState(propertyIndex, parentPropertyValue); node = node.nextSibling; } } /** * Called when the computed value of the given float property has changed. * On a use element, this propagates only to expanded content. * * @param propertyIndex index for the property whose value has changed. * @param parentPropertyValue the value that children of this node should * now inherit. */ protected void propagateFloatPropertyState( final int propertyIndex, final float parentPropertyValue) { // Propagate to proxies. if (firstProxy != null) { ElementNodeProxy proxy = firstProxy; while (proxy != null) { ((CompositeGraphicsNodeProxy) proxy) .proxiedFloatPropertyStateChange(propertyIndex, parentPropertyValue); proxy = proxy.nextProxy; } } // Propagate to expanded children. ModelNode node = getFirstExpandedChild(); while (node != null) { node.recomputeFloatPropertyState(propertyIndex, parentPropertyValue); node = node.nextSibling; } } /** * Called when the computed value of the given packed property has changed. * On a use element, this propagates only to expanded content. * * @param propertyIndex index for the property whose value has changed. * @param parentPropertyValue the value that children of this node should * now inherit. */ protected void propagatePackedPropertyState(final int propertyIndex, final int parentPropertyValue) { // Propagate to proxies. if (firstProxy != null) { ElementNodeProxy proxy = firstProxy; while (proxy != null) { ((CompositeGraphicsNodeProxy) proxy) .proxiedPackedPropertyStateChange(propertyIndex, parentPropertyValue); proxy = proxy.nextProxy; } } // Propagate to expanded children. ModelNode node = getFirstExpandedChild(); while (node != null) { node.recomputePackedPropertyState(propertyIndex, parentPropertyValue); node = node.nextSibling; } } /** * Recomputes the transform cache, if one exists. This should recursively * call recomputeTransformState on children node or expanded content, if * any. * * By default, because a ModelNode has no transform and no cached transform, * this only does a pass down. * * @param parentTransform the Transform applied to this node's parent. */ protected void recomputeTransformState(final Transform parentTransform) { txf = appendTransform(parentTransform, txf); inverseTxf = null; computeCanRenderTransformBit(txf); // inverseTxf = computeInverseTransform(txf, parentTransform, // inverseTxf); recomputeTransformState(txf, getFirstExpandedChild()); } /** * Paints this node into the input <code>RenderGraphics</code>. * * @param rg the <tt>RenderGraphics</tt> where the node should paint itself */ public void paint(final RenderGraphics rg) { if (canRenderState != 0) { return; } paint(getFirstExpandedChild(), rg); } /** * This method is called before an element is hooked into the tree to * validate it is in a state where it can be added. For a Use element, the * reference must have been set and resolved or must still be null (i.e., * not set). */ protected void preValidate() { if (loaded && proxy == null) { if (ref == null && (idRef != null) && !"".equals(idRef)) { throw new DOMException(DOMException.INVALID_STATE_ERR, Messages.formatMessage (Messages.ERROR_MISSING_REFERENCE, new String[] { getNamespaceURI(), getLocalName(), getId()})); } } } /** * Sets this <code>Use</code> element's idRef * * @param idRef the identifier of the referenced node. Should not be null */ public void setIdRef(final String idRef) { if (idRef == this.idRef || (idRef != null && idRef.equals(this.idRef))) { // No change return; } this.idRef = idRef; ownerDocument.resolveIDRef(this, idRef); // If the node is hooked into the document tree and the idRef has not // been resolved as a result of ownerDocument.resolveIDRef, then // we go into error. if (isInDocumentTree()) { if (proxy == null && (idRef != null) && !"".equals(idRef)) { throw new DOMException(DOMException.INVALID_ACCESS_ERR, Messages.formatMessage (Messages.ERROR_MISSING_REFERENCE, new String[] { getNamespaceURI(), getLocalName(), getId()})); } } } /** * @return the SVGConstants.SVG_USE_TAG value */ public String getLocalName() { return SVGConstants.SVG_USE_TAG; } /** * Used by <code>DocumentNode</code> to create a new instance from * a prototype <code>Use</code>. * * @param doc the <code>DocumentNode</code> for which a new node is * should be created. * @return a new <code>Use</code> for the requested document. */ public ElementNode newInstance(final DocumentNode doc) { return new Use(doc); } /** * @return the <code>Use</code> element's idRef. */ public String getIdRef() { return idRef; } /** * <code>IDRef</code> implementation. * * @param ref the resolved reference (mapped from the * id passed to the setIdRef method). */ public void resolveTo(final ElementNode ref) { // Only set the proxy if the use is in the document tree. if (isInDocumentTree()) { setProxy(ref.buildProxy()); } else { this.ref = ref; } } /** * When a Use element is hooked into the document tree, it may * expand immediately if its reference has already been set. */ void nodeHookedInDocumentTree() { super.nodeHookedInDocumentTree(); if (ref != null) { setProxy(ref.buildProxy()); } } /** * When a Use in unhooked from the document tree, it needs to set its * proxy to null and cleanly remove references to itself. */ final void nodeUnhookedFromDocumentTree() { super.nodeUnhookedFromDocumentTree(); if (proxy != null) { unhookExpandedQuiet(); proxy = null; } } /** * @return an adequate <code>ElementNodeProxy</code> for this node. */ ElementNodeProxy buildProxy() { return new UseProxy(this); } /** * Apply this node's x/y translation if it is not (0,0). * * @param tx the <code>Transform</code> to apply additional node * transforms to. This may be null. * @param workTx a <code>Transform</code> which can be re-used if a * new <code>Transform</code> needs to be created and workTx * is not the same instance as tx. * @return a transform with this node's transform added. */ protected Transform appendTransform(Transform tx, final Transform workTx) { if (transform == null && motion == null && x == 0 && y == 0) { return tx; } tx = recycleTransform(tx, workTx); if (motion != null) { tx.mMultiply(motion); } if (transform != null) { tx.mMultiply(transform); } tx.mTranslate(x, y); return tx; } /** * @param newX new x-axis origin */ public void setX(final float newX) { if (newX == x) { return; } modifyingNode(); this.x = newX; recomputeTransformState(); recomputeProxyTransformState(); modifiedNode(); } /** * @param newY new y-axis origin */ public void setY(final float newY) { if (newY == y) { return; } modifyingNode(); this.y = newY; recomputeTransformState(); recomputeProxyTransformState(); modifiedNode(); } /** * @return x-axis use origin */ public float getX() { return x; } /** * @return y-axis use origin */ public float getY() { return y; } /** * @return true if proxy is set */ public boolean hasDescendants() { return super.hasDescendants() || proxy != null; } /** * Sets the <code>ElementNodeProxy</code> as this use's expanded * content. * * @param proxy the proxy that references the <code><use></code>'s * expanded content. * @throws NullPointerException if the input proxy node is null */ void setProxy(final ElementNodeProxy proxy) { if (proxy == null) { throw new IllegalArgumentException(); } // If this node already had a proxy, make sure we remove that // proxy from the node's expanded content. if (this.proxy != null) { modifyingNode(); unhookExpandedQuiet(); modifiedNode(); } this.proxy = proxy; this.proxy.setParentQuiet(this); nodeInserted(this.proxy); this.proxy.nextSibling = null; // Now, notify potential proxies that the proxy has been set. ElementNodeProxy useProxy = firstProxy; while (useProxy != null) { ((UseProxy) useProxy).useProxySet(); useProxy = useProxy.nextProxy; } } /** * Some node types (such as <code>ElementNodeProxy</code>) have * expanded children that they compute in some specific * way depending on the implementation. * * @return a reference to the node's first expanded child, or null if there * are no expanded children. This forces the computation of expanded * content if needed. */ protected ModelNode getFirstExpandedChild() { return proxy; } /** * Some node types (such as <code>ElementNodeProxy</code>) have * expanded children that they compute in some specific * way depending on the implementation. * * @return a reference to the node's first expanded child, or null if there * are no expanded children. */ public ModelNode getFirstComputedExpandedChild() { return proxy; } /** * Some node types (such as <code>ElementNodeProxy</code>) have * expanded children that they compute in some specific * way depending on the implementation. * * @return a reference to the node's last expanded child, or null if there * are no expanded children. This forces the computation of expanded * content if needed. */ protected ModelNode getLastExpandedChild() { return proxy; } /** * Utility method. Unhooks the expanded content. */ protected void unhookExpandedQuiet() { unhookQuiet(proxy); proxy = null; ElementNodeProxy p = firstProxy; while (p != null) { p.unhookExpandedQuiet(); p = p.nextProxy; } } /** * @return the tight bounding box in the current user coordinate * space, that is, the least possible rectangle * including all contained graphical elements, but not including stroke. * The calculation is performed in the user coordinate space of the * element, that is, in the coordinate space used for drawing the element, * after applying the transform attribute, if any. In the process of * calculating the bounding box, any elements having the display * property (trait) set to none are ignored. * See <a * href="http://www.w3.org/TR/SVG/coords.html#ObjectBoundingBox">SVG * spec</a> for exact rules of bounding box calculation. */ public SVGRect getBBox() { Transform t = null; if (x != 0 || y != 0) { t = new Transform(1, 0, 0, 1, x, y); } return addBBox(null, t); } /** * @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 to apply from the node's coordinate space to * the target coordinate space. May be null for the identity * transform. * @return the node's bounding box in the target coordinate space. */ Box addBBox(Box bbox, Transform t) { return proxy.addBBox(bbox, proxy.appendTransform(t, null)); } /** * Override the getScreenCTM for <use> because the transform applied by * use includes the (x, y) translation which goeas 'deeper' than the * use's user space. So here, we translate back to the user space. * * @return the transformation matrix from current user units (that is, after * applying the transform attribute, if any) to the parent user * agent's notion of a "pixel". In the ideal case, it will be a physical * screen pixel on a display device; if the physical pixel size * is not known, one may use an algorithm resembling the CSS2 * definition of a "pixel". */ public SVGMatrix getScreenCTM() { SVGMatrix m = super.getScreenCTM(); if (m != null) { m = m.mTranslate(-x, -y); } return m; } /** * Supported traits: x, y * * @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) { return true; } else { return super.supportsTrait(traitName); } } /** * 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); } } /** * @return an array of namespaceURI, localName trait pairs required by * this element. */ public String[][] getRequiredTraitsNS() { return REQUIRED_TRAITS_NS; } /** * Use handles x and y 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 { return super.getTraitImpl(name); } } /** * Use 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 (idRef == null) { return ""; } return "#" + idRef; } else { return super.getTraitNSImpl(namespaceURI, name); } } /** * Use handles x and y 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 { 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) { return new FloatTraitAnim(this, traitName, TRAIT_TYPE_FLOAT); } 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 { 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) { return toAnimatedFloatArray(parseFloatTrait(traitName, value)); } else if (SVGConstants.SVG_Y_ATTRIBUTE == traitName) { return toAnimatedFloatArray(parseFloatTrait(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 (SVGConstants.XLINK_NAMESPACE_URI == namespaceURI && SVGConstants.SVG_HREF_ATTRIBUTE == traitName) { if (value == null || !value.startsWith("#")) { throw illegalTraitValue(traitName, value); } return value; } else { return super.validateTraitNS(namespaceURI, traitName, value, reqNamespaceURI, reqLocalName, reqTraitNamespace, reqTraitName); } } /** * Use handles x and y 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 { super.setTraitImpl(name, value); } } /** * Use 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 { if (SVGConstants.XLINK_NAMESPACE_URI == namespaceURI && SVGConstants.SVG_HREF_ATTRIBUTE == name) { if (value == null || !value.startsWith("#")) { throw illegalTraitValue(name, value); } setIdRef(value.substring(1)); } else { super.setTraitNSImpl(namespaceURI, name, value); } } /** * Use handles x and y 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 { if (SVGConstants.SVG_X_ATTRIBUTE == name) { setX(value); } else if (SVGConstants.SVG_Y_ATTRIBUTE == name) { setY(value); } else { super.setFloatTraitImpl(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_X_ATTRIBUTE == name || SVGConstants.SVG_Y_ATTRIBUTE == name) { return Float.toString(value[0][0]); } else { return super.toStringTrait(name, value); } } }