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}