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