OSDN Git Service

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