/* * $RCSfile: AnimateMotion.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.platform.MathSupport; import com.sun.perseus.util.SVGConstants; import org.w3c.dom.DOMException; import org.w3c.dom.svg.SVGElement; import java.util.Vector; /** * <code>AnimateMotion</code> represents an SVG Tiny * <code><animateMotion></code> element. * * @version $Id: AnimateMotion.java,v 1.5 2006/06/29 10:47:29 ln156897 Exp $ */ public class AnimateMotion extends AbstractAnimate { /** * Rotate type when an angle is specified in the rotate attribute. */ static final int ROTATE_ANGLE = 1; /** * Rotate type when the rotate attribute is set to auto. */ static final int ROTATE_AUTO = 2; /** * Rotate type when the rotate attribute is set to auto-reverse */ static final int ROTATE_AUTO_REVERSE = 3; /** * The path attribute value. */ String path; /** * The keyPoints attribute value */ float[] keyPoints; /** * The rotate angle. Used if rotateType is ROTATE_ANGLE */ float rotate; /** * The rotation's cos value */ float cosRotate = 1; /** * The rotation angle's sin value. */ float sinRotate; /** * The rotate type. One of ROTATE_ANGLE (an angle was specified) or * ROTATE_AUTO, ROTATE_AUTO_REVERSE */ int rotateType = ROTATE_ANGLE; /** * Used, temporarily, to hold the refValues for the path attribute. */ RefValues pathRefValues; /** * Used, temporarily, to hold the refValues for mpath child. */ RefValues mpathRefValues; /** * Builds a new AnimateMotion element that belongs to the given * document. This <code>AnimateMotion</code> will belong * to the <code>DocumentNode</code>'s time container. * * @param ownerDocument the document this node belongs to. * @throws IllegalArgumentException if the input ownerDocument is null */ public AnimateMotion(final DocumentNode ownerDocument) { super(ownerDocument, SVGConstants.SVG_ANIMATE_MOTION_TAG); // Default calcMode for animateMotion is paced. calcMode = CALC_MODE_PACED; // AnimateMotion operates on a fixed trait name traitName = SVGConstants.SVG_MOTION_PSEUDO_ATTRIBUTE; } /** * Used by <code>DocumentNode</code> to create a new instance from * a prototype <code>TimedElementNode</code>. * * @param doc the <code>DocumentNode</code> for which a new node is * should be created. * @return a new <code>TimedElementNode</code> for the requested document. */ public ElementNode newInstance(final DocumentNode doc) { return new AnimateMotion(doc); } /** * @param traitName the name of the trait which the element may support. * @return true if this element supports the given trait in one of the * trait accessor methods. */ boolean supportsTrait(final String traitName) { if (SVGConstants.SVG_PATH_ATTRIBUTE == traitName || SVGConstants.SVG_KEY_POINTS_ATTRIBUTE == traitName || SVGConstants.SVG_ROTATE_ATTRIBUTE == traitName) { return true; } else { return super.supportsTrait(traitName); } } /** * Returns the value of the trait value as a String value. In SVG Tiny only * fixed traits can be obtained as String. Syntax of the returned vlaue * matches the syntax of the corresponding attribute. This element is * entirely analogue of * {@link org.w3c.dom.svg.SVGElement#getTraitNS getTraitNS} with * namespaceURI set to null. * * The method is meant to be overridden by derived classes. The * implementation pattern is that derived classes will override the method * and call their super class' implementation. If the ElementNode * implementation is called, it means that the trait is either not supported * or that it cannot be seen as a String. * * @param name the requested trait name. * @return the trait 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_PATH_ATTRIBUTE == name) { return path; } else if (SVGConstants.SVG_KEY_POINTS_ATTRIBUTE == name) { return toStringTrait(keyPoints); } else if (SVGConstants.SVG_ROTATE_ATTRIBUTE == name) { switch (rotateType) { case ROTATE_ANGLE: return Float.toString(rotate); case ROTATE_AUTO: return SVGConstants.SVG_AUTO_VALUE; case ROTATE_AUTO_REVERSE: default: return SVGConstants.SVG_AUTO_REVERSE_VALUE; } } else { return super.getTraitImpl(name); } } /** * The following call lets the animate implementation map * the time segment indices and the time segment penetration * into refValues indices and penetration, in case these are * different. Typically, these are the same, but they may be * different, for example in the case of animateMotion with * keyPoints. * * @param si the time segment index. * @param sp the segment penetration. * @param sisp an array where the mapped si and sp value should * be stored. si is at index 0 and sp at index 1. */ protected void mapToSegmentProgress(final int si, final float sp, final float[] sisp) { // animateMotion also maps to segments directly unless there // is a keyPoints attribute specified, in which case it // overrides the default behavior. if (keyPoints == null || actualCalcMode == CALC_MODE_PACED) { super.mapToSegmentProgress(si, sp, sisp); return; } // We are dealing with a keyPoints animateMotion. // We only do the computation on the first component, because // the input and output indices for animateMotion with keyTimes // should be the same on all components (by definition). float startDist = keyPoints[si]; float endDist = keyPoints[si + 1]; float dist = sp * endDist + (1 - sp) * startDist; // Now that we know how far along we should be on the path, // find the corresponding segment. ((MotionRefValues) refValues).getSegmentAtDist(sisp, dist); } /** * @param name the trait's name. * @param value the trait's value. * * @throws DOMException with error code NOT_SUPPORTED_ERR 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_PATH_ATTRIBUTE == name) { checkWriteLoading(name); // path values are validated in the validate() method, // just like to/from/by and values are path = value; } else if (SVGConstants.SVG_KEY_POINTS_ATTRIBUTE == name) { checkWriteLoading(name); keyPoints = parseFloatArrayTrait(name, value, ';'); } else if (SVGConstants.SVG_ROTATE_ATTRIBUTE == name) { checkWriteLoading(name); if (SVGConstants.SVG_AUTO_VALUE.equals(value)) { rotate = 0; rotateType = ROTATE_AUTO; } else if (SVGConstants.SVG_AUTO_REVERSE_VALUE.equals(value)) { rotate = 0; rotateType = ROTATE_AUTO_REVERSE; } else { // The value is neither 'auto' nor 'auto-reverse'. It // must be an angle value. rotate = parseFloatTrait(name, value); cosRotate = MathSupport.cos(MathSupport.toRadians(rotate)); sinRotate = MathSupport.sin(MathSupport.toRadians(rotate)); rotateType = ROTATE_ANGLE; } } else { super.setTraitImpl(name, value); } } /** * Computes refTimes from the calcMode and keyTimes attributes. Validates * that the keyTimes attribute is compatible with the animate set up. This * may be overridden by subclasses (e.g., animateMotion), when there are * special rules for checking keyTimes compatiblity. */ protected void computeRefTimes() throws DOMException { // if there is no keyPoints defined or if the calcMode is // paced, we use the default behavior. if (keyPoints == null || actualCalcMode == CALC_MODE_PACED) { super.computeRefTimes(); return; } // Check keyTimes is compatible with the animation specification. // // a) keyTimes should be specified (i.e., not null) // // b) In all cases, the first keyTime must be zero. // // c) For non-discrete animations, the last keyTime must be one. // // d) There should be as many keyTimes as there are keyPoints // if (/* a) */ keyTimes == null || /* b) */ keyTimes.length < 1 || keyTimes[0] != 0 || /* c) */ (actualCalcMode != CALC_MODE_DISCRETE && keyTimes[keyTimes.length - 1] != 1) || /* d) */ keyTimes.length != keyPoints.length) { throw animationError( idRef, traitNamespace, traitName, targetElement.getNamespaceURI(), targetElement.getLocalName(), getId(), getNamespaceURI(), getLocalName(), Messages.formatMessage( Messages.ERROR_INVALID_ANIMATION_KEY_TIMES, new Object[] { getTrait(SVGConstants.SVG_KEY_TIMES_ATTRIBUTE) })); } // If the calcMode is _not_ discrete, we trim the last '1' // value. if (actualCalcMode != CALC_MODE_DISCRETE) { refTimes = new float[keyTimes.length - 1]; System.arraycopy(keyTimes, 0, refTimes, 0, refTimes.length); } else { refTimes = keyTimes; } } /** * Validates the path and mpath values sources. * * @throws DOMException if there is a validation error, for example if the * to value is incompatible with the target trait or if the target * trait is not animatable. */ final void validateValuesExtra() throws DOMException { // Validate the path attribute value pathRefValues = null; if (path != null) { pathRefValues = traitAnim.toRefValues( this, new String[] {path}, null, SVGConstants.SVG_PATH_ATTRIBUTE); } // Now, validate the mpath child, if any. mpathRefValues = null; SVGElement c = (SVGElement) getFirstElementChild(); SVGElement mpath = null; while (c != null) { if (SVGConstants.SVG_MPATH_TAG.equals(c.getLocalName()) && SVGConstants.SVG_NAMESPACE_URI.equals(c.getNamespaceURI())) { mpath = c; break; } c = (SVGElement) c.getNextElementSibling(); } if (mpath != null) { String pathHref = ((ElementNode) mpath).getTraitNSImpl( SVGConstants.XLINK_NAMESPACE_URI, SVGConstants.SVG_HREF_ATTRIBUTE); if (pathHref != null) { boolean pathHrefError = false; if (pathHref.startsWith("#")) { String pathId = pathHref.substring(1); ElementNode path = (ElementNode) ownerDocument.getElementById(pathId); if (path != null) { mpathRefValues = traitAnim.toRefValues( this, new String[] { path.getTraitImpl( SVGConstants.SVG_D_ATTRIBUTE) }, null, SVGConstants.SVG_D_ATTRIBUTE); } else { pathHrefError = true; } } else { pathHrefError = true; } if (pathHrefError) { throw animationError(idRef, traitNamespace, traitName, targetElement.getNamespaceURI(), targetElement.getLocalName(), getId(), getNamespaceURI(), getLocalName(), Messages.formatMessage (Messages.ERROR_INVALID_MPATH_HREF, new Object[] {pathHref})); } } else { throw animationError(idRef, traitNamespace, traitName, targetElement.getNamespaceURI(), targetElement.getLocalName(), getId(), getNamespaceURI(), getLocalName(), Messages.formatMessage (Messages.ERROR_MISSING_MPATH_HREF, null)); } } } /** * Computes the 'right' source for reference values, depending on the * precedence rules for the different values sources. * * @throws DOMException if there is no way to compute a set of reference * values, for example if none of the values sources is specified. */ final void selectRefValuesExtra() throws DOMException { if (mpathRefValues != null) { refValues = mpathRefValues; } else if (pathRefValues != null) { refValues = pathRefValues; } } }