OSDN Git Service

cd31d864376c040555e9276c710ff92246163cf5
[sie/sie.git] / org / w3c / dom / smil.js
1 /*SIE-SVG without Plugin under LGPL2.1 & GPL2.0 & Mozilla Public License\r
2  *公式ページは http://sie.sourceforge.jp/\r
3  *利用方法は <script defer="defer" type="text/javascript" src="sie.js"></script>\r
4  *http://sie.sourceforge.jp/\r
5  *Usage: <script defer="defer" type="text/javascript" src="sie.js"></script>\r
6  */\r
7 /* ***** BEGIN LICENSE BLOCK *****\r
8  * Version: MPL 1.1/GPL 2.0/LGPL 2.1\r
9  *\r
10  * The contents of this file are subject to the Mozilla Public License Version\r
11  * 1.1 (the "License"); you may not use this file except in compliance with\r
12  * the License. You may obtain a copy of the License at\r
13  * http://www.mozilla.org/MPL/\r
14  *\r
15  * Software distributed under the License is distributed on an "AS IS" basis,\r
16  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License\r
17  * for the specific language governing rights and limitations under the\r
18  * License.\r
19  *\r
20  * The Original Code is the Mozilla SVG Cairo Renderer project.\r
21  *\r
22  * The Initial Developer of the Original Code is IBM Corporation.\r
23  * Portions created by the Initial Developer are Copyright (C) 2004\r
24  * the Initial Developer. All Rights Reserved.\r
25  *\r
26  * Parts of this file contain code derived from the following files(s)\r
27  * of the Mozilla SVG project (these parts are Copyright (C) by their\r
28  * respective copyright-holders):\r
29  *    layout/svg/renderer/src/libart/nsSVGLibartBPathBuilder.cpp\r
30  *\r
31  * Contributor(s):DHRNAME revulo\r
32  *\r
33  * Alternatively, the contents of this file may be used under the terms of\r
34  * either of the GNU General Public License Version 2 or later (the "GPL"),\r
35  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),\r
36  * in which case the provisions of the GPL or the LGPL are applicable instead\r
37  * of those above. If you wish to allow use of your version of this file only\r
38  * under the terms of either the GPL or the LGPL, and not to allow others to\r
39  * use your version of this file under the terms of the MPL, indicate your\r
40  * decision by deleting the provisions above and replace them with the notice\r
41  * and other provisions required by the GPL or the LGPL. If you do not delete\r
42  * the provisions above, a recipient may use your version of this file under\r
43  * the terms of any one of the MPL, the GPL or the LGPL.\r
44  *\r
45  * ***** END LICENSE BLOCK ***** */\r
46 /*\r
47  * Copyright (c) 2000 World Wide Web Consortium,\r
48  * (Massachusetts Institute of Technology, Institut National de\r
49  * Recherche en Informatique et en Automatique, Keio University). All\r
50  * Rights Reserved. This program is distributed under the W3C's Software\r
51  * Intellectual Property License. This program is distributed in the\r
52  * hope that it will be useful, but WITHOUT ANY WARRANTY; without even\r
53  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR\r
54  * PURPOSE.\r
55  * See W3C License http://www.w3.org/Consortium/Legal/ for more details.\r
56  */\r
57 // File: smil.idl\r
58 /*\r
59 #ifndef _SMIL_IDL_\r
60 #define _SMIL_IDL_\r
61 \r
62 #include "dom.idl"\r
63 \r
64 #pragma prefix "dom.w3c.org"\r
65 module smil\r
66 {\r
67   typedef dom::DOMString DOMString;\r
68 */\r
69 /*ElementTimeControlはSVGAnimationElementに統合させる。\r
70  *というのは、多重継承が難しいため\r
71  */\r
72 function ElementTimeControl(ele) {\r
73   this._tar = ele;\r
74   /*_startと_endプロパティはミリ秒数を収納する。\r
75    *_startはアニメ開始時の秒数のリスト。_finishはアニメ終了時の秒数のリスト。\r
76    *なお、文書読み込み終了時(アニメ開始時刻)の秒数を0とする。\r
77    */\r
78   this._start = [];\r
79   this._finish = null;\r
80 };\r
81 ElementTimeControl.prototype = {\r
82   /*void*/  beginElement : function() {\r
83     var ttd = this.ownerDocument, evt = ttd.createEvent("TimeEvents");\r
84     evt.initTimeEvent("beginEvent", ttd.defaultView, 0);\r
85     this.dispatchEvent(evt);\r
86   },\r
87   /*void*/  endElement : function() {\r
88     var ttd = this.ownerDocument, evt = ttd.createEvent("TimeEvents");\r
89     evt.initTimeEvent("endEvent", ttd.defaultView, 0);\r
90     this.dispatchEvent(evt);\r
91   },\r
92   /*void*/  beginElementAt : function(/*float*/ offset) {\r
93     var ntc = this.ownerDocument.documentElement.getCurrentTime(),\r
94         start = this._start || [];\r
95     for (var i=0,sli=start.length;i<sli;++i) {\r
96       if (start[i] === (offset+ntc)) {\r
97         ntc = start = offset = void 0;\r
98         return;\r
99       }\r
100     }\r
101     start.push(offset + ntc);\r
102     this._start = start;\r
103   },\r
104   /*void*/  endElementAt : function(/*float*/ offset) {\r
105     var ntc = this.ownerDocument.documentElement.getCurrentTime(),\r
106         fin = this._finish || [];\r
107     for (var i=0,fli=fin.length;i<fli;++i) {\r
108       if (fin[i] === (offset+ntc)) {\r
109         ntc = fin = offset = void 0;\r
110         return;\r
111       }\r
112     }\r
113     fin.push(offset + ntc);\r
114     this._finish = fin;\r
115   }\r
116 };\r
117 \r
118 base("$event").up("TimeEvents").mix( {\r
119   /*readonly attribute views::AbstractView  this.view;*/\r
120   /*readonly attribute long*/   detail: 0,\r
121 /*void*/  initTimeEvent: function(/*DOMString*/ typeArg,\r
122                                      /*views::AbstractView*/ viewArg,\r
123                                      /*long*/ detailArg) {\r
124     this.type = typeArg;\r
125     this.view = viewArg;\r
126     this.detail = detailArg;\r
127   }\r
128 } );\r
129 \r
130 /*$frame オブジェクト\r
131  * 全体のフレームの管理を行う\r
132  */\r
133 base("$frame").mix ( {\r
134   /*フレームレート。1ミリ秒何フレームか。計算を省略するためミリ秒使用*/\r
135   fpms: 0.024,\r
136 \r
137   /*タイムラインのリスト (時間区間の設定ができる)*/\r
138   timelines: [],\r
139 \r
140   /*開始フレーム数。アニメーションの開始条件となる\r
141    * 単位はフレーム数であって、秒数ではない*/\r
142   begin: 0,\r
143   \r
144   /*開始時刻 (文書が読み込まれたときにDate.nowなどで取得)\r
145    * 単位はミリ秒数であって、フレーム数ではない*/\r
146   startTime: 0,\r
147 \r
148   /*活動継続時間 (Active Duration)のフレーム数。アニメーションの継続条件となる\r
149    * 単位はフレーム数であって、秒数ではない*/\r
150   activeTime: Number.MAX_VALUE,\r
151   \r
152   /*現在のフレーム数*/\r
153   currentFrame: 0,\r
154   \r
155   /*タイムラインの優先度*/\r
156   rank: 0,  \r
157 \r
158   /*setFrame メソッド\r
159    * フレーム数を数値num まで進めるか、戻す*/\r
160   setFrame: function( /*number*/ num) {\r
161     if((num < this.begin) || (num >= (this.begin+this.activeTime))) {\r
162       return;\r
163     }\r
164     this.currentFrame = num;\r
165     var timelines = this.timelines;\r
166     for (var i=0;i<timelines.length;++i) {\r
167       timelines[i].setFrame(num);\r
168     }\r
169   },\r
170   \r
171   /*addLineメソッド\r
172    * タイムラインを追加したあと、trueを返す\r
173    * ただし、引数objのobj.beginとobj.activeTimeが定まっていない場合はfalseを返す*/\r
174   addLine: function( /*$frame*/ obj ) {\r
175     if(!obj || (!obj.begin && (obj.begin !== 0))\r
176     || (!obj.activeTime && (obj.activeTime !== 0)) ) {\r
177       /*どちらのプロパティも未確認の場合、タイムラインは追加されない*/\r
178       return false;\r
179     }\r
180     if ( this.timelines.indexOf(obj) >= 0 ) {\r
181       this.removeLine(obj);\r
182     }\r
183     this.timelines.push( obj );\r
184     /*ランクをソートしておくことで、タイムラインを実行する順序を決める\r
185      * 主に、begin用のタイムラインの前に、end用のタイムラインが実行されるのを防ぐのが目的*/\r
186     this.timelines.sort( function (a, b) {\r
187       return a.rank - b.rank;\r
188     } )\r
189     return true;\r
190   },\r
191   \r
192   /*removeLine メソッド\r
193    * 指定されたタイムラインのオブジェクトを、リストから削除する*/\r
194   removeLine: function( /*$frame*/ timeline ) {\r
195     var list = this.timelines,\r
196         j = list.indexOf(timeline);\r
197     if (j > -1) {\r
198       list.splice(j, 1);      //Arrayのspliceを利用して、リストからtimelineを排除\r
199     }\r
200     list = j = void 0;\r
201   }\r
202 } ).mix( function($frame) {  \r
203   /*$begin オブジェクト\r
204    * 開始のタイミングを計算する*/\r
205   $frame.up("$begin").mix( {\r
206 \r
207     /*開始時刻やタイミングが書かれた文字列*/\r
208     string: "",\r
209 \r
210     /*イベントやindefinteで未解決かどうか*/\r
211     isResolved: false,\r
212     \r
213     /*イベントが適用される要素*/\r
214     eventTarget: document.documentElement,\r
215     \r
216     /*現在のフレーム数を改めて初期化*/\r
217     currentFrame: 0,\r
218     \r
219     /*イベント同期で使う時間差のフレーム数*/\r
220     eventOffset: 0,\r
221     \r
222     /*repeat(1)など文字列内に書かれたリピート回数*/\r
223     repeat: 0,\r
224     \r
225     /*accessKey(a)の"a"などキーイベントの対象となる文字列*/\r
226     accessKey: "",\r
227 \r
228     /*trim メソッド\r
229      * 文字列中の空白を除去*/\r
230     trim: function(str) {\r
231       /*strがString型以外のときは必ずエラーを出す*/\r
232       return str.replace(/[\s\n]+/g, "");\r
233     },\r
234 \r
235     /*offset メソッド\r
236      * 引数に渡された文字列から、ミリ秒単位に変換した時間を、解析して返す*/\r
237     offset: function(str) {\r
238       str = str || "0";\r
239       var plusminus = str.charAt(0),\r
240           /*parseFloatのエイリアス*/\r
241           _float = parseFloat,\r
242           s = _float( str.match(/[\d.]+ms$/) || "0") + sec() + min() + h();\r
243       if (plusminus === "-") {\r
244         s *= -1;\r
245       }\r
246       plusminus = _float = sec = min = h = void 0;\r
247       return s;\r
248       \r
249       /*00:00:0と00:0と、0sなどの文字列をミリ秒へ変換*/\r
250       function sec() {\r
251         return str2num( 1000, /[\d.]+s$/, /[\d.]+$/ );\r
252       };\r
253       function min() {\r
254         return str2num( 60000, /[\d.]+min$/, /\d\d:[^:]+$/ );\r
255       };\r
256       function h() {\r
257         return str2num( 3600000, /\d+:\d\d:/, /[\d.]+h$/ );\r
258       };\r
259       function str2num(s, /*RegExp*/ a, /*RegExp*/ b) {\r
260         return s*( _float(str.match(a) || "0") || _float(str.match(b) || "0") );\r
261       };\r
262     },\r
263 \r
264     /*event メソッド\r
265      * 引数の文字列から、idとイベントに相当する文字列のプロパティを持ったオブジェクトを返す\r
266      * idがない場合や、イベントがない場合は空文字列を該当のプロパティに入れる*/\r
267     event: function(str) {\r
268       str = str || "";\r
269       if (/[\+\-]/.test(str)) {\r
270         /*数値がある場合は切り取っておく*/\r
271         str = str.slice(0, str.search(/[\+\-]/));\r
272       }\r
273       if (str.indexOf(".") > -1) {\r
274         /*ドットが見つかった場合、IDとイベントに分けておく*/\r
275         var ide = str.split(".");\r
276         /* エラーが起きて、idが空文字列ならば、evtも空文字列。逆も然り*/\r
277         return {\r
278           id: (ide[1] && ide[0]),\r
279           event: (ide[0] && ide[1])\r
280         };\r
281       } else {\r
282         return {\r
283           id: "",\r
284           event: str\r
285         };\r
286       }\r
287     },\r
288     \r
289     /*parse メソッド\r
290      * stringプロパティを解析して、フレーム数を算出し、結果を$frame.beginプロパティに出力\r
291      * また、イベントリスナーに登録をしておく*/\r
292     parse: function() {\r
293       this.begin = 0;\r
294       var str = this.trim(this.string),\r
295           plusminus = str.search(/[\+\-]/),\r
296           event = null,\r
297           ele;\r
298       if (plusminus > 0) {\r
299         /*Event-Value +/- Clock-Value の場合*/\r
300         this.begin = this.offset( str.slice(plusminus) );\r
301         event = this.event(str);\r
302       } else if ( /[^\+\-\d]/.test(str.charAt(0)) ) {\r
303         /*Event-Valuen のみの場合*/\r
304         event = this.event(str);\r
305       } else {\r
306         /*+/- Clock-Value のみの場合*/\r
307         this.begin = this.offset( str );\r
308         /*イベントもindefiniteもないので、解決済みと考える*/\r
309         this.isResolved = true;\r
310       }\r
311       /*もしもあれば、リピートの回数を求める*/\r
312       this.repeat = /repeat\((\d+)\)/.test(str) ? +RegExp.$1 : 0;\r
313       /*もしもあれば、押されるはずのキーを求める*/\r
314       this.accessKey = /accessKey\(([^\)]+?)\)/.test(str) ? RegExp.$1 : "";\r
315       this.begin = Math.floor( this.begin * this.fpms);\r
316       if (event) {\r
317         ele = event.id ? this.eventTarget.ownerDocument.getElementById(event.id)\r
318                         : this.eventTarget;\r
319         /*イベントの時間差を設定しておく*/\r
320         this.eventOffset = this.begin;\r
321         if (this.repeat > 0) {\r
322           ele && ele.addEventListener("repeatEvent", (function(evt) {\r
323             if (evt.detail === this.repeat) {\r
324               this.listener(evt);\r
325             } }).bind(this), true);\r
326         } else if (this.accessKey) {\r
327           document.documentElement.addEventListener("keydown", (function(evt) {\r
328             if (evt.char === this.accessKey) {\r
329                 this.listener(evt);\r
330               } }).bind(this), false);\r
331         } else {\r
332           var evtName = /^(?:begin|end|repeat)$/.test(event.event) ? event.event + "Event"\r
333                           : event.event;\r
334           ele && ele.addEventListener(evtName, this.listener.bind(this), false);\r
335         }\r
336       } else {\r
337         /*イベントの影響を防ぐため\r
338          * すでに、フレームオブジェクトのタイムラインには登録済みなので、\r
339          * フレームではなく、自分独自のタイムラインに登録しておけばよい*/\r
340         this.$frame = this;\r
341       }\r
342       s = event = str = plusminus = ele = void 0;\r
343       return this;\r
344     },\r
345     \r
346     /*イベントのリスナーとして、parseメソッドで使う*/\r
347     listener: function(evt) {\r
348       evt = evt || { timeStamp: this.startTime };\r
349       if (!evt.timeStamp && (evt.timeStamp !== 0)) {\r
350         throw new Error();\r
351       }\r
352       /*イベントのリスナーが遅かった場合の、誤差の演算をしておく*/\r
353       this.begin = this.eventOffset + this.$frame.currentFrame - Math.floor( (Date.now() - evt.timeStamp) * this.fpms );\r
354       var s = this.$activate;\r
355       s.begin = this.begin;\r
356       this.activeTime = s.call() || Number.MAX_VALUE;\r
357       this.simpleDuration = s.simpleDur;\r
358       s = void 0;\r
359       this.$frame.addLine(this);\r
360     }\r
361     \r
362   /*$activate オブジェクト\r
363    * 活動継続時間などを計算するための計算実体\r
364    * $begin オブジェクトからの継承*/\r
365   } ).up("$activate").of( {\r
366     \r
367     /*単純継続時間のパースされる前の文字列*/\r
368     dur: "indefinite",\r
369     \r
370     /*活動をストップさせるためのオブジェクト*/\r
371     end: $frame.$begin.up("$end"),\r
372 \r
373     /*リピート回数*/\r
374     repeatCount: null,\r
375     \r
376     /*繰り返し時間*/\r
377     repeatDur: null,\r
378 \r
379     /*単純継続時間 (単位はフレーム数)*/\r
380     simpleDur: function() {\r
381       return ( (this.dur === "indefinite") || !this.dur ) ?\r
382                 null\r
383               : Math.floor(this.offset(this.dur) * this.fpms) ;\r
384     },\r
385 \r
386     /*最小値に制限される\r
387      * 最小値 <= 活動継続時間 とならなければならない*/\r
388      min: "0",\r
389      \r
390     /*最大値に制限される\r
391      * 活動継続時間 <= 最大値 とならなければならない*/\r
392      max: "indefinite",\r
393     \r
394     /*解決した(計算する)ときの時間*/\r
395     resolvedTime: function() {\r
396       return Date.now();\r
397     },\r
398     \r
399     /*関数型の呼び出しメソッド\r
400      * base.jsのofメソッドを活用して、関数型っぽい処理をする\r
401      * 以下では、活動継続時間を算出\r
402      * 計算方法はSMILアニメーション 3.3.4節を参照\r
403      * http://www.w3.org/TR/smil-animation/#ComputingActiveDur\r
404      */\r
405     call: function() {\r
406       var ind = "indefinite",\r
407           dur = this.simpleDur,\r
408           isIndefRepeatCount = (this.repeatCount === ind),\r
409           isIndefRepeatDur = (this.repeatDur === ind),\r
410           isIndefEnd = (this.end === ind),\r
411           isDur = dur || (dur === 0),\r
412           isEnd = this.end || (this.end === 0),\r
413           isRepeatCount = this.repeatCount || (this.repeatCount === 0),\r
414           isRepeatDur = this.repeatDur || (this.repeatDur === 0),\r
415           actList = [],\r
416           min = Math.floor(this.offset(this.min) * this.fpms),\r
417           max = (this.max === ind) ? null : Math.floor(this.offset(this.max) * this.fpms),\r
418           s;\r
419       if (indef()) {\r
420         return null;\r
421       }\r
422       if (isDur && this.repeatCount && !isIndefRepeatCount) {\r
423         actList.push( dur * this.repeatCount );\r
424       }\r
425       if (isRepeatDur && !isIndefRepeatDur) {\r
426         actList.push( Math.floor( this.offset(this.repeatDur) * this.fpms) );\r
427       }\r
428       if (isEnd && !isIndefEnd) {\r
429         actList.push( this.end - this.begin );\r
430       }\r
431       if (isDur && !isRepeatCount && !isRepeatDur) {\r
432         /*repeatCountやrepeatDur属性が指定されていない場合*/\r
433         actList.push( dur );\r
434       }\r
435 \r
436       /*長くなるため、インライン関数を活用\r
437        * indef関数は活動継続時間が不定かどうか、もし、不定なら真を返す*/\r
438       function indef() {\r
439         if(isIndefEnd) {\r
440           return true;\r
441         } else if (isEnd) {\r
442           return false;\r
443         }\r
444         return !!( (!isDur && !isRepeatDur)\r
445                    || (isIndefRepeatCount && !isRepeatDur)\r
446                    || (isIndefRepeatDur && !isRepeatCount)\r
447                    || (isIndefRepeatCount && isIndefRepeatDur)\r
448                  );\r
449       };\r
450       \r
451       ind = dur = isIndefRepeatCount = isIndefRepeatDurindef = isDur = isEnd = isRepeatDur = isRepeatCount = indef = void 0;\r
452 \r
453       if (actList.length === 1) {\r
454         s = actList[0];\r
455       } else if(actList.length > 1) {\r
456         /*属性が競合するときは、最小値をとること (SMILアニメーション 3.3.4節)*/\r
457         s = Math.min.apply(Math, actList);\r
458       } else {\r
459         return null;\r
460       }\r
461       if ( max && (min > max) ) {\r
462         return s;\r
463       }\r
464       min && (min > s) && (s = min);\r
465       max && (max < s) && (s = max);\r
466       return s;\r
467     }\r
468   } );\r
469   $frame.$begin.$end.of( {\r
470     call: function() {\r
471       if (!this.string) {\r
472         return null;\r
473       }\r
474       this.parse(this.string);\r
475       return this.isResolved ? this.begin\r
476                              : "indefinite";\r
477     }\r
478   } ).mix( {\r
479     /*イベントリスナー用の関数*/\r
480     listener: function(evt) {\r
481       evt = evt || { timeStamp: this.startTime };\r
482       if (!evt.timeStamp && (evt.timeStamp !== 0)) {\r
483         throw new Error();\r
484       }\r
485       if (this.begin <= 0) {\r
486         /*強制的に終了させる*/\r
487         this.removeLine(this.$begin);\r
488       }\r
489       this.begin = this.eventOffset + this.$frame.currentFrame - Math.floor( (Date.now() - evt.timeStamp) * this.fpms );\r
490       var s = this.$begin.$activate;\r
491       s.end = this.begin;\r
492       /*未解決だったendの値が、イベントの発生により、解決して再定義されたとき、\r
493        * $activateオブジェクトを使って活動継続時間を再計算する*/\r
494       this.$begin.activeTime = s.call();\r
495       s = void 0;\r
496     }\r
497   } );\r
498 } );\r
499 /*$from オブジェクト\r
500  * 呈示値 (presentation value)の計算をする。値そのものを返すための計算実体*/\r
501 base("$from").of( {\r
502   /*呈示値が書かれた文字列*/\r
503   string: "",\r
504   \r
505   /*呈示値の数値の部分だけを抜き出した配列を返す*/\r
506   numList: function() {\r
507     var s  = this.string.match(/[\-\+]?[\d\.]+(?:[eE][\-\+]?[\d\.]+)?/g)\r
508              || [];\r
509     if (s) {\r
510       /*mapメソッドで代用してもよい*/\r
511       for (var i=0;i<s.length;++i) {\r
512         s[i] = parseFloat(s[i]);\r
513       }\r
514     }\r
515     return s;\r
516   },\r
517   \r
518   /*呈示値の文字部分だけを抜き出した配列を返す*/\r
519   strList: function() {\r
520     /*replaceメソッドで1E-10などの対策*/\r
521     return this.string.replace(/\d[eE][\-\+\d]/g, "")\r
522                       .match(/[^\d\-\+\.]+/g);\r
523   },\r
524   \r
525   from: base("$from").up().mix( {\r
526           from: null\r
527         } ),\r
528   \r
529   /*$toオブジェクトにこのオブジェクトを適用させる関数*/\r
530   call: function() {\r
531     if (this.numList.length) {\r
532       var additive = [],\r
533           accumlate = [],\r
534           tu = this.underlying;\r
535       for (var i=0;i<this.numList.length;++i) {\r
536         /*0で配列を初期化しておく*/\r
537         additive[i] = accumlate[i] = 0;\r
538       }\r
539       tu.additive = additive;\r
540       tu.accumlate = accumlate;\r
541     }\r
542     /*文字部分の配置パターンは4通りあるので、ここでstrListを処理\r
543      * (1) a 0 の場合\r
544      * (2) 0 a\r
545      * (3) a 0 a (ノーマルパターン)\r
546      * (4) 0 a 0\r
547      * これらのパターンのうち、(1)(2)(4)を(3)のパターンに統一したのが以下の処理*/\r
548     /*文字列が1aのように、数値で始まるかどうか。始まったら真*/\r
549     if (!this.string || !this.numList.length || !this.strList) {\r
550       return this.numList;\r
551     }\r
552     var isNormal = (this.numList.length < this.strList.length);\r
553     if (/^[\-\+]?[\d\.]/.test(this.string) && !isNormal) {\r
554       /*文字列が1aのように、数値で始まる場合*/\r
555       this.strList.unshift("");\r
556     }\r
557     if (/\d$/.test(this.string) && !isNormal) {\r
558       /*文字列がa1のように、数値で終わる場合*/\r
559       this.strList.push("");\r
560     }\r
561     return this.numList;\r
562   }\r
563     \r
564 } )\r
565  .mix( {\r
566    /*advanceメソッドで使われる有効数字の桁数 (小数点の桁数を決めるときに使う)*/\r
567    degit: 0,\r
568    \r
569    /*additve属性やaccumlate属性が設定された、累積アニメーションか、加法アニメーションで使われる*/\r
570    underlying: {\r
571      additive: [0],\r
572      accumlate: [0]\r
573    },\r
574    \r
575    /*advance メソッド\r
576     * アニメーションの進行具合を示す進捗率 t (0 <= t <= 1)をもとに、現在の呈示値を算出するためのもの\r
577     * callメソッドが前もって呼び出されていることが前提となる*/\r
578     advance: function(t) {\r
579       if ( (t < 0) || (1 < t)) {\r
580         throw new Error("An Invalid Number Error");\r
581       }\r
582       if (!this.string || !this.from.length) {\r
583         return "";\r
584       }\r
585       var str = "",\r
586           numList = this.numList,\r
587           strList = this.strList,\r
588           fromNumList = this.from,\r
589           deg = this.degit,\r
590           underlying = this.underlying,\r
591           additive = underlying.additive,\r
592           accumlate = underlying.accumlate;\r
593       \r
594       for (var i=0,nuli=numList.length;i<nuli;++i) {\r
595         /*原点Oを(0,0,...0)とおく\r
596          *$fromと$toを、原点Oからの二つのベクトル (n次空間のベクトル)、ベクトルOFとベクトルOTと考える\r
597          *$fromと$toの二つの端の点FとTを結ぶ線分を、t : 1-t で内分する点をPとおく\r
598          * このときのベクトルOPを求めたのが以下の式*/\r
599         str += ( t * numList[i] + (1 - t) * fromNumList[i] + additive[i] + accumlate[i]).toFixed(deg);\r
600         strList && ( str += strList[i+1] );\r
601       }\r
602       /*文字列はcallメソッドにより、a0aのパターンになっているので、aの部分を追加*/\r
603       str = (strList ? strList[0] : "") + str;\r
604       numList = strList = fromNumList = i = nuli = deg = underlying = additive = accumlate = void 0;\r
605       return str;\r
606     },\r
607     \r
608     /*distanceメソッド\r
609      * fromベクトルから自分自身のベクトルへの距離 (ノルム)の数値を返す。callメソッドを使うので注意すること*/\r
610      distance: function(from) {\r
611        if (!from) {\r
612           return 0;\r
613        }\r
614        var toList = this.call(),\r
615            fromList = from.call ? from.call() : from,\r
616            s = 0;\r
617        if (!toList || !fromList) {\r
618          return 0;\r
619        }\r
620        for (var i=0, tli=toList.length; i<tli; ++i) {\r
621          s += (toList[i] - fromList[i])*(toList[i] - fromList[i]);\r
622        }\r
623        return Math.sqrt(s);\r
624      }\r
625   } )\r
626   /*fromプロパティの初期化*/\r
627  .up("$to").from = null;\r
628  \r
629  /*計算モードを定めるための計算実体\r
630   *補間の細かい制御などを行う*/\r
631  base("$calcMode").mix({\r
632    /*callメソッドで使う関数*/\r
633    _f: function (t) {\r
634          /*tは進捗率*/\r
635          var tkey = this.keyTime;\r
636          if ( (tkey === 0) && t) {\r
637            t = 0; \r
638          } else if (!tkey || !isFinite(tkey) ) {\r
639            return this.string;\r
640          } else {\r
641            t = t / tkey;\r
642            t = (t > 1) ? Math.floor(t) : t;\r
643          }\r
644          tkey = void 0;\r
645          return isNaN(t) ? this.string\r
646                          : this.to.advance(t);\r
647      }\r
648  }).of( {\r
649 \r
650    /*計算モード (calcMode属性の値)*/\r
651    mode: "linear",\r
652 \r
653    /*keyTimesの区間\r
654     * たとえば、"0, 0.5, 0.7, 1"の場合、時間の区間はそれぞれ、0.5 (=0.5-0)  0.2 (=0.7-0.5)  0.3 (=1-0.7)である\r
655     * このうち、どれか一つが値として入力される*/\r
656    keyTime: 1,\r
657    \r
658    /*keySpline属性の値を設定*/\r
659    keySplines: null,\r
660    \r
661    /*全体の行列ノルム(距離)*/\r
662    norm: 1,\r
663 \r
664    /*無効だった場合の呈示値*/\r
665    string: "",\r
666    \r
667    /*与えられたアニメーションの進捗率を使った時間の圧縮率を計算して呈示値を返すための関数を作る*/\r
668    call: function() {\r
669      var f = this._f.bind(this);\r
670      if (this.mode === "linear") {\r
671        this.to.call();\r
672        return f;\r
673      } else if (this.mode === "paced") {\r
674        /*keyTimes属性は無視され、ベクトルの距離の割合から計算される*/\r
675        this.keyTime = this.to.distance(this.to.from) / this.norm;\r
676        return f;\r
677      } else if (this.mode === "spline") {\r
678        var tk = this.keySplines,\r
679            /*必ず関数を返すようにするため、円周率を返す関数tfを返して、nullの代わりとする*/\r
680            tf = function(x) {\r
681                  return Math.PI;\r
682            };\r
683          if (!tk) {\r
684            return tf;\r
685          }\r
686         for (var i=0,tki = NaN;i<tk.length;++i) {\r
687          tki = tk[i];\r
688          if (isNaN(tki)) {\r
689            return tf;\r
690          }\r
691          if ( (tki < 0) || (1 < tki)) {\r
692            return tf;\r
693          }\r
694        }\r
695        this.to.call();\r
696        var x2 = tk[0],\r
697            y2 = tk[1],\r
698            x3 = tk[2],\r
699            y3 = tk[3],\r
700            x4 = 1,\r
701            y4 = 1,\r
702            Ax = x4-3*(x3-x2),\r
703            Bx = 3*(x3-2*x2),\r
704            Cx = 3*x2,\r
705            Ay = y4-3*(y3-y2),\r
706            By = 3*(y3-2*y2),\r
707            Cy = 3*y2,\r
708            _newton = Math.qubicnewton; //高速化のためのエイリアス\r
709        if ( ( (x2 === 0) || (x2 === 1) )\r
710             && (y2 === 0)\r
711             && ( (x3 === 1) || (x3 === 0) )\r
712             && (y3 === 1) ) {\r
713               /*linearモードと同じ効果 (収束ではない可能性を考慮)*/\r
714               this.to.call();\r
715               return f;\r
716        }\r
717        var tkey = this.keyTime;\r
718        if (tkey || isFinite(tkey) ) {\r
719          /*keyTimeから時間の収縮率を3次ベジェ曲線に適用しておく*/\r
720          Ax *= tkey;\r
721          Bx *= tkey;\r
722          Cx *= tkey;\r
723          Ay *= tkey;\r
724          By *= tkey;\r
725          Cy *= tkey;\r
726        }\r
727        tkey = tk = x2 = y2 = x3 = y3 = x4 = y4 = void 0;\r
728        return function (x) {\r
729           /*3次ベジェ曲線は媒介曲線\r
730            *x = (x4-3*(x3-x2)-x1)*t*t*t + 3*(x3-2*x2+x1)*t*t + 3*(x2-x1)*t + x1\r
731            *y = (y4-3*(y3-y2)-y1)*t*t*t + 3*(y3-2*y2+y1)*t*t + 3*(y2-y1)*t + y1\r
732            * ただし、0 <= t <= 1\r
733            * スプラインモードの場合、x1 = y1 = 0, x4 = y4 = 1\r
734            * ベジェ曲線のxの式が三次方程式であるため、その解 t から、ベジェ曲線の y を求める\r
735            * なお、ニュートン法の初期値はxとする\r
736            * なぜなら、xの式をみると、xが増加傾向となるスプラインモードでは、係数が負となる可能性が低いため*/\r
737           var t = _newton(Ax, Bx, Cx, -x, x);\r
738           return f(Ay*t*t*t + By*t*t + Cy*t);\r
739         };\r
740      } else if (this.mode === "discrete") {\r
741        return function (t) {\r
742          return isNaN(t) ? this.string\r
743                          : this.to.from.string;\r
744        }.bind(this);\r
745      }\r
746    }\r
747 } ).to = base("$from").$to;\r
748 \r
749 \r
750 /*ニュートン法により、三次方程式 a0x^3 + a1x^2 + a2x + a3 の解を求める\r
751  * 引数bは初期値*/\r
752 Math.qubicnewton = function(a0, a1, a2, a3, b) {\r
753   var eps = 1e-10,                          //収束誤差\r
754       fb = a0 *b*b*b + a1 *b*b + a2*b + a3; //方程式の結果\r
755   if (fb === 0) {\r
756     return b;\r
757   }\r
758   /*限界の収束回数は100回*/\r
759   for (var i=0;i<100;++i) {\r
760     /*数値nは与えられた三次方程式を微分したもの*/\r
761     var n = 3* a0 *b*b + 2 * a1 *b + a2;\r
762     if (!n || ( (fb < eps) && (fb > -eps) )) {\r
763       fb = eps = void 0;\r
764       return b;\r
765     } else {\r
766       /*以下は収束の漸化式*/\r
767       b =  b - fb / n;\r
768       fb = a0 *b*b*b + a1 *b*b + a2*b + a3;\r
769     }\r
770   }\r
771   return b; //収束しなかった結果\r
772 };\r
773 \r
774 /*$attribute オブジェクト\r
775  * アニメーションの時間調整と、呈示値の調整を一つのオブジェクトにまとめて行うことで、\r
776  * アニメーションサンドイッチの実装をする\r
777  * $calcModeオブジェクトから継承*/\r
778 base("$calcMode").up("$attribute").mix( {\r
779   \r
780   /*アニメーションの対象となる要素。たとえば、animate要素の親要素*/\r
781   element: null,\r
782   \r
783   /*$fromオブジェクトを作るためのひな形となるオブジェクト*/\r
784   $from: base("$from").up(),\r
785   \r
786   /*指定した要素の属性値を取得するメソッド*/\r
787   _getAttr: function(/*string*/ name, def) {\r
788     var nameSpace = null;\r
789     if (name.indexOf("xlink:") > -1) {\r
790       nameSpace = "http://www.w3.org/1999/xlink";\r
791     }\r
792     /*DOM Level2やIE11では、getAttributeNSメソッドは空文字を返す。他のブラウザではnullを返すことが多い\r
793      * \r
794      * >the empty string if that attribute does not have a specified or default value\r
795      * http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-ElGetAttrNS*/\r
796     return (this._ele.getAttributeNS(nameSpace, name) || def);\r
797   },\r
798   \r
799   _ele: document.documentElement,\r
800 \r
801   /*引数で指定した要素 ele の属性を解析して、フレームに追加する*/\r
802   push: function(/*Element Node*/ ele) {\r
803     if (!ele || !ele.hasAttribute) {\r
804       return null;\r
805     }\r
806     this.element = ele.parentNode || null;\r
807     var id;\r
808     if ( id = ele.getAttributeNS(null, "targetElement") ) {\r
809       this.element = ele.ownerDocument.getElementById(id);\r
810     }\r
811     /*getAttributeNSメソッドでうまくいかなかったため、NSなしで代用*/\r
812     if ( id = ele.getAttribute("xlink:href") ) {\r
813       this.element = ele.ownerDocument.getElementById(id.slice(1));\r
814     }\r
815     if (!( ele.hasAttribute("from") || ele.hasAttribute("to")\r
816          || ele.hasAttribute("by") || ele.hasAttribute("values") ) ) {\r
817       /*from属性、to、by、values属性が指定されていない場合、アニメーションの効果が出ないように調整する\r
818        *SMILアニメーションの仕様を参照\r
819        *\r
820        *>if none of the from, to, by or values attributes are specified, the animation will have no effect\r
821        *「3.2.2. Animation function values」より引用\r
822        *http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues\r
823       */\r
824       return null;\r
825     }\r
826     /*_getAttrメソッドで必要*/\r
827     this._ele = ele;\r
828     /*eleの属性の値を、それぞれオブジェクトに割り当て*/\r
829     var $frame = base("$frame"),\r
830         begin = $frame.$begin,\r
831         frame = begin.up().mix( {\r
832                   /*targetプロパティはbeginEventなどの発火で使う*/\r
833                   target: ele,\r
834                   eventTarget: (this.element || begin.eventTarget),\r
835                   string: this._getAttr("begin", "0"),\r
836                   $activate: begin.$activate.up().mix( {\r
837                     dur: this._getAttr("dur", null),\r
838                     end: begin.$end.up().mix( {\r
839                           eventTarget: (this.element || begin.eventTarget),\r
840                           string: this._getAttr("end", null)\r
841                         } ),\r
842                     repeatCount: this._getAttr("repeatCount", null),\r
843                     repeatDur: this._getAttr("repeatDur", null),\r
844                     min: this._getAttr("min", "0"),\r
845                     max: this._getAttr("max", "indefinite")\r
846                   } )\r
847                 } ).parse();\r
848     frame.$activate.end.$begin = frame;\r
849     if (frame.isResolved) {\r
850       /*開始時間が初期化されてしまうのを防ぐ*/\r
851       var cacheBegin = frame.begin;\r
852       frame.listener( {\r
853         /*アニメーションの開始をこのメソッドが呼ばれた時点とする*/\r
854         timeStamp: Date.now()\r
855       } );\r
856       frame.begin = cacheBegin;\r
857     }\r
858     /*setFrameメソッドを使ったときの、再帰スタックの使いすぎを防ぐため*/\r
859     frame.timelines = [];\r
860     begin = ele = id = void 0;\r
861     return frame;\r
862   },\r
863   \r
864   /*setValuesメソッド\r
865    * values属性やfrom属性やto属性を処理するためのメソッド\r
866    * valuesは配列、それ以外の引数は文字列\r
867    * 返り値は、values属性は配列、それ以外の属性のときは、\r
868    * 自分自身となる$attributeオブジェクトのコピーを返す*/\r
869    setValues: function(values, from, to, by) {\r
870      var $from = this.$from,\r
871          s = [this.up().mix( {\r
872                to: $from.up().mix( {\r
873                  from: $from.up()\r
874                } )\r
875              } )],\r
876          sto = s[0].to;\r
877      values = values && values.split(";");\r
878      /*from属性はオプションなので、条件には付け加えない*/\r
879      if (values && values.length) {\r
880        /*values属性が指定された場合、他の属性は無視される\r
881         * W3C仕様 SMIL アニメーション 3.2.2. アニメーション関数の値*/\r
882         s = [];\r
883        for (var i=1;i<values.length;++i) {\r
884          s.push( this.up().mix( {\r
885                to: $from.up().mix( {\r
886                  from: $from.up()\r
887                } )\r
888              } ) );\r
889          sto = s[s.length-1].to;\r
890          sto.string = values[i];\r
891          sto.from.string = values[i-1];\r
892        }\r
893      } else if (to) {\r
894        sto.string = to;\r
895        sto.from.string = from || "0";\r
896      } else if (by) {\r
897        sto.string = by;\r
898        sto.from.string = from || "0";\r
899        var toNumList = sto.call(),\r
900            fromNumList = sto.from;\r
901        for (var i=0;i<toNumList.length;++i) {\r
902          /*初期値と差分を足していく*/\r
903          toNumList[i] += fromNumList[i];\r
904        }\r
905      } else {\r
906        return null;\r
907      }\r
908      $from = sto = toNumList = fromNumList = void 0;\r
909      return s;\r
910    },\r
911    \r
912    /*setKeyメソッド\r
913     * 引数の要素のkeyTimes属性やkeySplines属性を処理するためのメソッド\r
914     * 必要な他の属性処理はsetValuesメソッドに任せている*/\r
915    setKey: function(ele) {\r
916      this._ele = ele;\r
917      var to = this.setValues(this._getAttr("values", null),\r
918           this._getAttr("from", null),\r
919           this._getAttr("to", null),\r
920           this._getAttr("by", null) ),\r
921          keyTimes = this._getAttr("keyTimes", null),\r
922          keySplines = this._getAttr("keySplines", null),\r
923          keys,\r
924          splines = keySplines && keySplines.split(";"),\r
925          isDiscrete = (this.mode === "discrete"),\r
926          toiKeySplines;\r
927     if (!isDiscrete && keyTimes && to) {\r
928       keys = this.$from.numList.call( {\r
929         string: keyTimes\r
930       } );\r
931       /*toオブジェクトはtoとfromで一組となっているのでlengthが加算される*/\r
932       if (keys.length && (keys.length !== (to.length+1))) {\r
933         /*keyTimes属性とvalues属性のリストの個数が合致しない場合、アニメーションの効果がない\r
934          * 仕様を参照 SMIL Animation 3.2.3. Animation function calculation modes\r
935          * http://www.w3.org/TR/smil-animation/#AnimFuncCalcMode*/\r
936         return null;\r
937       }\r
938       for (var i=0;i<to.length;++i) {\r
939         to[i].keyTime = keys[i+1] - keys[i];\r
940         if (splines) {\r
941           toiKeySplines = this.$from.numList.call( {\r
942             string: splines[i]\r
943           } );\r
944           /*空配列を返すため、nullに変えておく*/\r
945           to[i].keySplines = toiKeySplines.length ? toiKeySplines : null;\r
946         }\r
947       }\r
948     } else if (!isDiscrete && to) {\r
949       var per = 1 / to.length;\r
950       for (var i=0;i<to.length;++i) {\r
951         to[i].keyTime = per;\r
952         if (splines) {\r
953           toiKeySplines = this.$from.numList.call( {\r
954             string: splines[i]\r
955           } );\r
956           to[i].keySplines = toiKeySplines.length ? toiKeySplines : null;\r
957         }\r
958       }\r
959     } else if (to) {\r
960         /*discreteモードの処理*/\r
961       if (keyTimes) {\r
962         keys = this.$from.numList.call( {\r
963           string: keyTimes\r
964         } );\r
965         if (keys.length && (keys.length !== (to.length+1))) {\r
966           return null;\r
967         }\r
968         for (var i=0;i<to.length;++i) {\r
969           to[i].keyTime = keys[i];\r
970         }\r
971       } else {\r
972         var per = 1 / (to.length+1);\r
973         for (var i=0;i<to.length;++i) {\r
974           to[i].keyTime = per;\r
975         }\r
976       }\r
977       /*toオブジェクトが足らないので、一つ追加しておく*/\r
978       to.push( to[to.length-1].up().of( {\r
979             call: function() {\r
980               return function (t) {\r
981                  return isNaN(t) ? this.string\r
982                                  : this.to.string;\r
983               }.bind(this);\r
984             }\r
985       } ) );\r
986     }\r
987     if (this.mode === "paced") {\r
988       /*ベクトル全体の距離を算出*/\r
989       var norm = 0;\r
990       to.forEach( function(x) {\r
991         norm += x.to.distance(x.to.from);\r
992       } );\r
993       to.forEach( function(x) {\r
994          x.norm = norm;\r
995       } );\r
996     }\r
997     ele = keyTimes = keys = per = splines = void 0;\r
998     return to;\r
999    }\r
1000 } ).up("$setElement").mix( {\r
1001   /*to属性の値、文字列*/\r
1002   to: "",\r
1003   \r
1004   /*attributeName属性の値*/\r
1005   attrName: "",\r
1006   \r
1007   /*指定された属性の規定値*/\r
1008   defaultValue: "",\r
1009   \r
1010   /*もともと属性がターゲットの要素につけられていたかどうか*/\r
1011   isDefault: false,\r
1012   \r
1013   /*属性の名前空間*/\r
1014   attrNameSpace: null,\r
1015   \r
1016   /*initメソッドで使われるアニメーション関数*/\r
1017   _setFrame: function (frame) {\r
1018     this.element.setAttributeNS(this.attrNameSpace, this.attrName, this.to);\r
1019   },\r
1020   \r
1021   /*アニメーションが終了したかどうか*/\r
1022   isEnd: false,\r
1023   \r
1024   /*開始を設定されたタイムライン ($beginオブジェクト)*/\r
1025   timeline: base("$frame").$begin,\r
1026   \r
1027   /*アニメが終了した際の後処理\r
1028    * 終了した後は、ひたすらtrueを値として返す*/\r
1029   _setEndFrame: function(frame) {\r
1030               var line = this.timeline;\r
1031               if (( frame < (line.begin + line.activeTime) ) || this.isEnd) {\r
1032                 /*アニメーションが終了間近でなければ凍結の処理をしない*/\r
1033                 line = frame = void 0;\r
1034                 return true;\r
1035               } else if (!isNaN(line.begin + line.activeTime)) {\r
1036                 /*イベントが設定されていないか、解決済みである場合*/\r
1037                 this.isEnd = true;\r
1038                 /*removeの場合、アニメーションを凍結せずに、もとに戻す*/\r
1039                 if ((this.fill === "remove") && this.isDefault) {\r
1040                   this.element.setAttributeNS(this.attrNameSpace, this.attrName, this.defaultValue);\r
1041                 } else if (this.fill === "remove"){\r
1042                   this.element.removeAttributeNS(this.attrNameSpace, this.attrName);\r
1043                 }\r
1044                 line = frame = void 0;\r
1045                 return false;\r
1046               }\r
1047   },\r
1048   \r
1049   /*アニメーションの呈示値を呼び出す関数*/\r
1050   _tocall: function() {},\r
1051   \r
1052   init: function(ele) {\r
1053     var line = this.push(ele);\r
1054     if (ele && ele.getAttributeNS) {\r
1055       this._ele = ele;\r
1056       this.to = this._getAttr("to", "");\r
1057       this.attrName = this._getAttr("attributeName", "");\r
1058       this.fill = this._getAttr("fill", "remove");\r
1059     }\r
1060     var thisele = this.element;\r
1061     if (line && thisele) {\r
1062       this.timeline = line;\r
1063       this._ele = thisele;\r
1064       if (this.attrName.indexOf("xlink") > -1) {\r
1065         this.attrNameSpace = "http://www.w3.org/1999/xlink";\r
1066       }\r
1067       this.isDefault = thisele.hasAttributeNS(this.attrNameSpace, this.attrName);\r
1068       this.defaultValue = this._getAttr(this.attrName,\r
1069        thisele.ownerDocument.defaultView.getComputedStyle(thisele, "").getPropertyValue(this.attrName) );\r
1070       /*ラインの中に、属性処理をするためのラインを追加*/\r
1071       line.addLine(\r
1072        { setFrame: this._setFrame.bind(this),\r
1073          begin: 1,\r
1074          activeTime: 1,\r
1075          rank: 0\r
1076        }\r
1077       );\r
1078       base("$frame").addLine(\r
1079         { setFrame: this._setEndFrame.bind(this),\r
1080           begin: 1,\r
1081           activeTime: 1,\r
1082           rank: Number.MAX_VALUE //最低ランクにすることで、一番最後にタイムラインを実行させる\r
1083         }\r
1084       );\r
1085     }\r
1086     /*アニメーションが再起動する可能性もあるため、isEndプロパティはここで初期化*/\r
1087     this.isEnd = false;\r
1088     line = thisele = void 0;\r
1089   }\r
1090 }).up("$animateElement").mix( {\r
1091   /*アニメ関数の配列*/\r
1092   funcs: [],\r
1093 \r
1094   /*進捗率advanceから、呈示値を求める*/\r
1095   _tocall: function(advance) {\r
1096     var tf = this.funcs;\r
1097     for (var i=0;i<tf.length;++i) {\r
1098       var tfi = tf[i];\r
1099       /*keyTime(keyTimes属性で指定されたような値)で実行するかどうかを判別*/\r
1100       if (tfi.endKeyTime >= advance) {\r
1101         return tfi(advance - tfi.startKeyTime);\r
1102       }\r
1103     }   \r
1104     tf = i = tfi = void 0;\r
1105     return "";\r
1106   },\r
1107   \r
1108   _setFrame: function(currentTime) {\r
1109     /*durationは単純継続時間\r
1110      *advanceは継続時間内での、進捗率\r
1111      *  仕様を参照 http://www.w3.org/TR/smil-animation/#AnimFuncValues\r
1112      *進捗率advanceは、durationと進捗フレーム数とを割った余り(REMAINDER)で算出する\r
1113      * 仕様を参照 SMIL Animation 3.6.2 Interval timing\r
1114      * http://www.w3.org/TR/2001/REC-smil-animation-20010904/#IntervalTiming*/\r
1115     var line = this.timeline,\r
1116         duration = line.simpleDuration,\r
1117         /*単純継続時間が不定の場合、補間はせずに初期値が採用されるため、advanceは0となる\r
1118          * 仕様を参照 SMIL Animation 3.2.2. Animation function values のInterpolation and indefinite simple durations\r
1119          * http://www.w3.org/TR/2001/REC-smil-animation-20010904/#AnimFuncValues*/\r
1120         advance = duration ? ( (currentTime - line.begin) % duration ) / duration\r
1121                     : 0;\r
1122     this.element.setAttributeNS(this.attrNameSpace, this.attrName, this._tocall(advance));\r
1123     line = duration = advance = void 0;\r
1124   },\r
1125   \r
1126   _setEndFrame: function(frame) {\r
1127     /*上書きされたメソッドを呼び出してアニメーションの凍結作業をする*/\r
1128     if (!this.$setElement._setEndFrame.call(this, frame)\r
1129      && (this.fill === "freeze")) {\r
1130       var line = this.timeline,\r
1131           duration = line.simpleDuration;\r
1132       if (duration) {\r
1133         var advance = ( line.activeTime % duration ) / duration;\r
1134         /*例外が発生するため、進捗率が1を超えないように処理*/\r
1135         advancd = (advance > 1) ? 1 : advance;\r
1136          /*活動継続時間と単純継続時間が一致すると、余りは0となるため以下の処理*/\r
1137         advance = advance || 1;\r
1138       } else {\r
1139         advance = 0;\r
1140       }\r
1141       this.element.setAttributeNS(this.attrNameSpace, this.attrName, this._tocall(advance));\r
1142       line = duration = advance = void 0;\r
1143     }\r
1144   }\r
1145 \r
1146 /*initメソッドに追加処理\r
1147  * onメソッドについては、base.jsを参照のこと*/\r
1148 } ).on ("init", function(ele) {\r
1149   var isColor = /^fill|stroke|stop-color|color$/.test(this.attrName);\r
1150   if (isColor) {\r
1151     this.setValues = function() {\r
1152       /*RGB形式では補間に、小数を使わない*/\r
1153       var s = this.$attribute.setValues.apply(this, arguments);\r
1154       s.forEach(function(x) {\r
1155         x.to.degit = 0;\r
1156       } );\r
1157       return s;\r
1158     };\r
1159   }\r
1160   var to, \r
1161       keyTime = 0,\r
1162       /*関数fはrgbColor形式への変換処理で使う*/\r
1163       toRGB = function(x) { return x; };\r
1164   if (ele) {\r
1165     this.mode = ele.getAttributeNS(null, "calcMode") || "linear";\r
1166     to = this.setKey(ele);\r
1167   }\r
1168   if (isColor) {\r
1169     this.setValues = this.$attribute.setValues;\r
1170     /*#から始まる文字列を、rgb(.., .., ..,)形式へと変換するための関数*/\r
1171     toRGB = function(rgbColor) {\r
1172            var keyword = base("$CSSValue").$SVGColor._keywords[rgbColor];\r
1173            if (keyword) {\r
1174              return "rgb(" + keyword.join(", ") + ")";\r
1175            }\r
1176            if (rgbColor[0] === "#") {  //#を含む場合\r
1177               var s = "rgb(",\r
1178                   _parseInt = parseInt;\r
1179               if (rgbColor.length < 5) {\r
1180                 var r = rgbColor[1],\r
1181                     g = rgbColor[2],\r
1182                     b = rgbColor[3],\r
1183                 rgbColor = "#" + r + r + g + g + b + b;\r
1184               }\r
1185               rgbColor.match(/\#(\w{2})(\w{2})(\w{2})/);\r
1186               s += _parseInt(RegExp.$1, 16)\r
1187                 + ", "\r
1188                 + _parseInt(RegExp.$2, 16)\r
1189                 + ", "\r
1190                 + _parseInt(RegExp.$3, 16)\r
1191                 + ")";\r
1192               r = g = b = void 0;\r
1193               return s;\r
1194            }\r
1195            return rgbColor;\r
1196         };\r
1197   }\r
1198   if (to) {\r
1199     this.funcs = to.map( function(x) {\r
1200       x.to.string = toRGB(x.to.string);\r
1201       x.to.from.string = toRGB(x.to.from.string);\r
1202       var s = x.call();\r
1203       /*x.keyTimeプロパティは区間を示しているため、区切り時刻に変換しておく\r
1204        * startKeyTimeプロパティは区間のスタート時点\r
1205        * endKeyTimeプロパティは区間のエンド地点*/\r
1206       s.startKeyTime = keyTime;\r
1207       keyTime = s.endKeyTime = keyTime + x.keyTime;\r
1208       return s;\r
1209     } )\r
1210      .filter( function(s) {\r
1211        /*splineモードで、かつ、円周率を返す関数の場合は配列からはねておく*/\r
1212        return (this.mode !== "spline")\r
1213                || (s(0.1) !== Math.PI);\r
1214     }, this );\r
1215   }\r
1216 });\r
1217 \r
1218 /*$svgEventオブジェクトは、SVGEvent発火を監視するためのオブジェクト*/\r
1219 base("$frame").up("$svgEvent").mix( {\r
1220   /*イベントのスケジュール記録*/\r
1221   first: null,\r
1222   \r
1223   /*タイムラインの最後のキャッシュ*/\r
1224   lastTimeLine: null,\r
1225   \r
1226   /*setTimeTable メソッドはスケジュールの記録をつけるためのメソッド*/\r
1227   setTimeTable: function () {\r
1228     var timelines = this.timelines;\r
1229       for (var i=0, obj = null;i<timelines.length;++i) {\r
1230         if (!timelines[i].target) {\r
1231           /*target オブジェクトがないものは除外*/\r
1232           continue;\r
1233         }\r
1234         /*タイムラインから、beginEventとendEventを発火するスケジュールを作成*/\r
1235         var timeline = timelines[i],\r
1236             begin = timeline.begin,\r
1237             target = timeline.target,\r
1238             simpleDur = timeline.simpleDuration,\r
1239             activeTime = timeline.activeTime,\r
1240             first = {\r
1241               frame: begin,\r
1242               eventType: "begin",\r
1243               target: target,\r
1244               next: {\r
1245                 frame: begin+activeTime,\r
1246                 eventType: "end",\r
1247                 target: target,\r
1248                 next: null\r
1249               }\r
1250             };\r
1251         if (obj) {\r
1252           obj = obj.next.next = first;\r
1253         } else {\r
1254           obj = this.first = first;\r
1255         }\r
1256         if (simpleDur && (activeTime !== simpleDur)) {\r
1257           /*活動継続時間と単純持続時間が異なるとき、repeatイベントを設定\r
1258            * ただし、repeatイベントはendイベントが発生する前に起きるものと仮定*/\r
1259           first.next = {\r
1260             firstFrame: begin + simpleDur,\r
1261             frame: begin + simpleDur,\r
1262             eventType: "repeat",\r
1263             target: target,\r
1264             /*リピートの制限時間*/\r
1265             limit: begin + activeTime,\r
1266             /*リピートの回数 (n >= 1)*/\r
1267             count: 1,\r
1268             simpleDuration: simpleDur,\r
1269             next: first.next\r
1270           };\r
1271         }\r
1272       }\r
1273     timelines = obj = first = begin = target = simpleDur = activeTime = void 0;\r
1274   },\r
1275   \r
1276   $frame: base("$frame"),\r
1277   \r
1278   setFrame: function (num) {\r
1279     var timelines = this.timelines,\r
1280         lastTimeLine = timelines[timelines.length-1],\r
1281         s = this.$frame.setFrame(num);\r
1282     /*キャッシュのlastTimeLineプロパティを使って、再びスケジュールの計算をさせないようにする*/\r
1283     if (this.lastTimeLine !== lastTimeLine) {\r
1284       this.lastTimeLine = lastTimeLine;\r
1285       this.setTimeTable();\r
1286     }\r
1287     /*スケジュールに記録しておいたものを実行して、イベントを発火\r
1288      * また、発火した場合は記録から取り除いて、次回から再び発火しないようにする*/\r
1289     var obj = this.first,\r
1290         cobj = obj,\r
1291         floor = Math.floor;\r
1292     while(obj) {\r
1293       var frame = obj.frame,\r
1294           target = obj.target\r
1295           detail = 0;\r
1296       if (frame <= num) {\r
1297         /*IE11ではSVGEventsやDOMEventsを使うと問題が起きるため、MouseEventsで代用する*/\r
1298         if (obj.eventType === "repeat") {\r
1299           var simpleDuration = obj.simpleDuration;\r
1300           /*リピートイベントが、リピート制限内である場合\r
1301            *numの段階で、何回リピートしたかを求める*/\r
1302           detail = obj.count = floor( (num - obj.firstFrame) / simpleDuration) + 1;\r
1303           /*simpleDurationを足すことによって、リピートイベントが\r
1304            * 単純継続時間内に何度も繰り返されることを防ぐ*/\r
1305           frame += simpleDuration;\r
1306           obj.frame = frame;\r
1307         }\r
1308         /*obj.limitはrepeatイベントの制限で使われるもの*/\r
1309         if ((obj.eventType !== "repeat") || (frame >= obj.limit)) {\r
1310           /*ポインタの連結を変更することで、リストからobj を除去*/\r
1311           cobj.next = obj.next;\r
1312           if (this.first === obj) {\r
1313             cobj = this.first = obj.next;\r
1314           }\r
1315         } else {\r
1316           cobj = obj;\r
1317         }\r
1318         var evt = target.ownerDocument.createEvent("MouseEvents");\r
1319         evt.initMouseEvent(obj.eventType+"Event" ,true, true, window, detail, 0, 0, 0, 0, false, false, false, false, 0, target);\r
1320         target.dispatchEvent(evt);\r
1321       } else {\r
1322         /*next プロパティを書き換えるためのobj オブジェクトのキャッシュ*/\r
1323         cobj = obj;\r
1324       }\r
1325       obj = obj.next;\r
1326     }\r
1327     obj = first = frame = target = cobj = simpleDuration = detail = void 0;\r
1328     return s;\r
1329   }\r
1330 } );\r
1331 \r
1332 function getDocument() \r
1333 {\r
1334   var svg = document.getElementsByTagName("object"),\r
1335       svgns = "http://www.w3.org/2000/svg";\r
1336   for (var i=0;i<svg.length;++i) {\r
1337     if (svg) {\r
1338       var svgDoc = svg[i].getSVGDocument(),\r
1339           $set = base("$calcMode").$attribute.$setElement;\r
1340       init($set, "set");\r
1341       init($set.$animateElement, "animate");\r
1342       init($set.$animateElement, "animateColor");\r
1343     }\r
1344   }\r
1345   __step();\r
1346   \r
1347   function init (obj, name) {\r
1348     var eles = svgDoc.getElementsByTagNameNS(svgns, name)\r
1349     for (var i=0;i<eles.length;++i) {\r
1350       obj.up().init(eles.item(i));\r
1351     }\r
1352     eles = obj = void 0;\r
1353   };\r
1354 }\r
1355 window.addEventListener && window.addEventListener("load", getDocument);\r
1356 \r
1357 function __step() {\r
1358 if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Animation", "1.1")) {\r
1359   if (window.requestAnimationFrame && requestAnimationFrame) {\r
1360     /*IE11などSMILアニメーションに対応していないブラウザ用*/\r
1361     (function(frame) {\r
1362       var $f = base("$frame").$svgEvent;\r
1363       $f.startTime = Date.now();\r
1364       requestAnimationFrame(step);\r
1365       function step() {\r
1366         frame++;\r
1367         \r
1368           $f.setFrame(frame);\r
1369         \r
1370         requestAnimationFrame(step);\r
1371       };\r
1372     })(-1)\r
1373   } else {\r
1374     setInterval( (function(frame) {\r
1375       var $f = base("$frame").$svgEvent;\r
1376       $f.startTime = Date.now();\r
1377       return function () {\r
1378         frame++;\r
1379         $f.setFrame(frame);\r
1380       };\r
1381     })(-1), 1 );\r
1382   }\r
1383 }\r
1384 }\r
1385 //#endif // _SMIL_IDL_\r