0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026:
0027: package com.sun.perseus.model;
0028:
0029: import com.sun.perseus.util.SimpleTokenizer;
0030: import com.sun.perseus.util.SVGConstants;
0031:
0032: import org.w3c.dom.DOMException;
0033:
0034: import java.util.Vector;
0035:
0036: /**
0037: * <code>AbstractAnimate</code> is used as a base class for various
0038: * animation classes (see <code>Animate</code> and <code>AnimateMotion</code>).
0039: *
0040: * @version $Id: AbstractAnimate.java,v 1.5 2006/06/29 10:47:28 ln156897 Exp $
0041: */
0042: public abstract class AbstractAnimate extends Animation {
0043: /**
0044: * calcMode value for linear interpolation.
0045: */
0046: public static final int CALC_MODE_LINEAR = 1;
0047:
0048: /**
0049: * calcMode value for discrete interpolation
0050: */
0051: public static final int CALC_MODE_DISCRETE = 2;
0052:
0053: /**
0054: * calcMode value for paced interpolation.
0055: */
0056: public static final int CALC_MODE_PACED = 3;
0057:
0058: /**
0059: * calcMode for spline interpolation.
0060: */
0061: public static final int CALC_MODE_SPLINE = 4;
0062:
0063: /**
0064: * Minimum required flatness for keySpline approximations by
0065: * polylines.
0066: */
0067: public static final float MIN_FLATNESS_SQUARE = 0.01f * 0.01f;
0068:
0069: /**
0070: * End value specification
0071: */
0072: String to;
0073:
0074: /**
0075: * Starting value specification.
0076: */
0077: String from;
0078:
0079: /**
0080: * Intermediate value specification.
0081: */
0082: String by;
0083:
0084: /**
0085: * Complete values specification list.
0086: */
0087: String values;
0088:
0089: /**
0090: * The interpolation mode, one of the CALC_MODE_XYZ values.
0091: */
0092: int calcMode = CALC_MODE_LINEAR;
0093:
0094: /**
0095: * The actual calcMode. For types which do not support interpolation,
0096: * the calcMode is forced to be discrete.
0097: */
0098: int actualCalcMode = CALC_MODE_DISCRETE;
0099:
0100: /**
0101: * Key times, to control the animation pace. May be null or a list of
0102: * values between 0 and 1.
0103: * @see <a
0104: * href="http://www.w3.org/TR/smil20/smil-timing.html#animation-adef-keyTimes>SMIL
0105: * 2.0 Specification</a>
0106: */
0107: float[] keyTimes;
0108:
0109: /**
0110: * Key splines, used to control the speed of animation on a particular
0111: * time interval (interval pacing).
0112: *
0113: * May be null or an array of arrays of 4 floating point values.
0114: * @see <a
0115: * href="http://www.w3.org/TR/smil20/smil-timing.html#animation-animationNS-InterpolationKeysplines"/>SMIL
0116: * 2.0 Specification</a>
0117: */
0118: float[][] keySplines;
0119:
0120: /**
0121: * refSplines is an array of points which define a linear approximation of
0122: * the keySplines. refSplines is computed in the validate() method and
0123: * used in the curve() method. There is one float array per keySpline.
0124: */
0125: float[][][] refSplines;
0126:
0127: /**
0128: * Controls whether the animation is additive or not. True maps to the
0129: * SVG 'sum' value and false maps to the SVG 'replace' value.
0130: */
0131: boolean additive;
0132:
0133: /**
0134: * Controls whether the animation has a cumulative behavior or not. This
0135: * covers the behavior over multiple iterations of the simple duration.
0136: */
0137: boolean accumulate;
0138:
0139: /**
0140: * The RefValues corresponding to this <set> element. A <set>
0141: * element has a single segment with the same begin and end value.
0142: */
0143: RefValues refValues = null;
0144:
0145: /**
0146: * Used, temporarily, to hold the refValues for the to attribute.
0147: */
0148: RefValues toRefValues;
0149:
0150: /**
0151: * Used, temporarily, to hold the refValues for the from attribute.
0152: */
0153: RefValues fromRefValues;
0154:
0155: /**
0156: * Used, temporarily, to hold the refValues for the by attribute.
0157: */
0158: RefValues byRefValues;
0159:
0160: /**
0161: * Used, temporarily, to hold the refValues for the values attribute.
0162: */
0163: RefValues valuesRefValues;
0164:
0165: /**
0166: * refTimes holds key times for the animation.
0167: *
0168: * refTimes is computed in the validate method.
0169: *
0170: * refTimes holds the key times for the <em>begining</em> of each segment.
0171: * Therefore, there is as many refTimes as there are segments, in all cases.
0172: *
0173: * @see #validate
0174: */
0175: float[] refTimes;
0176:
0177: /**
0178: * A working buffer for mapping segment index and progress
0179: */
0180: float[] sisp = { 0, 0 };
0181:
0182: /**
0183: * Simple flag to check if we are dealing with a to-animation or
0184: * not. This flag is needed to control the addition and cumulative
0185: * behavior on to-animations, which is different than that on other
0186: * animations (e.g., a values or a from-to animation).
0187: */
0188: boolean isToAnimation;
0189:
0190: /**
0191: * Builds a new Animate element that belongs to the given
0192: * document. This <code>Animate</code> will belong
0193: * to the <code>DocumentNode</code>'s time container.
0194: *
0195: * @param ownerDocument the document this node belongs to.
0196: * @param localName the animation element's local name.
0197: * @throws IllegalArgumentException if the input ownerDocument is null
0198: */
0199: public AbstractAnimate(final DocumentNode ownerDocument,
0200: final String localName) {
0201: super (ownerDocument, localName);
0202: }
0203:
0204: /**
0205: * This is the Animate element's animation function.
0206: *
0207: * f(t) {
0208: * a. Compute the 'simple duration penetration' p
0209: * p = t / dur
0210: * where dur is the simple duration.
0211: *
0212: * b. Compute the 'current time segment' i
0213: * refTimes[i] <= p < refTimes[i+1]
0214: *
0215: * c. Compute the 'segment penetration' sp
0216: * sp = (p - refTimes[i])
0217: * /
0218: * (refTimes[i+1] - refTimes[i])
0219: * Note: 0 <= sp <= 1
0220: *
0221: * d. Compute the 'interpolated interval
0222: * penetration' isp
0223: *
0224: * isp = calcMode(sp)
0225: * Note: 0 <= isp <= 1
0226: *
0227: * e. Compute the animated value:
0228: * v = refValues.compute(isp)
0229: * v has the same number of components as refValues.
0230: * }
0231: *
0232: * @param t the animation's simple time.
0233: */
0234: Object[] f(final long t) {
0235: // a. Compute the simple duration penetration p
0236:
0237: // See: http://www.w3.org/TR/smil20/smil-timing.html#animation-animationNS-InterpolationAndIndefSimpleDur
0238: float p = 0;
0239: if (timedElementSupport.simpleDur != null
0240: && timedElementSupport.simpleDur.isResolved()) {
0241: p = t / (float) timedElementSupport.simpleDur.value;
0242: }
0243:
0244: // Iterate for each component.
0245: int nc = refValues.getComponents();
0246: float sp = 0;
0247: int si = 0;
0248: float endTime = 1;
0249: float beginTime = 0;
0250: int i = 0;
0251:
0252: // b. Compute the 'current time segment' index si[ci]
0253:
0254: // We iterate from 1 because the first value in refTimes
0255: // is always 0.
0256: for (i = 1; i < refTimes.length; i++) {
0257: if (p < refTimes[i]) {
0258: endTime = refTimes[i];
0259: break;
0260: }
0261: beginTime = refTimes[i];
0262: }
0263:
0264: si = i - 1;
0265:
0266: // c. Compute the segment penetration
0267: if (endTime == beginTime) {
0268: sp = 1;
0269: } else {
0270: sp = (p - beginTime) / (endTime - beginTime);
0271: }
0272:
0273: // d. Compute the 'interpolated segment penetration'
0274: sp = calcMode(sp, si);
0275:
0276: // At this point, we have computed:
0277: // a. the array of time segment indices corresponding to 't'
0278: // b. the array of penetration into the time segments.
0279: //
0280: // The following call lets the animate implementation map
0281: // the time segment indices and the time segment penetration
0282: // into refValues indices and penetration, in case these are
0283: // different. Typically, these are the same, but they may be
0284: // different, for example in the case of animateMotion with
0285: // keyPoints.
0286: sisp[0] = si;
0287: sisp[1] = sp;
0288: mapToSegmentProgress(si, sp, sisp);
0289: si = (int) sisp[0];
0290: sp = sisp[1];
0291:
0292: // If we are dealing with a to-animation, we need to get the
0293: // value for the single segment's start value.
0294: if (isToAnimation) {
0295: Object[] baseValue = baseVal.getBaseValue();
0296: refValues.getSegment(0).setStart(baseValue);
0297: return refValues.compute(si, sp);
0298: }
0299:
0300: // The reminder of this method is for non-additive animations
0301:
0302: // Compute the simple function value
0303: Object[] v = refValues.compute(si, sp);
0304:
0305: // Account for cumulative behavior.
0306: if (!isToAnimation && accumulate
0307: && timedElementSupport.curIter > 0) {
0308: // Last simple time value, lv
0309: Object[] lv = refValues.getSegment(
0310: refValues.getSegments() - 1).getEnd();
0311: Object[] r = traitAnim.multiply(lv,
0312: timedElementSupport.curIter);
0313: v = traitAnim.sum(r, v);
0314: }
0315:
0316: // Now, account for additive behavior.
0317: if (!additive) {
0318: return v;
0319: }
0320:
0321: return traitAnim.sum(baseVal.getBaseValue(), v);
0322: }
0323:
0324: /**
0325: * The following call lets the animate implementation map
0326: * the time segment indices and the time segment penetration
0327: * into refValues indices and penetration, in case these are
0328: * different. Typically, these are the same, but they may be
0329: * different, for example in the case of animateMotion with
0330: * keyPoints.
0331: */
0332: protected void mapToSegmentProgress(final int si, final float sp,
0333: final float[] sisp) {
0334: }
0335:
0336: /**
0337: * Intepolates the input value depending on the calcMode attribute
0338: * and, in the case of spline interpolation, depending on the
0339: * keySplines value.
0340: *
0341: * @param p the value to interpolate. Should be in the [0, 1] range.
0342: * @param si the time segment from which the computation is done. This
0343: * is needed to identify the correct keySplines to use in
0344: * case of spline animations.
0345: */
0346: float calcMode(final float p, final int si) {
0347: switch (actualCalcMode) {
0348: case CALC_MODE_DISCRETE:
0349: return 0;
0350: case CALC_MODE_LINEAR:
0351: // Linear does not modify the current penetration
0352: // because we are on an x = y line.
0353: case CALC_MODE_PACED:
0354: // Paced does not modify the current penetration
0355: // because we have computed the time intervals
0356: // so as to have constant velocity. There is no
0357: // further interpolation in this method.
0358: return p;
0359: case CALC_MODE_SPLINE:
0360: default:
0361: return curve(p, refSplines[si]);
0362: }
0363: }
0364:
0365: /**
0366: * Computes an array of points which do a linear approximation of the
0367: * input splines.
0368: *
0369: * @param splines the array of splines to approximate with a polyline.
0370: * The splines array should be an made of four element floats.
0371: * if any of the elements is null, a NullPointerException will
0372: * be thrown. If any of the element has a length less than four,
0373: * an ArrayIndexOutOfBoundsException will be thrown.
0374: * @return an array of point arrays. Each point array is a polyline
0375: * approximation of the input spline.
0376: */
0377: static float[][][] toRefSplines(final float[][] splines) {
0378: float[][][] rSplines = new float[splines.length][][];
0379:
0380: float[][] splineDef = new float[4][2];
0381: for (int i = 0; i < splines.length; i++) {
0382: Vector v = new Vector();
0383: v.addElement(new float[] { 0, 0 });
0384: splineDef[0][0] = 0;
0385: splineDef[0][1] = 0;
0386: splineDef[1][0] = splines[i][0];
0387: splineDef[1][1] = splines[i][1];
0388: splineDef[2][0] = splines[i][2];
0389: splineDef[2][1] = splines[i][3];
0390: splineDef[3][0] = 1;
0391: splineDef[3][1] = 1;
0392: toRefSpline(splineDef, v);
0393: float[][] polyline = new float[v.size()][];
0394: v.copyInto(polyline);
0395: rSplines[i] = polyline;
0396: }
0397:
0398: return rSplines;
0399: }
0400:
0401: /**
0402: * Converts the input spline curve (defined by its four control points)
0403: * into a polyline approximation (i.e., an array of points). If the curve
0404: * is flat enough (see the <code>isFlat</code> method), then the curve is
0405: * approximated to a line between its two end control points and the
0406: * last control point is added to the input <code>segment</code> vector.
0407: * If the curve is not flat enough, then it is sub-divided and the
0408: * subdivided curves are, recursively, tested for flatness.
0409: *
0410: * The flatness used for flatness test is controlled by the
0411: * <code>MIN_FLATNESS_SQUARE</code> constant.
0412: *
0413: * @param curve the spline curve to approximate. Should not be null. Should
0414: * be an array of four arrays of two elements. If there are less than
0415: * four arrays or if there are less than two elements in these arrays
0416: * then an ArrayIndexOutOfBoundsException is thrown. If any element
0417: * in the curve array is null, a NullPointerException is thrown.
0418: * @param segments the vector to which polyline points should be added.
0419: * Should not be null.
0420: */
0421: static void toRefSpline(final float[][] curve, final Vector segments) {
0422: if (isFlat(curve, MIN_FLATNESS_SQUARE)) {
0423: segments
0424: .addElement(new float[] { curve[3][0], curve[3][1] });
0425: return;
0426: }
0427:
0428: float[][] lcurve = new float[4][2];
0429: float[][] rcurve = new float[4][2];
0430:
0431: // L1 = P1
0432: lcurve[0] = curve[0];
0433:
0434: // L2 = (P1 + P2) / 2
0435: lcurve[1][0] = (curve[0][0] + curve[1][0]) / 2;
0436: lcurve[1][1] = (curve[0][1] + curve[1][1]) / 2;
0437:
0438: // H = (P2 + P3) / 2
0439: float[] h = new float[2];
0440: h[0] = (curve[1][0] + curve[2][0]) / 2;
0441: h[1] = (curve[1][1] + curve[2][1]) / 2;
0442:
0443: // L3 = (L2 + H) / 2
0444: lcurve[2][0] = (lcurve[1][0] + h[0]) / 2;
0445: lcurve[2][1] = (lcurve[1][1] + h[1]) / 2;
0446:
0447: // R3 = (P3 + P4) / 2
0448: rcurve[2][0] = (curve[2][0] + curve[3][0]) / 2;
0449: rcurve[2][1] = (curve[2][1] + curve[3][1]) / 2;
0450:
0451: // R2 = (H + R3) / 2
0452: rcurve[1][0] = (h[0] + rcurve[2][0]) / 2;
0453: rcurve[1][1] = (h[1] + rcurve[2][1]) / 2;
0454:
0455: // L4 = R1 = (L3 + R2) / 2
0456: lcurve[3][0] = (lcurve[2][0] + rcurve[1][0]) / 2;
0457: lcurve[3][1] = (lcurve[2][1] + rcurve[1][1]) / 2;
0458: rcurve[0] = lcurve[3];
0459:
0460: // R4 = P4
0461: rcurve[3] = curve[3];
0462:
0463: toRefSpline(lcurve, segments);
0464: toRefSpline(rcurve, segments);
0465: }
0466:
0467: /**
0468: * @param curve the spline curve to test for flatness. The curve is
0469: * considered flat if the distance from the two intermediate control
0470: * points from the line between the first and the last control points
0471: * is less than the desired flatness maximum. The input array must
0472: * have at least four elements and each one must be at least
0473: * 2 elements long. If not, an ArrayOutOfBoundsException is thrown.
0474: * The curve array should not be null.
0475: * @param flatness. The maximum distance allowed for the intermediate curve
0476: * control points.
0477: */
0478: static boolean isFlat(float[][] curve, float flatness) {
0479: // Compute the square distance of P2 and P3 to the (P1, P4) line.
0480: float dx = curve[3][0] - curve[0][0];
0481: float dy = curve[3][1] - curve[0][1];
0482: float div = dx * dx + dy * dy;
0483:
0484: if (div == 0) {
0485: return true;
0486: }
0487:
0488: float den = (dx * (curve[1][1] - curve[0][1]) - dy
0489: * (curve[1][0] - curve[0][0]));
0490:
0491: float dP2Sq = (den * den) / div;
0492:
0493: den = (dx * (curve[2][1] - curve[0][1]) - dy
0494: * (curve[2][0] - curve[0][0]));
0495:
0496: float dP3Sq = (den * den) / div;
0497:
0498: return (dP2Sq <= flatness && dP3Sq <= flatness);
0499:
0500: }
0501:
0502: /**
0503: * Computes the curve value for the requested value on the specified
0504: * curves, defined by a polyline approximation.
0505: *
0506: * The method assumes that the input polyline points are between 0 and 1 and
0507: * in increasing order along the x axis. The method considers the p value to
0508: * be on the polyline's x-axis and finds the two points between which it
0509: * lies and returns a linear approximation for that segment.
0510: *
0511: * For degenerate cases (e.g., if the polyline x-axis values are not in the
0512: * [0, 1] interval or if p is not in the [0, 1] interval either), the method
0513: * returns 0 if the input penetration p is less than the first polyline
0514: * point. The method returns 1 if the input penetration is more than the
0515: * last polyline x-axis coordinate.
0516: *
0517: * @param p the value for which the curve polynomial should be computed.
0518: * @param polyline the polyline curve approximation. Should not be null.
0519: * each element in the polyline array should not be null. Each
0520: * element in the array should be at least of length 2.
0521: */
0522: static float curve(final float p, final float[][] polyline) {
0523: int i = 0;
0524: for (; i < polyline.length; i++) {
0525: if (p < polyline[i][0]) {
0526: break;
0527: }
0528: }
0529:
0530: if (i == polyline.length || i == 0) {
0531: // Degenerate cases.
0532: //
0533: // This should never happen:
0534: //
0535: // Should not be polyline.length because the last entry in polyline
0536: // should be '1' and the maximum input value for p should be '1'.
0537: //
0538: // Should not get 0 because the first entry should be zero and the
0539: // input parameter should be greater or equal to zero.
0540: //
0541: return i / (float) polyline.length; // returns 0 for 0 and 1 for 1
0542: }
0543:
0544: float[] from = polyline[i - 1];
0545: float[] to = polyline[i];
0546:
0547: // Compute the progress between from and to:
0548: //
0549: // p = (1 - t) * from[0] + t.to[0]
0550: // p = t * (t[0] - from[0]) + from[0]
0551: // t = (p - from[0]) / (to[0] - from[0])
0552: //
0553: // So, the interpolated progress is:
0554: //
0555: // ip = (1 - t) * from[1] + t.to[1]
0556: // ip = (1 - (p - from[0]) / (to[0] - from[0])) * from[1]
0557: // + ((p - from[0]) / (to[0] - from[0])) * to[1]
0558: // ip = ((to[0] - from[0] - p + from[0]) / (to[0] - from[0])) * from[1]
0559: // + (to[1] * p - to[1] * from[0]) / (to[0] - from[0])
0560: // ip = ((to[0] - p) / (to[0] - from[0]) * from[1]
0561: // + (to[1] * p - to[1] * from[0]) / (to[0] - from[0])
0562: // ip = (to[0] * from[1] - p * from[1]) / (to[0] - from[0])
0563: // + (to[1] * p - to[1] * from[0]) / (to[0] - from[0])
0564: // ip = (to[0] * from[1] - p * from[1] + to[1] * p - to[1] * from[0])
0565: // / (to[0] - from[0])
0566: // ip = (to[0] * from[1] - to[1] * from[0] + p * (to[1] - from[1]))
0567: // / (to[0] - from[0])
0568: return (to[0] * from[1] - to[1] * from[0] + p
0569: * (to[1] - from[1]))
0570: / (to[0] - from[0]);
0571: }
0572:
0573: /**
0574: * Supported traits: to, attributeName
0575: *
0576: * @param traitName the name of the trait which the element may support.
0577: * @return true if this element supports the given trait in one of the
0578: * trait accessor methods.
0579: */
0580: boolean supportsTrait(final String traitName) {
0581: if (SVGConstants.SVG_TO_ATTRIBUTE == traitName
0582: || SVGConstants.SVG_FROM_ATTRIBUTE == traitName
0583: || SVGConstants.SVG_BY_ATTRIBUTE == traitName
0584: || SVGConstants.SVG_VALUES_ATTRIBUTE == traitName
0585: || SVGConstants.SVG_CALC_MODE_ATTRIBUTE == traitName
0586: || SVGConstants.SVG_KEY_TIMES_ATTRIBUTE == traitName
0587: || SVGConstants.SVG_KEY_SPLINES_ATTRIBUTE == traitName
0588: || SVGConstants.SVG_ADDITIVE_ATTRIBUTE == traitName
0589: || SVGConstants.SVG_ACCUMULATE_ATTRIBUTE == traitName) {
0590: return true;
0591: } else {
0592: return super .supportsTrait(traitName);
0593: }
0594: }
0595:
0596: // JAVADOC COMMENT ELIDED
0597: public String getTraitImpl(final String name) throws DOMException {
0598: if (SVGConstants.SVG_TO_ATTRIBUTE == name) {
0599: return to;
0600: } else if (SVGConstants.SVG_FROM_ATTRIBUTE == name) {
0601: return from;
0602: } else if (SVGConstants.SVG_BY_ATTRIBUTE == name) {
0603: return by;
0604: } else if (SVGConstants.SVG_VALUES_ATTRIBUTE == name) {
0605: return values;
0606: } else if (SVGConstants.SVG_CALC_MODE_ATTRIBUTE == name) {
0607: switch (calcMode) {
0608: case CALC_MODE_DISCRETE:
0609: return SVGConstants.SVG_DISCRETE_VALUE;
0610: case CALC_MODE_LINEAR:
0611: return SVGConstants.SVG_LINEAR_VALUE;
0612: case CALC_MODE_PACED:
0613: return SVGConstants.SVG_PACED_VALUE;
0614: case CALC_MODE_SPLINE:
0615: default:
0616: return SVGConstants.SVG_SPLINE_VALUE;
0617: }
0618: } else if (SVGConstants.SVG_KEY_TIMES_ATTRIBUTE == name) {
0619: return toStringTrait(keyTimes);
0620: } else if (SVGConstants.SVG_KEY_SPLINES_ATTRIBUTE == name) {
0621: return toStringTrait(keySplines);
0622: } else if (SVGConstants.SVG_ADDITIVE_ATTRIBUTE == name) {
0623: if (additive) {
0624: return SVGConstants.SVG_SUM_VALUE;
0625: }
0626: return SVGConstants.SVG_REPLACE_VALUE;
0627: } else if (SVGConstants.SVG_ACCUMULATE_ATTRIBUTE == name) {
0628: if (accumulate) {
0629: return SVGConstants.SVG_SUM_VALUE;
0630: }
0631: return SVGConstants.SVG_NONE_VALUE;
0632: } else {
0633: return super .getTraitImpl(name);
0634: }
0635: }
0636:
0637: // JAVADOC COMMENT ELIDED
0638: public void setTraitImpl(final String name, final String value)
0639: throws DOMException {
0640: if (SVGConstants.SVG_TO_ATTRIBUTE == name) {
0641: checkWriteLoading(name);
0642: to = value;
0643: } else if (SVGConstants.SVG_FROM_ATTRIBUTE == name) {
0644: checkWriteLoading(name);
0645: from = value;
0646: } else if (SVGConstants.SVG_BY_ATTRIBUTE == name) {
0647: checkWriteLoading(name);
0648: by = value;
0649: } else if (SVGConstants.SVG_VALUES_ATTRIBUTE == name) {
0650: checkWriteLoading(name);
0651: values = value;
0652: } else if (SVGConstants.SVG_KEY_TIMES_ATTRIBUTE == name) {
0653: checkWriteLoading(name);
0654:
0655: // Generic float array parsing
0656: keyTimes = parseFloatArrayTrait(name, value.replace(';',
0657: ','));
0658:
0659: // Now, check that the keyTimes values are from 0 to 1 and
0660: // in increasing order. Note that this only performs the
0661: // validations which can be made prior to a call to validate.
0662: if (keyTimes[0] < 0 || keyTimes[0] > 1) {
0663: throw illegalTraitValue(name, value);
0664: }
0665:
0666: for (int i = 1; i < keyTimes.length; i++) {
0667: if (keyTimes[i] < 0 || keyTimes[i] > 1) {
0668: throw illegalTraitValue(name, value);
0669: }
0670:
0671: if (keyTimes[i] < keyTimes[i - 1]) {
0672: throw illegalTraitValue(name, value);
0673: }
0674: }
0675: } else if (SVGConstants.SVG_KEY_SPLINES_ATTRIBUTE == name) {
0676: checkWriteLoading(name);
0677:
0678: SimpleTokenizer st = new SimpleTokenizer(value, ";");
0679: int n = st.countTokens();
0680: keySplines = new float[n][];
0681:
0682: for (int i = 0; i < n; i++) {
0683: String splineDef = st.nextToken();
0684: keySplines[i] = parseFloatArrayTrait(name, splineDef);
0685: if (keySplines[i].length != 4 || keySplines[i][0] < 0
0686: || keySplines[i][0] > 1 || keySplines[i][1] < 0
0687: || keySplines[i][1] > 1 || keySplines[i][2] < 0
0688: || keySplines[i][2] > 1 || keySplines[i][3] < 0
0689: || keySplines[i][3] > 1) {
0690: throw illegalTraitValue(name, value);
0691: }
0692: }
0693: } else if (SVGConstants.SVG_ADDITIVE_ATTRIBUTE == name) {
0694: checkWriteLoading(name);
0695:
0696: if (SVGConstants.SVG_REPLACE_VALUE.equals(value)) {
0697: additive = false;
0698: } else if (SVGConstants.SVG_SUM_VALUE.equals(value)) {
0699: additive = true;
0700: } else {
0701: throw illegalTraitValue(name, value);
0702: }
0703: } else if (SVGConstants.SVG_ACCUMULATE_ATTRIBUTE == name) {
0704: checkWriteLoading(name);
0705:
0706: if (SVGConstants.SVG_NONE_VALUE.equals(value)) {
0707: accumulate = false;
0708: } else if (SVGConstants.SVG_SUM_VALUE.equals(value)) {
0709: accumulate = true;
0710: } else {
0711: throw illegalTraitValue(name, value);
0712: }
0713: } else if (SVGConstants.SVG_CALC_MODE_ATTRIBUTE == name) {
0714: if (SVGConstants.SVG_DISCRETE_VALUE.equals(value)) {
0715: calcMode = CALC_MODE_DISCRETE;
0716: } else if (SVGConstants.SVG_LINEAR_VALUE.equals(value)) {
0717: calcMode = CALC_MODE_LINEAR;
0718: } else if (SVGConstants.SVG_PACED_VALUE.equals(value)) {
0719: calcMode = CALC_MODE_PACED;
0720: } else if (SVGConstants.SVG_SPLINE_VALUE.equals(value)) {
0721: calcMode = CALC_MODE_SPLINE;
0722: } else {
0723: throw illegalTraitValue(name, value);
0724: }
0725: } else {
0726: super .setTraitImpl(name, value);
0727: }
0728: }
0729:
0730: /**
0731: * Computes refTimes so that each animation segment lasts the same length
0732: * of time.
0733: */
0734: float[] getDefaultTiming(RefValues refValues) {
0735: int ns = refValues.getSegments();
0736: float[] refTimes = new float[ns];
0737: float startTime = 0;
0738: float segLength = 1 / (float) ns;
0739: for (int i = 0; i < ns; i++) {
0740: refTimes[i] = startTime;
0741: startTime += segLength;
0742: }
0743:
0744: return refTimes;
0745: }
0746:
0747: /**
0748: * Computes refTimes so that there is a paced speed on each values segment.
0749: */
0750: float[] getPacedTiming(RefValues refValues) {
0751: // a) Compute the refValues length, D
0752: // D = sum(from 0 to n; seg(i).length());
0753: //
0754: // b) Compute the overall paced velocity, V
0755: // V = D / dur;
0756: //
0757: // c) For each segment i, compute its refTime:
0758: // refTime[j][i] = refTime[j][i-1] + (seg(i).length(j) / V(j)) / dur;
0759:
0760: float D = refValues.getLength();
0761:
0762: int ns = refValues.getSegments();
0763: refTimes = new float[ns];
0764: float prevRefTime = 0;
0765:
0766: if (D > 0) {
0767: // For each segment index si
0768: for (int si = 1; si < ns; si++) {
0769: refTimes[si] = prevRefTime
0770: + refValues.getLength(si - 1) / D;
0771: prevRefTime = refTimes[si];
0772: }
0773: } else {
0774: // Segments are all of zero length, they should be evenly spaced
0775: // in time.
0776: float sl = 1f / ns;
0777: for (int si = 1; si < ns; si++) {
0778: refTimes[si] = si * sl;
0779: }
0780: }
0781:
0782: return refTimes;
0783: }
0784:
0785: /**
0786: * Validating an Animate consists in:
0787: * a) Setting its target element. If there was no idRef, then targetElement
0788: * is still null and will be positioned to the parent node.
0789: *
0790: * b) Validating the from, to, by and values traits with the targetElement,
0791: * using the target trait name, namespace and value.
0792: *
0793: * c) Validating the keyTimes and the keySplines trait values to check they
0794: * are compatible with the values specification.
0795: *
0796: * @throws DOMException if there is a validation error, for example if the
0797: * to value is incompatible with the target trait or if the target
0798: * trait is not animatable.
0799: */
0800: void validate() throws DOMException {
0801: // =====================================================================
0802: // a) Set the target element.
0803: if (targetElement == null) {
0804: targetElement = (ElementNode) parent;
0805: }
0806:
0807: // =====================================================================
0808: // Check that the traitName attribute was specified.
0809: if (traitName == null) {
0810: throw illegalTraitValue(
0811: SVGConstants.SVG_ATTRIBUTE_NAME_ATTRIBUTE,
0812: traitName);
0813: }
0814:
0815: // =====================================================================
0816: // b) Validate the to/from/by/values traits with the target element.
0817:
0818: // Note that traitName should _never_ be null when validate() is
0819: // invoked. It is either required (e.g., for Animate) or fixed (e.g.,
0820: // for AnimateMotion. If, for example in some unit test configutations,
0821: // this method is called without a specified traitName, the method
0822: // generates a NullPointerException.
0823: traitAnim = targetElement.getSafeTraitAnimNS(traitNamespace,
0824: traitName);
0825:
0826: // If the traitAnim type does not support interpolation, force the
0827: // calcMode to be discrete.
0828: if (traitAnim.supportsInterpolation()) {
0829: actualCalcMode = calcMode;
0830: } else {
0831: actualCalcMode = CALC_MODE_DISCRETE;
0832: }
0833:
0834: validateValues();
0835:
0836: // Now, apply precedence rules to compute the actual RefValues.
0837: selectRefValues();
0838:
0839: // If the animation has no effect, stop here.
0840: if (hasNoEffect) {
0841: return;
0842: }
0843:
0844: // =====================================================================
0845: // If we are dealing with discrete timing, we need to add a last time
0846: // segment so that we implement the desired behavior for discrete
0847: // timing. With a single interval with start/end values, only the start
0848: // value will be shown. If we add a new interval with the end value,
0849: // then the end value will be shown for the last time interval.
0850: if (actualCalcMode == CALC_MODE_DISCRETE) {
0851: refValues.makeDiscrete();
0852: }
0853:
0854: // Initialize the refValues
0855: refValues.initialize();
0856:
0857: // =====================================================================
0858: // c. Validate that keyTimes and keySplines trait values are compatible
0859: // with the values specification.
0860: computeRefTimes();
0861:
0862: // c.1 Validate that keySplines is compatible with the animation set-up
0863: //
0864: // See:
0865: // http://www.w3.org/TR/2001/REC-smil20-20010807/smil20.html#animation-animationNS-InterpolationKeysplines
0866: //
0867: // The attribute is ignored unless the CALC_MODE is spline. For spline
0868: // interpolation, there must be one fewer sets of control points in the
0869: // keySplines attribute than there are keyTimes.
0870: //
0871: if (actualCalcMode == CALC_MODE_SPLINE) {
0872: if (keySplines == null
0873: || refTimes.length != keySplines.length) {
0874: throw animationError(
0875: idRef,
0876: traitNamespace,
0877: traitName,
0878: targetElement.getNamespaceURI(),
0879: targetElement.getLocalName(),
0880: getId(),
0881: getNamespaceURI(),
0882: getLocalName(),
0883: Messages
0884: .formatMessage(
0885: Messages.ERROR_INVALID_ANIMATION_KEY_SPLINES,
0886: new Object[] {
0887: getTrait(SVGConstants.SVG_KEY_SPLINES_ATTRIBUTE),
0888: getTrait(SVGConstants.SVG_KEY_TIMES_ATTRIBUTE) }));
0889: }
0890:
0891: // Turn the keySplines attribute into a set of (x, y) points
0892: // which can be interpolated between.
0893: refSplines = toRefSplines(keySplines);
0894: }
0895: }
0896:
0897: /**
0898: * Computes refTimes from the calcMode and keyTimes attributes. Validates
0899: * that the keyTimes attribute is compatible with the animate set up. This
0900: * may be overridden by subclasses (e.g., animateMotion), when there are
0901: * special rules for checking keyTimes compatiblity.
0902: */
0903: protected void computeRefTimes() throws DOMException {
0904: // c.1 Validate that keyTimes is compatible with the animation set-up
0905: //
0906: // if (calcMode == paced)
0907: // refTimes such that change velocity is constant
0908: // refTimes = getPacedTiming(refValues) // see follow on slides
0909: //
0910: // else if (keyTimes defined)
0911: // refTimes = keyTimes
0912: // if (calcMode == discreet)
0913: // refTimes[n] = 1 // Accounts for the added nth value
0914: // // in refValues
0915: //
0916: // else refTimes = f(refValues)
0917: // the simple duration is divided into n-1 intervals,
0918: // where n = refValues.length
0919: // refTimes = defaultTiming(refValues)
0920: if (actualCalcMode == CALC_MODE_PACED) {
0921: // keyTimes are ignored
0922: // See: http://www.w3.org/TR/smil20/smil-timing.html#animation-adef-keyTimes
0923: refTimes = getPacedTiming(refValues);
0924: } else if (keyTimes != null) {
0925: // Check keyTimes is compatible with the animation specification.
0926: //
0927: // a) In all cases, the first keyTime must be zero.
0928: //
0929: // b) For non-discrete animations,
0930: // b.1) the last keyTime must be one.
0931: // b.2) there should be one more keyTimes than there are
0932: // segments.
0933: //
0934: // c) For discrete animations,
0935: // c.1) there should be as many keyTimes as there are segments.
0936: //
0937: if (/* a) */keyTimes.length < 1
0938: || keyTimes[0] != 0
0939: ||
0940: /* b) */(actualCalcMode != CALC_MODE_DISCRETE && (/* b.1) */keyTimes[keyTimes.length - 1] != 1 ||
0941: /* b.2) */keyTimes.length != refValues
0942: .getSegments() + 1))
0943: ||
0944: /* c) */(actualCalcMode == CALC_MODE_DISCRETE &&
0945: /* c.1) */keyTimes.length != refValues
0946: .getSegments())) {
0947: throw animationError(
0948: idRef,
0949: traitNamespace,
0950: traitName,
0951: targetElement.getNamespaceURI(),
0952: targetElement.getLocalName(),
0953: getId(),
0954: getNamespaceURI(),
0955: getLocalName(),
0956: Messages
0957: .formatMessage(
0958: Messages.ERROR_INVALID_ANIMATION_KEY_TIMES,
0959: new Object[] { getTrait(SVGConstants.SVG_KEY_TIMES_ATTRIBUTE) }));
0960: }
0961:
0962: // If the calcMode is _not_ discrete, we trim the last '1'
0963: // value.
0964: if (actualCalcMode != CALC_MODE_DISCRETE) {
0965: refTimes = new float[keyTimes.length - 1];
0966: System.arraycopy(keyTimes, 0, refTimes, 0,
0967: refTimes.length);
0968: } else {
0969: refTimes = keyTimes;
0970: }
0971:
0972: // Validate that there are as many refTimes as there are refValues.
0973: // We do the check using the first component, as we know there is at
0974: // least one component no matter what value type we are dealing
0975: // with.
0976: if (refTimes.length != refValues.getSegments()) {
0977: throw animationError(
0978: idRef,
0979: traitNamespace,
0980: traitName,
0981: targetElement.getNamespaceURI(),
0982: targetElement.getLocalName(),
0983: getId(),
0984: getNamespaceURI(),
0985: getLocalName(),
0986: Messages
0987: .formatMessage(
0988: Messages.ERROR_INVALID_ANIMATION_KEY_TIMES,
0989: new Object[] { getTrait(SVGConstants.SVG_KEY_TIMES_ATTRIBUTE) }));
0990: }
0991: } else {
0992: // Simple case, give the same time length to each segment.
0993: refTimes = getDefaultTiming(refValues);
0994: }
0995:
0996: }
0997:
0998: /**
0999: * Validates the different so-called values attributes, such as the
1000: * to/from/by and values attributes. Derived classes may have more 'values'
1001: * attributes, and may override this method to validate these additional
1002: * attributes.
1003: *
1004: * @throws DOMException if there is a validation error, for example if the
1005: * to value is incompatible with the target trait or if the target
1006: * trait is not animatable.
1007: */
1008: final void validateValues() throws DOMException {
1009: // b.1) Validate the to traits value
1010: toRefValues = null;
1011: if (to != null) {
1012: toRefValues = traitAnim.toRefValues(this ,
1013: new String[] { to }, null,
1014: SVGConstants.SVG_TO_ATTRIBUTE);
1015: }
1016:
1017: // b.2) Validate the by traits value
1018: byRefValues = null;
1019: if (by != null) {
1020: byRefValues = traitAnim.toRefValues(this ,
1021: new String[] { by }, null,
1022: SVGConstants.SVG_BY_ATTRIBUTE);
1023: }
1024:
1025: // b.3) Validate the from traits value
1026: fromRefValues = null;
1027: if (from != null) {
1028: fromRefValues = traitAnim.toRefValues(this ,
1029: new String[] { from }, null,
1030: SVGConstants.SVG_FROM_ATTRIBUTE);
1031: }
1032:
1033: // b.4) Validate the values trait value
1034: valuesRefValues = null;
1035: if (values != null) {
1036: String[] v = parseStringArrayTrait(
1037: SVGConstants.SVG_VALUES_ATTRIBUTE, values, ";");
1038: valuesRefValues = traitAnim.toRefValues(this , v, null,
1039: SVGConstants.SVG_VALUES_ATTRIBUTE);
1040: }
1041:
1042: validateValuesExtra();
1043: }
1044:
1045: /**
1046: * Allows extension classes to validate addition values sources.
1047: *
1048: * @throws DOMException if there is a validation error, for example if the
1049: * to value is incompatible with the target trait or if the target
1050: * trait is not animatable.
1051: */
1052: void validateValuesExtra() throws DOMException {
1053: }
1054:
1055: /**
1056: * Allows extensions to select a different source for refValues, in case
1057: * the extension has addition values sources that have higher precedence
1058: * than the default. For example, animateMotion has the path attribute and
1059: * the mpath children which have higher precedence.
1060: *
1061: * @throws DOMException if there is no way to compute a set of reference
1062: * values, for example if none of the values sources is specified.
1063: */
1064: void selectRefValuesExtra() throws DOMException {
1065: }
1066:
1067: /**
1068: * Computes the 'right' source for reference values, depending on the
1069: * precedence rules for the different values sources.
1070: *
1071: * @throws DOMException if there is no way to compute a set of reference
1072: * values, for example if none of the values sources is specified.
1073: */
1074: final void selectRefValues() throws DOMException {
1075: refValues = null;
1076: selectRefValuesExtra();
1077: if (refValues != null) {
1078: return;
1079: }
1080:
1081: // Pseudo-code for accounting for precedence rules.
1082: //
1083: // If (values defined)
1084: // // values anim
1085: // refValues = values;
1086: // else if (from defined)
1087: // if (to defined)
1088: // // from-to anim
1089: // refValues = (from; to)
1090: // else if (by defined)
1091: // // from-by anim
1092: // refValues = (from; from+by)
1093: // else
1094: // // no effect, there is nothing like a from anim
1095: // else if (by defined)
1096: // // by anim
1097: // refValues = (0; by) and force additive anim
1098: // else if (to defined)
1099: // // to anim
1100: // refValues = (baseVal; to)
1101: // else
1102: // // no effect
1103: //
1104: isToAnimation = false;
1105: if (values != null) {
1106: refValues = valuesRefValues;
1107: } else if (from != null) {
1108: refValues = fromRefValues;
1109:
1110: if (to != null) {
1111: // from-to animatin
1112: refValues.getSegment(0).collapse(
1113: toRefValues.getSegment(0), this );
1114: } else if (by != null) {
1115: // from-by animation
1116: if (refValues.getSegment(0).isAdditive()) {
1117: try {
1118: refValues.getSegment(0).addToEnd(
1119: byRefValues.getSegment(0).getStart());
1120: } catch (IllegalArgumentException iae) {
1121: // Incompatible by value
1122: throw new DOMException(
1123: DOMException.NOT_SUPPORTED_ERR,
1124: Messages
1125: .formatMessage(
1126: Messages.ERROR_INCOMPATIBLE_FROM_BY,
1127: new Object[] {
1128: traitName,
1129: traitNamespace,
1130: getLocalName(),
1131: getNamespaceURI(),
1132: from, to }));
1133: }
1134: } else {
1135: throw new DOMException(
1136: DOMException.NOT_SUPPORTED_ERR,
1137: Messages
1138: .formatMessage(
1139: Messages.ERROR_ATTRIBUTE_NOT_ADDITIVE_FROM_BY,
1140: new Object[] { traitName,
1141: traitNamespace,
1142: getLocalName(),
1143: getNamespaceURI() }));
1144: }
1145: } else {
1146: throw animationError(
1147: idRef,
1148: traitNamespace,
1149: traitName,
1150: targetElement.getNamespaceURI(),
1151: targetElement.getLocalName(),
1152: getId(),
1153: getNamespaceURI(),
1154: getLocalName(),
1155: Messages
1156: .formatMessage(
1157: Messages.ERROR_INVALID_ANIMATION_FROM_ANIM,
1158: null));
1159: }
1160: } else if (by != null) {
1161: // by animation
1162: if (!byRefValues.getSegment(0).isAdditive()) {
1163: throw new DOMException(
1164: DOMException.NOT_SUPPORTED_ERR,
1165: Messages
1166: .formatMessage(
1167: Messages.ERROR_ATTRIBUTE_NOT_ADDITIVE_BY,
1168: new Object[] { traitName,
1169: traitNamespace,
1170: getLocalName(),
1171: getNamespaceURI() }));
1172: }
1173: refValues = byRefValues;
1174: refValues.getSegment(0).setZeroStart();
1175: additive = true;
1176: } else if (to != null) {
1177: // to animation
1178: // We cannot compute the base value yet so we can only set the
1179: // refValues to the toRefValues and delay computing the segment's
1180: // begin until we actually have a baseValue. This will have to be
1181: // updated on each computing cycle because the base value may change
1182: // over time.
1183: isToAnimation = true;
1184: refValues = toRefValues;
1185: } else {
1186: // SMIL Animation
1187: // specification:
1188: // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues
1189: // "similarly, if none of the from, to, by or values attributes are
1190: // specified, the animation will have no effect."
1191: hasNoEffect = true;
1192: }
1193: }
1194:
1195: }
|