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 private TimeBase beginTime; 068 private TimeBase durTime; 069 private TimeBase endTime; 070 private 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 private 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 private 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 @Override 113 public void loaderStartElement(SVGLoaderHelper helper, Attributes attrs, SVGElement parent) throws SAXException 114 { 115 //Load style string 116 super.loaderStartElement(helper, attrs, parent); 117 118 attribName = attrs.getValue("attributeName"); 119 String attribType = attrs.getValue("attributeType"); 120 if (attribType != null) 121 { 122 attribType = attribType.toLowerCase(); 123 if (attribType.equals("css")) this.attribType = AT_CSS; 124 else if (attribType.equals("xml")) this.attribType = AT_XML; 125 } 126 127 String beginTime = attrs.getValue("begin"); 128 String durTime = attrs.getValue("dur"); 129 String endTime = attrs.getValue("end"); 130 131 try 132 { 133 if (beginTime != null) 134 { 135 helper.animTimeParser.ReInit(new StringReader(beginTime)); 136 this.beginTime = helper.animTimeParser.Expr(); 137 this.beginTime.setParentElement(this); 138 } 139 140 if (durTime != null) 141 { 142 helper.animTimeParser.ReInit(new StringReader(durTime)); 143 this.durTime = helper.animTimeParser.Expr(); 144 this.durTime.setParentElement(this); 145 } 146 147 if (endTime != null) 148 { 149 helper.animTimeParser.ReInit(new StringReader(endTime)); 150 this.endTime = helper.animTimeParser.Expr(); 151 this.endTime.setParentElement(this); 152 } 153 } 154 catch (Exception e) 155 { 156 throw new SAXException(e); 157 } 158 159// this.beginTime = TimeBase.parseTime(beginTime); 160// this.durTime = TimeBase.parseTime(durTime); 161// this.endTime = TimeBase.parseTime(endTime); 162 163 String fill = attrs.getValue("fill"); 164 165 if (fill != null) 166 { 167 if (fill.equals("remove")) this.fillType = FT_REMOVE; 168 if (fill.equals("freeze")) this.fillType = FT_FREEZE; 169 if (fill.equals("hold")) this.fillType = FT_HOLD; 170 if (fill.equals("transiton")) this.fillType = FT_TRANSITION; 171 if (fill.equals("auto")) this.fillType = FT_AUTO; 172 if (fill.equals("default")) this.fillType = FT_DEFAULT; 173 } 174 175 String additiveStrn = attrs.getValue("additive"); 176 177 if (additiveStrn != null) 178 { 179 if (additiveStrn.equals("replace")) this.additiveType = AD_REPLACE; 180 if (additiveStrn.equals("sum")) this.additiveType = AD_SUM; 181 } 182 183 String accumulateStrn = attrs.getValue("accumulate"); 184 185 if (accumulateStrn != null) 186 { 187 if (accumulateStrn.equals("replace")) this.accumulateType = AC_REPLACE; 188 if (accumulateStrn.equals("sum")) this.accumulateType = AC_SUM; 189 } 190 } 191 192 public String getAttribName() { return attribName; } 193 public int getAttribType() { return attribType; } 194 public int getAdditiveType() { return additiveType; } 195 public int getAccumulateType() { return accumulateType; } 196 197 public void evalParametric(AnimationTimeEval state, double curTime) 198 { 199 evalParametric(state, curTime, Double.NaN, Double.NaN); 200 } 201 202 /** 203 * Compares current time to start and end times and determines what degree 204 * of time interpolation this track currently represents. Returns 205 * Float.NaN if this track cannot be evaluated at the passed time (ie, 206 * it is before or past the end of the track, or it depends upon 207 * an unknown event) 208 * @param state - A structure that will be filled with information 209 * regarding the applicability of this animatoin element at the passed 210 * time. 211 * @param curTime - Current time in seconds 212 * @param repeatCount - Optional number of repetitions of length 'dur' to 213 * do. Set to Double.NaN to not consider this in the calculation. 214 * @param repeatDur - Optional amoun tof time to repeat the animaiton. 215 * Set to Double.NaN to not consider this in the calculation. 216 */ 217 protected void evalParametric(AnimationTimeEval state, double curTime, double repeatCount, double repeatDur) 218 { 219 double begin = (beginTime == null) ? 0 : beginTime.evalTime(); 220 if (Double.isNaN(begin) || begin > curTime) 221 { 222 state.set(Double.NaN, 0); 223 return; 224 } 225 226 double dur = (durTime == null) ? Double.NaN : durTime.evalTime(); 227 if (Double.isNaN(dur)) 228 { 229 state.set(Double.NaN, 0); 230 return; 231 } 232 233 //Determine end point of this animation 234 double end = (endTime == null) ? Double.NaN : endTime.evalTime(); 235 double repeat; 236// if (Double.isNaN(repeatDur)) 237// { 238// repeatDur = dur; 239// } 240 if (Double.isNaN(repeatCount) && Double.isNaN(repeatDur)) 241 { 242 repeat = Double.NaN; 243 } 244 else 245 { 246 repeat = Math.min( 247 Double.isNaN(repeatCount) ? Double.POSITIVE_INFINITY : dur * repeatCount, 248 Double.isNaN(repeatDur) ? Double.POSITIVE_INFINITY : repeatDur); 249 } 250 if (Double.isNaN(repeat) && Double.isNaN(end)) 251 { 252 //If neither and end point nor a repeat is specified, end point is 253 // implied by duration. 254 end = begin + dur; 255 } 256 257 double finishTime; 258 if (Double.isNaN(end)) 259 { 260 finishTime = begin + repeat; 261 } 262 else if (Double.isNaN(repeat)) 263 { 264 finishTime = end; 265 } 266 else 267 { 268 finishTime = Math.min(end, repeat); 269 } 270 271 double evalTime = Math.min(curTime, finishTime); 272// if (curTime > finishTime) evalTime = finishTime; 273 274 275// double evalTime = curTime; 276 277// boolean pastEnd = curTime > evalTime; 278 279// if (!Double.isNaN(end) && curTime > end) { pastEnd = true; evalTime = Math.min(evalTime, end); } 280// if (!Double.isNaN(repeat) && curTime > repeat) { pastEnd = true; evalTime = Math.min(evalTime, repeat); } 281 282 double ratio = (evalTime - begin) / dur; 283 int rep = (int)ratio; 284 double interp = ratio - rep; 285 286 //Adjust for roundoff 287 if (interp < 0.00001) interp = 0; 288 289// state.set(interp, rep); 290// if (!pastEnd) 291// { 292// state.set(interp, rep, false); 293// return; 294// } 295 296 //If we are still within the clip, return value 297 if (curTime == evalTime) 298 { 299 state.set(interp, rep); 300 return; 301 } 302 303 //We are past end of clip. Determine to clamp or ignore. 304 switch (fillType) 305 { 306 default: 307 case FT_REMOVE: 308 case FT_AUTO: 309 case FT_DEFAULT: 310 state.set(Double.NaN, rep); 311 return; 312 case FT_FREEZE: 313 case FT_HOLD: 314 case FT_TRANSITION: 315 state.set(interp == 0 ? 1 : interp, rep); 316 return; 317 } 318 319 } 320 321 double evalStartTime() 322 { 323 return beginTime == null ? Double.NaN : beginTime.evalTime(); 324 } 325 326 double evalDurTime() 327 { 328 return durTime == null ? Double.NaN : durTime.evalTime(); 329 } 330 331 /** 332 * Evaluates the ending time of this element. Returns 0 if not specified. 333 * 334 * @see hasEndTime 335 */ 336 double evalEndTime() 337 { 338 return endTime == null ? Double.NaN : endTime.evalTime(); 339 } 340 341 /** 342 * Checks to see if an end time has been specified for this element. 343 */ 344 boolean hasEndTime() { return endTime != null; } 345 346 /** 347 * Updates all attributes in this diagram associated with a time event. 348 * Ie, all attributes with track information. 349 * @return - true if this node has changed state as a result of the time 350 * update 351 */ 352 @Override 353 public boolean updateTime(double curTime) 354 { 355 //Animation elements to not change with time 356 return false; 357 } 358 359 public void rebuild() throws SVGException 360 { 361 AnimTimeParser animTimeParser = new AnimTimeParser(new StringReader("")); 362 363 rebuild(animTimeParser); 364 } 365 366 protected void rebuild(AnimTimeParser animTimeParser) throws SVGException 367 { 368 StyleAttribute sty = new StyleAttribute(); 369 370 if (getPres(sty.setName("begin"))) 371 { 372 String newVal = sty.getStringValue(); 373 animTimeParser.ReInit(new StringReader(newVal)); 374 try { 375 this.beginTime = animTimeParser.Expr(); 376 } catch (ParseException ex) { 377 Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, 378 "Could not parse '" + newVal + "'", ex); 379 } 380 } 381 382 if (getPres(sty.setName("dur"))) 383 { 384 String newVal = sty.getStringValue(); 385 animTimeParser.ReInit(new StringReader(newVal)); 386 try { 387 this.durTime = animTimeParser.Expr(); 388 } catch (ParseException ex) { 389 Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, 390 "Could not parse '" + newVal + "'", ex); 391 } 392 } 393 394 if (getPres(sty.setName("end"))) 395 { 396 String newVal = sty.getStringValue(); 397 animTimeParser.ReInit(new StringReader(newVal)); 398 try { 399 this.endTime = animTimeParser.Expr(); 400 } catch (ParseException ex) { 401 Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, 402 "Could not parse '" + newVal + "'", ex); 403 } 404 } 405 406 if (getPres(sty.setName("fill"))) 407 { 408 String newVal = sty.getStringValue(); 409 if (newVal.equals("remove")) this.fillType = FT_REMOVE; 410 if (newVal.equals("freeze")) this.fillType = FT_FREEZE; 411 if (newVal.equals("hold")) this.fillType = FT_HOLD; 412 if (newVal.equals("transiton")) this.fillType = FT_TRANSITION; 413 if (newVal.equals("auto")) this.fillType = FT_AUTO; 414 if (newVal.equals("default")) this.fillType = FT_DEFAULT; 415 } 416 417 if (getPres(sty.setName("additive"))) 418 { 419 String newVal = sty.getStringValue(); 420 if (newVal.equals("replace")) this.additiveType = AD_REPLACE; 421 if (newVal.equals("sum")) this.additiveType = AD_SUM; 422 } 423 424 if (getPres(sty.setName("accumulate"))) 425 { 426 String newVal = sty.getStringValue(); 427 if (newVal.equals("replace")) this.accumulateType = AC_REPLACE; 428 if (newVal.equals("sum")) this.accumulateType = AC_SUM; 429 } 430 431 } 432 433 /** 434 * @return the beginTime 435 */ 436 public TimeBase getBeginTime() 437 { 438 return beginTime; 439 } 440 441 /** 442 * @param beginTime the beginTime to set 443 */ 444 public void setBeginTime(TimeBase beginTime) 445 { 446 this.beginTime = beginTime; 447 } 448 449 /** 450 * @return the durTime 451 */ 452 public TimeBase getDurTime() 453 { 454 return durTime; 455 } 456 457 /** 458 * @param durTime the durTime to set 459 */ 460 public void setDurTime(TimeBase durTime) 461 { 462 this.durTime = durTime; 463 } 464 465 /** 466 * @return the endTime 467 */ 468 public TimeBase getEndTime() 469 { 470 return endTime; 471 } 472 473 /** 474 * @param endTime the endTime to set 475 */ 476 public void setEndTime(TimeBase endTime) 477 { 478 this.endTime = endTime; 479 } 480 481 /** 482 * @return the fillType 483 */ 484 public int getFillType() 485 { 486 return fillType; 487 } 488 489 /** 490 * @param fillType the fillType to set 491 */ 492 public void setFillType(int fillType) 493 { 494 this.fillType = fillType; 495 } 496 497 /** 498 * @param additiveType the additiveType to set 499 */ 500 public void setAdditiveType(int additiveType) 501 { 502 this.additiveType = additiveType; 503 } 504 505 /** 506 * @param accumulateType the accumulateType to set 507 */ 508 public void setAccumulateType(int accumulateType) 509 { 510 this.accumulateType = accumulateType; 511 } 512}