/* * $RCSfile: ModelNode.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.j2d.Box; import com.sun.perseus.j2d.Path; import com.sun.perseus.j2d.RenderGraphics; import com.sun.perseus.j2d.Transform; import org.w3c.dom.DOMException; import org.w3c.dom.svg.SVGRect; import org.w3c.dom.svg.SVGMatrix; import org.w3c.dom.events.EventListener; import org.w3c.dom.events.EventTarget; /** * The central structure manipulated in Perseus is called the <em>Model</em>. It * is used for the in-memory representation of an SVG Tiny document structure. * * <p>The <code>ModelNode</code> abstraction represents an atomic node in the * <b><code>Model</code></b>'s tree structure. </p> * * <p>A <code>ModelNode</code> may have regular children (which can be added on * <code>CompositeNode</code> implementations, for example), and expanded * children (which are automatically computed by the node, for example on * <code>Text</code> or on <code>Use</code>). In rendering order, expanded * content comes second.</p> * * <p>A <code>ModelNode</code> can have a parent, which may be null, but it * always belongs to a <code>DocumentNode</code> instance which is guaranteed to * no be null.</p> * * <p>A <code>ModelNode</code> can be painted into a * @link {com.sun.perseus.j2d.RenderGraphics RenderGraphics}. * * <p>A <code>ModelNode</code> is subjet to interactivity and its * @link {#nodeHitAt nodeHitAt} method can be used to perform hit detection.</p> * * @version $Id: ModelNode.java,v 1.24 2006/06/29 10:47:33 ln156897 Exp $ */ public abstract class ModelNode implements EventTarget { /** * Used in cases the parent node is null. */ protected final Transform IDENTITY = new Transform(null); /** * The parent node */ protected ModelNode parent; /** * The next sibling */ protected ModelNode nextSibling; /** * The next sibling */ protected ModelNode prevSibling; /** * Mask for the renderable bit. */ public static final int CAN_RENDER_RENDERABLE_MASK = ~0x1; /** * Mask for the required features bit */ public static final int CAN_RENDER_REQUIRED_FEATURES_MASK = ~(1 << 1); /** * Mask for the required extensions bit */ public static final int CAN_RENDER_REQUIRED_EXTENSIONS_MASK = ~(1 << 2); /** * Mask for the required languages bit */ public static final int CAN_RENDER_SYSTEM_LANGUAGE_MASK = ~(1 << 3); /** * Mask for conditions met, i.e., requiredFeatures, requiredExtensions, * and systemLanguage. */ public static final int CAN_RENDER_CONDITIONS_MET_MASK = CAN_RENDER_REQUIRED_FEATURES_MASK & CAN_RENDER_REQUIRED_EXTENSIONS_MASK & CAN_RENDER_SYSTEM_LANGUAGE_MASK; /** * Mask defining which bits from the canRenderState value are copied over * to newly created proxies. See the description of the canRenderState * bits below. */ public static final int CAN_RENDER_PROXY_BITS_MASK = 0x7EE; /** * Mask for the non-invertible transform bit */ public static final int CAN_RENDER_NON_INVERTIBLE_TXF_MASK = ~(1 << 4); /** * Mask for the display bit */ public static final int CAN_RENDER_DISPLAY_MASK = ~(1 << 5); /** * Mask for the zero-width bit */ public static final int CAN_RENDER_ZERO_WIDTH_MASK = ~(1 << 6); /** * Mask for the zero-height bit */ public static final int CAN_RENDER_ZERO_HEIGHT_MASK = ~(1 << 7); /** * Mask for the empty viewbox bit */ public static final int CAN_RENDER_EMPTY_VIEWBOX_MASK = ~(1 << 8); /** * Mask for the empty path bit */ public static final int CAN_RENDER_EMPTY_PATH_MASK = ~(1 << 9); /** * Mask for the zero font-size bit */ public static final int CAN_RENDER_ZERO_FONT_SIZE_MASK = ~(1 << 10); /** * Mask for the in-document-tree bit */ public static final int CAN_RENDER_IN_DOCUMENT_TREE_MASK = ~(1 << 11); /** * Mask for the paren canRenderState bit */ public static final int CAN_RENDER_PARENT_STATE_MASK = ~(1 << 12); /** * Mask for the renderable bit. */ public static final int CAN_RENDER_RENDERABLE_BIT = ~CAN_RENDER_RENDERABLE_MASK; /** * Mask for the required features bit */ public static final int CAN_RENDER_REQUIRED_FEATURES_BIT = ~CAN_RENDER_REQUIRED_FEATURES_MASK; /** * Mask for the required extensions bit */ public static final int CAN_RENDER_REQUIRED_EXTENSIONS_BIT = ~CAN_RENDER_REQUIRED_EXTENSIONS_MASK; /** * Mask for the system language bit */ public static final int CAN_RENDER_SYSTEM_LANGUAGE_BIT = ~CAN_RENDER_SYSTEM_LANGUAGE_MASK; /** * Mask for the conditionsMet bits */ public static final int CAN_RENDER_CONDITIONS_MET_BITS = ~CAN_RENDER_CONDITIONS_MET_MASK; /** * Mask for the non-invertible transform bit */ public static final int CAN_RENDER_NON_INVERTIBLE_TXF_BIT = ~CAN_RENDER_NON_INVERTIBLE_TXF_MASK; /** * Mask for the display bit */ public static final int CAN_RENDER_DISPLAY_BIT = ~CAN_RENDER_DISPLAY_MASK; /** * Mask for the zero-width bit */ public static final int CAN_RENDER_ZERO_WIDTH_BIT = ~CAN_RENDER_ZERO_WIDTH_MASK; /** * Mask for the zero-height bit */ public static final int CAN_RENDER_ZERO_HEIGHT_BIT = ~CAN_RENDER_ZERO_HEIGHT_MASK; /** * Mask for the empty viewbox bit */ public static final int CAN_RENDER_EMPTY_VIEWBOX_BIT = ~CAN_RENDER_EMPTY_VIEWBOX_MASK; /** * Mask for the empty path bit */ public static final int CAN_RENDER_EMPTY_PATH_BIT = ~CAN_RENDER_EMPTY_PATH_MASK; /** * Mask for the zero font-size bit */ public static final int CAN_RENDER_ZERO_FONT_SIZE_BIT = ~CAN_RENDER_ZERO_FONT_SIZE_MASK; /** * Mask for the in document tree bit */ public static final int CAN_RENDER_IN_DOCUMENT_TREE_BIT = ~CAN_RENDER_IN_DOCUMENT_TREE_MASK; /** * Mask for the parent cansRenderState bit */ public static final int CAN_RENDER_PARENT_STATE_BIT = ~CAN_RENDER_PARENT_STATE_MASK; /** * Pre-computes whether or not this node can render. This is a bit-mask. * * 0 : renderable (i.e., initialized in the class constructor). All * classes. * 1 : required features. ElementNode * 2 : required extensions. ElementNode * 3 : required languages. ElementNode. * 4 : non-invertible transform. StructureNode, AbstractRenderingNode. * 5 : display. CompositeGraphicsNode. * 6 : width is zero. Ellipse, ImageNode, Rect, SVG, Viewport * 7 : height is zero. Ellipse, ImageNode, Rect, SVG, Viewport * 8 : viewBox width or height is zero or negative. * 9 : path is null or has no segments. ShapeNode. * 10 : font-size is negative or zero. Text. * 11 : node in document tree. * 12 : parent canRenderState. This 1 bit value is set if any of the bits * in the parent node's canRenderState is set. * * Only bits 1/2/3 are special in that they need to be applied from a node * to its proxies. Other bits (e.g., bit 5 (display)) do not need to be * propagated because they are set when the property changes on the node and * there is already propagation of property changes to the proxies. * * By default, ModelNodes are not renderable and are not in the document * tree. */ protected int canRenderState = CAN_RENDER_RENDERABLE_BIT | CAN_RENDER_IN_DOCUMENT_TREE_BIT; /** * Used to track if this node is loaded or not. A node becomes loaded after * its <code>markLoaded</code> method is called. Note: the default is * <b>true</b> because when used through scripting, nodes are considered * fully loaded and operational. When used by the builder module or a * parser, the flag needs to be first turned off (see the * <code>setLoaded</code> method to control the progressive rendering * behavior (see the <code>CanvasManager</code> class). */ protected boolean loaded = true; /** * The owner <code>DocumentNode</code> */ protected DocumentNode ownerDocument; /** * By default, a node's bounding box is not accounted for. This is * overridden by children classes to specify when and under which condition * their bounding box should be accounted for. * * @return true if the node's bounding box should be accounted for. */ protected boolean contributeBBox() { return false; } /** * @return the <code>UpdateListener</code> associated with the nearest * <code>DocumentNode</code> ancestor or null if there is no * <code>Viewporpt</code> ancestor */ protected UpdateListener getUpdateListener() { // // It is important to _not_ use ownerDocument.getUpdateListener // because we do not report changes to elements which are not // hooked into the document tree. // // May be that should be changed in the future: we could also // always report updates to the listener and let it figure out // whether or not the reporting node is in the Document tree // or not yet. // if (parent == null) { return null; } else { return parent.getUpdateListener(); } } /** * Returns the next sibling object of this object, or * <code>null</code> if this is the last sibling. * * @return the next sibling object. */ public ModelNode getNextSiblingNode() { return nextSibling; } /** * Returns the previous sibling object of this object, or * <code>null</code> if this is the first child node and there * is no previous sibling. * * @return the next sibling object. */ public ModelNode getPreviousSiblingNode() { return prevSibling; } /** * @return a reference to the parent <code>ModelNode</code> */ public ModelNode getParent() { return parent; } /** * @param newParent this node's new parent */ protected void setParent(final ModelNode newParent) { modifyingNode(); setParentQuiet(newParent); nodeInserted(this); } /** * 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) { if (parent == newParent) { return; } // If the new parent is not null, check whether or not // it is in the document tree. In all cases, we need // to unhook and then hook again. if (parent != null) { onUnhookedFromDocumentTree(); } parent = newParent; // If the parent is not null and in the document tree, // we process that condition again. if (parent != null) { if (parent.isInDocumentTree()) { onHookedInDocumentTree(); } } } /** * Utility method. Unhooks a node and all its following siblings, * quietly setting the parent and or proxy to null. * * @param node the root of the branch to unhook. */ protected final void unhookQuiet(ModelNode node) { if (node != null) { if (node.prevSibling != null) { node.prevSibling.nextSibling = null; } } while (node != null) { node.setParentQuiet(null); if (node instanceof ElementNodeProxy) { ((ElementNodeProxy) node).setProxied(null); } node.unhookChildrenQuiet(); node.unhookExpandedQuiet(); node = node.nextSibling; } } /** * Utility method. Returns true if the input node is part of the * Document tree, i.e., if its top most ancestor is equal to its * ownerDocument. * * @return true if this node is a <code>DocumentNode</code> or if * one of its ancestors is a <code>DocumentNode</code> */ protected boolean isInDocumentTree() { return ((canRenderState & CAN_RENDER_IN_DOCUMENT_TREE_BIT) == 0); } /** * Returns the <code>DocumentNode</code> this node is attached * to. * * @return the <code>DocumentNode</code> this node belongs to. The return * value should never be null. */ public DocumentNode getOwnerDocument() { return ownerDocument; } /** * By default, this node returns true if there are no children. * <code>ModelNode</code> implementations with expanded content * should override this method. * * @return true if this node has, or may have children or expanded * children. A node may not know in advance if it has expanded * children because in some cases, the computation of expanded * content is done lazily. So this returns false if the node * does not have children and will never have expanded content. */ public boolean hasDescendants() { return getFirstChildNode() != null; } /** * Paints the input node and all its siblings into the * <code>RenderGraphics</code> * * @param node the <code>ModelNode</code> to paint. * @param rg the <code>RenderGraphics</code> where the nodes should * be painted */ static void paint(ModelNode node, final RenderGraphics rg) { while (node != null) { node.paint(rg); node = node.nextSibling; } } /** * @return the inherited value for the requested property. */ protected final Object getInheritedPropertyState(final int propertyIndex) { if (parent == null) { return ownerDocument.getInitialPropertyState(propertyIndex); } return parent.getPropertyState(propertyIndex); } /** * @return the inherited value for the requested float property. */ protected final float getInheritedFloatPropertyState( final int propertyIndex) { if (parent == null) { return ownerDocument.getInitialFloatPropertyState(propertyIndex); } return parent.getFloatPropertyState(propertyIndex); } /** * @return the inherited value for the requested packed property. */ protected final int getInheritedPackedPropertyState( final int propertyIndex) { if (parent == null) { return ownerDocument.getInitialPackedPropertyState(propertyIndex); } return parent.getPackedPropertyState(propertyIndex); } /** * Returns the value for the given property. * * @return the value for the given property, null if the property is * unknown. */ protected Object getPropertyState(final int propertyIndex) { return ownerDocument.getInitialPropertyState(propertyIndex); } /** * Returns the value for the given packed property. * * @return the value of the given property. */ protected int getPackedPropertyState(final int propertyIndex) { return ownerDocument.getInitialPackedPropertyState(propertyIndex); } /** * Returns the value for the given float property. * * @return the value of the given property. */ protected float getFloatPropertyState(final int propertyIndex) { return ownerDocument.getInitialFloatPropertyState(propertyIndex); } /** * Sets the computed value for the given property. * * @param propertyIndex the property index * @param propertyValue the computed value for the property. */ protected void setPropertyState(final int propertyIndex, final Object propertyValue) { } /** * Sets the computed value for the given float property. * * @param propertyIndex the property index * @param propertyValue the computed value for the property. */ protected void setFloatPropertyState(final int propertyIndex, final float propertyValue) { } /** * Sets the computed value for the given packed property. * * @param propertyIndex the property index * @param propertyValue the computed value for the property. */ protected void setPackedPropertyState(final int propertyIndex, final int propertyValue) { } /** * Checks the state of the property value. * * @param propertyIndex the property index * @param propertyValue the computed value for the property. */ protected boolean isPropertyState(final int propertyIndex, final Object propertyValue) { // By default, return true so that we don't bother setting an unknown // property. return true; } /** * Checks the state of the float property value. * * @param propertyIndex the property index * @param propertyValue the computed value for the property. */ protected boolean isFloatPropertyState(final int propertyIndex, final float propertyValue) { // By default, return true so that we don't bother setting an unknown // property. return true; } /** * Checks the state of the packed property value. * * @param propertyIndex the property index * @param propertyValue the computed value for the property. */ protected boolean isPackedPropertyState(final int propertyIndex, final int propertyValue) { // By default, return true so that we don't bother setting an unknown // property. return true; } /** * Recomputes the given property's state given the new parent property. * By default, this simply propagates to children. * * @param propertyIndex index for the property whose value is changing. * @param parentPropertyValue the value that children of this node should * now inherit. * */ protected void recomputePropertyState(final int propertyIndex, final Object parentPropertyValue) { propagatePropertyState(propertyIndex, parentPropertyValue); } /** * Recomputes the given float property's state given the new parent * property. By default, this simply propagates to children. * * @param propertyIndex index for the property whose value is changing. * @param parentPropertyValue the value that children of this node should * now inherit. * */ protected void recomputeFloatPropertyState( final int propertyIndex, final float parentPropertyValue) { propagateFloatPropertyState(propertyIndex, parentPropertyValue); } /** * Recomputes the given packed property's state given the new parent * property. By default, this simply propagates to children. * * @param propertyIndex index for the property whose value is changing. * @param parentPropertyValue the value that children of this node should * now inherit. * */ protected void recomputePackedPropertyState(final int propertyIndex, final int parentPropertyValue) { propagatePackedPropertyState(propertyIndex, parentPropertyValue); } /** * Recomputes all inherited properties and propages the recomputation to * children. */ void recomputeInheritedProperties() { ModelNode c = getFirstChildNode(); while (c != null) { c.recomputeInheritedProperties(); c = c.nextSibling; } } /** * Called when the canRenderState changes. If both values are zero, or * if both values are non zero, then children need to recompute their * canRenderState and propagate if need be. * * @param oldCanRenderState the old value for canRenderState. * @param newCanRenderState the new value for canRenderState */ protected void propagateCanRenderState(final int oldCanRenderState, final int newCanRenderState) { if (oldCanRenderState != newCanRenderState && ((oldCanRenderState == 0) || (newCanRenderState == 0))) { if (newCanRenderState == 0) { // Clear the parent can render state bit and propagate. // Propagate to regular children. ModelNode node = getFirstChildNode(); boolean nodeDisplay = false; int nodeOldState = 0; while (node != null) { nodeOldState = node.canRenderState; node.canRenderState &= CAN_RENDER_PARENT_STATE_MASK; node.propagateCanRenderState(nodeOldState, node.canRenderState); node = node.nextSibling; } // Propagate to expanded children. node = getFirstComputedExpandedChild(); while (node != null) { nodeOldState = node.canRenderState; node.canRenderState &= CAN_RENDER_PARENT_STATE_MASK; node.propagateCanRenderState(nodeOldState, node.canRenderState); node = node.nextSibling; } } else { // Set the parent can render state bit and propagate. // Propagate to regular children. ModelNode node = getFirstChildNode(); boolean nodeDisplay = false; int nodeOldState = 0; while (node != null) { nodeOldState = node.canRenderState; node.canRenderState |= CAN_RENDER_PARENT_STATE_BIT; node.propagateCanRenderState(nodeOldState, node.canRenderState); node = node.nextSibling; } // Propagate to expanded children. node = getFirstComputedExpandedChild(); while (node != null) { nodeOldState = node.canRenderState; node.canRenderState |= CAN_RENDER_PARENT_STATE_BIT; node.propagateCanRenderState(nodeOldState, node.canRenderState); node = node.nextSibling; } } } } /** * Called when the computed value of the given property has changed. * By default, propagate both to regular content and 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 regular children. ModelNode node = getFirstChildNode(); while (node != null) { node.recomputePropertyState(propertyIndex, parentPropertyValue); node = node.nextSibling; } // Propagate to expanded children. node = getFirstExpandedChild(); while (node != null) { node.recomputePropertyState(propertyIndex, parentPropertyValue); node = node.nextSibling; } } /** * Called when the computed value of the given float property has changed. * By default, propagate both to regular content and 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 regular children. ModelNode node = getFirstChildNode(); while (node != null) { node.recomputeFloatPropertyState(propertyIndex, parentPropertyValue); node = node.nextSibling; } // Propagate to expanded children. node = getFirstExpandedChild(); while (node != null) { node.recomputeFloatPropertyState(propertyIndex, parentPropertyValue); node = node.nextSibling; } } /** * Called when the computed value of the given packed property has changed. * By default, propagate both to regular content and 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 regular children. ModelNode node = getFirstChildNode(); while (node != null) { node.recomputePackedPropertyState(propertyIndex, parentPropertyValue); node = node.nextSibling; } // Propagate to expanded children. node = getFirstExpandedChild(); while (node != null) { node.recomputePackedPropertyState(propertyIndex, parentPropertyValue); node = node.nextSibling; } } /** * @return this node's cached transform. */ public Transform getTransformState() { // By default, a ModelNode does not add any new transform, so its // cached transform state is the same as its parent node. if (parent != null) { return parent.getTransformState(); } return IDENTITY; } /** * @return this node's cached inverse transform. */ Transform getInverseTransformState() { // By default, a ModelNode does not add any new transform, so its // cached inverse transform state is the same as its parent node. if (parent != null) { return parent.getInverseTransformState(); } return IDENTITY; } /** * 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. */ protected void recomputeTransformState() { if (parent == null) { recomputeTransformState(new Transform(null)); } else { recomputeTransformState(parent.getTransformState()); } } /** * 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) { recomputeTransformState(getTransformState(), getFirstChildNode()); recomputeTransformState(getTransformState(), getFirstExpandedChild()); computeCanRenderTransformBit(getTransformState()); } /** * Recomputes the transform cache of the input <code>ModelNode</code> * and all its siblings. Implementation helper. * * @param parentTransform the parent transform. * @param node first node whose transform cache should be * cleared. */ void recomputeTransformState(final Transform parentTransform, ModelNode node) { while (node != null) { node.recomputeTransformState(parentTransform); node = node.nextSibling; } } /** * @return true if this node is hooked to the document tree, i.e., if it top * most ancestor is the DocumentNode. */ boolean inDocumentTree() { return parent != null && parent.inDocumentTree(); } /** * @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() { // The CTM is null if the element is not in the document tree. if (!inDocumentTree()) { return null; } return new Transform(getTransformState()); } /** * Utility method to recycle a <code>Transform</code> instance when * possible. * * @param tx the <code>Transform</code> to use to initialize the returned * value. * @param workTx the candidate <code>Transform</code> instance which may be * re-cycled. The instance can be recycled if it is different * (i.e., a different reference) from the input base transform * <code>tx</code>. */ protected final Transform recycleTransform(final Transform tx, final Transform workTx) { if (workTx != null && workTx != tx) { // We recycle workTx workTx.setTransform(tx); return workTx; } else { // System.err.println(">>>>>>> creating new Transform instance..."); // We cannot use workTx because it is null or // it is the same as tx. return new Transform(tx); } } /** * Should be overridden by derived classes to apply any node specific * transform to the input transform. * * If the input transform is null and this node does not add any * transform, the return value should be null. * * If the input transform is null and this node adds a transform, the * return value should be an object equal to this node's transform, * but not be the same instance. * * If the input transform is not null and this node does not add any * transform, the return value should be the same as the input transform. * * If the input transform is not null and this node adds a transform, * the return value should be a new (different) transform. * * The second parameter, workTxf can be re-used to hold the result * only if it is different from tx. If it is referencing the same instance * as tx and a new Transform needs to be created, then it should not * be reused. * IMPL NOTE : This is a coding style to recycle <code>Transform</code> * instances in animation loops. * * @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(final Transform tx, final Transform workTx) { // Does nothing by default. See derived classes. return tx; } /** * @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, final Transform t) { return bbox; } /** * Implementation helper. Adds the input shape's bounding box to the * input bounding box rect. If the shape is null, nothing is added. * * @param path the shape whose bounds, transformed through t, should be * added to bbox. * @param t the transform to the target bounds space. * @param bbox the bounding box to which the shape's bounds should be * added. * @return a rectangle that encompasses bbox and the path's bounding box, * after transformation to the target coordinate space. */ static Box addShapeBBox(Box bbox, final Path path, final Transform t) { if (path == null || path.getNumberOfSegments() == 0) { return bbox; } Box b = null; if (t == null) { b = path.getBounds(); } else { b = path.getTransformedBounds(t); } return addBBox(bbox, b.getX(), b.getY(), b.getWidth(), b.getHeight()); } /** * Implementation helper. Adds the input rectangle to the input bounding box * rect. * * @param bbox the rectangle to which the new box should be added. * @param x the x-axis origin of the new rect to add * @param y the y-axis origin of the new rect to add * @param width the width of the rect to add * @param height the height of the rect to add * @return a bounding box that encompasses bbox and the (x, y, width, * height) rectangle. */ static Box addBBox(Box bbox, final float x, final float y, final float width, final float height) { if (bbox == null) { bbox = new Box(x, y, width, height); return bbox; } bbox.width = bbox.x + bbox.width; bbox.height = bbox.y + bbox.height; if (bbox.x > x) { bbox.x = x; } if (bbox.y > y) { bbox.y = y; } if (bbox.width < x + width) { bbox.width = x + width; } if (bbox.height < y + height) { bbox.height = y + height; } bbox.width -= bbox.x; bbox.height -= bbox.y; return bbox; } /** * Implementation helper. Computes the bounding box of the rectangle * transformed by the input matrix. * * @param bbox the bounding box to which this node's bounds should be * added. * @param x the rectangle's x-axis origin * @param y the rectangle's y-axis origin * @param width the rectangle's width * @param height the rectangle's height * @param m the matrix transforming to the target coordinate space * into which the returned rectangle should be. * @return the matrix from the input rectangle's coordinate space to * the target coordinate space. */ static Box addTransformedBBox(Box bbox, final float x, final float y, final float width, final float height, final Transform m) { if (m == null) { return addBBox(bbox, x, y, width, height); } float tx = (float) (m.getComponent(0) * x + m.getComponent(2) * y + m.getComponent(4)); float ty = (float) (m.getComponent(1) * x + m.getComponent(3) * y + m.getComponent(5)); float dx1 = (float) (m.getComponent(0) * width); float dy1 = (float) (m.getComponent(1) * width); float dx2 = (float) (m.getComponent(2) * height); float dy2 = (float) (m.getComponent(3) * height); if (dx1 < 0) { tx += dx1; dx1 = -dx1; } if (dx2 > 0) { dx1 += dx2; } else { dx1 -= dx2; tx += dx2; } if (dy1 < 0) { ty += dy1; dy1 = -dy1; } if (dy2 > 0) { dy1 += dy2; } else { dy1 -= dy2; ty += dy2; } return addBBox(bbox, tx, ty, dx1, dy1); } /** * @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 bbox; } /** * 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) { return null; } /** * Returns the <code>ModelNode</code>, if any, hit by the * point at coordinate x/y, in viewport space. The input * node, and all its siblings, are tested. * * @param node the first node on the set of nodes to test. * @param pt the point which is hit * @return the node hit at the given coordinate, or null if * no node was hit. */ protected final ModelNode nodeHitAt(ModelNode node, final float[] pt) { ModelNode hit = null; while (node != null) { hit = node.nodeHitAt(pt); if (hit != null) { return hit; } node = node.prevSibling; } return null; } /** * The node's URI base to use to resolve URI references * If a URI base value was set on this node, then that value * is returned. Otherwise, this method returns the parent's * URI base. If there is not URI base on this node and if there * is not parent, then this method returns null. * * @return the node's URI base to use to resolve relative URI references. */ protected String getURIBase() { if (parent != null) { return parent.getURIBase(); } return null; } /** * Recomputes the requiredFeatures bit in the canRenderState bit mask. * * @param requiredFeatures if ConditionalProcessing.checkFeatures returns * true, the bit is cleared. Otherwise, the bit is set. */ final void computeCanRenderRequiredFeaturesBit( final String[] requiredFeatures) { int oldCanRenderState = canRenderState; if (requiredFeatures == null || ConditionalProcessing.checkFeatures(requiredFeatures)) { canRenderState &= CAN_RENDER_REQUIRED_FEATURES_MASK; } else { canRenderState |= CAN_RENDER_REQUIRED_FEATURES_BIT; } propagateCanRenderState(oldCanRenderState, canRenderState); } /** * Recomputes the requiredExtensions bit in the canRenderState bit mask. * * @param requiredExtensions if ConditionalProcessing.checkExtensions * returns true, the bit is cleared. Otherwise, the bit is set. */ final void computeCanRenderRequiredExtensionsBit( final String[] requiredExtensions) { int oldCanRenderState = canRenderState; if (requiredExtensions == null || ConditionalProcessing.checkExtensions(requiredExtensions)) { canRenderState &= CAN_RENDER_REQUIRED_EXTENSIONS_MASK; } else { canRenderState |= CAN_RENDER_REQUIRED_EXTENSIONS_BIT; } propagateCanRenderState(oldCanRenderState, canRenderState); } /** * Recomputes the systemLanguage bit in the canRenderState bit mask. * * @param systemLanguage if ConditionalProcessing.checkLanguage returns * true, the bit is cleared. Otherwise, the bit is set. */ final void computeCanRenderSystemLanguageBit( final String[] systemLanguage) { int oldCanRenderState = canRenderState; if (systemLanguage == null || ConditionalProcessing.checkLanguage(systemLanguage)) { canRenderState &= CAN_RENDER_SYSTEM_LANGUAGE_MASK; } else { canRenderState |= CAN_RENDER_SYSTEM_LANGUAGE_BIT; } propagateCanRenderState(oldCanRenderState, canRenderState); } /** * Recomputes the non-invertible transform bit in the canRenderState * bit mask. * * @param transform the transform which drives the non-invertible * transform bit. If non invertible, the bit is set. Otherwise, the * bit is cleared. */ final void computeCanRenderTransformBit(final Transform transform) { int oldCanRenderState = canRenderState; if (transform == null || transform.isInvertible()) { canRenderState &= CAN_RENDER_NON_INVERTIBLE_TXF_MASK; } else { canRenderState |= CAN_RENDER_NON_INVERTIBLE_TXF_BIT; } propagateCanRenderState(oldCanRenderState, canRenderState); } /** * Recomputes the empty viewBox bit in the canRenderState * bit mask. * * @param viewBox the viewBox value which drives the empty viewBox * bit. If null or if positive width/height, the bit is cleared. * Otherwise, the bit is set. */ final void computeCanRenderEmptyViewBoxBit(final float[][] viewBox) { int oldCanRenderState = canRenderState; if (viewBox == null || (viewBox[1][0] > 0 && viewBox[2][0] > 0)) { canRenderState &= CAN_RENDER_EMPTY_VIEWBOX_MASK; } else { canRenderState |= CAN_RENDER_EMPTY_VIEWBOX_BIT; } propagateCanRenderState(oldCanRenderState, canRenderState); } /** * Recomputes the display bit in the canRenderState bit mask. * * @param display the display value. If true, the bit is cleared. If * false, the bit is set. */ final void computeCanRenderDisplayBit(final boolean display) { int oldCanRenderState = canRenderState; if (display) { canRenderState &= CAN_RENDER_DISPLAY_MASK; } else { canRenderState |= CAN_RENDER_DISPLAY_BIT; } propagateCanRenderState(oldCanRenderState, canRenderState); } /** * Recomputes the zero-width bit in the canRenderState bit mask. * * @param width the new width value. If zero or negative, the bit * is set. Otherwise, the bit is cleared. */ final void computeCanRenderWidthBit(final float width) { int oldCanRenderState = canRenderState; if (width > 0) { canRenderState &= CAN_RENDER_ZERO_WIDTH_MASK; } else { canRenderState |= CAN_RENDER_ZERO_WIDTH_BIT; } propagateCanRenderState(oldCanRenderState, canRenderState); } /** * Recomputes the zero-height bit in the canRenderState bit mask. * * @param height the new height value. If zero or negative, the bit * is set. Otherwise, the bit is cleared. */ final void computeCanRenderHeightBit(final float height) { int oldCanRenderState = canRenderState; if (height > 0) { canRenderState &= CAN_RENDER_ZERO_HEIGHT_MASK; } else { canRenderState |= CAN_RENDER_ZERO_HEIGHT_BIT; } propagateCanRenderState(oldCanRenderState, canRenderState); } /** * Recomputes the zero-fontSize bit in the canRenderState bit mask. * * @param fontSize the new fontSize value. If zero or negative, the bit * is set. Otherwise, the bit is cleared. */ final void computeCanRenderFontSizeBit(final float fontSize) { int oldCanRenderState = canRenderState; if (fontSize > 0) { canRenderState &= CAN_RENDER_ZERO_FONT_SIZE_MASK; } else { canRenderState |= CAN_RENDER_ZERO_FONT_SIZE_BIT; } propagateCanRenderState(oldCanRenderState, canRenderState); } /** * Recomputes the empty path bit in the canRenderStaet bit mask. * * @param path the value that determines the bit state. If null or * if no segments, the bit is set. Otherwise, the bit is cleared. */ final void computeCanRenderEmptyPathBit(final Path path) { int oldCanRenderState = canRenderState; if (path == null || path.getNumberOfSegments() == 0) { canRenderState |= CAN_RENDER_EMPTY_PATH_BIT; } else { canRenderState &= CAN_RENDER_EMPTY_PATH_MASK; } propagateCanRenderState(oldCanRenderState, canRenderState); } /** * By default, there is no node rendering. * * @return true if the node has some rendering of its own, * i.e., rendering beyond what its children or * expanded content render. */ protected boolean hasNodeRendering() { return false; } /** * 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 abstract void clearLayouts(); /** * Clears the text layouts in the input node and all its * siblings. Implementation helper. * * @param node the first node whose text layouts should be cleared. */ void clearLayouts(ModelNode node) { while (node != null) { node.clearLayouts(); node = node.nextSibling; } } /** * @return true if this node is considered loaded. This is used * in support of progressive rendering. */ public final boolean isLoaded() { return loaded; } /** * @param isLoaded the new loaded state */ public void setLoaded(final boolean isLoaded) { loaded = isLoaded; } /** * @return true if the node needs to be fully loaded before it * can be painted */ boolean getPaintNeedsLoad() { return false; } /** * Used to notify the <code>UpdateListener</code>, if any, of * an upcoming node modification * */ protected void modifyingNode() { UpdateListener updateListener = getUpdateListener(); if (updateListener != null) { updateListener.modifyingNode(this); } } /** * Used to notify the <code>UpdateListener</code>, if any, of * an change in a node's rendering * */ protected void modifyingNodeRendering() { UpdateListener updateListener = getUpdateListener(); if (updateListener != null) { updateListener.modifyingNodeRendering(this); } } /** * Used to notify the <code>UpdateListener</code>, if any, of * a completed node modification * */ protected void modifiedNode() { UpdateListener updateListener = getUpdateListener(); if (updateListener != null) { updateListener.modifiedNode(this); } } /** * Used to notify the <code>UpdateListener</code>, if any, of * a node insertion * * @param node the node which was just inserted */ protected static void nodeInserted(final ModelNode node) { UpdateListener updateListener = node.getUpdateListener(); if (updateListener != null) { updateListener.nodeInserted(node); } } /** * Add an event listener that will be notified when an event of the * desired type happens on this target or one of its descendants. * * An EventListener that is added to an EventTarget currently processing an * event is not triggered by the current actions. Duplicate instances * of EventListeners (registered on the same EventTarget with * the same parameters) are discarded. There's even no need to remove * such duplicates with the removeEventListener method. * * @param type The type of event to listen to. * @param listener the event listener * @param useCapture If true, the listener will be called while in the event * flow capture phase. Otherwise, the listener will be called while in the * bubble phase. If the event's target is this target, then the listener * will be called while in the 'at target' phase of event flow. * * @throws DOMException with error code NOT_SUPPORTED_ERROR if useCapture is * true since capture phase is not supported in SVG Tiny. * @throws NullPointerException if <code>listener</code> is null. * @throws NullPointerException if <code>type</code> is null. */ public void addEventListener(final String type, final EventListener listener, final boolean useCapture) throws DOMException { ownerDocument.eventSupport .addEventListener(this, type, useCapture ? EventSupport.CAPTURE_PHASE : EventSupport.BUBBLE_PHASE, listener); } /** * Remove an event listener that was previously registered. * * An EventListener that is removed from an EventTarget currently processing an * event is not triggered by the current actions. Nothing happens when * removeEventListener arguments do not identify any currently * registered EventListener on this EventTarget. * * @param type The type of event that was listened to. * @param listener The previously registered listener. * @param useCapture If true, the listener was listening to events in the * capture phase of event flow, otherwise the listener was listening to * events in the bubble phase. * * @throws DOMException with error code NOT_SUPPORTED_ERROR if useCapture is * true since capture phase is not supported in SVG Tiny. * @throws NullPointerException if <code>type</code> is null. */ public void removeEventListener( final String type, final EventListener listener, final boolean useCapture) throws DOMException { ownerDocument.eventSupport .removeEventListener(this, type, useCapture ? EventSupport.CAPTURE_PHASE : EventSupport.BUBBLE_PHASE, listener); } /** * Delegates to the associated <code>EventSupport</code> class. * * @param evt the event to dispatch */ public void dispatchEvent(final ModelEvent evt) { ownerDocument.eventSupport.dispatchEvent(evt); } // ========================================================================= // Methods with default implementations which are typically overridden in // extension. // ========================================================================= /** * To be overriddent by derived classes, such as TimedElementNode, * if they need to perform special operations when hooked into the * document tree. */ void onHookedInDocumentTree() { // Clear the 'in document tree' can render bit int oldCanRenderState = canRenderState; canRenderState &= CAN_RENDER_IN_DOCUMENT_TREE_MASK; // Set the parent's canRenderState bit if (parent.canRenderState == 0) { canRenderState &= CAN_RENDER_PARENT_STATE_MASK; } else { canRenderState |= CAN_RENDER_PARENT_STATE_BIT; } propagateCanRenderState(oldCanRenderState, canRenderState); recomputeTransformState(); recomputeInheritedProperties(); } /** * To be overriddent by derived classes, such as TimedElementNode, * if they need to perform special operations when unhooked from the * document tree. */ void onUnhookedFromDocumentTree() { // Set the 'in document tree' can render bit int oldCanRenderState = canRenderState; canRenderState |= CAN_RENDER_IN_DOCUMENT_TREE_BIT; // Clear the parent's canRenderState bit canRenderState &= CAN_RENDER_PARENT_STATE_MASK; propagateCanRenderState(oldCanRenderState, canRenderState); recomputeTransformState(); recomputeInheritedProperties(); } /** * 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) { // By default, do not paint anything. } /** * To be overridden by nodes which may have a rendering if they need * to do something specific when * they have been rendered. */ protected void nodeRendered() { // By default, do nothing. } // ========================================================================= // Abstract method to override in extensions. // ========================================================================= /** * Utility method. Unhooks the children. */ protected abstract void unhookChildrenQuiet(); /** * Utility method. Unhooks the expanded content. */ protected abstract void unhookExpandedQuiet(); /** * @return a reference to the node's first child, or null if there * are no children. */ public abstract ModelNode getFirstChildNode(); /** * @return a reference to the node's last child, or null if there * are no children. */ public abstract ModelNode getLastChildNode(); /** * 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. */ abstract ModelNode getFirstExpandedChild(); /** * 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. */ abstract ModelNode getFirstComputedExpandedChild(); /** * 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. */ abstract ModelNode getLastExpandedChild(); }