001/* 002 * SVG Salamander 003 * Copyright (c) 2004, Mark McKay 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or 007 * without modification, are permitted provided that the following 008 * conditions are met: 009 * 010 * - Redistributions of source code must retain the above 011 * copyright notice, this list of conditions and the following 012 * disclaimer. 013 * - Redistributions in binary form must reproduce the above 014 * copyright notice, this list of conditions and the following 015 * disclaimer in the documentation and/or other materials 016 * provided with the distribution. 017 * 018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 019 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 020 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 021 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 022 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 025 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 026 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 027 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 029 * OF THE POSSIBILITY OF SUCH DAMAGE. 030 * 031 * Mark McKay can be contacted at mark@kitfox.com. Salamander and other 032 * projects can be found at http://www.kitfox.com 033 * 034 * Created on August 15, 2004, 2:52 AM 035 */ 036 037package com.kitfox.svg.animation; 038 039import com.kitfox.svg.SVGConst; 040import com.kitfox.svg.SVGElement; 041import com.kitfox.svg.SVGException; 042import com.kitfox.svg.SVGLoaderHelper; 043import com.kitfox.svg.animation.parser.AnimTimeParser; 044import com.kitfox.svg.animation.parser.ParseException; 045import com.kitfox.svg.xml.StyleAttribute; 046import java.io.StringReader; 047import java.util.logging.Level; 048import java.util.logging.Logger; 049import org.xml.sax.Attributes; 050import org.xml.sax.SAXException; 051 052 053/** 054 * @author Mark McKay 055 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> 056 */ 057public abstract class AnimationElement extends SVGElement 058{ 059 protected String attribName; 060// protected String attribType; 061 protected int attribType = AT_AUTO; 062 063 public static final int AT_CSS = 0; 064 public static final int AT_XML = 1; 065 public static final int AT_AUTO = 2; //Check CSS first, then XML 066 067 protected TimeBase beginTime; 068 protected TimeBase durTime; 069 protected TimeBase endTime; 070 protected int fillType = FT_AUTO; 071 072 /** <a href="http://www.w3.org/TR/smil20/smil-timing.html#adef-fill">More about the <b>fill</b> attribute</a> */ 073 public static final int FT_REMOVE = 0; 074 public static final int FT_FREEZE = 1; 075 public static final int FT_HOLD = 2; 076 public static final int FT_TRANSITION = 3; 077 public static final int FT_AUTO = 4; 078 public static final int FT_DEFAULT = 5; 079 080 /** Additive state of track */ 081 public static final int AD_REPLACE = 0; 082 public static final int AD_SUM = 1; 083 084 int additiveType = AD_REPLACE; 085 086 /** Accumlative state */ 087 public static final int AC_REPLACE = 0; 088 public static final int AC_SUM = 1; 089 090 int accumulateType = AC_REPLACE; 091 092 /** Creates a new instance of AnimateEle */ 093 public AnimationElement() 094 { 095 } 096 097 public static String animationElementToString(int attrValue) 098 { 099 switch (attrValue) 100 { 101 case AT_CSS: 102 return "CSS"; 103 case AT_XML: 104 return "XML"; 105 case AT_AUTO: 106 return "AUTO"; 107 default: 108 throw new RuntimeException("Unknown element type"); 109 } 110 } 111 112 public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException 113 { 114 //Load style string 115 super.loaderStartElement(helper, attrs, parent); 116 117 attribName = attrs.getValue("attributeName"); 118 String attribType = attrs.getValue("attributeType"); 119 if (attribType != null) 120 { 121 attribType = attribType.toLowerCase(); 122 if (attribType.equals("css")) this.attribType = AT_CSS; 123 else if (attribType.equals("xml")) this.attribType = AT_XML; 124 } 125 126 String beginTime = attrs.getValue("begin"); 127 String durTime = attrs.getValue("dur"); 128 String endTime = attrs.getValue("end"); 129 130 try 131 { 132 if (beginTime != null) 133 { 134 helper.animTimeParser.ReInit(new StringReader(beginTime)); 135 this.beginTime = helper.animTimeParser.Expr(); 136 this.beginTime.setParentElement(this); 137 } 138 139 if (durTime != null) 140 { 141 helper.animTimeParser.ReInit(new StringReader(durTime)); 142 this.durTime = helper.animTimeParser.Expr(); 143 this.durTime.setParentElement(this); 144 } 145 146 if (endTime != null) 147 { 148 helper.animTimeParser.ReInit(new StringReader(endTime)); 149 this.endTime = helper.animTimeParser.Expr(); 150 this.endTime.setParentElement(this); 151 } 152 } 153 catch (Exception e) 154 { 155 throw new SAXException(e); 156 } 157 158// this.beginTime = TimeBase.parseTime(beginTime); 159// this.durTime = TimeBase.parseTime(durTime); 160// this.endTime = TimeBase.parseTime(endTime); 161 162 String fill = attrs.getValue("fill"); 163 164 if (fill != null) 165 { 166 if (fill.equals("remove")) this.fillType = FT_REMOVE; 167 if (fill.equals("freeze")) this.fillType = FT_FREEZE; 168 if (fill.equals("hold")) this.fillType = FT_HOLD; 169 if (fill.equals("transiton")) this.fillType = FT_TRANSITION; 170 if (fill.equals("auto")) this.fillType = FT_AUTO; 171 if (fill.equals("default")) this.fillType = FT_DEFAULT; 172 } 173 174 String additiveStrn = attrs.getValue("additive"); 175 176 if (additiveStrn != null) 177 { 178 if (additiveStrn.equals("replace")) this.additiveType = AD_REPLACE; 179 if (additiveStrn.equals("sum")) this.additiveType = AD_SUM; 180 } 181 182 String accumulateStrn = attrs.getValue("accumulate"); 183 184 if (accumulateStrn != null) 185 { 186 if (accumulateStrn.equals("replace")) this.accumulateType = AC_REPLACE; 187 if (accumulateStrn.equals("sum")) this.accumulateType = AC_SUM; 188 } 189 } 190 191 public String getAttribName() { return attribName; } 192 public int getAttribType() { return attribType; } 193 public int getAdditiveType() { return additiveType; } 194 public int getAccumulateType() { return accumulateType; } 195 196 public void evalParametric(AnimationTimeEval state, double curTime) 197 { 198 evalParametric(state, curTime, Double.NaN, Double.NaN); 199 } 200 201 /** 202 * Compares current time to start and end times and determines what degree 203 * of time interpolation this track currently represents. Returns 204 * Float.NaN if this track cannot be evaluated at the passed time (ie, 205 * it is before or past the end of the track, or it depends upon 206 * an unknown event) 207 * @param state - A structure that will be filled with information 208 * regarding the applicability of this animatoin element at the passed 209 * time. 210 * @param curTime - Current time in seconds 211 * @param repeatCount - Optional number of repetitions of length 'dur' to 212 * do. Set to Double.NaN to not consider this in the calculation. 213 * @param repeatDur - Optional amoun tof time to repeat the animaiton. 214 * Set to Double.NaN to not consider this in the calculation. 215 */ 216 protected void evalParametric(AnimationTimeEval state, double curTime, double repeatCount, double repeatDur) 217 { 218 double begin = (beginTime == null) ? 0 : beginTime.evalTime(); 219 if (Double.isNaN(begin) || begin > curTime) 220 { 221 state.set(Double.NaN, 0); 222 return; 223 } 224 225 double dur = (durTime == null) ? Double.NaN : durTime.evalTime(); 226 if (Double.isNaN(dur)) 227 { 228 state.set(Double.NaN, 0); 229 return; 230 } 231 232 //Determine end point of this animation 233 double end = (endTime == null) ? Double.NaN : endTime.evalTime(); 234 double repeat; 235// if (Double.isNaN(repeatDur)) 236// { 237// repeatDur = dur; 238// } 239 if (Double.isNaN(repeatCount) && Double.isNaN(repeatDur)) 240 { 241 repeat = Double.NaN; 242 } 243 else 244 { 245 repeat = Math.min( 246 Double.isNaN(repeatCount) ? Double.POSITIVE_INFINITY : dur * repeatCount, 247 Double.isNaN(repeatDur) ? Double.POSITIVE_INFINITY : repeatDur); 248 } 249 if (Double.isNaN(repeat) && Double.isNaN(end)) 250 { 251 //If neither and end point nor a repeat is specified, end point is 252 // implied by duration. 253 end = begin + dur; 254 } 255 256 double finishTime; 257 if (Double.isNaN(end)) 258 { 259 finishTime = begin + repeat; 260 } 261 else if (Double.isNaN(repeat)) 262 { 263 finishTime = end; 264 } 265 else 266 { 267 finishTime = Math.min(end, repeat); 268 } 269 270 double evalTime = Math.min(curTime, finishTime); 271// if (curTime > finishTime) evalTime = finishTime; 272 273 274// double evalTime = curTime; 275 276// boolean pastEnd = curTime > evalTime; 277 278// if (!Double.isNaN(end) && curTime > end) { pastEnd = true; evalTime = Math.min(evalTime, end); } 279// if (!Double.isNaN(repeat) && curTime > repeat) { pastEnd = true; evalTime = Math.min(evalTime, repeat); } 280 281 double ratio = (evalTime - begin) / dur; 282 int rep = (int)ratio; 283 double interp = ratio - rep; 284 285 //Adjust for roundoff 286 if (interp < 0.00001) interp = 0; 287 288// state.set(interp, rep); 289// if (!pastEnd) 290// { 291// state.set(interp, rep, false); 292// return; 293// } 294 295 //If we are still within the clip, return value 296 if (curTime == evalTime) 297 { 298 state.set(interp, rep); 299 return; 300 } 301 302 //We are past end of clip. Determine to clamp or ignore. 303 switch (fillType) 304 { 305 default: 306 case FT_REMOVE: 307 case FT_AUTO: 308 case FT_DEFAULT: 309 state.set(Double.NaN, rep); 310 return; 311 case FT_FREEZE: 312 case FT_HOLD: 313 case FT_TRANSITION: 314 state.set(interp == 0 ? 1 : interp, rep); 315 return; 316 } 317 318 } 319 320 double evalStartTime() 321 { 322 return beginTime == null ? Double.NaN : beginTime.evalTime(); 323 } 324 325 double evalDurTime() 326 { 327 return durTime == null ? Double.NaN : durTime.evalTime(); 328 } 329 330 /** 331 * Evaluates the ending time of this element. Returns 0 if not specified. 332 * 333 * @see hasEndTime 334 */ 335 double evalEndTime() 336 { 337 return endTime == null ? Double.NaN : endTime.evalTime(); 338 } 339 340 /** 341 * Checks to see if an end time has been specified for this element. 342 */ 343 boolean hasEndTime() { return endTime != null; } 344 345 /** 346 * Updates all attributes in this diagram associated with a time event. 347 * Ie, all attributes with track information. 348 * @return - true if this node has changed state as a result of the time 349 * update 350 */ 351 public boolean updateTime(double curTime) 352 { 353 //Animation elements to not change with time 354 return false; 355 } 356 357 public void rebuild() throws SVGException 358 { 359 AnimTimeParser animTimeParser = new AnimTimeParser(new StringReader("")); 360 361 rebuild(animTimeParser); 362 } 363 364 protected void rebuild(AnimTimeParser animTimeParser) throws SVGException 365 { 366 StyleAttribute sty = new StyleAttribute(); 367 368 if (getPres(sty.setName("begin"))) 369 { 370 String newVal = sty.getStringValue(); 371 animTimeParser.ReInit(new StringReader(newVal)); 372 try { 373 this.beginTime = animTimeParser.Expr(); 374 } catch (ParseException ex) { 375 Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, 376 "Could not parse '" + newVal + "'", ex); 377 } 378 } 379 380 if (getPres(sty.setName("dur"))) 381 { 382 String newVal = sty.getStringValue(); 383 animTimeParser.ReInit(new StringReader(newVal)); 384 try { 385 this.durTime = animTimeParser.Expr(); 386 } catch (ParseException ex) { 387 Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, 388 "Could not parse '" + newVal + "'", ex); 389 } 390 } 391 392 if (getPres(sty.setName("end"))) 393 { 394 String newVal = sty.getStringValue(); 395 animTimeParser.ReInit(new StringReader(newVal)); 396 try { 397 this.endTime = animTimeParser.Expr(); 398 } catch (ParseException ex) { 399 Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, 400 "Could not parse '" + newVal + "'", ex); 401 } 402 } 403 404 if (getPres(sty.setName("fill"))) 405 { 406 String newVal = sty.getStringValue(); 407 if (newVal.equals("remove")) this.fillType = FT_REMOVE; 408 if (newVal.equals("freeze")) this.fillType = FT_FREEZE; 409 if (newVal.equals("hold")) this.fillType = FT_HOLD; 410 if (newVal.equals("transiton")) this.fillType = FT_TRANSITION; 411 if (newVal.equals("auto")) this.fillType = FT_AUTO; 412 if (newVal.equals("default")) this.fillType = FT_DEFAULT; 413 } 414 415 if (getPres(sty.setName("additive"))) 416 { 417 String newVal = sty.getStringValue(); 418 if (newVal.equals("replace")) this.additiveType = AD_REPLACE; 419 if (newVal.equals("sum")) this.additiveType = AD_SUM; 420 } 421 422 if (getPres(sty.setName("accumulate"))) 423 { 424 String newVal = sty.getStringValue(); 425 if (newVal.equals("replace")) this.accumulateType = AC_REPLACE; 426 if (newVal.equals("sum")) this.accumulateType = AC_SUM; 427 } 428 429 } 430}