Merge branch 'master' of github.com:bodymovin/bodymovin
diff --git a/player/index.html b/player/index.html
index 2c7e787..cb85320 100644
--- a/player/index.html
+++ b/player/index.html
@@ -61,6 +61,7 @@
     <script src="js/mask.js"></script>
     <script src="js/elements/BaseElement.js"></script>
     <script src="js/elements/svgElements/SVGBaseElement.js"></script>
+    <script src="js/elements/ShapeElement.js"></script>
     <script src="js/elements/TextElement.js"></script>
     <script src="js/elements/svgElements/SVGTextElement.js"></script>
     <script src="js/elements/svgElements/effects/SVGTintEffect.js"></script>
@@ -72,7 +73,6 @@
     <script src="js/elements/svgElements/SVGEffects.js"></script>
     <script src="js/elements/CompElement.js"></script>
     <script src="js/elements/ImageElement.js"></script>
-    <script src="js/elements/ShapeElement.js"></script>
     <script src="js/elements/SolidElement.js"></script>
     <script src="js/elements/canvasElements/CVBaseElement.js" data-light-skip="true"></script>
     <script src="js/elements/canvasElements/CVCompElement.js" data-light-skip="true"></script>
@@ -120,7 +120,7 @@
     var animData = {
         container: elem,
         renderer: 'svg',
-        loop: false,
+        loop: true,
         autoplay: true,
         rendererSettings: {
             progressiveLoad:false
diff --git a/player/js/3rd_party/transformation-matrix.js b/player/js/3rd_party/transformation-matrix.js
index d1045eb..6ad47ac 100644
--- a/player/js/3rd_party/transformation-matrix.js
+++ b/player/js/3rd_party/transformation-matrix.js
@@ -169,6 +169,7 @@
                 this.props[14] = this.props[12] * c2 + this.props[13] * g2 + this.props[14] * k2 + this.props[15] * o2 ;
                 this.props[15] = this.props[12] * d2 + this.props[13] * h2 + this.props[14] * l2 + this.props[15] * p2 ;
             }
+            this._identityCalculated = false;
             return this;
         }
 
@@ -214,9 +215,21 @@
         this.props[14] = m1 * c2 + n1 * g2 + o1 * k2 + p1 * o2 ;
         this.props[15] = m1 * d2 + n1 * h2 + o1 * l2 + p1 * p2 ;
 
+        this._identityCalculated = false;
         return this;
     }
 
+    function isIdentity() {
+        if(!this._identityCalculated){
+            this._identity = !(this.props[0] !== 1 || this.props[1] !== 0 || this.props[2] !== 0 || this.props[3] !== 0
+                || this.props[4] !== 0 || this.props[5] !== 1 || this.props[6] !== 0 || this.props[7] !== 0
+                || this.props[8] !== 0 || this.props[9] !== 0 || this.props[10] !== 1 || this.props[11] !== 0
+                || this.props[12] !== 0 || this.props[13] !== 0 || this.props[14] !== 0 || this.props[15] !== 1);
+            this._identityCalculated = true;
+        }
+        return this._identity;
+    }
+
     function clone(matr){
         var i;
         for(i=0;i<16;i+=1){
@@ -282,6 +295,9 @@
         return [x * this.props[0] + y * this.props[4] + z * this.props[8] + this.props[12],x * this.props[1] + y * this.props[5] + z * this.props[9] + this.props[13],x * this.props[2] + y * this.props[6] + z * this.props[10] + this.props[14]];
     }
     function applyToPointStringified(x, y) {
+        if(this.isIdentity()) {
+            return x + ',' + y;
+        }
         return (bm_rnd(x * this.props[0] + y * this.props[4] + this.props[12]))+','+(bm_rnd(x * this.props[1] + y * this.props[5] + this.props[13]));
     }
 
@@ -334,6 +350,9 @@
         this.inversePoints = inversePoints;
         this.inversePoint = inversePoint;
         this._t = this.transform;
+        this.isIdentity = isIdentity;
+        this._identity = true;
+        this._identityCalculated = false;
 
         this.props = [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1];
 
diff --git a/player/js/elements/ShapeElement.js b/player/js/elements/ShapeElement.js
index 1c278ac..ebaaa39 100644
--- a/player/js/elements/ShapeElement.js
+++ b/player/js/elements/ShapeElement.js
@@ -8,6 +8,8 @@
 }
 createElement(SVGBaseElement, IShapeElement);
 
+IShapeElement.prototype.identityMatrix = new Matrix();
+
 IShapeElement.prototype.lcEnum = {
     '1': 'butt',
     '2': 'round',
@@ -359,6 +361,24 @@
 
 };
 
+IShapeElement.prototype.buildShapeString = function(pathNodes, length, closed, mat) {
+    var i, shapeString = '';
+    for(i = 1; i < length; i += 1) {
+        if (i === 1) {
+            shapeString += " M" + mat.applyToPointStringified(pathNodes.v[0][0], pathNodes.v[0][1]);
+        }
+        shapeString += " C" + mat.applyToPointStringified(pathNodes.o[i - 1][0], pathNodes.o[i - 1][1]) + " " + mat.applyToPointStringified(pathNodes.i[i][0], pathNodes.i[i][1]) + " " + mat.applyToPointStringified(pathNodes.v[i][0], pathNodes.v[i][1]);
+    }
+    if (length === 1) {
+        shapeString += " M" + mat.applyToPointStringified(pathNodes.v[0][0], pathNodes.v[0][1]);
+    }
+    if (closed && length) {
+        shapeString += " C" + mat.applyToPointStringified(pathNodes.o[i - 1][0], pathNodes.o[i - 1][1]) + " " + mat.applyToPointStringified(pathNodes.i[0][0], pathNodes.i[0][1]) + " " + mat.applyToPointStringified(pathNodes.v[0][0], pathNodes.v[0][1]);
+        shapeString += 'z';
+    }
+    return shapeString;
+}
+
 IShapeElement.prototype.renderPath = function(pathData,viewData){
     var len, i, j, jLen,pathStringTransformed,redraw,pathNodes,l, lLen = viewData.elements.length;
     var lvl = viewData.lvl;
@@ -382,7 +402,8 @@
                 for(j=0;j<jLen;j+=1){
                     pathNodes = paths.shapes[j];
                     if(pathNodes && pathNodes._length){
-                        len = pathNodes._length;
+                        pathStringTransformed += this.buildShapeString(pathNodes, pathNodes._length, pathNodes.c, mat);
+                        /*len = pathNodes._length;
                         for (i = 1; i < len; i += 1) {
                             if (i == 1) {
                                 pathStringTransformed += " M" + mat.applyToPointStringified(pathNodes.v[0][0], pathNodes.v[0][1]);
@@ -395,7 +416,7 @@
                         if (pathNodes.c) {
                             pathStringTransformed += " C" + mat.applyToPointStringified(pathNodes.o[i - 1][0], pathNodes.o[i - 1][1]) + " " + mat.applyToPointStringified(pathNodes.i[0][0], pathNodes.i[0][1]) + " " + mat.applyToPointStringified(pathNodes.v[0][0], pathNodes.v[0][1]);
                             pathStringTransformed += 'z';
-                        }
+                        }*/
                     }
                 }
                 viewData.caches[l] = pathStringTransformed;
@@ -407,7 +428,8 @@
                 for(j=0;j<jLen;j+=1){
                     pathNodes = paths.shapes[j];
                     if(pathNodes && pathNodes._length){
-                        len = pathNodes._length;
+                        pathStringTransformed += this.buildShapeString(pathNodes, pathNodes._length, pathNodes.c, this.identityMatrix);
+                        /*len = pathNodes._length;
                         for (i = 1; i < len; i += 1) {
                             if (i == 1) {
                                 //pathStringTransformed += " M" + groupTransform.mat.applyToPointStringified(pathNodes.v[0][0], pathNodes.v[0][1]);
@@ -424,7 +446,7 @@
                             //pathStringTransformed += " C" + groupTransform.mat.applyToPointStringified(pathNodes.o[i - 1][0], pathNodes.o[i - 1][1]) + " " + groupTransform.mat.applyToPointStringified(pathNodes.i[0][0], pathNodes.i[0][1]) + " " + groupTransform.mat.applyToPointStringified(pathNodes.v[0][0], pathNodes.v[0][1]);
                             pathStringTransformed += " C" + pathNodes.o[i - 1].join(',') + " " + pathNodes.i[0].join(',') + " " + pathNodes.v[0].join(',');
                             pathStringTransformed += 'z';
-                        }
+                        }*/
                     }
                 }
                 viewData.caches[l] = pathStringTransformed;
diff --git a/player/js/elements/TextElement.js b/player/js/elements/TextElement.js
index e39239e..e7fba20 100644
--- a/player/js/elements/TextElement.js
+++ b/player/js/elements/TextElement.js
@@ -113,8 +113,9 @@
     var k, kLen, pathNodes;
     var shapeStr = '';
     for(j=0;j<jLen;j+=1){
-        kLen = shapes[j].ks.k.i.length;
         pathNodes = shapes[j].ks.k;
+        shapeStr += this.buildShapeString(pathNodes, pathNodes.i.length, true, matrixHelper);
+        /*kLen = pathNodes.i.length;
         for(k=1;k<kLen;k+=1){
             if(k==1){
                 shapeStr += " M"+matrixHelper.applyToPointStringified(pathNodes.v[0][0],pathNodes.v[0][1]);
@@ -122,7 +123,7 @@
             shapeStr += " C"+matrixHelper.applyToPointStringified(pathNodes.o[k-1][0],pathNodes.o[k-1][1]) + " "+matrixHelper.applyToPointStringified(pathNodes.i[k][0],pathNodes.i[k][1]) + " "+matrixHelper.applyToPointStringified(pathNodes.v[k][0],pathNodes.v[k][1]);
         }
         shapeStr += " C"+matrixHelper.applyToPointStringified(pathNodes.o[k-1][0],pathNodes.o[k-1][1]) + " "+matrixHelper.applyToPointStringified(pathNodes.i[0][0],pathNodes.i[0][1]) + " "+matrixHelper.applyToPointStringified(pathNodes.v[0][0],pathNodes.v[0][1]);
-        shapeStr += 'z';
+        shapeStr += 'z';*/
     }
     return shapeStr;
 };
@@ -586,4 +587,6 @@
     }
 };
 
+ITextElement.prototype.buildShapeString = IShapeElement.prototype.buildShapeString;
+
 ITextElement.prototype.emptyProp = new LetterProps();
diff --git a/player/js/elements/svgElements/SVGTextElement.js b/player/js/elements/svgElements/SVGTextElement.js
index a18f69a..bebb0f5 100644
--- a/player/js/elements/svgElements/SVGTextElement.js
+++ b/player/js/elements/svgElements/SVGTextElement.js
@@ -9,6 +9,7 @@
 SVGTextElement.prototype.createPathShape = ITextElement.prototype.createPathShape;
 SVGTextElement.prototype.getMeasures = ITextElement.prototype.getMeasures;
 SVGTextElement.prototype.prepareFrame = ITextElement.prototype.prepareFrame;
+SVGTextElement.prototype.buildShapeString = ITextElement.prototype.buildShapeString;
 
 SVGTextElement.prototype.createElements = function(){
 
@@ -131,7 +132,7 @@
             }
         }
         if(singleShape) {
-            xPos += letters[i].l;
+            xPos += letters[i].l || 0;
             xPos += documentData.tr/1000*documentData.s;
         }
         //
diff --git a/player/js/module.js b/player/js/module.js
index 99cae5b..a70d47b 100644
--- a/player/js/module.js
+++ b/player/js/module.js
@@ -119,7 +119,7 @@
     bodymovinjs.setQuality = setQuality;
     bodymovinjs.installPlugin = installPlugin;
     bodymovinjs.__getFactory = getFactory;
-    bodymovinjs.version = '4.1.9';
+    bodymovinjs.version = '4.8.0';
 
     function checkReady(){
         if (document.readyState === "complete") {
diff --git a/player/js/utils/DataManager.js b/player/js/utils/DataManager.js
index 0cc7b79..8719af6 100644
--- a/player/js/utils/DataManager.js
+++ b/player/js/utils/DataManager.js
@@ -165,6 +165,28 @@
         }
     }())
 
+    var checkChars = (function() {
+        var minimumVersion = [4,7,99];
+        return function (animationData){
+            if(animationData.chars && !checkVersion(minimumVersion,animationData.v)){
+                var i, len = animationData.chars.length, j, jLen, k, kLen;
+                var pathData, paths;
+                for(i = 0; i < len; i += 1) {
+                    if(animationData.chars[i].data && animationData.chars[i].data.shapes) {
+                        paths = animationData.chars[i].data.shapes[0].it;
+                        jLen = paths.length;
+
+                        for(j = 0; j < jLen; j += 1) {
+                            pathData = paths[j].ks.k;
+                            convertPathsToAbsoluteValues(paths[j].ks.k);
+                        }
+                    }
+                }
+            }
+        }
+
+    }())
+
     var checkColors = (function(){
         var minimumVersion = [4,1,9];
 
@@ -461,6 +483,7 @@
         }
         checkColors(animationData);
         checkText(animationData);
+        checkChars(animationData);
         checkShapes(animationData);
         completeLayers(animationData.layers, animationData.assets, fontManager);
         animationData.__complete = true;
diff --git a/player/js/utils/PropertyFactory.js b/player/js/utils/PropertyFactory.js
index 0b64858..b3a110d 100644
--- a/player/js/utils/PropertyFactory.js
+++ b/player/js/utils/PropertyFactory.js
@@ -287,7 +287,11 @@
             return ExpressionValue(this.or);
         }
         function rotationGetter() {
-            return ExpressionValue(this.r, 1/degToRads);
+            if(this.r) {
+                return ExpressionValue(this.r, 1/degToRads);
+            } else {
+                return ExpressionValue(this.rz, 1/degToRads);
+            }
         }
         function scaleGetter() {
             return ExpressionValue(this.s, 100);
diff --git a/player/js/utils/expressions/LayerInterface.js b/player/js/utils/expressions/LayerInterface.js
index 335437f..59fb53f 100644
--- a/player/js/utils/expressions/LayerInterface.js
+++ b/player/js/utils/expressions/LayerInterface.js
@@ -74,6 +74,7 @@
                 case 2:
                     return _thisLayerFunction.shapeInterface;
                 case 1:
+                case 6:
                 case "Transform":
                 case "transform":
                 case "ADBE Transform Group":
diff --git a/player/js/utils/expressions/ShapeInterface.js b/player/js/utils/expressions/ShapeInterface.js
index a3b01db..ceaf41f 100644
--- a/player/js/utils/expressions/ShapeInterface.js
+++ b/player/js/utils/expressions/ShapeInterface.js
@@ -617,20 +617,8 @@
                 if(shape.r.ix === value){
                     return interfaceFunction.rotation;
                 }
-                if(shape.pt.ix === value){
-                    return interfaceFunction.points;
-                }
-                if(shape.or.ix === value || 'ADBE Vector Star Outer Radius' === value){
-                    return interfaceFunction.outerRadius;
-                }
-                if(shape.os.ix === value){
-                    return interfaceFunction.outerRoundness;
-                }
-                if(shape.ir && (shape.ir.ix === value || 'ADBE Vector Star Inner Radius' === value)){
-                    return interfaceFunction.innerRadius;
-                }
-                if(shape.is && shape.is.ix === value){
-                    return interfaceFunction.innerRoundness;
+                if(shape.s.ix === value || value === 'Size'){
+                    return interfaceFunction.size;
                 }
 
             }
diff --git a/player/js/utils/expressions/TransformInterface.js b/player/js/utils/expressions/TransformInterface.js
index 71c6e88..dc549b2 100644
--- a/player/js/utils/expressions/TransformInterface.js
+++ b/player/js/utils/expressions/TransformInterface.js
@@ -5,23 +5,28 @@
                 case "scale":
                 case "Scale":
                 case "ADBE Scale":
+                case 6:
                     return _thisFunction.scale;
                 case "rotation":
                 case "Rotation":
                 case "ADBE Rotation":
                 case "ADBE Rotate Z":
+                case 10:
                     return _thisFunction.rotation;
                 case "position":
                 case "Position":
                 case "ADBE Position":
+                case 2:
                     return transform.position;
                 case "anchorPoint":
                 case "AnchorPoint":
                 case "Anchor Point":
                 case "ADBE AnchorPoint":
+                case 1:
                     return _thisFunction.anchorPoint;
                 case "opacity":
                 case "Opacity":
+                case 11:
                     return _thisFunction.opacity;
             }
         }