OSDN Git Service

Modify the _parse method
[sie/sie.git] / org / w3c / dom / smil.js
index d05eb04..b85c60f 100644 (file)
@@ -1,61 +1,28 @@
-/*SIE-SVG without Plugin under LGPL2.1 & GPL2.0 & Mozilla Public License\r
- *公式ページは http://sie.sourceforge.jp/\r
- *利用方法は <script defer="defer" type="text/javascript" src="sie.js"></script>\r
- *http://sie.sourceforge.jp/\r
- *Usage: <script defer="defer" type="text/javascript" src="sie.js"></script>\r
+/*SIE under the MIT Lisence\r
  */\r
-/* ***** BEGIN LICENSE BLOCK *****\r
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1\r
+/* Copyright 2016 dhrname and other contributors\r
+ * http://sie.osdn.jp/\r
  *\r
- * The contents of this file are subject to the Mozilla Public License Version\r
- * 1.1 (the "License"); you may not use this file except in compliance with\r
- * the License. You may obtain a copy of the License at\r
- * http://www.mozilla.org/MPL/\r
- *\r
- * Software distributed under the License is distributed on an "AS IS" basis,\r
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License\r
- * for the specific language governing rights and limitations under the\r
- * License.\r
- *\r
- * The Original Code is the Mozilla SVG Cairo Renderer project.\r
- *\r
- * The Initial Developer of the Original Code is IBM Corporation.\r
- * Portions created by the Initial Developer are Copyright (C) 2004\r
- * the Initial Developer. All Rights Reserved.\r
- *\r
- * Parts of this file contain code derived from the following files(s)\r
- * of the Mozilla SVG project (these parts are Copyright (C) by their\r
- * respective copyright-holders):\r
- *    layout/svg/renderer/src/libart/nsSVGLibartBPathBuilder.cpp\r
- *\r
- * Contributor(s):DHRNAME revulo\r
- *\r
- * Alternatively, the contents of this file may be used under the terms of\r
- * either of the GNU General Public License Version 2 or later (the "GPL"),\r
- * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),\r
- * in which case the provisions of the GPL or the LGPL are applicable instead\r
- * of those above. If you wish to allow use of your version of this file only\r
- * under the terms of either the GPL or the LGPL, and not to allow others to\r
- * use your version of this file under the terms of the MPL, indicate your\r
- * decision by deleting the provisions above and replace them with the notice\r
- * and other provisions required by the GPL or the LGPL. If you do not delete\r
- * the provisions above, a recipient may use your version of this file under\r
- * the terms of any one of the MPL, the GPL or the LGPL.\r
- *\r
- * ***** END LICENSE BLOCK ***** */\r
-/*\r
- * Copyright (c) 2000 World Wide Web Consortium,\r
- * (Massachusetts Institute of Technology, Institut National de\r
- * Recherche en Informatique et en Automatique, Keio University). All\r
- * Rights Reserved. This program is distributed under the W3C's Software\r
- * Intellectual Property License. This program is distributed in the\r
- * hope that it will be useful, but WITHOUT ANY WARRANTY; without even\r
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR\r
- * PURPOSE.\r
- * See W3C License http://www.w3.org/Consortium/Legal/ for more details.\r
+ * Permission is hereby granted, free of charge, to any person obtaining\r
+ * a copy of this software and associated documentation files (the\r
+ * "Software"), to deal in the Software without restriction, including\r
+ * without limitation the rights to use, copy, modify, merge, publish,\r
+ * distribute, sublicense, and/or sell copies of the Software, and to\r
+ * permit persons to whom the Software is furnished to do so, subject to\r
+ * the following conditions:\r
+ * \r
+ * The above copyright notice and this permission notice shall be\r
+ * included in all copies or substantial portions of the Software.\r
+ * \r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
  */\r
 \r
-\r
 /*$frame オブジェクト\r
  * 全体のフレームの管理を行う\r
  */\r
@@ -66,7 +33,7 @@ base("$frame").mix ( {
   /*タイムラインのリスト (時間区間の設定ができる)*/\r
   timelines: [],\r
 \r
-  /*開始フレーム数。アニメーションの開始条件となる\r
+  /*é\96\8bå§\8bã\83\95ã\83¬ã\83¼ã\83 æ\95°ã\81®å\80\99è£\9cã\80\82ã\82¢ã\83\8bã\83¡ã\83¼ã\82·ã\83§ã\83³ã\81®é\96\8bå§\8bæ\9d¡ä»¶ã\81¨ã\81ªã\82\8b\r
    * 単位はフレーム数であって、秒数ではない*/\r
   begin: 0,\r
 \r
@@ -137,6 +104,165 @@ base("$frame").mix ( {
     list = j = void 0;\r
   }\r
 } ).mix( function($frame) {\r
+  $frame.up("$list").mix( {\r
+    /*開始時刻リスト (後述のupdateStateメソッドで使う)*/\r
+    beginList: { \r
+      next: null,\r
+      value: Number.MAX_VALUE\r
+    },\r
+    \r
+    /*終了時刻リスト (後述のupdateStateメソッドで使う)*/\r
+    endList: {\r
+       next: null,\r
+       value: Number.MAX_VALUE\r
+    },\r
+    \r
+    addBeginList: function (num) {\r
+      return ( this.beginList = {\r
+                  value: num,\r
+                  next: this.beginList\r
+                } );\r
+    },\r
+    \r
+    addEndList: function (num) {\r
+      return ( this.endList = {\r
+                  value: num,\r
+                  next: this.endList\r
+                } );\r
+    },\r
+    \r
+    /*引数に渡された時刻リストの中から、現在フレームf以下の、最大値を求めるメソッド\r
+     * -1を返したときはリストの中にf以下の値がないことを示す*/\r
+    getMaxList: function (f, list) {\r
+      var maxTime = -1; /*時刻の最大値*/\r
+      while( list ) {\r
+        var v = list.value;\r
+        /*f以下の開始リスト値のうち、最大値をstartTimeに代入*/\r
+        if ( (v <= f)\r
+             && (maxTime <= v) ) {\r
+          maxTime = v;\r
+        }\r
+        list = list.next;\r
+      }\r
+      return maxTime;\r
+    },\r
+      \r
+    /*現在の要素の状態を数値で示す(マジックナンバーは後述の大文字プロパティを使う)*/\r
+    state: 0,\r
+    \r
+    /*アニメーション待機状態を示す定数*/\r
+    WAITING: 0,\r
+    \r
+    /*アニメーション開始状態を示す定数*/\r
+    BEGINNING: 1,\r
+    \r
+    /*アニメーション再生中の状態を示す定数*/\r
+    PLAYING: 2,\r
+    \r
+    /*アニメーション終了状態を示す定数*/\r
+    ENDING: 3,\r
+    \r
+    /*終了処理をした後の待機状態を示す定数*/\r
+    POSTWAITING: 4,\r
+    \r
+    /*引数で指定されたフレーム数に応じて、stateプロパティを更新するメソッド*/\r
+    updateState: function( /*number*/ f) {\r
+      if (f === void 0) {\r
+        /*引数fが指定されないときには状態を更新しない*/\r
+        return this;\r
+      }\r
+      var state = this.state,\r
+          wait = /*this.WAITING*/ 0,\r
+          begin = /*this.BEGINNING*/ 1,\r
+          play = /*this.PLAYING*/ 2,\r
+          end = /*this.ENDING*/ 3,\r
+          post = /*this.POSTWAITING*/ 4;\r
+      /*beginListプロパティと、endListプロパティの中で、\r
+       * 現在フレーム数 f より大きい数値は、更新できる条件と無関係なので、除外しておく\r
+       * また、f以下の値の中から、最大値を探して、\r
+       * それをbeginプロパティの値cacheBeginと比較する*/\r
+      var startTime = this.getMaxList(f, this.beginList),\r
+          endTime = this.getMaxList(f, this.endList),\r
+          cacheBegin = this.begin;\r
+      if ( (state === wait) || (state === post) ) {\r
+        if (startTime > cacheBegin) {\r
+          this.state = begin;\r
+          /*beginプロパティに開始時刻をキャッシュ用に保存*/\r
+          this.begin = startTime;\r
+        } else if (!f && !startTime) {\r
+          /*開始時刻が0ならば、アニメーションを開始*/\r
+          this.state = begin;\r
+        }\r
+      } else if (state === begin) {\r
+        this.state = play;\r
+      } else if (state === play) {\r
+        if ( (endTime >= cacheBegin) || (startTime > cacheBegin) ) {\r
+          /*終了時刻に到達したか、再び開始イベントが発火されたとき*/\r
+          this.state = end;\r
+        }\r
+      } else if (state === end) {\r
+        if (endTime >= cacheBegin) {\r
+          this.state = post;\r
+        } else {\r
+          /*再生中に開始イベントが発火されて、終了状態となったとき*/\r
+          this.state = begin;\r
+          this.begin = startTime;\r
+        }\r
+      } else {\r
+        this.state = begin;\r
+      }\r
+      cacheBegin = startTime = endTime = void 0;\r
+      return this;\r
+    },\r
+    \r
+    /*addEventメソッドで使われるイベントリスト(開始時に登録されたリスナー関数が呼び出される)*/\r
+    _beginListenerList: [],\r
+    \r
+    /*addEventメソッドで使われるイベントリスト(終了時に登録されたリスナー関数が呼び出される)*/\r
+    _endListenerList: [],\r
+    \r
+    /*addEventメソッドで使われるイベントリスト(再生時に登録されたリスナー関数が呼び出される)*/\r
+    _playListenerList: [],\r
+    \r
+    /*開始と再生と終了時に発火されるイベントリスナーを登録するメソッド*/\r
+    addEvent: function ( /*string*/ eventName, /*fnction*/ listener) {\r
+      this["_" +eventName+ "ListenerList"].push(listener);\r
+    },\r
+    \r
+    /*入力されたフレーム数fの場面に切り替えるメソッド*/\r
+    setFrame: function( /*number*/ f) {\r
+      this.currentFrame = f;\r
+      var state = this.updateState(f).state;\r
+      /*アニメーション開始と再生と、終了状態のときに、beginとplayとendイベントを呼び出しておいて、\r
+       * 次の状態(再生状態)に遷移する*/\r
+      if (state === /*this.PLAYING*/ 2) {\r
+        var list = this._playListenerList;\r
+        for (var i=0;i<list.length;++i) {\r
+          list[i](this);\r
+        }\r
+      } else if (state === /*this.BEGINNING*/ 1) {\r
+        list = this._beginListenerList;\r
+        for (var i=0;i<list.length;++i) {\r
+          list[i](this);\r
+        }\r
+        this.updateState(f);\r
+      } else if (state === /*this.ENDING*/ 3) {\r
+        list = this._endListenerList;\r
+        for (var i=0;i<list.length;++i) {\r
+          list[i](this);\r
+        }\r
+        if (this.updateState(f).state === /*this.BEGINNING*/ 1) {\r
+          /*再生中にbeginイベントが呼び出された場合*/\r
+          this.updateState(f);\r
+        }\r
+      }\r
+      state = list = void 0;\r
+    }\r
+  } ).mix( function() {\r
+    /*後述の$beginや$endで使うメソッド*/\r
+    this.addList = this.addBeginList;\r
+  } );\r
+  \r
   /*$endFrame オブジェクト\r
    * 終了時の処理をするためのフレームを集める*/\r
   $frame.up("$endFrame").mix( {\r
@@ -227,19 +353,22 @@ base("$frame").mix ( {
           event: str\r
         };\r
       }\r
-    },\r
+    }, \r
     \r
-    /*parse メソッド\r
-     * stringプロパティを解析して、フレーム数を算出し、結果を$frame.beginプロパティに出力\r
+    /*_parse メソッド\r
+     * 引数の文字列を解析して、フレーム数を算出し、結果を$frame.beginプロパティに出力\r
      * また、イベントリスナーに登録をしておく*/\r
-    parse: function() {\r
-      /*初期値を設定*/\r
-      this.begin = 0;\r
-      this.isResolved = false;\r
-      var str = this.trim(this.string),\r
-          plusminus = str.search(/[\+\-]/),\r
+    _parse: function (str) {\r
+      var plusminus = str.search(/[\+\-]/),\r
           event = null,\r
-          ele;\r
+          ele,\r
+          /*endListのvalueプロパティには、活動継続フレーム数と開始フレーム数を足したものが入る*/\r
+          endList = this.$list.addEndList(Number.MAX_VALUE);\r
+      if (this.$end) {\r
+        /*$endオブジェクトを継承していた場合、\r
+         *活動継続フレーム数関連のリストは無効とする*/\r
+        endList = { value: 0};\r
+      }\r
       if (str === "indefinite") {\r
         this.begin = Number.MAX_VALUE;\r
       } else if (plusminus > 0) {\r
@@ -260,33 +389,71 @@ base("$frame").mix ( {
       /*もしもあれば、押されるはずのキーを求める*/\r
       this.accessKey = /accessKey\(([^\)]+?)\)/.test(str) ? RegExp.$1 : "";\r
       this.begin = Math.floor( this.begin * this.fpms);\r
-      if (event) {\r
+      if (str === "indefinite") {\r
+        /*begin属性の値がindefiniteの場合は、何もしない。\r
+         * 開始時刻はlistenerメソッドの呼び出しか、beginElementメソッドに依存*/\r
+      } else if (event) {\r
         ele = event.id ? this.eventTarget.ownerDocument.getElementById(event.id)\r
                         : this.eventTarget;\r
-        /*イベントの時間差を設定しておく*/\r
-        this.eventOffset = this.begin;\r
+        /*イベントの時間差を設定しておく\r
+         * eventOffsetとobjListの変数はクロージャとしてlistener関数で使われる*/\r
+        var eventOffset = this.begin,\r
+            /*objListのvalueプロパティはあとで書き換えられる(イベントの場合のみ)*/\r
+            objList = this.$list.addList(Number.MAX_VALUE),\r
+        /*イベントのリスナーとして使う*/\r
+            listener = function(evt) {\r
+              objList.value = this.begin = eventOffset + this.$frame.currentFrame;\r
+              endList.value = this.$list.begin + this.activeTime;\r
+              this.isResolved = true;\r
+            };\r
+        this.eventOffset = eventOffset;\r
         if (this.repeat > 0) {\r
           ele && ele.addEventListener("repeatEvent", (function(evt) {\r
             if (evt.detail === this.repeat) {\r
-              this.listener(evt);\r
+              listener.call(this, evt);\r
             } }).bind(this), true);\r
         } else if (this.accessKey) {\r
           document.documentElement.addEventListener("keydown", (function(evt) {\r
             if (evt.char === this.accessKey) {\r
-                this.listener(evt);\r
+                listener.call(this, evt);\r
               } }).bind(this), false);\r
         } else {\r
           var evtName = /^(?:begin|end|repeat)$/.test(event.event) ? event.event + "Event"\r
                           : event.event;\r
-          ele && ele.addEventListener(evtName, this.listener.bind(this), false);\r
+          ele && ele.addEventListener(evtName, listener.bind(this), false);\r
         }\r
       } else {\r
-        /*イベントの影響を防ぐため\r
-         * すでに、フレームオブジェクトのタイムラインには登録済みなので、\r
-         * フレームではなく、自分独自のタイムラインに登録しておけばよい*/\r
-        this.$frame = this;\r
+        /*開始リストに登録しておく($endの場合は終了リストに登録)*/\r
+        this.$list.addList(this.begin);\r
+        /*活動継続時間から算出される終了フレーム数は、終了リストに入れておく*/\r
+        endList.value = this.begin + this.activeTime;\r
       }\r
       s = event = str = plusminus = ele = void 0;\r
+    },\r
+    \r
+    /*stringプロパティを解析して、\r
+     * 開始フレーム数の算出や、イベントリスナーの登録をするメソッド*/\r
+    parse: function() {\r
+      /*初期値を設定*/\r
+      this.begin = 0;\r
+      this.isResolved = false;\r
+      /*$listオブジェクトを更新*/\r
+      this.$list = this.$list.up();\r
+      /*beginとend属性を考慮に入れないで、活動継続時間を求める*/\r
+      var s = this.$activate.up();\r
+      this.activeTime = s.call() || Number.MAX_VALUE;\r
+      this.simpleDuration = s.simpleDur;\r
+      var str = this.trim(this.string);\r
+      if (str.indexOf(";") > -1){\r
+        /*;で区切られたリストを一つずつ解析*/\r
+        var list = str.split(";");\r
+        for (var i=0;i<list.length;++i) {\r
+          this._parse(list[i]);\r
+        }\r
+      } else {\r
+        this._parse(str);\r
+      }\r
+      s = str = void 0;\r
       return this;\r
     },\r
     \r
@@ -340,8 +507,8 @@ base("$frame").mix ( {
     },\r
     \r
     /*関数型の呼び出しメソッド\r
-     * base.jsのofメソッドを活用して、関数型っぽい処理をする\r
-     * 以下では、活動継続時間を算出\r
+     * base.jsのofメソッドを活用して活動継続時間を算出\r
+     * ただし、begin属性とend属性については、別途、$frame.$listで定める\r
      * 計算方法はSMILアニメーション 3.3.4節を参照\r
      * http://www.w3.org/TR/smil-animation/#ComputingActiveDur\r
      */\r
@@ -350,9 +517,7 @@ base("$frame").mix ( {
           dur = this.simpleDur,\r
           isIndefRepeatCount = (this.repeatCount === ind),\r
           isIndefRepeatDur = (this.repeatDur === ind),\r
-          isIndefEnd = (this.end === ind),\r
           isDur = dur || (dur === 0),\r
-          isEnd = this.end || (this.end === 0),\r
           isRepeatCount = this.repeatCount || (this.repeatCount === 0),\r
           isRepeatDur = this.repeatDur || (this.repeatDur === 0),\r
           actList = [],\r
@@ -360,6 +525,11 @@ base("$frame").mix ( {
           max = (this.max === ind) ? null : Math.floor(this.offset(this.max) * this.fpms),\r
           s;\r
       if (indef()) {\r
+        /*注意点として、活動継続時間が不定かつ、min属性とmax属性が指定されたときの記述が仕様にないため、\r
+         * W3Cのテストスイート(animate-elem-66-t.svg)に従い、max属性の値を返す*/\r
+        if (max) {\r
+          return max;\r
+        }\r
         return null;\r
       }\r
       if (isDur && this.repeatCount && !isIndefRepeatCount) {\r
@@ -368,22 +538,15 @@ base("$frame").mix ( {
       if (isRepeatDur && !isIndefRepeatDur) {\r
         actList.push( Math.floor( this.offset(this.repeatDur) * this.fpms) );\r
       }\r
-      if (isEnd && !isIndefEnd) {\r
-        actList.push( this.end - this.begin );\r
-      }\r
       if (isDur && !isRepeatCount && !isRepeatDur) {\r
         /*repeatCountやrepeatDur属性が指定されていない場合*/\r
         actList.push( dur );\r
       }\r
 \r
       /*長くなるため、インライン関数を活用\r
-       * indef関数は活動継続時間が不定かどうか、もし、不定なら真を返す*/\r
+       * indef関数はbeginとend属性を考慮せずに、\r
+       * 活動継続時間が不定かどうか、もし、不定なら真を返す*/\r
       function indef() {\r
-        if(isIndefEnd) {\r
-          return true;\r
-        } else if (isEnd) {\r
-          return false;\r
-        }\r
         return !!( (!isDur && !isRepeatDur)\r
                    || (isIndefRepeatCount && !isRepeatDur)\r
                    || (isIndefRepeatDur && !isRepeatCount)\r
@@ -414,11 +577,15 @@ base("$frame").mix ( {
       if (!this.string) {\r
         return null;\r
       }\r
+      /*addListメソッドには、addBeginList関数が入っているはずなので、それを変更する*/\r
+      this.$list.addList = this.$list.addEndList;\r
       this.parse(this.string);\r
       return this.isResolved ? this.begin\r
                              : "indefinite";\r
     }\r
   } ).mix( {\r
+    $list: $frame.$begin.$list.up(),\r
+    \r
     /*イベントリスナー用の関数*/\r
     listener: function(evt) {\r
       if (this.begin <= 0) {\r
@@ -863,6 +1030,22 @@ base("$calcMode").up("$attribute").mix( {
     this.__cacheAttr = "";\r
     value = attrName = ele = void 0;\r
   },\r
+  \r
+  /*アニメーションの対象となる要素を値として返すメソッド\r
+   * もっぱら、pushメソッドで使われる*/\r
+  initTargetElement: function() {\r
+    var ele = this._ele;\r
+    var s = ele.parentNode || null;\r
+    var id = ele.getAttribute("xlink:href");\r
+    /*getAttributeNSメソッドでうまくいかなかったため、NSなしで代用*/\r
+    if (id) {\r
+      return ele.ownerDocument.getElementById(id.slice(1));\r
+    }\r
+    if ( id = ele.getAttributeNS(null, "targetElement") ) {\r
+      return ele.ownerDocument.getElementById(id);\r
+    }\r
+    return s;\r
+  },\r
 \r
   /*引数で指定した要素 ele の属性を解析して、フレームに追加する*/\r
   push: function(/*Element Node*/ ele) {\r
@@ -871,17 +1054,14 @@ base("$calcMode").up("$attribute").mix( {
     }\r
     /*キャッシュを初期化しておく*/\r
     this.__cacheAttr = "";\r
-    this.element = ele.parentNode || null;\r
-    var id;\r
-    if ( id = ele.getAttributeNS(null, "targetElement") ) {\r
-      this.element = ele.ownerDocument.getElementById(id);\r
-    }\r
-    /*getAttributeNSメソッドでうまくいかなかったため、NSなしで代用*/\r
-    if ( id = ele.getAttribute("xlink:href") ) {\r
-      this.element = ele.ownerDocument.getElementById(id.slice(1));\r
-    }\r
+    \r
+\r
    /*getAttrメソッドとhasAttrValuesメソッドで必要*/\r
     this._ele = ele;\r
+    \r
+    /*initTargetElementメソッドを使って、elementプロパティの初期化*/\r
+    this.element = this.initTargetElement();\r
+    \r
     if (!this.hasAttrValues()) {\r
       /*from属性、to、by、values属性が指定されていない場合、アニメーションの効果が出ないように調整する\r
        *SMILアニメーションの仕様を参照\r
@@ -892,7 +1072,7 @@ base("$calcMode").up("$attribute").mix( {
       */\r
       return null;\r
     }\r
-    \r
\r
     /*属性値の設定*/\r
     this.attrName = this.getAttr("attributeName", "");\r
     var attrName = this.attrName;\r
@@ -971,7 +1151,7 @@ base("$calcMode").up("$attribute").mix( {
     }\r
     /*setFrameメソッドを使ったときの、再帰スタックの使いすぎを防ぐため*/\r
     frame.timelines = [];\r
-    begin = ele = id = void 0;\r
+    begin = ele = void 0;\r
     return frame;\r
   },\r
   \r
@@ -1130,46 +1310,17 @@ base("$calcMode").up("$attribute").mix( {
   to: "",\r
   \r
   /*initメソッドで使われるアニメーション関数*/\r
-  _setFrame: function (frame) {\r
-    this.state = "playing";\r
+  _setFrame: function ($list) {\r
     this.setAttribute(this.to);\r
   },\r
   \r
-  /*アニメーション中かどうかの判別\r
-   * 1, idling アニメがまだ始まっていない待機状態 (規定値)\r
-   * 2, playing アニメーションの再生中\r
-   */\r
-  state: "idling",\r
-  \r
   /*開始を設定されたタイムライン ($beginオブジェクト)*/\r
   timeline: base("$frame").$begin,\r
   \r
-  /*_setEndFrameメソッドで終了処理をさせたいときに、呼び出されるメソッド\r
-   * 終了処理が必要なときだけ、trueを返す*/\r
-  checkEnd: function (frame) {\r
-    var line = this.timeline,\r
-        end = line.begin + line.activeTime;\r
-    if (!line.isResolved || isNaN(end)) {\r
-      /*未解決など問題が発生したとき*/\r
-      line = frame = void 0;\r
-      return false;\r
-    } else if (\r
-          ( ( frame < line.begin ) || ( end <= frame )\r
-          ) && (this.state === "playing") ) {\r
-      /*stateプロパティを書き換えることで、一度のみ、終了処理させる*/\r
-      this.state = "idling";\r
-      line = frame = void 0;\r
-      return true;\r
-    } else {\r
-      line = frame = void 0;\r
-      return false;\r
-    }\r
-  },\r
-  \r
   /*アニメが終了した際の後処理*/\r
-  _setEndFrame: function (frame) {\r
+  _setEndFrame: function ($list) {\r
     /*removeの場合、アニメーションを凍結せずに、もとに戻す*/\r
-    if (this.checkEnd(frame) && (this.fill === "remove")) {\r
+    if (this.fill === "remove") {\r
       this.removeAttribute();\r
     }\r
   },\r
@@ -1187,22 +1338,14 @@ base("$calcMode").up("$attribute").mix( {
     var thisele = this.element;\r
     if (line && thisele) {\r
       this.timeline = line;\r
-      /*ラインの中に、属性処理をするためのラインを追加*/\r
-      line.addLine(\r
-       { setFrame: this._setFrame.bind(this),\r
-         begin: 1,\r
-         activeTime: 1\r
-       }\r
-      );\r
-      base("$frame").$endFrame.addLine(\r
-        { setFrame: this._setEndFrame.bind(this),\r
-          begin: 1,\r
-          activeTime: 1\r
-        }\r
-      );\r
+      /*$begin.$listのイベントに属性処理を追加*/\r
+      line.$list.addEvent("begin", this._setFrame.bind(this));\r
+      line.$list.addEvent("play", this._setFrame.bind(this));\r
+      line.$list.addEvent("end", this._setEndFrame.bind(this));\r
+      base("$frame").addLine(line.$list);\r
+      /*アニメーションが再起動する可能性もあるため、$listのstateプロパティはここで初期化*/\r
+      line.$list.state = line.$list.WAITING;\r
     }\r
-    /*アニメーションが再起動する可能性もあるため、stateプロパティはここで初期化*/\r
-    this.state = "idling";\r
     line = thisele = void 0;\r
   }\r
 }).up("$animateElement").mix( {\r
@@ -1235,7 +1378,8 @@ base("$calcMode").up("$attribute").mix( {
     return "";\r
   },\r
   \r
-  _setFrame: function(currentTime) {\r
+  _setFrame: function($list) {\r
+    var currentTime = $list.currentFrame;\r
     /*durationは単純継続時間\r
      *advanceは継続時間内での、進捗率\r
      *  仕様を参照 http://www.w3.org/TR/smil-animation/#AnimFuncValues\r
@@ -1250,16 +1394,13 @@ base("$calcMode").up("$attribute").mix( {
         advance = duration ? ( (currentTime - line.begin) % duration ) / duration\r
                     : 0;\r
     this.setAttribute(this.tocall(advance));\r
-    this.state = "playing";\r
     line = duration = advance = void 0;\r
   },\r
   \r
   /*_setEndFrameメソッドは、終了処理と凍結作業をするときに、falseを返す*/\r
-  _setEndFrame: function(frame) {\r
+  _setEndFrame: function($list) {\r
+    var frame = $list.currentFrame;\r
     /*上書きされたメソッドを呼び出してアニメーションの凍結作業をする*/\r
-    if (!this.checkEnd(frame)) {\r
-      return;\r
-    }\r
     if (this.fill === "freeze") {\r
       var line = this.timeline,\r
           duration = line.simpleDuration;\r
@@ -1479,32 +1620,12 @@ base("$calcMode").up("$attribute").mix( {
     } );\r
     degits = void 0;\r
     return s;\r
-  }\r
-\r
-/*initメソッドに追加処理\r
- * onメソッドについては、base.jsを参照のこと*/\r
-} ).on ("init", function(ele) {\r
-  var isColor = /^(?:fill|stroke|stop-color|color)$/.test(this.attrName);\r
-  if (isColor) {\r
-    /*通常は、小数点以下の桁数を既定値の1桁とする\r
-     *RGB形式では補間に、小数を使わないため、0桁に設定\r
-     * (なお、この作業は、setKeyメソッドの前に済ませておく必要がある)*/\r
-    this.degits = 0;\r
-  }\r
-  var to, \r
-      keyTime = 0,\r
-      /*関数toRGBはrgbColor形式への変換処理で使う*/\r
-      toRGB = function(x) { return x; };\r
-  if (ele) {\r
-    this.mode = ele.getAttributeNS(null, "calcMode") || "linear";\r
-    this.setString();\r
-    to = this.setKey(ele);\r
-  }\r
-  if (isColor) {\r
-    /*#から始まる文字列を、rgb(.., .., ..,)形式へと変換するための関数*/\r
-    var keywords = this._keywords;\r
-    toRGB = function(rgbColor) {\r
-           var keyword = keywords[rgbColor];\r
+  },\r
+  \r
+  /*#から始まる文字列#aabbcc、例えばを、rgb(.., .., ..,)形式へと変換するためのメソッド\r
+   * initメソッドで使われる*/\r
+  toRGB: function(rgbColor) {\r
+           var keyword = this._keywords[rgbColor];\r
            if (keyword) {\r
              return "rgb(" + keyword.join(", ") + ")";\r
            }\r
@@ -1528,7 +1649,28 @@ base("$calcMode").up("$attribute").mix( {
               return s;\r
            }\r
            return rgbColor;\r
-        };\r
+        }\r
+\r
+/*initメソッドに追加処理\r
+ * onメソッドについては、base.jsを参照のこと*/\r
+} ).on ("init", function(ele) {\r
+  var to, \r
+      keyTime = 0,\r
+      /*関数toRGBはrgbColor形式への変換処理で使う*/\r
+      toRGB = function(x) { return x; },\r
+      isColor = /^(?:fill|stroke|stop-color|color)$/.test(this.attrName);\r
+  if (isColor) {\r
+    /*通常は、小数点以下の桁数を既定値の1桁とする\r
+     *RGB形式では補間に、小数を使わないため、0桁に設定\r
+     * (なお、この作業は、setKeyメソッドの前に済ませておく必要がある)*/\r
+    this.degits = 0;\r
+    /*たとえば、fill属性などである場合には、rgbColor形式への変換処理をする*/\r
+    toRGB = this.toRGB.bind(this);\r
+  }\r
+  if (ele) {\r
+    this.mode = ele.getAttributeNS(null, "calcMode") || "linear";\r
+    this.setString();\r
+    to = this.setKey(ele);\r
   }\r
   if (to) {\r
     this.funcs = to.map( function(x) {\r
@@ -1580,16 +1722,10 @@ base("$calcMode").up("$attribute").mix( {
     *小数点以下の桁数を決めるdegitsプロパティの値も大きくしておく*/\r
    degits: 15,\r
    \r
-   /*$animateElementオブジェクトのtocallメソッドをオーバライド*/\r
-   tocall: function (advance) {\r
-     if (this.numberOfList < 0) {\r
-       throw new Error("Number of The List Error");\r
-     }\r
-     var playList = this.element.__transformList,\r
-         currentItem = playList[this.numberOfList];\r
-     currentItem.value = this.type+ "(" +this.$animateElement.tocall.call(this, advance)+ ")";\r
-     currentItem.isPlaying = true;\r
-     var s = this.defaultValue || "";\r
+   /*tocallメソッド(後述の$motionElementオブジェクトも含む)で使うメソッド\r
+    * __transformListの値を結合して、文字列として返す*/\r
+   joinList: function(s) {\r
+     var playList = this.element.__transformList;\r
      for (var i=0;i<playList.length;++i) {\r
        var item = playList[i],\r
            value = item.value;\r
@@ -1601,7 +1737,19 @@ base("$calcMode").up("$attribute").mix( {
          s = value;\r
        }\r
      }\r
-     return s;\r
+     return s.trim();\r
+   },\r
+   \r
+   /*$animateElementオブジェクトのtocallメソッドをオーバライド*/\r
+   tocall: function (advance) {\r
+     if (this.numberOfList < 0) {\r
+       throw new Error("Number of The List Error");\r
+     }\r
+     var currentItem = this.element.__transformList[this.numberOfList];\r
+     currentItem.value = this.type+ "(" +this.$animateElement.tocall.call(this, advance)+ ")";\r
+     currentItem.isPlaying = true;\r
+     currentItem.isSum = this.isSum;\r
+     return this.joinList(this.defaultValue || "");\r
    },\r
    \r
    /*後の_setFrameメソッドで使うダミー*/\r
@@ -1611,18 +1759,19 @@ base("$calcMode").up("$attribute").mix( {
      /*__transformListの中で、自分より後の項目に再生中のものがあれば、\r
       *アニメーションを再生させないで、後に続く項目に任せる*/\r
      var list = this.element.__transformList,\r
-         isActive = false;\r
-     for (var i=this.numberOfList;i<list.length;++i) {\r
-       if (list[i].isPlaying) {\r
-         isActive = true;\r
+         isPostActive = false,\r
+         length = list.length;\r
+     if ( (length !== 1) && (this.numberOfList < (length - 1) ) ) {\r
+       /*リストの項目が一つだけであったり、自分自身が最後尾であれば、アニメーションを再生する\r
+        * また、後に続く項目で再生中のものがあったら、今回は再生しない*/\r
+       for (var i=this.numberOfList+1;i<length;++i) {\r
+         if (list[i].isPlaying) {\r
+           isPostActive = true;\r
+         }\r
        }\r
      }\r
-     if (this.numberOfList === (list.length - 1) ) {\r
-       /*ただし、自分自身が最後尾であれば、アニメーションを再生する*/\r
-       isActive = false;\r
-     }\r
      /*__setAttributeはダミーなので、アニメーションが再生されない*/\r
-     this.setAttribute = isActive ? this.__setAttribute\r
+     this.setAttribute = isPostActive ? this.__setAttribute\r
                                     : this.$animateElement.setAttribute;\r
      /*上書きされたメソッドを呼び出す*/\r
      this.$animateElement._setFrame.call(this, currentFrame);\r
@@ -1630,14 +1779,22 @@ base("$calcMode").up("$attribute").mix( {
    \r
    _setEndFrame: function(currentFrame) {\r
      var list = this.element.__transformList;\r
-     if (!this.checkEnd(currentFrame) || (this.fill !== "remove") || !list) {\r
+     if ((this.fill !== "remove") || !list) {\r
        return;\r
      }\r
      if (!this.isSum) {\r
        /*凍結処理をしないで、かつ、元の状態に戻して、効果が出ないようにする*/\r
-       this.removeAttribute();\r
        list[this.numberOfList]\r
         && (list[this.numberOfList].isPlaying = false);\r
+        for (var i = 0;i<list.length;++i) {\r
+          var listi = list[i];\r
+          if (listi.isPlaying || !listi.isRemove) {\r
+            /*他のanimateTransform要素が活性化しているか、凍結処理が必要ならば、\r
+             * 属性の初期化はしない*/\r
+            return;\r
+          }\r
+        }\r
+       this.removeAttribute();\r
      } else {\r
        /*凍結処理をしないで、かつ、効果を出すが、変形させないようにする*/\r
        list[this.numberOfList]\r
@@ -1648,19 +1805,6 @@ base("$calcMode").up("$attribute").mix( {
     /*setAddメソッドのオーバライド\r
      * additive属性のsumに対する振る舞いが異なるため*/\r
     setAdd: function() {},\r
-    \r
-    /*setAccumメソッドのオーバライド*/\r
-    setAccum: function (ele, to) {\r
-    /*accumulate属性がsum (蓄積アニメーション)の場合*/\r
-    if (ele.getAttributeNS(null, "accumulate") === "sum") {\r
-      ele.addEventListener("repeatEvent", function(evt) {\r
-        to.forEach( function(x) {\r
-          x.to.call();\r
-          x.to.setAccumulate(evt.detail);\r
-        } )\r
-      }, false);\r
-    }\r
-  },\r
   })\r
  .on("init", function (ele) {\r
    if (!ele || !ele.parentNode) {\r
@@ -1684,19 +1828,125 @@ base("$calcMode").up("$attribute").mix( {
      /*isPlayingプロパティはアニメーション再生終了後でもtrueとなるので注意*/\r
      parent.__transformList.push( {isPlaying: false,\r
                                    value: "translate(0)",\r
-                                   isSum: this.isSum\r
+                                   isSum: this.isSum,\r
+                                   isRemove: (this.fill === "remove")\r
                                   } );\r
    }\r
   } )\r
   .up("$motionElement")\r
+  .mix( function() {\r
+    /*setRFrameメソッドを再定義*/\r
+    this._setFrame = this.$animateElement._setFrame;\r
+  })\r
   .mix( {\r
     numberOfList: -1,\r
-    mode: "paced"\r
+    mode: "paced",\r
+        \r
+    /*hasAttrValuesメソッドのオーバライド\r
+     * path属性に対応させるため*/\r
+    hasAttrValues: function () {\r
+      if (this.$attribute.hasAttrValues.call(this)) {\r
+        return true;\r
+      } else {\r
+        return (this._ele.hasAttribute("path")\r
+                 || this._ele.getElementsByTagNameNS(this.path.namespaceURI, "mpath").length);\r
+      }\r
+    },\r
+    \r
+    path: document.createElementNS("http://www.w3.org/2000/svg", "path"),\r
+    \r
+    rotate: "0",\r
+    \r
+    /*tocallメソッドのオーバライド*/\r
+    tocall: function(advance) {\r
+      /*モーションは仕様の関係上、かならず、CTMの一番初めに置かれ、前置積となる\r
+     * なお、__transformListプロパティはtransform属性の値であり、CTMを表現する。\r
+     * 必ず、CTMの一番目に設定する*/\r
+      return ("translate(" +this.$animateElement.tocall.call(this, advance)+ ") "\r
+              + this.joinList(this.defaultValue || "")).trim();\r
+    },\r
+    \r
+    /*図の現在の角度rを求めて、rotate(rの文字列(最後に括弧はいらない)で返すメソッド*/\r
+    getRotate: function(path, advanceLength, rotate) {\r
+      /*パスセグメントの数値を求めてから、動いている図形の傾き角度r(ラジアンではなく度数)を算出する*/\r
+      var length = path.getPathSegAtLength(advanceLength),\r
+          seg = path.pathSegList.getItem(length),\r
+          command = seg.pathSegTypeAsLetter,\r
+          /*pi*180ではEdgeでうまく作動しない(原因は不明)*/\r
+          pi = Math.PI;\r
+      if (command === "M") {\r
+        /*次のセグメントがどのコマンドによるかで、計算方式が違う*/\r
+        var nextSeg = path.pathSegList.getItem(length+1),\r
+            nextCommand = nextSeg.pathSegTypeAsLetter;\r
+        if (nextCommand === "M") {\r
+          return "";\r
+        } else if (nextCommand === "L") {\r
+          return ") rotate(" +(Math.atan2(nextSeg.y-seg.y, nextSeg.x-seg.x)/pi*180 + rotate)+ "";\r
+        } else if (nextCommand === "C") {\r
+          return ") rotate(" +(Math.atan2(nextSeg.y1-seg.y, nextSeg.x1-seg.x)/pi*180 + rotate)+ "";\r
+        }\r
+      } else if ((command === "L") && (length-1 >= 0)) {\r
+        var preSeg = path.pathSegList.getItem(length-1);\r
+        return ") rotate(" +(Math.atan2(seg.y-preSeg.y, seg.x-preSeg.x)/pi*180 + rotate)+ "";\r
+      } else if (command === "C") {\r
+        /*3次ベジェ曲線を微分する方法はニュートン法など数値解析の必要があるので、\r
+         * 以下の通り、別の方法を採用する。\r
+         * 現在位置から一歩進んだ曲線上の点Bをとり、それを、現在の点Aと結んで線分ABとしたとき、\r
+         * その直線の傾きからおおよその角度を求める*/\r
+         var point = path.getPointAtLength(advanceLength),\r
+             x = point.x,\r
+             y = point.y;\r
+         /*一歩進んだ点*/\r
+         point = path.getPointAtLength(advanceLength+1);\r
+         return ") rotate(" +(Math.atan2(point.y-y, point.x-x)/pi*180 + rotate)+ "";\r
+      }\r
+    },\r
+    \r
+    /*$animateElement.tocallメソッドを置き換えるためのメソッド\r
+     * mpath要素が指定されたときか、path属性のときにのみ使われる*/\r
+    _tocallForPath: function(advance) {\r
+      var path = this.path,\r
+          advanceLength = advance * path.getTotalLength();\r
+      /*全体の距離から、現在進めている距離を算出して、そこから、現在点を導き出す*/\r
+      var point = path.getPointAtLength(advanceLength),\r
+          rotate = 0; //追加すべき角度\r
+      if (this.rotate === "0") {\r
+        return point.x+ "," +point.y;\r
+      } else if (this.rotate === "auto") {\r
+        rotate = 0;\r
+      } else if (this.rotate === "auto-reverse") {\r
+        rotate = 180;\r
+      } else {\r
+        rotate = +this.rotate;\r
+      }\r
+      return point.x+ "," +point.y + this.getRotate(path, advanceLength, rotate);\r
+    }\r
   } )\r
   .on("init", function (ele) {\r
+    if (!ele || !ele.parentNode) {\r
+     return;\r
+    }\r
     /*type属性で変更されないように($animateTransformElementのinitメソッドを参照のこと)*/\r
     this.type = "translate";\r
+    /*isSumプロパティは常にtrueにしておく。animateTransform要素とは挙動が違うため\r
+     * また、$aniamteTransformElementのtocallメソッド参照*/\r
+    this.isSum = true;\r
     this.mode = this.getAttr("mode", "paced");\r
+    this.rotate = this.getAttr("rotate", "0");\r
+    this.path = this.path.cloneNode(true);\r
+    var mpath = ele.getElementsByTagNameNS(this.path.namespaceURI, "mpath");\r
+    /*$animateは後で、プロパティを書き換えるために使う。tocallメソッドも参照*/\r
+    var $animate = this.$animateElement;\r
+    if (mpath.length) {\r
+      var p = ele.ownerDocument.getElementById(\r
+        mpath[0].getAttributeNS("http://www.w3.org/1999/xlink", "href").slice(1)\r
+      );\r
+      p && this.path.setAttributeNS(null, "d", p.getAttributeNS(null, "d"));\r
+      this.$animateElement = $animate.up().mix ( {tocall: this._tocallForPath} );\r
+    } else if (ele.hasAttributeNS(null, "path")) {\r
+      this.path.setAttributeNS(null, "d", ele.getAttributeNS(null, "path"));\r
+      this.$animateElement = $animate.up().mix ( {tocall: this._tocallForPath} );\r
+    }\r
   } );\r
 \r
 /*$svgEventオブジェクトは、SVGEvent発火を監視するためのオブジェクト*/\r
@@ -1711,8 +1961,8 @@ base("$frame").up("$svgEvent").mix( {
   setTimeTable: function () {\r
     var timelines = this.timelines;\r
       for (var i=0, obj = null;i<timelines.length;++i) {\r
-        if (!timelines[i].target) {\r
-          /*target オブジェクトがないものは除外*/\r
+        if (!timelines[i].target || !timelines[i].isResolved) {\r
+          /*一度スケジュールを作成して実行したり、未解決だったタイムラインは、処理しない*/\r
           continue;\r
         }\r
         /*タイムラインから、beginEventとendEventを発火するスケジュールを作成*/\r
@@ -1720,8 +1970,8 @@ base("$frame").up("$svgEvent").mix( {
             begin = timeline.begin,\r
             target = timeline.target,\r
             simpleDur = timeline.simpleDuration,\r
-            activeTime = timeline.activeTime,\r
-            first = {\r
+            activeTime = timeline.activeTime;\r
+        var first = {\r
               frame: begin,\r
               eventType: "begin",\r
               target: target,\r
@@ -1733,26 +1983,37 @@ base("$frame").up("$svgEvent").mix( {
               }\r
             };\r
         if (obj) {\r
-          obj = obj.next.next = first;\r
+          obj.next.next = first;\r
+        } else if (!this.first) {\r
+          /*firstプロパティがすでにある場合、書き換えない*/\r
+          this.first = first;\r
         } else {\r
-          obj = this.first = first;\r
+          /*firstプロパティがある場合、そのリストのリンクをたどっていって、\r
+           * 最後の尾を変数firstとつなげる*/\r
+          var fst = this.first;\r
+          while(fst.next) {\r
+            fst = fst.next;\r
+          }\r
+          fst.next = first;\r
         }\r
+        obj = first;\r
         if (simpleDur && (activeTime !== simpleDur)) {\r
           /*活動継続時間と単純持続時間が異なるとき、repeatイベントを設定\r
            * ただし、repeatイベントはendイベントが発生する前に起きるものと仮定*/\r
-          first.next = {\r
-            firstFrame: begin + simpleDur,\r
-            frame: begin + simpleDur,\r
-            eventType: "repeat",\r
-            target: target,\r
-            /*リピートの制限時間*/\r
-            limit: begin + activeTime,\r
-            /*リピートの回数 (n >= 1)*/\r
-            count: 1,\r
-            simpleDuration: simpleDur,\r
-            next: first.next\r
-          };\r
+          for (var a = first, m= begin + simpleDur, n=1;m < begin + activeTime; m+=simpleDur, ++n) {\r
+            a.next = {\r
+              frame: m,\r
+              eventType: "repeat",\r
+              target: target,\r
+              /*リピートの回数 (n >= 1)*/\r
+              count: n,\r
+              next: a.next\r
+            };\r
+            a = a.next;\r
+          }\r
         }\r
+        /*一度、スケジュールを作っておいたタイムラインは次回から処理しないようにする*/\r
+        timeline.target = null;\r
       }\r
     timelines = obj = first = begin = target = simpleDur = activeTime = void 0;\r
   },\r
@@ -1780,22 +2041,15 @@ base("$frame").up("$svgEvent").mix( {
       if (frame <= num) {\r
         /*IE11ではSVGEventsやDOMEventsを使うと問題が起きるため、MouseEventsで代用する*/\r
         if (obj.eventType === "repeat") {\r
-          var simpleDuration = obj.simpleDuration;\r
-          /*リピートイベントが、リピート制限内である場合\r
-           *numの段階で、何回リピートしたかを求める*/\r
-          detail = obj.count = floor( (num - obj.firstFrame) / simpleDuration) + 1;\r
-          /*simpleDurationを足すことによって、リピートイベントが\r
-           * 単純継続時間内に何度も繰り返されることを防ぐ*/\r
-          frame += simpleDuration;\r
-          obj.frame = frame;\r
+          /*detailは何回リピートしたか*/\r
+          detail = obj.count;\r
         }\r
-        /*obj.limitはrepeatイベントの制限で使われるもの*/\r
-        if ((obj.eventType !== "repeat") || (frame >= obj.limit)) {\r
-          /*ポインタの連結を変更することで、リストからobj を除去*/\r
-          cobj.next = obj.next;\r
-          if (this.first === obj) {\r
-            cobj = this.first = obj.next;\r
-          }\r
+        /*ポインタの連結を変更することで、リストからobj を除去\r
+         * 一度除去したものはイベントを発生させない*/\r
+        cobj.next = obj.next;\r
+        if (this.first === obj) {\r
+          cobj = obj.next;\r
+          this.first = cobj;\r
         } else {\r
           cobj = obj;\r
         }\r
@@ -1808,7 +2062,7 @@ base("$frame").up("$svgEvent").mix( {
       }\r
       obj = obj.next;\r
     }\r
-    obj = num = first = frame = target = cobj = simpleDuration = detail = void 0;\r
+    obj = num = first = frame = target = cobj = detail = void 0;\r
     return s;\r
   }\r
 } );\r
@@ -1866,7 +2120,9 @@ function getDocument()
 window.addEventListener && window.addEventListener("load", getDocument);\r
 \r
 function __step() {\r
-if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Animation", "1.1")) {\r
+/*EdgeはhasFeatureメソッドでtrueを返す*/\r
+if (!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Animation", "1.1")\r
+    || window.navigator.userAgent.toLowerCase().indexOf("edge")) {\r
   if (window.requestAnimationFrame && requestAnimationFrame) {\r
     /*IE11などSMILアニメーションに対応していないブラウザ用*/\r
     /*cancelはアニメーションの中止ハンドル*/\r