OSDN Git Service

Edit the comment
[sie/sie.git] / org / w3c / dom / smil.js
1 /*SIE under the MIT Lisence\r
2  */\r
3 /* Copyright 2016 dhrname and other contributors\r
4  * http://sie.osdn.jp/\r
5  *\r
6  * Permission is hereby granted, free of charge, to any person obtaining\r
7  * a copy of this software and associated documentation files (the\r
8  * "Software"), to deal in the Software without restriction, including\r
9  * without limitation the rights to use, copy, modify, merge, publish,\r
10  * distribute, sublicense, and/or sell copies of the Software, and to\r
11  * permit persons to whom the Software is furnished to do so, subject to\r
12  * the following conditions:\r
13  * \r
14  * The above copyright notice and this permission notice shall be\r
15  * included in all copies or substantial portions of the Software.\r
16  * \r
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
24  */\r
25 \r
26 /*$frame オブジェクト\r
27  * 全体のフレームの管理を行う\r
28  */\r
29 base("$frame").mix ( {\r
30   /*フレームレート。1ミリ秒何フレームか。計算を省略するためミリ秒使用*/\r
31   fpms: 0.024,\r
32 \r
33   /*タイムラインのリスト (時間区間の設定ができる)*/\r
34   timelines: [],\r
35 \r
36   /*開始フレーム数の候補。アニメーションの開始条件となる\r
37    * 単位はフレーム数であって、秒数ではない\r
38    * なお、初期値については、開始フレームが負の値を考慮しているため*/\r
39   begin: -Number.MAX_VALUE,\r
40 \r
41   /*活動継続時間 (Active Duration)のフレーム数。アニメーションの継続条件となる\r
42    * 単位はフレーム数であって、秒数ではない*/\r
43   activeTime: Number.MAX_VALUE,\r
44   \r
45   /*現在のフレーム数*/\r
46   currentFrame: 0,\r
47   \r
48   /*アニメーションを開始させるメソッド*/\r
49   startAnimation: function() {\r
50     /*__step関数は最後に書く*/\r
51     __step();\r
52   },\r
53   \r
54   /*アニメーションが停止した状態かどうか、停止しているならばtrue*/\r
55   isPaused: false,\r
56 \r
57   /*アニメーションを停止させるメソッド*/\r
58   pauseAnimation: function() {\r
59     this.isPaused = true;\r
60   },\r
61 \r
62   /*setFrame メソッド\r
63    * フレーム数を数値num まで進めるか、戻す*/\r
64   setFrame: function( /*number*/ num) {\r
65     this.currentFrame = num;\r
66     var timelines = this.timelines;\r
67     for (var i=0;i<timelines.length;++i) {\r
68       if (timelines[i] === this) {\r
69         /*自分自身が含まれていると、永久に再帰を繰り返して、「スタック領域が不足」してしまう*/\r
70         continue;\r
71       }\r
72       timelines[i].setFrame(num);\r
73     }\r
74   },\r
75   \r
76   /*addLineメソッド\r
77    * タイムラインを追加したあと、trueを返す\r
78    * ただし、引数objのobj.beginとobj.activeTimeが定まっていない場合はfalseを返す*/\r
79   addLine: function( /*$frame*/ obj ) {\r
80     if(!obj || (!obj.begin && (obj.begin !== 0))\r
81     || (!obj.activeTime && (obj.activeTime !== 0)) ) {\r
82       /*どちらのプロパティも未確認の場合、タイムラインは追加されない*/\r
83       return false;\r
84     }\r
85     var timelines = this.timelines;\r
86     if ( timelines.indexOf(obj) >= 0 ) {\r
87       this.removeLine(obj);\r
88     }\r
89     timelines.push(obj);\r
90     timelines = void 0;
91     return true;\r
92   },\r
93   \r
94   /*removeLine メソッド\r
95    * 指定されたタイムラインのオブジェクトを、リストから削除する*/\r
96   removeLine: function( /*$frame*/ timeline ) {\r
97     var list = this.timelines,\r
98         j = list.indexOf(timeline);\r
99     if (j > -1) {\r
100       list.splice(j, 1);      //Arrayのspliceを利用して、リストからtimelineを排除\r
101     }\r
102     list = j = void 0;\r
103   }\r
104 } ).mix( function($frame) {\r
105   $frame.up("$list").mix( {\r
106     /*終了時刻(単位フレーム数)のキャッシュとして使う*/\r
107     end: 0,\r
108     \r
109     /*開始時刻から終了時刻までのフレーム数\r
110      * これがactiveTimeより短ければ、活動継続時間とみなす*/\r
111     beginEnd: Number.MAX_VALUE,\r
112     \r
113     /*開始時刻(単位フレーム数)リスト (後述のupdateStateメソッドで使う)*/\r
114     beginList: { \r
115       next: null,\r
116       value: Number.MAX_VALUE\r
117     },\r
118     \r
119     /*終了時刻(単位フレーム数)リスト (後述のupdateStateメソッドで使う)*/\r
120     endList: {\r
121        next: null,\r
122        value: Number.MAX_VALUE\r
123     },\r
124     \r
125     addBeginList: function (num) {\r
126       return ( this.beginList = {\r
127                   value: num,\r
128                   next: this.beginList\r
129                 } );\r
130     },\r
131     \r
132     addEndList: function (num) {\r
133       return ( this.endList = {\r
134                   value: num,\r
135                   next: this.endList\r
136                 } );\r
137     },\r
138     \r
139     /*引数に渡された時刻リストの中から、現在フレームf以下の、最大値を求めるメソッド\r
140      * Number.MIN_VALUEの値を返したときはリストの中にf以下の値がないことを示す*/\r
141     getMaxList: function (f, list) {\r
142       var maxTime = -Number.MAX_VALUE; /*時刻の最大値*/\r
143       while( list ) {\r
144         var v = list.value;\r
145         /*f以下の開始リスト値のうち、最大値をmaxTimeに代入*/\r
146         if ( (v <= f)\r
147              && (maxTime <= v) ) {\r
148           maxTime = v;\r
149         }\r
150         list = list.next;\r
151       }\r
152       return maxTime;\r
153     },\r
154       \r
155     /*現在の要素の状態を数値で示す(マジックナンバーは後述の大文字プロパティを使う)*/\r
156     state: 0,\r
157     \r
158     /*アニメーション待機状態を示す定数*/\r
159     WAITING: 0,\r
160     \r
161     /*アニメーション開始状態を示す定数*/\r
162     BEGINNING: 1,\r
163     \r
164     /*アニメーション再生中の状態を示す定数*/\r
165     PLAYING: 2,\r
166     \r
167     /*アニメーション終了状態を示す定数*/\r
168     ENDING: 3,\r
169     \r
170     /*終了処理をした後の待機状態を示す定数*/\r
171     POSTWAITING: 4,\r
172     \r
173     /*初期化用メソッド*/\r
174     init: function() {\r
175       this.state = this.WAITING;\r
176       this.begin = 0;\r
177       return this;\r
178     },\r
179     \r
180     /*引数で指定されたフレーム数に応じて、stateプロパティを更新するメソッド*/\r
181     updateState: function( /*number*/ f) {\r
182       if (f === void 0) {\r
183         /*引数fが指定されないときには状態を更新しない*/\r
184         return this;\r
185       }\r
186       var state = this.state,\r
187           wait = /*this.WAITING*/ 0,\r
188           begin = /*this.BEGINNING*/ 1,\r
189           play = /*this.PLAYING*/ 2,\r
190           end = /*this.ENDING*/ 3,\r
191           post = /*this.POSTWAITING*/ 4;\r
192       /*beginListプロパティと、endListプロパティの中で、\r
193        * 現在フレーム数 f より大きい数値は、更新できる条件と無関係なので、除外しておく\r
194        * だから、f以下の値の中から、最大値を探して、\r
195        * それをbeginプロパティの値cacheBeginと比較する*/\r
196       var startTime = this.getMaxList(f, this.beginList);\r
197       if (startTime === -Number.MAX_VALUE) {\r
198         (state > post) && (this.state = begin);\r
199         return this;\r
200       }\r
201       var endTime = this.getMaxList(f, this.endList),\r
202           isWait = (state === wait),\r
203           cacheBegin = this.begin;\r
204       if ( !startTime && isWait) {\r
205           /*開始時刻が0ならば、アニメーションを開始\r
206            * ただし、アニメが終了した後のPOSTWAITING状態の時には作動させない*/\r
207           this.begin = 0;\r
208           this.state = begin;\r
209       } else if ( isWait || (state === post) ) {\r
210         if (startTime > cacheBegin) {\r
211           this.state = begin;\r
212           /*beginプロパティに開始時刻をキャッシュ用に保存*/\r
213           this.begin = startTime;\r
214         }\r
215       } else if (state === begin) {\r
216         if (endTime >= cacheBegin) {\r
217           /*終了時刻にもう到達したときは、直接BEGINNING状態からENDING状態へ移行*/\r
218           this.state = end;\r
219           /*endTimeを終了時刻とみなす*/\r
220           (endTime > 0) && (this.end = endTime);\r
221           /*activeTimeプロパティは、begin属性とend属性が反映されていないため、\r
222            * beginEndプロパティに別に設定しておく*/\r
223           this.beginEnd = 0;\r
224         } else {\r
225           this.state = play;\r
226         }\r
227       } else if (state === play) {\r
228         /*activeTimeプロパティを比較して、変数endTimeを書き換える*/\r
229         var act = cacheBegin + this.activeTime;\r
230         endTime = (endTime > act) ? act\r
231                                   : endTime;\r
232         if ( (f >= act) || (endTime >= cacheBegin) || (startTime > cacheBegin) ) {\r
233           /*終了時刻に到達したか、再び開始イベントが発火されたとき*/\r
234           this.state = end;\r
235           if (endTime > 0) {\r
236             /*endTimeを終了時刻とみなす*/\r
237             this.end = endTime;\r
238             /*activeTimeプロパティは、begin属性とend属性が反映されていないため、\r
239              * beginEndプロパティに別に設定しておく*/\r
240             this.beginEnd = endTime - cacheBegin;\r
241           }\r
242         }\r
243       } else if (state === end) {\r
244         if (startTime > cacheBegin) {\r
245           /*再生中に開始イベントが発火されて、終了状態となったとき*/\r
246           this.state = begin;\r
247           this.begin = startTime;\r
248         } else {\r
249           this.state = post;\r
250         }\r
251       } else {\r
252         this.state = begin;\r
253       }\r
254       cacheBegin = startTime = endTime = isWait = state = void 0;\r
255       return this;\r
256     },\r
257     \r
258     /*addEventメソッドで使われるイベントリスト(開始時に登録されたリスナー関数が呼び出される)*/\r
259     _beginListenerList: [],\r
260     \r
261     /*addEventメソッドで使われるイベントリスト(終了時に登録されたリスナー関数が呼び出される)*/\r
262     _endListenerList: [],\r
263     \r
264     /*addEventメソッドで使われるイベントリスト(再生時に登録されたリスナー関数が呼び出される)*/\r
265     _playListenerList: [],\r
266     \r
267     /*開始と再生と終了時に発火されるイベントリスナーを登録するメソッド*/\r
268     addEvent: function ( /*string*/ eventName, /*fnction*/ listener) {\r
269       var evtName = "_" +eventName+ "ListenerList";\r
270       /*プロトタイプ継承していた場合は新しく配列を作成*/\r
271       if (!this.hasOwnProperty(evtName)) {\r
272         this[evtName] = [];\r
273       }\r
274       this[evtName].push(listener);\r
275     },\r
276     \r
277     /*入力されたフレーム数fの場面に切り替えるメソッド*/\r
278     setFrame: function( /*number*/ f) {\r
279       this.currentFrame = f;\r
280       var state = this.updateState(f).state;\r
281       /*アニメーション開始と再生と、終了状態のときに、beginとplayとendイベントを呼び出しておいて、\r
282        * 次の状態(再生状態)に遷移する*/\r
283       if (state === /*this.PLAYING*/ 2) {\r
284         var list = this._playListenerList;\r
285         for (var i=0;i<list.length;++i) {\r
286           list[i](this);\r
287         }\r
288       } else if (state === /*this.BEGINNING*/ 1) {\r
289         list = this._beginListenerList;\r
290         for (var i=0;i<list.length;++i) {\r
291           list[i](this);\r
292         }\r
293         /*開始時刻と終了時刻が一致した場合はstateはENDING状態\r
294          * それ以外はPLAYING状態*/\r
295         state = this.updateState(f).state;\r
296       }\r
297       if (state === /*this.ENDING*/ 3) {\r
298         list = this._endListenerList;\r
299         for (var i=0;i<list.length;++i) {\r
300           list[i](this);\r
301         }\r
302         if (this.updateState(f).state === /*this.BEGINNING*/ 1) {\r
303           /*再生中にbeginイベントが呼び出された場合*/\r
304           this.updateState(f);\r
305         }\r
306       }\r
307       state = list = void 0;\r
308     }\r
309   } ).mix( function() {\r
310     /*後述の$beginや$endで使うメソッド*/\r
311     this.addList = this.addBeginList;\r
312   } );\r
313    \r
314   /*$begin オブジェクト\r
315    * 開始のタイミングを計算する*/\r
316   $frame.up("$begin").mix( {\r
317 \r
318     /*開始時刻やタイミングが書かれた文字列*/\r
319     string: "",\r
320 \r
321     /*イベントやindefinteで未解決かどうか*/\r
322     isResolved: false,\r
323     \r
324     /*イベントが適用される要素*/\r
325     eventTarget: document.documentElement,\r
326     \r
327     /*現在のフレーム数を改めて初期化*/\r
328     currentFrame: 0,\r
329     \r
330     /*イベント同期で使う時間差のフレーム数*/\r
331     eventOffset: 0,\r
332     \r
333     /*repeat(1)など文字列内に書かれたリピート回数*/\r
334     repeat: 0,\r
335     \r
336     /*accessKey(a)の"a"などキーイベントの対象となる文字列*/\r
337     accessKey: "",\r
338 \r
339     /*trim メソッド\r
340      * 文字列中の空白を除去*/\r
341     trim: function(str) {\r
342       /*strがString型以外のときは必ずエラーを出す*/\r
343       return str.replace(/[\s\n]+/g, "");\r
344     },\r
345 \r
346     /*offset メソッド\r
347      * 引数に渡された文字列から、ミリ秒単位に変換した時間を、解析して返す*/\r
348     offset: function(str) {\r
349       str = str || "0";\r
350       var plusminus = str.charAt(0),\r
351           /*parseFloatのエイリアス*/\r
352           _float = parseFloat,\r
353           s = _float( str.match(/[\d.]+ms$/) || "0") + sec() + min() + h();\r
354       if (plusminus === "-") {\r
355         s *= -1;\r
356       }\r
357       plusminus = _float = sec = min = h = void 0;\r
358       return s;\r
359       \r
360       /*00:00:0と00:0と、0sなどの文字列をミリ秒へ変換*/\r
361       function sec() {\r
362         return str2num( 1000, /[\d.]+s$/, /[\d.]+$/ );\r
363       };\r
364       function min() {\r
365         return str2num( 60000, /[\d.]+min$/, /\d\d:[^:]+$/ );\r
366       };\r
367       function h() {\r
368         return str2num( 3600000, /\d+:\d\d:/, /[\d.]+h$/ );\r
369       };\r
370       function str2num(s, /*RegExp*/ a, /*RegExp*/ b) {\r
371         return s*( _float(str.match(a) || "0") || _float(str.match(b) || "0") );\r
372       };\r
373     },\r
374 \r
375     /*event メソッド\r
376      * 引数の文字列から、idとイベントに相当する文字列のプロパティを持ったオブジェクトを返す\r
377      * idがない場合や、イベントがない場合は空文字列を該当のプロパティに入れる*/\r
378     event: function(str) {\r
379       str = str || "";\r
380       if (/[\+\-]/.test(str)) {\r
381         /*数値がある場合は切り取っておく*/\r
382         str = str.slice(0, str.search(/[\+\-]/));\r
383       }\r
384       if (str.indexOf(".") > -1) {\r
385         /*ドットが見つかった場合、IDとイベントに分けておく*/\r
386         var ide = str.split(".");\r
387         /* エラーが起きて、idが空文字列ならば、evtも空文字列。逆も然り*/\r
388         return {\r
389           id: (ide[1] && ide[0]),\r
390           event: (ide[0] && ide[1])\r
391         };\r
392       } else {\r
393         return {\r
394           id: "",\r
395           event: str\r
396         };\r
397       }\r
398     }, \r
399     \r
400     /*_parse メソッド\r
401      * 引数の文字列を解析して、フレーム数を算出し、結果を$frame.beginプロパティに出力\r
402      * また、イベントリスナーに登録をしておく*/\r
403     _parse: function (str) {\r
404       var plusminus = str.search(/[\+\-]/),\r
405           event = null,\r
406           ele,\r
407           /*endListのvalueプロパティには、活動継続フレーム数と開始フレーム数を足したものが入る*/\r
408           endList = this.$list.addEndList(Number.MAX_VALUE);\r
409       if (str === "indefinite") {\r
410         this.begin = Number.MAX_VALUE;\r
411       } else if (plusminus > 0) {\r
412         /*Event-Value +/- Clock-Value の場合*/\r
413         this.begin = this.offset( str.slice(plusminus) );\r
414         event = this.event(str);\r
415       } else if ( /[^\+\-\d]/.test(str.charAt(0)) ) {\r
416         /*Event-Valuen のみの場合*/\r
417         event = this.event(str);\r
418       } else {\r
419         /*+/- Clock-Value のみの場合*/\r
420         this.begin = this.offset( str );\r
421         /*イベントもindefiniteもないので、解決済みと考える*/\r
422         this.isResolved = true;\r
423       }\r
424       /*もしもあれば、リピートの回数を求める*/\r
425       this.repeat = /repeat\((\d+)\)/.test(str) ? +RegExp.$1 : 0;\r
426       /*もしもあれば、押されるはずのキーを求める*/\r
427       this.accessKey = /accessKey\(([^\)]+?)\)/.test(str) ? RegExp.$1 : "";\r
428       this.begin = Math.floor( this.begin * this.fpms);\r
429       if (str === "indefinite") {\r
430         /*begin属性の値がindefiniteの場合は、何もしない。\r
431          * 開始時刻はbeginElementメソッドに依存*/\r
432       } else if (event) {\r
433         ele = event.id ? this.eventTarget.ownerDocument.getElementById(event.id)\r
434                         : this.eventTarget;\r
435         /*イベントの時間差を設定しておく\r
436          * eventOffsetとobjListの変数はクロージャとしてlistener関数で使われる*/\r
437         var eventOffset = this.begin,\r
438             /*objListのvalueプロパティはあとで書き換えられる(イベントの場合のみ)*/\r
439             objList = this.$list.addList(Number.MAX_VALUE),\r
440         /*イベントのリスナーとして使う*/\r
441             listener = function(evt) {\r
442               objList.value = this.begin = eventOffset + base("$frame").currentFrame;\r
443               this.isResolved = true;\r
444             };\r
445         this.eventOffset = eventOffset;\r
446         if (this.repeat > 0) {\r
447           ele && ele.addEventListener("repeatEvent", (function(evt) {\r
448             if (evt.detail === this.repeat) {\r
449               listener.call(this, evt);\r
450             } }).bind(this), true);\r
451         } else if (this.accessKey) {\r
452           document.documentElement.addEventListener("keydown", (function(evt) {\r
453             if (evt.char === this.accessKey) {\r
454                 listener.call(this, evt);\r
455               } }).bind(this), false);\r
456         } else {\r
457           var evtName = /^(?:begin|end|repeat)$/.test(event.event) ? event.event + "Event"\r
458                           : event.event;\r
459           ele && ele.addEventListener(evtName, listener.bind(this), false);\r
460         }\r
461       } else {\r
462         /*開始リストに登録しておく($endの場合は終了リストに登録)*/\r
463         this.$list.addList(this.begin);\r
464       }\r
465       s = event = str = plusminus = ele = void 0;\r
466     },\r
467     \r
468     /*stringプロパティを解析して、\r
469      * 開始フレーム数の算出や、イベントリスナーの登録をするメソッド*/\r
470     parse: function() {\r
471       /*初期値を設定*/\r
472       this.begin = 0;\r
473       this.isResolved = false;\r
474       var str = this.trim(this.string);\r
475       if (str.indexOf(";") > -1){\r
476         /*;で区切られたリストを一つずつ解析*/\r
477         var list = str.split(";");\r
478         for (var i=0;i<list.length;++i) {\r
479           this._parse(list[i]);\r
480         }\r
481       } else {\r
482         this._parse(str);\r
483       }\r
484       s = str = void 0;\r
485       return this;\r
486     },\r
487     \r
488     /*$listと$activateオブジェクトを更新して、活動継続時間を求めるメソッド*/\r
489     updateList: function() {\r
490       this.$list = this.$list.up();\r
491       /*beginとend属性を考慮に入れないで、活動継続時間を求める*/\r
492       var s = ( this.$activate = this.$activate.up() );\r
493       /*$endオブジェクトに付属している$listプロパティを更新したものと一致させておく*/\r
494       s.end && (s.end.$list = this.$list);\r
495       this.activeTime = this.$list.activeTime = s.call() || Number.MAX_VALUE;\r
496       this.simpleDuration = s.simpleDur;\r
497       return this;\r
498     }\r
499     \r
500   /*$activate オブジェクト\r
501    * 活動継続時間などを計算するための計算実体\r
502    * $begin オブジェクトからの継承*/\r
503   } ).up("$activate").of( {\r
504     \r
505     /*単純継続時間のパースされる前の文字列*/\r
506     dur: "indefinite",\r
507     \r
508     /*活動をストップさせるためのオブジェクト*/\r
509     end: $frame.$begin.up("$end"),\r
510 \r
511     /*リピート回数*/\r
512     repeatCount: null,\r
513     \r
514     /*繰り返し時間*/\r
515     repeatDur: null,\r
516 \r
517     /*単純継続時間 (単位はフレーム数)*/\r
518     simpleDur: function() {\r
519       return ( (this.dur === "indefinite") || !this.dur ) ?\r
520                 null\r
521               : Math.floor(this.offset(this.dur) * this.fpms) ;\r
522     },\r
523 \r
524     /*最小値に制限される\r
525      * 最小値 <= 活動継続時間 とならなければならない*/\r
526      min: "0",\r
527      \r
528     /*最大値に制限される\r
529      * 活動継続時間 <= 最大値 とならなければならない*/\r
530      max: "indefinite",\r
531     \r
532     /*解決した(計算する)ときの時間*/\r
533     resolvedTime: function() {\r
534       return Date.now();\r
535     },\r
536     \r
537     /*関数型の呼び出しメソッド\r
538      * base.jsのofメソッドを活用して活動継続時間を算出\r
539      * ただし、begin属性とend属性については、別途、$frame.$listで定める\r
540      * 計算方法はSMILアニメーション 3.3.4節を参照\r
541      * http://www.w3.org/TR/smil-animation/#ComputingActiveDur\r
542      */\r
543     call: function() {\r
544       var ind = "indefinite",\r
545           dur = this.simpleDur,\r
546           isIndefRepeatCount = (this.repeatCount === ind),\r
547           isIndefRepeatDur = (this.repeatDur === ind),\r
548           isDur = dur || (dur === 0),\r
549           isRepeatCount = this.repeatCount || (this.repeatCount === 0),\r
550           isRepeatDur = this.repeatDur || (this.repeatDur === 0),\r
551           actList = [],\r
552           min = Math.floor(this.offset(this.min) * this.fpms),\r
553           max = (this.max === ind) ? null : Math.floor(this.offset(this.max) * this.fpms),\r
554           s;\r
555       if (indef()) {\r
556         /*注意点として、活動継続時間が不定かつ、min属性とmax属性が指定されたときの記述が仕様にないため、\r
557          * W3Cのテストスイート(animate-elem-66-t.svg)に従い、max属性の値を返す*/\r
558         if (max) {\r
559           return max;\r
560         }\r
561         return null;\r
562       }\r
563       if (isDur && this.repeatCount && !isIndefRepeatCount) {\r
564         actList.push( dur * this.repeatCount );\r
565       }\r
566       if (isRepeatDur && !isIndefRepeatDur) {\r
567         actList.push( Math.floor( this.offset(this.repeatDur) * this.fpms) );\r
568       }\r
569       if (isDur && !isRepeatCount && !isRepeatDur) {\r
570         /*repeatCountやrepeatDur属性が指定されていない場合*/\r
571         actList.push( dur );\r
572       }\r
573 \r
574       /*長くなるため、インライン関数を活用\r
575        * indef関数はbeginとend属性を考慮せずに、\r
576        * 活動継続時間が不定かどうか、もし、不定なら真を返す*/\r
577       function indef() {\r
578         return !!( (!isDur && !isRepeatDur)\r
579                    || (isIndefRepeatCount && !isRepeatDur)\r
580                    || (isIndefRepeatDur && !isRepeatCount)\r
581                    || (isIndefRepeatCount && isIndefRepeatDur)\r
582                  );\r
583       };\r
584       \r
585       ind = dur = isIndefRepeatCount = isIndefRepeatDurindef = isDur = isEnd = isRepeatDur = isRepeatCount = indef = void 0;\r
586 \r
587       if (actList.length === 1) {\r
588         s = actList[0];\r
589       } else if(actList.length > 1) {\r
590         /*属性が競合するときは、最小値をとること (SMILアニメーション 3.3.4節)*/\r
591         s = Math.min.apply(Math, actList);\r
592       } else {\r
593         return null;\r
594       }\r
595       if ( max && (min > max) ) {\r
596         return s;\r
597       }\r
598       min && (min > s) && (s = min);\r
599       max && (max < s) && (s = max);\r
600       return s;\r
601     }\r
602   } );\r
603   $frame.$begin.$end.of( {\r
604     call: function() {\r
605       if (!this.string) {\r
606         return null;\r
607       }\r
608       /*addListメソッドには、addBeginList関数が入っているはずなので、それを一時的に変更する*/\r
609       this.$list.addList = this.$list.addEndList;\r
610       this.parse(this.string);\r
611       this.$list.addList = this.$list.addBeginList;\r
612       return this.isResolved ? this.begin\r
613                              : "indefinite";\r
614     }\r
615   } ).mix( {\r
616     $list: $frame.$begin.$list.up()\r
617   } );\r
618 } );\r
619 /*$from オブジェクト\r
620  * 呈示値 (presentation value)の計算をする。値そのものを返すための計算実体*/\r
621 base("$from").of( {\r
622   /*呈示値が書かれた文字列*/\r
623   string: "",\r
624   \r
625   /*呈示値の数値の部分だけを抜き出した配列を返す*/\r
626   numList: function() {\r
627     var s  = this.string.match(/[\-\+]?[\d\.]+(?:[eE][\-\+]?[\d\.]+)?/g)\r
628              || [];\r
629     if (s) {\r
630       /*mapメソッドで代用してもよい*/\r
631       for (var i=0;i<s.length;++i) {\r
632         s[i] = parseFloat(s[i]);\r
633       }\r
634     }\r
635     return s;\r
636   },\r
637   \r
638   /*呈示値の文字部分だけを抜き出した配列を返す*/\r
639   strList: function() {\r
640     /*replaceメソッドで1E-10などの対策*/\r
641     return this.string.replace(/\d[eE][\-\+\d]/g, "")\r
642                       .match(/[^\d\-\+\.]+/g);\r
643   },\r
644   \r
645   from: base("$from").up().mix( {\r
646           from: null\r
647         } ),\r
648   \r
649   /*$toオブジェクトにこのオブジェクトを適用させる関数*/\r
650   call: function() {\r
651     /*advanceメソッドで使うために、stringを保持*/\r
652     this.numList.string = this.string;\r
653     if (this.numList.length\r
654           && (this.additive[0] === 0)\r
655           && (this.accumulate[0] === 0)\r
656           ) {\r
657       /*配列の項目がundefinedだと困るので、配列を初期化する*/\r
658       var additive = [],\r
659           accumulate = [];\r
660       for (var i=0;i<this.numList.length;++i) {\r
661         /*0で配列を初期化しておく*/\r
662         additive[i] = accumulate[i] = 0;\r
663       }\r
664       this.additive = additive;\r
665       this.accumulate = accumulate;\r
666     }\r
667     /*文字部分の配置パターンは4通りあるので、ここでstrListを処理\r
668      * (1) a 0 の場合\r
669      * (2) 0 a\r
670      * (3) a 0 a (ノーマルパターン)\r
671      * (4) 0 a 0\r
672      * これらのパターンのうち、(1)(2)(4)を(3)のパターンに統一したのが以下の処理*/\r
673     /*文字列が1aのように、数値で始まるかどうか。始まったら真*/\r
674     if (!this.string || !this.numList.length || !this.strList) {\r
675       return this.numList;\r
676     }\r
677     var isNormal = (this.numList.length < this.strList.length);\r
678     if (/^[\-\+]?[\d\.]/.test(this.string) && !isNormal) {\r
679       /*文字列が1aのように、数値で始まる場合*/\r
680       this.strList.unshift("");\r
681     }\r
682     if (/\d$/.test(this.string) && !isNormal) {\r
683       /*文字列がa1のように、数値で終わる場合*/\r
684       this.strList.push("");\r
685     }\r
686     return this.numList;\r
687   }\r
688     \r
689 } )\r
690  .mix( {\r
691    /*advanceメソッドの返り値で使われる小数点以下の桁数*/\r
692    degit: 0,\r
693    \r
694    /*additve属性やaccumulate属性が設定された、累積アニメーションか、加法アニメーションで使われる*/\r
695    additive: [0],\r
696    accumulate: [0],\r
697    \r
698    /*advance メソッド\r
699     * アニメーションの進行具合を示す進捗率 t (0 <= t <= 1)をもとに、現在の呈示値を算出するためのもの\r
700     * callメソッドが前もって呼び出されていることが前提となる*/\r
701     advance: function(t) {\r
702       if ( (t < 0) || (1 < t)) {\r
703         throw new Error("An Invalid Number Error");\r
704       }\r
705       if (!this.string) {\r
706         return "";\r
707       } else if (!this.from.length) {\r
708         /*discreteのために、this.stringに数値が入っていない場合の対処*/\r
709         if (t === 1) {\r
710           return this.string.trim();\r
711         }\r
712         return this.from.string.trim();\r
713       }\r
714       var str = "",\r
715           numList = this.numList,\r
716           strList = this.strList,\r
717           fromNumList = this.from,\r
718           deg = this.degit,\r
719           additive = this.additive,\r
720           accumulate = this.accumulate;\r
721       for (var i=0,nuli=numList.length;i<nuli;++i) {\r
722         /*原点Oを(0,0,...0)とおく\r
723          *$fromと$toを、原点Oからの二つのベクトル (n次空間のベクトル)、ベクトルOFとベクトルOTと考える\r
724          *$fromと$toの二つの端の点FとTを結ぶ線分を、t : 1-t で内分する点をPとおく\r
725          * このときのベクトルOPを求めたのが以下の式*/\r
726         str += ( t * numList[i] + (1 - t) * fromNumList[i] + additive[i] + accumulate[i]).toFixed(deg);\r
727         strList && ( str += strList[i+1] );\r
728       }\r
729       /*文字列はcallメソッドにより、a0aのパターンになっているので、aの部分を追加*/\r
730       str = (strList ? strList[0] : "") + str;\r
731       numList = strList = fromNumList = i = nuli = deg = additive = accumulate = void 0;\r
732       return str.trim();\r
733     },\r
734     \r
735     /*distanceメソッド\r
736      * fromベクトルから自分自身のベクトルへの距離 (ノルム)の数値を返す。callメソッドを使うので注意すること*/\r
737      distance: function(from) {\r
738        if (!from) {\r
739           return 0;\r
740        }\r
741        var toList = this.call(),\r
742            fromList = from.call ? from.call() : from,\r
743            s = 0;\r
744        if (!toList || !fromList) {\r
745          return 0;\r
746        }\r
747        for (var i=0, tli=toList.length; i<tli; ++i) {\r
748          s += (toList[i] - fromList[i])*(toList[i] - fromList[i]);\r
749        }\r
750        return Math.sqrt(s);\r
751      },\r
752      \r
753      /*setAdditive メソッド\r
754       * additve属性がsumのときに使われるメソッド\r
755       * 引数は親要素の、現在の属性値*/\r
756       setAdditive: function(str) {\r
757         if (!str) {\r
758           return 0;\r
759         }\r
760         var from = this.$from.up();\r
761         from.string = str;\r
762         return ( this.additive = from.call() );\r
763       },\r
764      \r
765      /*setAccumulate メソッド\r
766       * accumulate属性がsumのときに使われるメソッド\r
767       * 引数は現在のリピート回数*/\r
768       setAccumulate: function(num) {\r
769         if (!num || isNaN(num)) {\r
770           return 0;\r
771         }\r
772         return ( this.accumulate = this.numList.map( function(d) {\r
773           return d * num;\r
774         } ) );\r
775       }\r
776   } )\r
777   /*fromプロパティの初期化*/\r
778  .up("$to").from = null;\r
779  \r
780  /*計算モードを定めるための計算実体\r
781   *補間の細かい制御などを行う*/\r
782  base("$calcMode").mix({\r
783    /*計算モード (calcMode属性の値)*/\r
784    mode: "linear",\r
785 \r
786    /*keyTimesの区間\r
787     * たとえば、"0, 0.5, 0.7, 1"の場合、時間の区間はそれぞれ、0.5 (=0.5-0)  0.2 (=0.7-0.5)  0.3 (=1-0.7)である\r
788     * このうち、どれか一つが値として入力される*/\r
789    keyTime: 1,\r
790    \r
791    /*keySpline属性の値を設定*/\r
792    keySplines: null,\r
793 \r
794    /*callメソッドで使う関数\r
795     * 進捗率を時間の圧縮率( = keyTimeプロパティ)で割ることで、現在、どこまで進んでいるのかを求めることができる*/\r
796    _f: function (t) {\r
797          /*tは進捗率*/\r
798          var tkey = this.keyTime;\r
799          if ( (tkey === 0) && t) {\r
800            t = 0; \r
801          } else if (!tkey || !isFinite(tkey) ) {\r
802            return this.string;\r
803          } else {\r
804            t = t / tkey;\r
805            t = (t > 1) ? Math.floor(t) : t;\r
806          }\r
807          tkey = void 0;\r
808          return isNaN(t) ? this.string\r
809                          : this.to.advance(t);\r
810      },\r
811      \r
812      /*discreteモードのときには、上記の_f関数の代わりに、以下の関数を用いる*/\r
813      funcForDiscrete: function (t) {\r
814         if (isNaN(t)) {\r
815           return this.string;\r
816         } else if (t === 1) {\r
817           return this.to.string;\r
818         } else {\r
819           return this.to.advance(0);\r
820         }\r
821       }\r
822  }).of( {\r
823    \r
824    /*全体の行列ノルム(距離)*/\r
825    norm: 1,\r
826 \r
827    /*無効だった場合の呈示値*/\r
828    string: "",\r
829    \r
830    /*与えられたアニメーションの進捗率を使った時間の圧縮率を計算して呈示値を返すための関数を作る*/\r
831    call: function() {\r
832      var f = this._f.bind(this);\r
833      if (this.mode === "linear") {\r
834        this.to.call();\r
835        return f;\r
836      } else if (this.mode === "paced") {\r
837        /*keyTimes属性は無視され、ベクトルの距離の割合から計算される*/\r
838        this.keyTime = this.to.distance(this.to.from) / this.norm;\r
839        return f;\r
840      } else if (this.mode === "spline") {\r
841        var tk = this.keySplines,\r
842            /*必ず関数を返すようにするため、円周率を返す関数tfを返して、nullの代わりとする*/\r
843            tf = function(x) {\r
844                  return Math.PI;\r
845            };\r
846       tf.isNotAnimate = true;\r
847       if (!tk) {\r
848          return tf;\r
849        }\r
850       for (var i=0,tki = NaN;i<tk.length;++i) {\r
851        tki = tk[i];\r
852        if (isNaN(tki)) {\r
853          return tf;\r
854        }\r
855        if ( (tki < 0) || (1 < tki)) {\r
856          return tf;\r
857        }\r
858      }\r
859      this.to.call();\r
860      var x2 = tk[0],\r
861          y2 = tk[1],\r
862          x3 = tk[2],\r
863          y3 = tk[3],\r
864          x4 = 1,\r
865          y4 = 1,\r
866          Ax = x4-3*(x3-x2),\r
867          Bx = 3*(x3-2*x2),\r
868          Cx = 3*x2,\r
869          Ay = y4-3*(y3-y2),\r
870          By = 3*(y3-2*y2),\r
871          Cy = 3*y2,\r
872          _newton = Math.qubicnewton; //高速化のためのエイリアス\r
873      if ( ( (x2 === 0) || (x2 === 1) )\r
874           && (y2 === 0)\r
875           && ( (x3 === 1) || (x3 === 0) )\r
876           && (y3 === 1) ) {\r
877             /*linearモードと同じ効果 (収束ではない可能性を考慮)*/\r
878             this.to.call();\r
879             return f;\r
880      }\r
881      var tkey = this.keyTime;\r
882      if (tkey || isFinite(tkey) ) {\r
883        /*keyTimeから時間の収縮率を3次ベジェ曲線に適用しておく*/\r
884        Ax *= tkey;\r
885        Bx *= tkey;\r
886        Cx *= tkey;\r
887        Ay *= tkey;\r
888        By *= tkey;\r
889        Cy *= tkey;\r
890      }\r
891      tkey = tk = x2 = y2 = x3 = y3 = x4 = y4 = void 0;\r
892      return function (x) {\r
893         /*3次ベジェ曲線は媒介曲線\r
894          *x = (x4-3*(x3-x2)-x1)*t*t*t + 3*(x3-2*x2+x1)*t*t + 3*(x2-x1)*t + x1\r
895          *y = (y4-3*(y3-y2)-y1)*t*t*t + 3*(y3-2*y2+y1)*t*t + 3*(y2-y1)*t + y1\r
896          * ただし、0 <= t <= 1\r
897          * スプラインモードの場合、x1 = y1 = 0, x4 = y4 = 1\r
898          * ベジェ曲線のxの式が三次方程式であるため、その解 t から、ベジェ曲線の y を求める\r
899          * なお、ニュートン法の初期値はxとする\r
900          * なぜなら、xの式をみると、xが増加傾向となるスプラインモードでは、係数が負となる可能性が低いため*/\r
901         var t = _newton(Ax, Bx, Cx, -x, x);\r
902         return f(Ay*t*t*t + By*t*t + Cy*t);\r
903       };\r
904     } else if (this.mode === "discrete") {\r
905       this.to.call();\r
906       return this.funcForDiscrete.bind(this);\r
907     }\r
908   }\r
909 } ).to = base("$from").$to;\r
910 \r
911 \r
912 /*ニュートン法により、三次方程式 a0x^3 + a1x^2 + a2x + a3 の解を求める\r
913  * 引数bは初期値*/\r
914 Math.qubicnewton = function(a0, a1, a2, a3, b) {\r
915   var eps = 1e-15,                          //収束誤差\r
916       fb = a0 *b*b*b + a1 *b*b + a2*b + a3; //方程式の結果\r
917   if (fb === 0) {\r
918     return b;\r
919   }\r
920   /*限界の収束回数は100回*/\r
921   for (var i=0;i<100;i=(i+1)|0) {\r
922     /*数値nは与えられた三次方程式を微分したもの*/\r
923     var n = 3* a0 *b*b + 2 * a1 *b + a2;\r
924     if (!n || ( (fb < eps) && (fb > -eps) )) {\r
925       fb = eps = void 0;\r
926       return b;\r
927     } else {\r
928       /*以下は収束の漸化式*/\r
929       b =  b - fb / n;\r
930       fb = a0 *b*b*b + a1 *b*b + a2*b + a3;\r
931     }\r
932   }\r
933   return b; //収束しなかった結果\r
934 };\r
935 \r
936 /*$attribute オブジェクト\r
937  * アニメーションの時間調整と、呈示値の調整を一つのオブジェクトにまとめて行うことで、\r
938  * アニメーションサンドイッチの実装をする\r
939  * $calcModeオブジェクトから継承*/\r
940 base("$calcMode").up("$attribute").mix( {\r
941   \r
942   /*アニメーションの対象となる要素。たとえば、animate要素の親要素*/\r
943   element: null,\r
944   \r
945   /*$fromオブジェクトを作るためのひな形となるオブジェクト*/\r
946   $from: base("$from").up(),\r
947   \r
948   /*attributeName属性の値*/\r
949   attrName: "",\r
950   \r
951   /*属性の名前空間*/\r
952   attrNameSpace: null,\r
953   \r
954     /*指定された属性の規定値*/\r
955   defaultValue: "",\r
956   \r
957   /*もともと属性がターゲットの要素につけられていたかどうか*/\r
958   isDefault: false,\r
959   \r
960   /*attributeType属性がCSSだったときや、autoで属性名がCSSプロパティ名だったときには、true*/\r
961   isCSS: false,\r
962   \r
963   /*計算モードdicreteやsplineなど*/\r
964   mode: "linear",\r
965   \r
966   /*指定した要素の属性値を取得するメソッド*/\r
967   getAttr: function(/*string*/ name, def) {\r
968     var nameSpace = null;\r
969     if (name.indexOf("xlink:") > -1) {\r
970       nameSpace = "http://www.w3.org/1999/xlink";\r
971     }\r
972     var s = this._ele.getAttributeNS(nameSpace, name);\r
973     if (this.element) {\r
974         var view = this.element.ownerDocument.defaultView;\r
975       if (s === "inherit") {\r
976         return view.getComputedStyle(this.element.parentNode, "").getPropertyValue(this.attrName);\r
977       } else if (s === "currentColor") {\r
978         return view.getComputedStyle(this._ele, "").getPropertyValue("color");\r
979       }\r
980     }\r
981     /*DOM Level2やIE11では、getAttributeNSメソッドは空文字列を返す。他のブラウザではnullを返すことが多い\r
982      * \r
983      * >the empty string if that attribute does not have a specified or default value\r
984      * http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-ElGetAttrNS*/\r
985     return (s || def);\r
986   },\r
987   \r
988   _ele: document.documentElement,\r
989   \r
990   /*指定された要素にvalues属性が付いているかどうかのチェックできるメソッド*/\r
991   hasAttrValues: function () {\r
992     var ele = this._ele;\r
993     if (!ele) {\r
994       return false;\r
995     } else {\r
996       return ( ele.hasAttribute("from") || ele.hasAttribute("to")\r
997          || ele.hasAttribute("by") || ele.hasAttribute("values") );\r
998     }\r
999   },\r
1000   \r
1001   /*後述のsetAttributeメソッドで使うキャッシュ\r
1002    * これを使うことで、二度同じ値を設定せずにすむので、高速化できる*/\r
1003   __cacheAttr: "",\r
1004   \r
1005   /*アニメーションするために、対象の属性やプロパティを変化させるメソッド*/\r
1006   setAttribute: function (value) {\r
1007     var attrName = this.attrName;\r
1008     if (!attrName) {\r
1009       return;\r
1010     }\r
1011     if (this.__cacheAttr === value) {\r
1012       /*同じ値であれば、再設定しない*/\r
1013       return;\r
1014     } else {\r
1015       this.__cacheAttr = value;\r
1016     }\r
1017     var ele = this.element;\r
1018     if (this.isCSS) {\r
1019       /*スタイルシートのプロパティを上書きしておく*/\r
1020       ele.style.setProperty(attrName, value, "");\r
1021     } else {\r
1022       ele.setAttributeNS(this.attrNameSpace, attrName, value);\r
1023     }\r
1024     value = attrName = ele = void 0;\r
1025   },\r
1026   \r
1027   /*setAttributeメソッドとは逆の効果で、無効化させるメソッド*/\r
1028   removeAttribute: function () {\r
1029     var attrName = this.attrName;\r
1030     if (!attrName) {\r
1031       return;\r
1032     }\r
1033     var ele = this.element;\r
1034     if (this.isDefault) {\r
1035       this.setAttribute(this.defaultValue);\r
1036     } else {\r
1037       /*初期段階でターゲットの要素に属性が指定されていない場合は、\r
1038        * 現在の属性値を削除するだけでよい*/\r
1039       ele.removeAttributeNS(this.attrNameSpace, attrName);\r
1040       /*スタイルシートのプロパティも削除しておく。removePropertyでないことに注意*/\r
1041       this.isCSS && ele.style.setProperty(attrName, this.defaultValue, "");\r
1042     }\r
1043     this.__cacheAttr = "";\r
1044     value = attrName = ele = void 0;\r
1045   },\r
1046   \r
1047   /*アニメーションの対象となる要素を値として返すメソッド\r
1048    * もっぱら、pushメソッドで使われる*/\r
1049   initTargetElement: function() {\r
1050     var ele = this._ele;\r
1051     var s = ele.parentNode || null;\r
1052     var id = ele.getAttribute("xlink:href");\r
1053     /*getAttributeNSメソッドでうまくいかなかったため、NSなしで代用*/\r
1054     if (id) {\r
1055       return ele.ownerDocument.getElementById(id.slice(1));\r
1056     }\r
1057     if ( id = ele.getAttributeNS(null, "targetElement") ) {\r
1058       return ele.ownerDocument.getElementById(id);\r
1059     }\r
1060     return s;\r
1061   },\r
1062   \r
1063   /*repeatイベントの発火時刻リスト\r
1064    * setSmilEventメソッドを見よ*/\r
1065   _repeatList: [],\r
1066   \r
1067   /*リピート回数を示すプロパティ\r
1068    * setSmilEventメソッドを見よ*/\r
1069   _repeatCount: 0,\r
1070   \r
1071   /*SMILイベント関連を発火させるためのメソッド\r
1072    * もっぱら、$attributeオブジェクトのpush メソッドで使われる*/\r
1073    setSmilEvent: function($list) {\r
1074     $list.addEvent("begin", function($list) {\r
1075         var target = this._ele,\r
1076             detail = 0;\r
1077         /*IE11のために、MouseEventsでSMILEventsの代用をする*/\r
1078         var evt = target.ownerDocument.createEvent("MouseEvents");\r
1079         evt.initMouseEvent("beginEvent" ,true, true, window, detail, 0, 0, 0, 0, false, false, false, false, 0, target);\r
1080         target.dispatchEvent(evt);\r
1081         /*repeatイベントのために、_repeatListプロパティを初期化する*/\r
1082         var list = this._repeatList = [],\r
1083             active = $list.activeTime,\r
1084             begin = $list.begin,\r
1085             simpleDuration = this.timeline.simpleDuration;\r
1086             if (simpleDuration && (simpleDuration !== active)\r
1087                   && (active !== Number.MAX_VALUE) ) {\r
1088               /*活動継続時間と単純持続時間が異なるとき、repeatイベントを設定*/\r
1089               for (var m= simpleDuration, n=1;m < active ; m+=simpleDuration) {\r
1090                 list.push( {\r
1091                   frame: begin + m,\r
1092                   /*リピートの回数 (n >= 1)*/\r
1093                   count: n\r
1094                 } );\r
1095                 ++n;\r
1096               }\r
1097             }\r
1098     }.bind(this) );\r
1099     \r
1100     $list.addEvent("play", function($list) {\r
1101       var target = this._ele,\r
1102           detail = 0,\r
1103           frame = $list.currentFrame,\r
1104           list = this._repeatList;\r
1105       if (!list.length) {\r
1106         return;\r
1107       }\r
1108       for (var i=0;i<list.length;++i) {\r
1109         if ( (this._repaetCount >= i+1)\r
1110             || (list[i].frame >= frame) ) {\r
1111           this._repeatCount = detail;\r
1112           break;\r
1113         } \r
1114         detail = list[i].count;\r
1115         var evt = target.ownerDocument.createEvent("MouseEvents");\r
1116         evt.initMouseEvent("repeatEvent" ,true, true, window, detail, 0, 0, 0, 0, false, false, false, false, 0, target);\r
1117         target.dispatchEvent(evt);\r
1118       }\r
1119     }.bind(this) );\r
1120     \r
1121     $list.addEvent("end", function() {\r
1122         var target = this._ele,\r
1123             detail = 0;\r
1124         var evt = target.ownerDocument.createEvent("MouseEvents");\r
1125         evt.initMouseEvent("endEvent" ,true, true, window, detail, 0, 0, 0, 0, false, false, false, false, 0, target);\r
1126         target.dispatchEvent(evt);\r
1127     }.bind(this) );\r
1128    },\r
1129 \r
1130   /*引数で指定した要素 ele の属性を解析して、フレームに追加する*/\r
1131   push: function(/*Element Node*/ ele) {\r
1132     if (!ele || !ele.hasAttribute) {\r
1133       return null;\r
1134     }\r
1135     /*キャッシュを初期化しておく*/\r
1136     this.__cacheAttr = "";\r
1137     \r
1138 \r
1139    /*getAttrメソッドとhasAttrValuesメソッドで必要*/\r
1140     this._ele = ele;\r
1141     \r
1142     /*initTargetElementメソッドを使って、elementプロパティの初期化*/\r
1143     this.element = this.initTargetElement();\r
1144     \r
1145     if (!this.hasAttrValues()) {\r
1146       /*from属性、to、by、values属性が指定されていない場合、アニメーションの効果が出ないように調整する\r
1147        *SMILアニメーションの仕様を参照\r
1148        *\r
1149        *>if none of the from, to, by or values attributes are specified, the animation will have no effect\r
1150        *「3.2.2. Animation function values」より引用\r
1151        *http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues\r
1152       */\r
1153       return null;\r
1154     }\r
1155  \r
1156     /*属性値の設定*/\r
1157     this.attrName = this.getAttr("attributeName", "");\r
1158     var attrName = this.attrName;\r
1159     \r
1160     /*属性タイプの設定*/\r
1161     var attrType = this.getAttr("attributeType", "auto");\r
1162     var computedStyle = this.element && this.element.ownerDocument.defaultView.getComputedStyle(this.element, "");\r
1163     if ( (attrType === "CSS")\r
1164          || ( (attrType === "auto") && this.element \r
1165              && computedStyle.getPropertyValue(attrName)\r
1166              && !/^(width|height|transform)$/.test(attrName)\r
1167             )\r
1168         ) {\r
1169         this.isCSS = true;\r
1170     }\r
1171     \r
1172     /*xlink関連の属性のときは名前空間を変更しておく*/\r
1173     if (attrName.indexOf("xlink") > -1) {\r
1174       this.attrNameSpace = "http://www.w3.org/1999/xlink";\r
1175     }\r
1176     /*thiseleはターゲットとなる要素(たとえば、親要素)*/\r
1177     var thisele = this.element;\r
1178     if (thisele) {\r
1179       /*規定値を設定しておく。属性を初期化するときに使われる*/\r
1180       this._ele = thisele;\r
1181       this.isDefault = thisele.hasAttributeNS(this.attrNameSpace, attrName);\r
1182       this.defaultValue = this.getAttr(attrName,\r
1183        computedStyle.getPropertyValue(attrName) );\r
1184       this._ele = ele; /*_eleプロパティを元に戻しておく*/\r
1185     }\r
1186     /*eleの属性の値を、それぞれオブジェクトに割り当て*/\r
1187     var $frame = base("$frame"),\r
1188         begin = $frame.$begin,\r
1189         frame = begin.up().mix( {\r
1190                   /*targetプロパティはbeginEventなどの発火で使う*/\r
1191                   target: ele,\r
1192                   eventTarget: (this.element || begin.eventTarget),\r
1193                   string: this.getAttr("begin", "0"),\r
1194                   $activate: begin.$activate.up().mix( {\r
1195                     dur: this.getAttr("dur", null),\r
1196                     end: begin.$end.up().mix( {\r
1197                           eventTarget: (this.element || begin.eventTarget),\r
1198                           string: this.getAttr("end", null)\r
1199                         } ),\r
1200                     repeatCount: this.getAttr("repeatCount", null),\r
1201                     repeatDur: this.getAttr("repeatDur", null),\r
1202                     min: this.getAttr("min", "0"),\r
1203                     max: this.getAttr("max", "indefinite")\r
1204                   } )\r
1205                 } ).updateList().parse();\r
1206     $frame.addLine(frame.$list.init());\r
1207     \r
1208     /*beginElementメソッドを追加*/\r
1209     var objList = frame.$list.addList(Number.MAX_VALUE),\r
1210         /*endListのvalueプロパティには、活動継続フレーム数と開始フレーム数を足したものが入る*/\r
1211         endList = frame.$list.addEndList(Number.MAX_VALUE);\r
1212     ele.beginElement = (frame.string !== "indefinite") ? function(){}\r
1213                         : function() {\r
1214                             objList.value = frame.begin = base("$frame").currentFrame;\r
1215                             frame.isResolved = true;\r
1216                             var evt = this.ownerDocument.createEvent("MouseEvents");\r
1217                             evt.initMouseEvent("beginEvent" ,true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, this);\r
1218                             this.dispatchEvent(evt);\r
1219                           };\r
1220     /*endElementメソッドを追加*/\r
1221     var endFrame = frame.$activate.end || {};\r
1222     ele.endElement = (endFrame.string !== "indefinite") ? function(){}\r
1223                         : function() {\r
1224                             if (frame.isResolved) {\r
1225                               endFrame.isResolved = true;\r
1226                               endList.value  = base("$frame").currentFrame;\r
1227                               var evt = this.ownerDocument.createEvent("MouseEvents");\r
1228                               evt.initMouseEvent("endEvent" ,true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, this);\r
1229                               this.dispatchEvent(evt);\r
1230                             }\r
1231                           };\r
1232     /*setFrameメソッドを使ったときの、再帰スタックの使いすぎを防ぐため*/\r
1233     frame.timelines = [];\r
1234     this.setSmilEvent(frame.$list);\r
1235     begin = ele = void 0;\r
1236     return frame;\r
1237   },\r
1238   \r
1239   /*setValuesメソッド\r
1240    * values属性やfrom属性やto属性を処理するためのメソッド\r
1241    * valuesは配列、それ以外の引数は文字列\r
1242    * 返り値は、values属性は配列、それ以外の属性のときは、\r
1243    * 自分自身となる$attributeオブジェクトのコピーを返す*/\r
1244    setValues: function(values, from, to, by) {\r
1245      var $from = this.$from,\r
1246          s = [this.up().mix( {\r
1247                to: $from.up().mix( {\r
1248                  from: $from.up()\r
1249                } )\r
1250              } )],\r
1251          sto = s[0].to;\r
1252      values = values && values.split(";");\r
1253      /*from属性はオプションなので、条件には付け加えない*/\r
1254      if (values && values.length) {\r
1255        /*values属性が指定された場合、他の属性は無視される\r
1256         * W3C仕様 SMIL アニメーション 3.2.2. アニメーション関数の値*/\r
1257         s = [];\r
1258        for (var i=1;i<values.length;++i) {\r
1259          s.push( this.up().mix( {\r
1260                to: $from.up().mix( {\r
1261                  from: $from.up()\r
1262                } )\r
1263              } ) );\r
1264          sto = s[s.length-1].to;\r
1265          sto.string = values[i];\r
1266          sto.from.string = values[i-1];\r
1267        }\r
1268      } else if (to) {\r
1269        sto.string = to;\r
1270        sto.from.string = from || "0";\r
1271      } else if (by) {\r
1272        sto.string = by;\r
1273        sto.from.string = from || "0";\r
1274        var toNumList = sto.call(),\r
1275            fromNumList = sto.from;\r
1276        for (var i=0;i<toNumList.length;++i) {\r
1277          /*初期値と差分を足していく*/\r
1278          toNumList[i] += fromNumList[i];\r
1279        }\r
1280      } else {\r
1281        return null;\r
1282      }\r
1283      $from = sto = toNumList = fromNumList = void 0;\r
1284      return s;\r
1285    },\r
1286    \r
1287    /*setKeyメソッド\r
1288     * 引数の要素のkeyTimes属性やkeySplines属性を処理するためのメソッド\r
1289     * 必要な他の属性処理はsetValuesメソッドに任せている*/\r
1290    setKey: function(ele) {\r
1291      this._ele = ele;\r
1292      var to = this.setValues(this.getAttr("values", null),\r
1293           this.getAttr("from", null),\r
1294           this.getAttr("to", null),\r
1295           this.getAttr("by", null) ),\r
1296          keyTimes = this.getAttr("keyTimes", null),\r
1297          keySplines = this.getAttr("keySplines", null),\r
1298          keys,\r
1299          splines = keySplines && keySplines.split(";"),\r
1300          isDiscrete = (this.mode === "discrete"),\r
1301          toiKeySplines;\r
1302     if (!isDiscrete && keyTimes && to) {\r
1303       keys = this.$from.numList.call( {\r
1304         string: keyTimes\r
1305       } );\r
1306       /*keysTime属性の最初は0でなければならない。そうでなければエラー(SVG 1.1 2ndの仕様を参照)*/\r
1307       if (keys.length && (keys[0] !== 0)) {\r
1308         return null;\r
1309       }\r
1310       /*toオブジェクトはtoとfromで一組となっているのでlengthが加算される*/\r
1311       if (keys.length && (keys.length !== (to.length+1))) {\r
1312         /*keyTimes属性とvalues属性のリストの個数が合致しない場合、アニメーションの効果がない\r
1313          * 仕様を参照 SMIL Animation 3.2.3. Animation function calculation modes\r
1314          * http://www.w3.org/TR/smil-animation/#AnimFuncCalcMode*/\r
1315         return null;\r
1316       }\r
1317       for (var i=0;i<to.length;++i) {\r
1318         to[i].keyTime = keys[i+1] - keys[i];\r
1319         if (splines) {\r
1320           toiKeySplines = this.$from.numList.call( {\r
1321             string: splines[i]\r
1322           } );\r
1323           /*空配列を返すため、nullに変えておく*/\r
1324           to[i].keySplines = toiKeySplines.length ? toiKeySplines : null;\r
1325         }\r
1326       }\r
1327     } else if (!isDiscrete && to) {\r
1328       var per = 1 / to.length;\r
1329       for (var i=0;i<to.length;++i) {\r
1330         to[i].keyTime = per;\r
1331         if (splines) {\r
1332           toiKeySplines = this.$from.numList.call( {\r
1333             string: splines[i]\r
1334           } );\r
1335           to[i].keySplines = toiKeySplines.length ? toiKeySplines : null;\r
1336         }\r
1337       }\r
1338     } else if (to) {\r
1339         /*discreteモードの処理*/\r
1340       if (keyTimes) {\r
1341         keys = this.$from.numList.call( {\r
1342           string: keyTimes\r
1343         } );\r
1344       /*keysTime属性の最初は0でなければならない。そうでなければエラー(SVG 1.1 2ndの仕様を参照)*/\r
1345         if (keys.length && (keys[0] !== 0)) {\r
1346           return null;\r
1347         }\r
1348         if (keys.length && (keys.length !== (to.length+1))) {\r
1349           return null;\r
1350         }\r
1351         for (var i=0;i<to.length;++i) {\r
1352           to[i].keyTime = keys[i+1] - keys[i];\r
1353         }\r
1354       } else {\r
1355         var per = 1 / (to.length+1);\r
1356         for (var i=0;i<to.length;++i) {\r
1357           to[i].keyTime = per;\r
1358         }\r
1359       }\r
1360       /*toオブジェクトが足らないので、一つ追加しておく*/\r
1361       to.push( to[to.length-1].up().mix( function(){\r
1362           if (!keys) {\r
1363             return;\r
1364           }\r
1365           this.keyTime = 1-keys[keys.length-1];\r
1366         } ).of( {\r
1367             call: function() {\r
1368               return function (t) {\r
1369                  return isNaN(t) ? this.string\r
1370                                  : this.to.advance(1);\r
1371               }.bind(this);\r
1372             }\r
1373           } \r
1374         ) );\r
1375     }\r
1376     if (this.mode === "paced") {\r
1377       /*ベクトル全体の距離を算出*/\r
1378       var norm = 0;\r
1379       to.forEach( function(x) {\r
1380         norm += x.to.distance(x.to.from);\r
1381       } );\r
1382       to.forEach( function(x) {\r
1383          x.norm = norm;\r
1384       } );\r
1385     }\r
1386     ele = keyTimes = keys = per = splines = void 0;\r
1387     return to;\r
1388    }\r
1389 } ).up("$setElement").mix( {\r
1390   /*to属性の値、文字列*/\r
1391   to: "",\r
1392   \r
1393   /*initメソッドで使われるアニメーション関数*/\r
1394   _setFrame: function ($list) {\r
1395     this.setAttribute(this.to);\r
1396   },\r
1397   \r
1398   /*開始を設定されたタイムライン ($beginオブジェクト)*/\r
1399   timeline: base("$frame").$begin,\r
1400   \r
1401   /*アニメが終了した際の後処理*/\r
1402   _setEndFrame: function ($list) {\r
1403     /*removeの場合、アニメーションを凍結せずに、もとに戻す*/\r
1404     if (this.fill === "remove") {\r
1405       this.removeAttribute();\r
1406     }\r
1407   },\r
1408   \r
1409   /*アニメーションの呈示値を呼び出す関数*/\r
1410   tocall: function() {},\r
1411   \r
1412   init: function(ele) {\r
1413     var line = this.push(ele);\r
1414     if (ele && ele.getAttributeNS) {\r
1415       this._ele = ele;\r
1416       this.to = this.getAttr("to", "");\r
1417       this.fill = this.getAttr("fill", "remove");\r
1418     }\r
1419     var thisele = this.element;\r
1420     if (line && thisele) {\r
1421       this.timeline = line;\r
1422       /*$begin.$listのイベントに属性処理を追加*/\r
1423       line.$list.addEvent("begin", this._setFrame.bind(this));\r
1424       line.$list.addEvent("play", this._setFrame.bind(this));\r
1425       line.$list.addEvent("end", this._setEndFrame.bind(this));\r
1426       /*アニメーションが再起動する可能性もあるため、$listのstateプロパティはここで初期化*/\r
1427       line.$list.state = line.$list.WAITING;\r
1428     }\r
1429     line = thisele = void 0;\r
1430   }\r
1431 }).up("$animateElement").mix( {\r
1432   /*アニメ関数の配列*/\r
1433   funcs: [],\r
1434 \r
1435   /*進捗率advanceから、呈示値を求める*/\r
1436   tocall: function(advance) {\r
1437     var tf = this.funcs;\r
1438     if (this.mode !== "discrete") {\r
1439       for (var i=0;i<tf.length;++i) {\r
1440         var tfi = tf[i];\r
1441         /*keyTime(keyTimes属性で指定されたような値)で実行するかどうかを判別*/\r
1442         if (tfi.endKeyTime >= advance) {\r
1443           return tfi(advance - tfi.startKeyTime);\r
1444         }\r
1445       }\r
1446     } else {\r
1447       var result = "";\r
1448       for (var i=0;i<tf.length;++i) {\r
1449         var tfi = tf[i];\r
1450         if (advance >= tfi.startKeyTime) {\r
1451           result = tfi(advance);\r
1452         }\r
1453       }\r
1454       advance = tf = tfi = void 0;\r
1455       return result;\r
1456     }\r
1457     tf = i = tfi = void 0;\r
1458     return "";\r
1459   },\r
1460   \r
1461   _setFrame: function($list) {\r
1462     var currentFrame = $list.currentFrame;\r
1463     /*durationは単純継続時間\r
1464      *advanceは継続時間内での、進捗率\r
1465      *  仕様を参照 http://www.w3.org/TR/smil-animation/#AnimFuncValues\r
1466      *進捗率advanceは、durationと進捗フレーム数とを割った余り(REMAINDER)で算出する\r
1467      * 仕様を参照 SMIL Animation 3.6.2 Interval timing\r
1468      * http://www.w3.org/TR/2001/REC-smil-animation-20010904/#IntervalTiming*/\r
1469     var line = this.timeline,\r
1470         duration = line.simpleDuration,\r
1471         /*単純継続時間が不定の場合、補間はせずに初期値が採用されるため、advanceは0となる\r
1472          * 仕様を参照 SMIL Animation 3.2.2. Animation function values のInterpolation and indefinite simple durations\r
1473          * http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues*/\r
1474         advance = duration ? ( (currentFrame - $list.begin) % duration ) / duration\r
1475                     : 0;\r
1476     this.setAttribute(this.tocall(advance));\r
1477     line = duration = advance = void 0;\r
1478   },\r
1479   \r
1480   /*凍結処理をするために、進捗率を最後まで進めて調整するメソッド\r
1481    * 返り値は調整された進捗率\r
1482    * もっぱら、_setEndFrameメソッドで使われる*/\r
1483   getAdvanceEnd: function($list) {\r
1484     var line = this.timeline,\r
1485         duration = line.simpleDuration;\r
1486     if (duration) {\r
1487       var time = (line.activeTime > $list.beginEnd) ? $list.beginEnd\r
1488                   : line.activeTime;\r
1489       var advance = ( time % duration ) / duration;\r
1490       /*例外が発生するため、進捗率が1を超えないように処理*/\r
1491       advance = (advance > 1) ? 1 : advance;\r
1492        /*活動継続時間と単純継続時間が一致すると、余りは0となるため以下の処理*/\r
1493       advance = advance || 1;\r
1494     } else {\r
1495       advance = 0;\r
1496     }\r
1497     return advance;\r
1498   },\r
1499   \r
1500   /*_setEndFrameメソッドは、終了処理と凍結作業をする*/\r
1501   _setEndFrame: function($list) {\r
1502     /*上書きされたメソッドを呼び出してアニメーションの凍結作業をする*/\r
1503     if (this.fill === "freeze") {\r
1504       this.setAttribute(this.tocall( this.getAdvanceEnd($list) ));\r
1505       line = duration = advance = void 0;\r
1506     } else {\r
1507       this.removeAttribute();\r
1508     }\r
1509   },\r
1510   \r
1511   /*getAttrメソッドをオーバライド*/\r
1512   getAttr: function (name, def) {\r
1513     var s= this.$attribute.getAttr.apply(this, arguments);\r
1514     if ((name === "from") && !s && this.defaultValue) {\r
1515       /*from属性がない場合、対象要素の既定値を返す*/\r
1516       return this.defaultValue;\r
1517     }\r
1518     return s;\r
1519   },\r
1520   \r
1521   _keywords: {\r
1522     aliceblue:    [240,248,255],\r
1523     antiquewhite: [250,235,215],\r
1524     aqua:         [0,255,255],\r
1525     aquamarine:   [127,255,212],\r
1526     azure:        [240,255,255],\r
1527     beige:        [245,245,220],\r
1528     bisque:       [255,228,196],\r
1529     black:        [0,0,0],\r
1530     blanchedalmond:[255,235,205],\r
1531     blue:         [0,0,255],\r
1532     blueviolet:   [138,43,226],\r
1533     brown:        [165,42,42],\r
1534     burlywood:    [222,184,135],\r
1535     cadetblue:    [95,158,160],\r
1536     chartreuse:   [127,255,0],\r
1537     chocolate:    [210,105,30],\r
1538     coral:        [255,127,80],\r
1539     cornflowerblue:[100,149,237],\r
1540     cornsilk:     [255,248,220],\r
1541     crimson:      [220,20,60],\r
1542     cyan:         [0,255,255],\r
1543     darkblue:     [0,0,139],\r
1544     darkcyan:     [0,139,139],\r
1545     darkgoldenrod:[184,134,11],\r
1546     darkgray:     [169,169,169],\r
1547     darkgreen:    [0,100,0],\r
1548     darkgrey:     [169,169,169],\r
1549     darkkhaki:    [189,183,107],\r
1550     darkmagenta:  [139,0,139],\r
1551     darkolivegreen:[85,107,47],\r
1552     darkorange:    [255,140,0],\r
1553     darkorchid:   [153,50,204],\r
1554     darkred:      [139,0,0],\r
1555     darksalmon:   [233,150,122],\r
1556     darkseagreen: [143,188,143],\r
1557     darkslateblue:[72,61,139],\r
1558     darkslategray:[47,79,79],\r
1559     darkslategrey:[47,79,79],\r
1560     darkturquoise:[0,206,209],\r
1561     darkviolet:   [148,0,211],\r
1562     deeppink:     [255,20,147],\r
1563     deepskyblue:  [0,191,255],\r
1564     dimgray:      [105,105,105],\r
1565     dimgrey:      [105,105,105],\r
1566     dodgerblue:   [30,144,255],\r
1567     firebrick:    [178,34,34],\r
1568     floralwhite:  [255,250,240],\r
1569     forestgreen:  [34,139,34],\r
1570     fuchsia:      [255,0,255],\r
1571     gainsboro:    [220,220,220],\r
1572     ghostwhite:   [248,248,255],\r
1573     gold:         [255,215,0],\r
1574     goldenrod:    [218,165,32],\r
1575     gray:         [128,128,128],\r
1576     grey:         [128,128,128],\r
1577     green:        [0,128,0],\r
1578     greenyellow:  [173,255,47],\r
1579     honeydew:     [240,255,240],\r
1580     hotpink:      [255,105,180],\r
1581     indianred:    [205,92,92],\r
1582     indigo:       [75,0,130],\r
1583     ivory:        [255,255,240],\r
1584     khaki:        [240,230,140],\r
1585     lavender:     [230,230,250],\r
1586     lavenderblush:[255,240,245],\r
1587     lawngreen:    [124,252,0],\r
1588     lemonchiffon: [255,250,205],\r
1589     lightblue:    [173,216,230],\r
1590     lightcoral:   [240,128,128],\r
1591     lightcyan:    [224,255,255],\r
1592     lightgoldenrodyellow:[250,250,210],\r
1593     lightgray:    [211,211,211],\r
1594     lightgreen:   [144,238,144],\r
1595     lightgrey:    [211,211,211],\r
1596     lightpink:    [255,182,193],\r
1597     lightsalmon:  [255,160,122],\r
1598     lightseagree: [32,178,170],\r
1599     lightskyblue: [135,206,250],\r
1600     lightslategray:[119,136,153],\r
1601     lightslategrey:[119,136,153],\r
1602     lightsteelblue:[176,196,222],\r
1603     lightyellow:  [255,255,224],\r
1604     lime:         [0,255,0],\r
1605     limegreen:    [50,205,50],\r
1606     linen:        [250,240,230],\r
1607     magenta:      [255,0,255],\r
1608     maroon:       [128,0,0],\r
1609     mediumaquamarine:[102,205,170],\r
1610     mediumblue:    [0,0,205],\r
1611     mediumorchid:  [186,85,211],\r
1612     mediumpurple:  [147,112,219],\r
1613     mediumseagreen:[60,179,113],\r
1614     mediumslateblue:[123,104,238],\r
1615     mediumspringgreen:[0,250,154],\r
1616     mediumturquoise:[72,209,204],\r
1617     mediumvioletred:[199,21,133],\r
1618     midnightblue:  [25,25,112],\r
1619     mintcream:     [245,255,250],\r
1620     mistyrose:     [255,228,225],\r
1621     moccasin:      [255,228,181],\r
1622     navajowhite:   [255,222,173],\r
1623     navy:          [0,0,128],\r
1624     oldlace:       [253,245,230],\r
1625     olive:         [128,128,0],\r
1626     olivedrab:     [107,142,35],\r
1627     orange:        [255,165,0],\r
1628     orangered:     [255,69,0],\r
1629     orchid:        [218,112,214],\r
1630     palegoldenrod: [238,232,170],\r
1631     palegreen:     [152,251,152],\r
1632     paleturquoise: [175,238,238],\r
1633     palevioletred: [219,112,147],\r
1634     papayawhip:    [255,239,213],\r
1635     peachpuff:     [255,218,185],\r
1636     peru:          [205,133,63],\r
1637     pink:          [255,192,203],\r
1638     plum:          [221,160,221],\r
1639     powderblue:    [176,224,230],\r
1640     purple:        [128,0,128],\r
1641     red:           [255,0,0],\r
1642     rosybrown:     [188,143,143],\r
1643     royalblue:     [65,105,225],\r
1644     saddlebrown:   [139,69,19],\r
1645     salmon:        [250,128,114],\r
1646     sandybrown:    [244,164,96],\r
1647     seagreen:      [46,139,87],\r
1648     seashell:      [255,245,238],\r
1649     sienna:        [160,82,45],\r
1650     silver:        [192,192,192],\r
1651     skyblue:       [135,206,235],\r
1652     slateblue:     [106,90,205],\r
1653     slategray:     [112,128,144],\r
1654     slategrey:     [112,128,144],\r
1655     snow:          [255,250,250],\r
1656     springgreen:   [0,255,127],\r
1657     steelblue:     [70,130,180],\r
1658     tan:           [210,180,140],\r
1659     teal:          [0,128,128],\r
1660     thistle:       [216,191,216],\r
1661     tomato:        [255,99,71],\r
1662     turquoise:     [64,224,208],\r
1663     violet:        [238,130,238],\r
1664     wheat:         [245,222,179],\r
1665     white:         [255,255,255],\r
1666     whitesmoke:    [245,245,245],\r
1667     yellow:        [255,255,0],\r
1668     yellowgreen:   [154,205,50]\r
1669   },\r
1670   \r
1671   setAdd: function (ele, to) {\r
1672     /*additive属性がsum (加法アニメーション)の場合*/\r
1673     if (ele.getAttributeNS(null, "additive") === "sum") {\r
1674       var attrValue = ele.parentNode.getAttributeNS(null, this.attrName);\r
1675       ele.addEventListener("beginEvent", function(evt) {\r
1676         to.forEach( function(x) {\r
1677           x.to.setAdditive(attrValue);\r
1678         } )\r
1679       }, false);\r
1680     }\r
1681   },\r
1682   setAccum: function (ele, to) {\r
1683     /*accumulate属性がsum (蓄積アニメーション)の場合*/\r
1684     if (ele.getAttributeNS(null, "accumulate") === "sum") {\r
1685       ele.addEventListener("repeatEvent", function(evt) {\r
1686         to.forEach( function(x) {\r
1687           x.to.call();\r
1688           x.to.setAccumulate(evt.detail);\r
1689         } )\r
1690       }, false);\r
1691     }\r
1692   },\r
1693   \r
1694   /*displayなど補間をしなくてもよい属性への対処するためのメソッド*/\r
1695   setString: function() {\r
1696     if (/^(?:display|class|edgeMode|(gradient|marker|pattern|maskContent|mask|patternContent|primitive)Units|in|in2|method|mode|operator|preserveAspectRatio|result|spacing|spreadMethod|stitchTiles|target|type|xlink:href|yChannelSelector|color-interpolation|(clip|fill)-rule|cursor|filter|font-(family|stretch|style|variant)|image-rendering|marker-(end|mid|start)|mask|overflow|pointer-events|shape-rendering|stroke-(linecap|linejoin)|text-(anchor|decoration|rendering)|visibility)$/.test(this.attrName) ) {\r
1697       this.mode = "discrete";\r
1698     }\r
1699   },\r
1700   \r
1701   /*小数点以下の桁数を指定するため、setValuesメソッドをオーバライドする*/\r
1702   degits: 1,\r
1703   setValues: function() {\r
1704     var s = this.$attribute.setValues.apply(this, arguments),\r
1705         degits = this.degits;\r
1706     s && s.forEach(function(x) {\r
1707       x.to.degit = degits;\r
1708     } );\r
1709     degits = void 0;\r
1710     return s;\r
1711   },\r
1712   \r
1713   /*#から始まる文字列#aabbcc、例えばを、rgb(.., .., ..,)形式へと変換するためのメソッド\r
1714    * initメソッドで使われる*/\r
1715   toRGB: function(rgbColor) {\r
1716            var keyword = this._keywords[rgbColor];\r
1717            if (keyword) {\r
1718              return "rgb(" + keyword.join(", ") + ")";\r
1719            }\r
1720            if (rgbColor && (rgbColor[0] === "#")) {  //#を含む場合\r
1721               var s = "rgb(",\r
1722                   _parseInt = parseInt;\r
1723               if (rgbColor.length < 5) {\r
1724                 var r = rgbColor[1],\r
1725                     g = rgbColor[2],\r
1726                     b = rgbColor[3],\r
1727                 rgbColor = "#" + r + r + g + g + b + b;\r
1728               }\r
1729               rgbColor.match(/\#(\w{2})(\w{2})(\w{2})/);\r
1730               s += _parseInt(RegExp.$1, 16)\r
1731                 + ", "\r
1732                 + _parseInt(RegExp.$2, 16)\r
1733                 + ", "\r
1734                 + _parseInt(RegExp.$3, 16)\r
1735                 + ")";\r
1736               r = g = b = void 0;\r
1737               return s;\r
1738            }\r
1739            return rgbColor;\r
1740         }\r
1741 \r
1742 /*initメソッドに追加処理\r
1743  * onメソッドについては、base.jsを参照のこと*/\r
1744 } ).on ("init", function(ele) {\r
1745   var to, \r
1746       keyTime = 0,\r
1747       /*関数toRGBはrgbColor形式への変換処理で使う*/\r
1748       toRGB = function(x) { return x; },\r
1749       isColor = /^(?:fill|stroke|stop-color|color)$/.test(this.attrName);\r
1750   if (isColor) {\r
1751     /*通常は、小数点以下の桁数を既定値の1桁とする\r
1752      *RGB形式では補間に、小数を使わないため、0桁に設定\r
1753      * (なお、この作業は、setKeyメソッドの前に済ませておく必要がある)*/\r
1754     this.degits = 0;\r
1755     /*たとえば、fill属性などである場合には、rgbColor形式への変換処理をする*/\r
1756     toRGB = this.toRGB.bind(this);\r
1757   }\r
1758   if (ele) {\r
1759     this.mode = ele.getAttributeNS(null, "calcMode") || "linear";\r
1760     this.setString();\r
1761     to = this.setKey(ele);\r
1762   }\r
1763   if (to) {\r
1764     this.funcs = to.map( function(x) {\r
1765       x.to.string = toRGB(x.to.string);\r
1766       x.to.from.string = toRGB(x.to.from.string);\r
1767       var s = x.call();\r
1768       /*x.keyTimeプロパティは区間を示しているため、区切り時刻に変換しておく\r
1769        * startKeyTimeプロパティは区間のスタート時点\r
1770        * endKeyTimeプロパティは区間のエンド地点*/\r
1771       s.startKeyTime = keyTime;\r
1772       keyTime = s.endKeyTime = keyTime + x.keyTime;\r
1773       return s;\r
1774     } )\r
1775      .filter( function(s) {\r
1776        if (!this.timeline.isResolved) {\r
1777          /*begin属性などにイベントを設定していた(未解決の)場合、後のs(0.1)がうまく作動せず、\r
1778           * 例外を出してしまうため、ここで返しておく*/\r
1779          return true;\r
1780        }\r
1781        /*splineモードで、かつ、アニメーションが無効な関数の場合は配列から外しておく\r
1782         * 無効な場合に関しては、$calcModeオブジェクトのcallメソッドを参照*/\r
1783        return (this.mode !== "spline") || !s.isNotAnimate;\r
1784     }, this );\r
1785     this.setAdd(ele, to);\r
1786     this.setAccum(ele, to);\r
1787   }\r
1788   keywords = toRGB = isColor = void 0;\r
1789 } )\r
1790 /*$animateTranformElementオブジェクト\r
1791  * animateTransform要素に関連するオブジェクト*/\r
1792  .up("$animateTransformElement")\r
1793  .mix({\r
1794    /*__transformListで何番目のアイテムかを示すプロパティ*/\r
1795    numberOfList: -1,\r
1796    \r
1797    /*type属性の値*/\r
1798    type: "translate",\r
1799    \r
1800    /*attributeName属性の値はtransformで固定*/\r
1801    attrName: "transform",\r
1802    \r
1803    /*明確にisCSSプロパティを設定しておくことで、プロトタイプチェーンを使わせず最適化*/\r
1804    isCSS: false,\r
1805    \r
1806    /*additive属性の値がsumのときにtrue*/\r
1807    isSum: false,\r
1808    \r
1809    /*transform属性の値に使われる数値は精密であることが求められるため、\r
1810     *小数点以下の桁数を決めるdegitsプロパティの値も大きくしておく*/\r
1811    degits: 15,\r
1812    \r
1813    /*tocallメソッド(後述の$motionElementオブジェクトも含む)で使うメソッド\r
1814     * __transformListの値を結合して、文字列として返す*/\r
1815    joinList: function(s) {\r
1816      var playList = this.element.__transformList;\r
1817      for (var i=0;i<playList.length;++i) {\r
1818        var item = playList[i],\r
1819            value = item.value;\r
1820        if (item.isSum) {\r
1821          s += " " + value;\r
1822        } else if (item.isPlaying) {\r
1823          /*他のanimateTransform要素がadditive属性の値にreplaceをすでに設定していた、\r
1824           *かつ、その要素がアニメーション再生中の場合はsを初期化*/\r
1825          s = value;\r
1826        }\r
1827      }\r
1828      return s.trim();\r
1829    },\r
1830    \r
1831    /*$animateElementオブジェクトのtocallメソッドをオーバライド*/\r
1832    tocall: function (advance) {\r
1833      if (this.numberOfList < 0) {\r
1834        throw new Error("Number of The List Error");\r
1835      }\r
1836      var currentItem = this.element.__transformList[this.numberOfList];\r
1837      currentItem.value = this.type+ "(" +this.$animateElement.tocall.call(this, advance)+ ")";\r
1838      currentItem.isPlaying = true;\r
1839      currentItem.isSum = this.isSum;\r
1840      return this.joinList(this.defaultValue || "");\r
1841    },\r
1842    \r
1843    /*後の_setFrameメソッドで使うダミー*/\r
1844    __setAttribute: function(){},\r
1845    \r
1846    _setFrame: function($list) {\r
1847     var currentFrame = $list.currentFrame;\r
1848      /*__transformListの中で、自分より後の項目に再生中のものがあれば、\r
1849       *アニメーションを再生させないで、後に続く項目に任せる*/\r
1850      var list = this.element.__transformList,\r
1851          isPostActive = false,\r
1852          length = list.length;\r
1853      if ( (length !== 1) && (this.numberOfList < (length - 1) ) ) {\r
1854        /*リストの項目が一つだけであったり、自分自身が最後尾であれば、アニメーションを再生する\r
1855         * また、後に続く項目で再生中のものがあったら、今回は再生しない*/\r
1856        for (var i=this.numberOfList+1;i<length;++i) {\r
1857          if (list[i].isPlaying) {\r
1858            isPostActive = true;\r
1859          }\r
1860        }\r
1861      }\r
1862      /*__setAttributeはダミーなので、アニメーションが再生されない*/\r
1863      this.setAttribute = isPostActive ? this.__setAttribute\r
1864                                     : this.$animateElement.setAttribute;\r
1865      /*上書きされたメソッドを呼び出す*/\r
1866      this.$animateElement._setFrame.call(this, $list);\r
1867    },\r
1868    \r
1869    _setEndFrame: function($list) {\r
1870      var list = this.element.__transformList;\r
1871      if (!list) {\r
1872        return;\r
1873      }\r
1874      var item = list[this.numberOfList];\r
1875      if (this.fill === "remove") {\r
1876        if (!item) {\r
1877          return;\r
1878        } else if (!this.isSum) {\r
1879          /*凍結処理をしないで、かつ、元の状態に戻して、効果が出ないようにする*/\r
1880          item.isPlaying = false;\r
1881        } else {\r
1882          /*凍結処理をしないで、かつ、効果を出すが、変形させないようにする*/\r
1883          item.value = "translate(0)";\r
1884        }\r
1885      } else {\r
1886        /*凍結処理をする\r
1887         * 自前のtocallメソッドはvalueプロパティを書きかえてしまうため、\r
1888         * 上書きメソッドを呼び出す*/\r
1889        item.value = this.type+ "(" \r
1890                                       +this.$animateElement.tocall.call( this, this.getAdvanceEnd($list) )+ ")";\r
1891      }\r
1892      this.setAttribute( this.joinList(this.defaultValue || "") );\r
1893      item = void 0;\r
1894    },\r
1895    \r
1896     /*setAddメソッドのオーバライド\r
1897      * additive属性のsumに対する振る舞いが異なるため*/\r
1898     setAdd: function() {},\r
1899   })\r
1900  .on("init", function (ele) {\r
1901    if (!ele || !ele.parentNode) {\r
1902      return;\r
1903    }\r
1904    this.getAttr = this.$attribute.getAttr;\r
1905    this.type = this.getAttr("type", "translate");\r
1906    this.attrName = "transform";\r
1907    var parent = this.element;\r
1908    this.isDefault = parent.hasAttributeNS(null, "transform");\r
1909    this.defaultValue = parent.getAttributeNS(null, "transform") || "";\r
1910    this.isSum = (this.getAttr("additive", "replace") === "sum");\r
1911    if (!parent.__transformList) {\r
1912      parent.__transformList = [];\r
1913      this.numberOfList = -1;\r
1914    }\r
1915    if (this.hasAttrValues()\r
1916     && (this.numberOfList < 0) ) {\r
1917      /*もし、今まで、このオブジェクトで、initメソッドを実行していなければ*/\r
1918      this.numberOfList = parent.__transformList.length;\r
1919      /*isPlayingプロパティはアニメーション再生終了後でもtrueとなるので注意*/\r
1920      parent.__transformList.push( {isPlaying: false,\r
1921                                    value: "translate(0)",\r
1922                                    isSum: this.isSum\r
1923                                   } );\r
1924    }\r
1925   } )\r
1926   .up("$motionElement")\r
1927   .mix( function() {\r
1928     /*$animateTransformElementオブジェクトのでは、うまくいかないため、\r
1929      * setRFrameとsetEndFrameメソッドを再定義*/\r
1930     this._setFrame = this.$animateElement._setFrame;\r
1931     this._setEndFrame = this.$animateElement._setEndFrame;\r
1932   })\r
1933   .mix( {\r
1934     numberOfList: -1,\r
1935     mode: "paced",\r
1936         \r
1937     /*hasAttrValuesメソッドのオーバライド\r
1938      * path属性に対応させるため*/\r
1939     hasAttrValues: function () {\r
1940       if (this.$attribute.hasAttrValues.call(this)) {\r
1941         return true;\r
1942       } else {\r
1943         return (this._ele.hasAttribute("path")\r
1944                  || this._ele.getElementsByTagNameNS(this.path.namespaceURI, "mpath").length);\r
1945       }\r
1946     },\r
1947     \r
1948     path: document.createElementNS("http://www.w3.org/2000/svg", "path"),\r
1949     \r
1950     rotate: "0",\r
1951     \r
1952     /*tocallメソッドのオーバライド*/\r
1953     tocall: function(advance) {\r
1954       /*モーションは仕様の関係上、かならず、CTMの一番初めに置かれ、前置積となる\r
1955      * なお、__transformListプロパティはtransform属性の値であり、CTMを表現する。\r
1956      * 必ず、CTMの一番目に設定する*/\r
1957       return ("translate(" +this.$animateElement.tocall.call(this, advance)+ ") "\r
1958               + this.joinList(this.defaultValue || "")).trim();\r
1959     },\r
1960     \r
1961     /*図の現在の角度rを求めて、rotate(rの文字列(最後に括弧はいらない)で返すメソッド*/\r
1962     getRotate: function(path, advanceLength, rotate) {\r
1963       /*パスセグメントの数値を求めてから、動いている図形の傾き角度r(ラジアンではなく度数)を算出する*/\r
1964       var length = path.getPathSegAtLength(advanceLength),\r
1965           seg = path.pathSegList.getItem(length),\r
1966           command = seg.pathSegTypeAsLetter,\r
1967           /*pi*180ではEdgeでうまく作動しない(原因は不明)*/\r
1968           pi = Math.PI;\r
1969       if (command === "M") {\r
1970         /*次のセグメントがどのコマンドによるかで、計算方式が違う*/\r
1971         var nextSeg = path.pathSegList.getItem(length+1),\r
1972             nextCommand = nextSeg.pathSegTypeAsLetter;\r
1973         if (nextCommand === "M") {\r
1974           return "";\r
1975         } else if (nextCommand === "L") {\r
1976           return ") rotate(" +(Math.atan2(nextSeg.y-seg.y, nextSeg.x-seg.x)/pi*180 + rotate)+ "";\r
1977         } else if (nextCommand === "C") {\r
1978           return ") rotate(" +(Math.atan2(nextSeg.y1-seg.y, nextSeg.x1-seg.x)/pi*180 + rotate)+ "";\r
1979         }\r
1980       } else if ((command === "L") && (length-1 >= 0)) {\r
1981         var preSeg = path.pathSegList.getItem(length-1);\r
1982         return ") rotate(" +(Math.atan2(seg.y-preSeg.y, seg.x-preSeg.x)/pi*180 + rotate)+ "";\r
1983       } else if (command === "C") {\r
1984         /*3次ベジェ曲線を微分する方法はニュートン法など数値解析の必要があるので、\r
1985          * 以下の通り、別の方法を採用する。\r
1986          * 現在位置から一歩進んだ曲線上の点Bをとり、それを、現在の点Aと結んで線分ABとしたとき、\r
1987          * その直線の傾きからおおよその角度を求める*/\r
1988          var point = path.getPointAtLength(advanceLength),\r
1989              x = point.x,\r
1990              y = point.y;\r
1991          /*一歩進んだ点*/\r
1992          point = path.getPointAtLength(advanceLength+1);\r
1993          return ") rotate(" +(Math.atan2(point.y-y, point.x-x)/pi*180 + rotate)+ "";\r
1994       }\r
1995     },\r
1996     \r
1997     /*this.$animateElement.tocallメソッドを置き換えるためのメソッド\r
1998      * mpath要素が指定されたときか、path属性のときにのみ使われる*/\r
1999     _tocallForPath: function(advance) {\r
2000       var path = this.path,\r
2001           advanceLength = advance * path.getTotalLength();\r
2002       /*全体の距離から、現在進めている距離を算出して、そこから、現在点を導き出す*/\r
2003       var point = path.getPointAtLength(advanceLength),\r
2004           rotate = 0; //追加すべき角度\r
2005       if (this.rotate === "0") {\r
2006         return point.x+ "," +point.y;\r
2007       } else if (this.rotate === "auto") {\r
2008         rotate = 0;\r
2009       } else if (this.rotate === "auto-reverse") {\r
2010         rotate = 180;\r
2011       } else {\r
2012         rotate = +this.rotate;\r
2013       }\r
2014       return point.x+ "," +point.y + this.getRotate(path, advanceLength, rotate);\r
2015     }\r
2016   } )\r
2017   .on("init", function (ele) {\r
2018     if (!ele || !ele.parentNode) {\r
2019      return;\r
2020     }\r
2021     /*type属性で変更されないように($animateTransformElementのinitメソッドを参照のこと)*/\r
2022     this.type = "translate";\r
2023     /*isSumプロパティは常にtrueにしておく。animateTransform要素とは挙動が違うため\r
2024      * また、$aniamteTransformElementのtocallメソッド参照*/\r
2025     this.isSum = true;\r
2026     this.mode = this.getAttr("mode", "paced");\r
2027     this.rotate = this.getAttr("rotate", "0");\r
2028     this.path = this.path.cloneNode(true);\r
2029     var mpath = ele.getElementsByTagNameNS(this.path.namespaceURI, "mpath");\r
2030     /*$animateは後で、プロパティを書き換えるために使う。tocallメソッドも参照*/\r
2031     var $animate = this.$animateElement;\r
2032     if (mpath.length) {\r
2033       var p = ele.ownerDocument.getElementById(\r
2034         mpath[0].getAttributeNS("http://www.w3.org/1999/xlink", "href").slice(1)\r
2035       );\r
2036       p && this.path.setAttributeNS(null, "d", p.getAttributeNS(null, "d"));\r
2037       this.$animateElement = $animate.up().mix ( {tocall: this._tocallForPath} );\r
2038     } else if (ele.hasAttributeNS(null, "path")) {\r
2039       this.path.setAttributeNS(null, "d", ele.getAttributeNS(null, "path"));\r
2040       this.$animateElement = $animate.up().mix ( {tocall: this._tocallForPath} );\r
2041     }\r
2042   } );\r
2043 \r
2044 function getDocument() \r
2045 {\r
2046   var svg = document.getElementsByTagName("object"),\r
2047       svgns = "http://www.w3.org/2000/svg";\r
2048   if (svg) {\r
2049     for (var i=0;i<svg.length;++i) {\r
2050       getElement( svg[i].getSVGDocument() );\r
2051     }\r
2052   }\r
2053   /*SVG文書から呼び出されたときも処理する*/\r
2054   getElement(document);\r
2055   /*idはアニメの中止ハンドル*/\r
2056   var id = __step(),\r
2057       idstop = function() {\r
2058         /*アニメーションを中止する関数*/\r
2059         window.cancelAnimationFrame && cancelAnimationFrame(id);\r
2060       };\r
2061   base("$frame").on("pauseAnimation", idstop);\r
2062   window.addEventListener("unload", idstop);\r
2063   \r
2064   /*文書からアニメーション関連要素を取り出して、オブジェクトを初期化*/\r
2065   function getElement (svgDoc) {\r
2066       var $set = base("$calcMode").$attribute.$setElement,\r
2067           $animate = $set.$animateElement;\r
2068       init($set, "set");\r
2069       init($animate, "animate");\r
2070       init($animate, "animateColor");\r
2071       init($animate.$animateTransformElement, "animateTransform");\r
2072       init($animate.$animateTransformElement.$motionElement, "animateMotion");\r
2073         /*リンクのハッシュ読み取りで、ハイパーリンクのイベント処理\r
2074        * たとえば、a要素のxlink:href="#hoge"で、<animate id="hoge"のとき、\r
2075        * animate要素がハイパーリンク作動と同時に動くようになる\r
2076        * \r
2077        * ただし、SMIL アニメーションの仕様では、\r
2078        * animate要素の開始時刻まで、時を進める操作をするだけ*/\r
2079        svgDoc.defaultView.addEventListener("hashchange", function() {\r
2080            var hash = svgDoc.defaultView.location.hash.slice(1);\r
2081            svgDoc.getElementById(hash).beginElement();\r
2082          });\r
2083 \r
2084       function init (obj, name) {\r
2085         var eles = svgDoc.getElementsByTagNameNS(svgns, name)\r
2086         for (var i=0;i<eles.length;++i) {\r
2087           obj.up().init(eles.item(i));\r
2088         }\r
2089         eles = obj = void 0;\r
2090       };\r
2091   };\r
2092 }\r
2093 \r
2094 window.addEventListener && window.addEventListener("load", getDocument);\r
2095 \r
2096 function __step() {\r
2097 /*EdgeはhasFeatureメソッドでtrueを返す*/\r
2098 if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Animation", "1.1")\r
2099     || (window.navigator.userAgent.toLowerCase().indexOf("edge") > 0)) {\r
2100   if (window.requestAnimationFrame && requestAnimationFrame) {\r
2101     /*IE11やEdgeなどSMILアニメーションに対応していないブラウザ用*/\r
2102     /*cancelはアニメーションの中止ハンドル*/\r
2103     var cancel = {\r
2104        handle: null\r
2105       };\r
2106     (function(frame) {\r
2107       var _cancel = cancel; /*cancelのエイリアス*/\r
2108       var step = function () {\r
2109         if (!this.isPaused) {\r
2110           frame++;\r
2111           try {\r
2112             this.setFrame(frame);\r
2113           } catch(e) {\r
2114           }\r
2115           _cancel.handle = requestAnimationFrame(step);\r
2116         }\r
2117       }.bind(base("$frame"));\r
2118       _cancel.handle = requestAnimationFrame(step);\r
2119     })(-1);\r
2120     return cancel;\r
2121   } else {\r
2122     setInterval( (function(frame) {\r
2123       var $f = base("$frame");\r
2124       return function () {\r
2125         frame++;\r
2126         $f.setFrame(frame);\r
2127       };\r
2128     })(-1), 1 );\r
2129   }\r
2130 }\r
2131 }\r
2132 //#endif // _SMIL_IDL_\r