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    }