Merge branch '48_text_source_rect_at_time'
diff --git a/player/index.html b/player/index.html
index ddbc3a3..94b28d9 100644
--- a/player/index.html
+++ b/player/index.html
@@ -11,7 +11,7 @@
         }
 
         #bodymovin{
-            background-color:#333;
+            background-color:#fff;
             width:100%;
             height:100%;
             /*width:800px;
@@ -50,6 +50,8 @@
     <script src="js/utils/imagePreloader.js"></script>
     <script src="js/utils/featureSupport.js"></script>
     <script src="js/utils/filters.js"></script>
+    <script src="js/utils/text/TextAnimatorProperty.js"></script>
+    <script src="js/utils/text/LetterProps.js"></script>
     <script src="js/utils/pooling/pooling.js"></script>
     <script src="js/utils/pooling/point_pool.js"></script>
     <script src="js/utils/pooling/shape_pool.js"></script>
@@ -121,7 +123,7 @@
     var animData = {
         container: elem,
         renderer: 'svg',
-        loop: true,
+        loop: false,
         autoplay: true,
         rendererSettings: {
             progressiveLoad:false
diff --git a/player/js/elements/BaseElement.js b/player/js/elements/BaseElement.js
index 72b0931..9a72244 100644
--- a/player/js/elements/BaseElement.js
+++ b/player/js/elements/BaseElement.js
@@ -172,7 +172,7 @@
     if(!this.data.sr){
         this.data.sr = 1;
     }
-    this.dynamicProperties = [];
+    this.dynamicProperties = this.dynamicProperties || [];
     if(this.data.ef){
         this.effects = new EffectsManager(this.data,this,this.dynamicProperties);
         //this.effect = this.effectsManager.bind(this.effectsManager);
@@ -241,4 +241,13 @@
 
 };
 
+BaseElement.prototype.sourceRectAtTime = function(){
+    return {
+        top:0,
+        left:0,
+        width:100,
+        height:100
+    }
+};
+
 BaseElement.prototype.mHelper = new Matrix();
\ No newline at end of file
diff --git a/player/js/elements/TextElement.js b/player/js/elements/TextElement.js
index e7fba20..5e651dd 100644
--- a/player/js/elements/TextElement.js
+++ b/player/js/elements/TextElement.js
@@ -1,104 +1,30 @@
 function ITextElement(data, animationItem,parentContainer,globalData){
 }
 ITextElement.prototype.init = function(){
+    this._frameId = -1;
+    this.lettersChangedFlag = true;
+    this.currentTextDocumentData = this.data.t.d.k[0].s;
+    this.dynamicProperties = this.dynamicProperties || [];
+    this.textAnimator = new TextAnimatorProperty(this.data.t, this.renderType, this);
     this._parent.init.call(this);
-
-    this.lettersChangedFlag = false;
-    this.currentTextDocumentData = {};
-    var data = this.data;
-    this.viewData = {
-        m:{
-            a: PropertyFactory.getProp(this,data.t.m.a,1,0,this.dynamicProperties)
-        }
-    };
-    var textData = this.data.t;
-    if(textData.a.length){
-        this.viewData.a = Array.apply(null,{length:textData.a.length});
-        var i, len = textData.a.length, animatorData, animatorProps;
-        for(i=0;i<len;i+=1){
-            animatorProps = textData.a[i];
-            animatorData = {
-                a: {},
-                s: {}
-            };
-            if('r' in animatorProps.a) {
-                animatorData.a.r = PropertyFactory.getProp(this,animatorProps.a.r,0,degToRads,this.dynamicProperties);
-            }
-            if('rx' in animatorProps.a) {
-                animatorData.a.rx = PropertyFactory.getProp(this,animatorProps.a.rx,0,degToRads,this.dynamicProperties);
-            }
-            if('ry' in animatorProps.a) {
-                animatorData.a.ry = PropertyFactory.getProp(this,animatorProps.a.ry,0,degToRads,this.dynamicProperties);
-            }
-            if('sk' in animatorProps.a) {
-                animatorData.a.sk = PropertyFactory.getProp(this,animatorProps.a.sk,0,degToRads,this.dynamicProperties);
-            }
-            if('sa' in animatorProps.a) {
-                animatorData.a.sa = PropertyFactory.getProp(this,animatorProps.a.sa,0,degToRads,this.dynamicProperties);
-            }
-            if('s' in animatorProps.a) {
-                animatorData.a.s = PropertyFactory.getProp(this,animatorProps.a.s,1,0.01,this.dynamicProperties);
-            }
-            if('a' in animatorProps.a) {
-                animatorData.a.a = PropertyFactory.getProp(this,animatorProps.a.a,1,0,this.dynamicProperties);
-            }
-            if('o' in animatorProps.a) {
-                animatorData.a.o = PropertyFactory.getProp(this,animatorProps.a.o,0,0.01,this.dynamicProperties);
-            }
-            if('p' in animatorProps.a) {
-                animatorData.a.p = PropertyFactory.getProp(this,animatorProps.a.p,1,0,this.dynamicProperties);
-            }
-            if('sw' in animatorProps.a) {
-                animatorData.a.sw = PropertyFactory.getProp(this,animatorProps.a.sw,0,0,this.dynamicProperties);
-            }
-            if('sc' in animatorProps.a) {
-                animatorData.a.sc = PropertyFactory.getProp(this,animatorProps.a.sc,1,0,this.dynamicProperties);
-            }
-            if('fc' in animatorProps.a) {
-                animatorData.a.fc = PropertyFactory.getProp(this,animatorProps.a.fc,1,0,this.dynamicProperties);
-            }
-            if('fh' in animatorProps.a) {
-                animatorData.a.fh = PropertyFactory.getProp(this,animatorProps.a.fh,0,0,this.dynamicProperties);
-            }
-            if('fs' in animatorProps.a) {
-                animatorData.a.fs = PropertyFactory.getProp(this,animatorProps.a.fs,0,0.01,this.dynamicProperties);
-            }
-            if('fb' in animatorProps.a) {
-                animatorData.a.fb = PropertyFactory.getProp(this,animatorProps.a.fb,0,0.01,this.dynamicProperties);
-            }
-            if('t' in animatorProps.a) {
-                animatorData.a.t = PropertyFactory.getProp(this,animatorProps.a.t,0,0,this.dynamicProperties);
-            }
-            animatorData.s = PropertyFactory.getTextSelectorProp(this,animatorProps.s,this.dynamicProperties);
-            animatorData.s.t = animatorProps.s.t;
-            this.viewData.a[i] = animatorData;
-        }
-    }else{
-        this.viewData.a = [];
-    }
-    if(textData.p && 'm' in textData.p){
-        this.viewData.p = {
-            f: PropertyFactory.getProp(this,textData.p.f,0,0,this.dynamicProperties),
-            l: PropertyFactory.getProp(this,textData.p.l,0,0,this.dynamicProperties),
-            r: textData.p.r,
-            m: this.maskManager.getMaskProperty(textData.p.m)
-        };
-        this.maskPath = true;
-    } else {
-        this.maskPath = false;
-    }
+    this.textAnimator.searchProperties(this.dynamicProperties);
+    this.buildNewText();
 };
+
 ITextElement.prototype.prepareFrame = function(num) {
-    var i = 0, len = this.data.t.d.k.length;
-    var textDocumentData = this.data.t.d.k[i].s;
-    i += 1;
-    while(i<len){
-        if(this.data.t.d.k[i].t > num){
+    if(this._frameId === this.globalData.frameId) {
+         return;
+    }
+    this._frameId = this.globalData.frameId;
+    var textKeys = this.data.t.d.k;
+    var i = 0, len = textKeys.length;
+    while(i < len) {
+        textDocumentData = textKeys[i].s;
+        i += 1;
+        if(i === len || textKeys[i].t > num){
             break;
         }
-        textDocumentData = this.data.t.d.k[i].s;
-        i += 1;
-    }
+    } 
     this.lettersChangedFlag = false;
     if(textDocumentData !== this.currentTextDocumentData){
         this.currentTextDocumentData = textDocumentData;
@@ -128,465 +54,6 @@
     return shapeStr;
 };
 
-ITextElement.prototype.getMeasures = function(){
-
-    var matrixHelper = this.mHelper;
-    var renderType = this.renderType;
-    var data = this.data;
-    var xPos,yPos;
-    var i, len;
-    var documentData = this.currentTextDocumentData;
-    var letters = documentData.l;
-    if(this.maskPath) {
-        var mask = this.viewData.p.m;
-        if(!this.viewData.p.n || this.viewData.p.mdf){
-            var paths = mask.v;
-            if(this.viewData.p.r){
-                paths = reversePath(paths);
-            }
-            var pathInfo = {
-                tLength: 0,
-                segments: []
-            };
-            len = paths.v.length - 1;
-            var pathData;
-            var totalLength = 0;
-            for (i = 0; i < len; i += 1) {
-                pathData = {
-                    s: paths.v[i],
-                    e: paths.v[i + 1],
-                    to: [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]],
-                    ti: [paths.i[i + 1][0] - paths.v[i + 1][0], paths.i[i + 1][1] - paths.v[i + 1][1]]
-                };
-                bez.buildBezierData(pathData);
-                pathInfo.tLength += pathData.bezierData.segmentLength;
-                pathInfo.segments.push(pathData);
-                totalLength += pathData.bezierData.segmentLength;
-            }
-            i = len;
-            if (mask.v.c) {
-                pathData = {
-                    s: paths.v[i],
-                    e: paths.v[0],
-                    to: [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]],
-                    ti: [paths.i[0][0] - paths.v[0][0], paths.i[0][1] - paths.v[0][1]]
-                };
-                bez.buildBezierData(pathData);
-                pathInfo.tLength += pathData.bezierData.segmentLength;
-                pathInfo.segments.push(pathData);
-                totalLength += pathData.bezierData.segmentLength;
-            }
-            this.viewData.p.pi = pathInfo;
-        }
-        var pathInfo = this.viewData.p.pi;
-
-        var currentLength = this.viewData.p.f.v, segmentInd = 0, pointInd = 1, currentPoint, prevPoint, points;
-        var segmentLength = 0, flag = true;
-        var segments = pathInfo.segments;
-        if (currentLength < 0 && mask.v.c) {
-            if (pathInfo.tLength < Math.abs(currentLength)) {
-                currentLength = -Math.abs(currentLength) % pathInfo.tLength;
-            }
-            segmentInd = segments.length - 1;
-            points = segments[segmentInd].bezierData.points;
-            pointInd = points.length - 1;
-            while (currentLength < 0) {
-                currentLength += points[pointInd].partialLength;
-                pointInd -= 1;
-                if (pointInd < 0) {
-                    segmentInd -= 1;
-                    points = segments[segmentInd].bezierData.points;
-                    pointInd = points.length - 1;
-                }
-            }
-
-        }
-        points = segments[segmentInd].bezierData.points;
-        prevPoint = points[pointInd - 1];
-        currentPoint = points[pointInd];
-        var partialLength = currentPoint.partialLength;
-        var perc, tanAngle;
-    }
-
-
-    len = letters.length;
-    xPos = 0;
-    yPos = 0;
-    var yOff = documentData.s*1.2*.714;
-    var firstLine = true;
-    var renderedData = this.viewData, animatorProps, animatorSelector;
-    var j, jLen;
-    var lettersValue = Array.apply(null,{length:len}), letterValue;
-
-    jLen = renderedData.a.length;
-    var lastLetter;
-
-    var mult, ind = -1, offf, xPathPos, yPathPos;
-    var initPathPos = currentLength,initSegmentInd = segmentInd, initPointInd = pointInd, currentLine = -1;
-    var elemOpacity;
-    var sc,sw,fc,k;
-    var lineLength = 0;
-    var letterSw,letterSc,letterFc,letterM,letterP,letterO;
-    for( i = 0; i < len; i += 1) {
-        matrixHelper.reset();
-        elemOpacity = 1;
-        if(letters[i].n) {
-            xPos = 0;
-            yPos += documentData.yOffset;
-            yPos += firstLine ? 1 : 0;
-            currentLength = initPathPos ;
-            firstLine = false;
-            lineLength = 0;
-            if(this.maskPath) {
-                segmentInd = initSegmentInd;
-                pointInd = initPointInd;
-                points = segments[segmentInd].bezierData.points;
-                prevPoint = points[pointInd - 1];
-                currentPoint = points[pointInd];
-                partialLength = currentPoint.partialLength;
-                segmentLength = 0;
-            }
-            lettersValue[i] = this.emptyProp;
-        }else{
-            if(this.maskPath) {
-                if(currentLine !== letters[i].line){
-                    switch(documentData.j){
-                        case 1:
-                            currentLength += totalLength - documentData.lineWidths[letters[i].line];
-                            break;
-                        case 2:
-                            currentLength += (totalLength - documentData.lineWidths[letters[i].line])/2;
-                            break;
-                    }
-                    currentLine = letters[i].line;
-                }
-                if (ind !== letters[i].ind) {
-                    if (letters[ind]) {
-                        currentLength += letters[ind].extra;
-                    }
-                    currentLength += letters[i].an / 2;
-                    ind = letters[i].ind;
-                }
-                currentLength += renderedData.m.a.v[0] * letters[i].an / 200;
-                var animatorOffset = 0;
-                for (j = 0; j < jLen; j += 1) {
-                    animatorProps = renderedData.a[j].a;
-                    if ('p' in animatorProps) {
-                        animatorSelector = renderedData.a[j].s;
-                        mult = animatorSelector.getMult(letters[i].anIndexes[j],data.t.a[j].s.totalChars);
-                        if(mult.length){
-                            animatorOffset += animatorProps.p.v[0] * mult[0];
-                        } else{
-                            animatorOffset += animatorProps.p.v[0] * mult;
-                        }
-
-                    }
-                    if ('a' in animatorProps) {
-                        animatorSelector = renderedData.a[j].s;
-                        mult = animatorSelector.getMult(letters[i].anIndexes[j],data.t.a[j].s.totalChars);
-                        if(mult.length){
-                            animatorOffset += animatorProps.a.v[0] * mult[0];
-                        } else{
-                            animatorOffset += animatorProps.a.v[0] * mult;
-                        }
-
-                    }
-                }
-                flag = true;
-                while (flag) {
-                    if (segmentLength + partialLength >= currentLength + animatorOffset || !points) {
-                        perc = (currentLength + animatorOffset - segmentLength) / currentPoint.partialLength;
-                        xPathPos = prevPoint.point[0] + (currentPoint.point[0] - prevPoint.point[0]) * perc;
-                        yPathPos = prevPoint.point[1] + (currentPoint.point[1] - prevPoint.point[1]) * perc;
-                        matrixHelper.translate(-renderedData.m.a.v[0]*letters[i].an/200, -(renderedData.m.a.v[1] * yOff / 100));
-                        flag = false;
-                    } else if (points) {
-                        segmentLength += currentPoint.partialLength;
-                        pointInd += 1;
-                        if (pointInd >= points.length) {
-                            pointInd = 0;
-                            segmentInd += 1;
-                            if (!segments[segmentInd]) {
-                                if (mask.v.c) {
-                                    pointInd = 0;
-                                    segmentInd = 0;
-                                    points = segments[segmentInd].bezierData.points;
-                                } else {
-                                    segmentLength -= currentPoint.partialLength;
-                                    points = null;
-                                }
-                            } else {
-                                points = segments[segmentInd].bezierData.points;
-                            }
-                        }
-                        if (points) {
-                            prevPoint = currentPoint;
-                            currentPoint = points[pointInd];
-                            partialLength = currentPoint.partialLength;
-                        }
-                    }
-                }
-                offf = letters[i].an / 2 - letters[i].add;
-                matrixHelper.translate(-offf, 0, 0);
-            } else {
-                offf = letters[i].an/2 - letters[i].add;
-                matrixHelper.translate(-offf,0,0);
-
-                // Grouping alignment
-                matrixHelper.translate(-renderedData.m.a.v[0]*letters[i].an/200, -renderedData.m.a.v[1]*yOff/100, 0);
-            }
-
-            lineLength += letters[i].l/2;
-            for(j=0;j<jLen;j+=1){
-                animatorProps = renderedData.a[j].a;
-                if ('t' in animatorProps) {
-                    animatorSelector = renderedData.a[j].s;
-                    mult = animatorSelector.getMult(letters[i].anIndexes[j],data.t.a[j].s.totalChars);
-                    if(this.maskPath) {
-                        if(mult.length) {
-                            currentLength += animatorProps.t*mult[0];
-                        } else {
-                            currentLength += animatorProps.t*mult;
-                        }
-                    }else{
-                        if(mult.length) {
-                            xPos += animatorProps.t.v*mult[0];
-                        } else {
-                            xPos += animatorProps.t.v*mult;
-                        }
-                    }
-                }
-            }
-            lineLength += letters[i].l/2;
-            if(documentData.strokeWidthAnim) {
-                sw = documentData.sw || 0;
-            }
-            if(documentData.strokeColorAnim) {
-                if(documentData.sc){
-                    sc = [documentData.sc[0], documentData.sc[1], documentData.sc[2]];
-                }else{
-                    sc = [0,0,0];
-                }
-            }
-            if(documentData.fillColorAnim) {
-                fc = [documentData.fc[0], documentData.fc[1], documentData.fc[2]];
-            }
-            for(j=0;j<jLen;j+=1){
-                animatorProps = renderedData.a[j].a;
-                if ('a' in animatorProps) {
-                    animatorSelector = renderedData.a[j].s;
-                    mult = animatorSelector.getMult(letters[i].anIndexes[j],data.t.a[j].s.totalChars);
-
-                    if(mult.length){
-                        matrixHelper.translate(-animatorProps.a.v[0]*mult[0], -animatorProps.a.v[1]*mult[1], animatorProps.a.v[2]*mult[2]);
-                    } else {
-                        matrixHelper.translate(-animatorProps.a.v[0]*mult, -animatorProps.a.v[1]*mult, animatorProps.a.v[2]*mult);
-                    }
-                }
-            }
-            for(j=0;j<jLen;j+=1){
-                animatorProps = renderedData.a[j].a;
-                if ('s' in animatorProps) {
-                    animatorSelector = renderedData.a[j].s;
-                    mult = animatorSelector.getMult(letters[i].anIndexes[j],data.t.a[j].s.totalChars);
-                    if(mult.length){
-                        matrixHelper.scale(1+((animatorProps.s.v[0]-1)*mult[0]),1+((animatorProps.s.v[1]-1)*mult[1]),1);
-                    } else {
-                        matrixHelper.scale(1+((animatorProps.s.v[0]-1)*mult),1+((animatorProps.s.v[1]-1)*mult),1);
-                    }
-                }
-            }
-            for(j=0;j<jLen;j+=1) {
-                animatorProps = renderedData.a[j].a;
-                animatorSelector = renderedData.a[j].s;
-                mult = animatorSelector.getMult(letters[i].anIndexes[j],data.t.a[j].s.totalChars);
-                if ('sk' in animatorProps) {
-                    if(mult.length) {
-                        matrixHelper.skewFromAxis(-animatorProps.sk.v * mult[0], animatorProps.sa.v * mult[1]);
-                    } else {
-                        matrixHelper.skewFromAxis(-animatorProps.sk.v * mult, animatorProps.sa.v * mult);
-                    }
-                }
-                if ('r' in animatorProps) {
-                    if(mult.length) {
-                        matrixHelper.rotateZ(-animatorProps.r.v * mult[2]);
-                    } else {
-                        matrixHelper.rotateZ(-animatorProps.r.v * mult);
-                    }
-                }
-                if ('ry' in animatorProps) {
-
-                    if(mult.length) {
-                        matrixHelper.rotateY(animatorProps.ry.v*mult[1]);
-                    }else{
-                        matrixHelper.rotateY(animatorProps.ry.v*mult);
-                    }
-                }
-                if ('rx' in animatorProps) {
-                    if(mult.length) {
-                        matrixHelper.rotateX(animatorProps.rx.v*mult[0]);
-                    } else {
-                        matrixHelper.rotateX(animatorProps.rx.v*mult);
-                    }
-                }
-                if ('o' in animatorProps) {
-                    if(mult.length) {
-                        elemOpacity += ((animatorProps.o.v)*mult[0] - elemOpacity)*mult[0];
-                    } else {
-                        elemOpacity += ((animatorProps.o.v)*mult - elemOpacity)*mult;
-                    }
-                }
-                if (documentData.strokeWidthAnim && 'sw' in animatorProps) {
-                    if(mult.length) {
-                        sw += animatorProps.sw.v*mult[0];
-                    } else {
-                        sw += animatorProps.sw.v*mult;
-                    }
-                }
-                if (documentData.strokeColorAnim && 'sc' in animatorProps) {
-                    for(k=0;k<3;k+=1){
-                        if(mult.length) {
-                            sc[k] = Math.round(255*(sc[k] + (animatorProps.sc.v[k] - sc[k])*mult[0]));
-                        } else {
-                            sc[k] = Math.round(255*(sc[k] + (animatorProps.sc.v[k] - sc[k])*mult));
-                        }
-                    }
-                }
-                if (documentData.fillColorAnim) {
-                    if('fc' in animatorProps){
-                        for(k=0;k<3;k+=1){
-                            if(mult.length) {
-                                fc[k] = fc[k] + (animatorProps.fc.v[k] - fc[k])*mult[0];
-                            } else {
-                                fc[k] = fc[k] + (animatorProps.fc.v[k] - fc[k])*mult;
-                                //console.log('mult',mult);
-                                //console.log(Math.round(fc[k] + (animatorProps.fc.v[k] - fc[k])*mult));
-                            }
-                        }
-                    }
-                    if('fh' in animatorProps){
-                        if(mult.length) {
-                            fc = addHueToRGB(fc,animatorProps.fh.v*mult[0]);
-                        } else {
-                            fc = addHueToRGB(fc,animatorProps.fh.v*mult);
-                        }
-                    }
-                    if('fs' in animatorProps){
-                        if(mult.length) {
-                            fc = addSaturationToRGB(fc,animatorProps.fs.v*mult[0]);
-                        } else {
-                            fc = addSaturationToRGB(fc,animatorProps.fs.v*mult);
-                        }
-                    }
-                    if('fb' in animatorProps){
-                        if(mult.length) {
-                            fc = addBrightnessToRGB(fc,animatorProps.fb.v*mult[0]);
-                        } else {
-                            fc = addBrightnessToRGB(fc,animatorProps.fb.v*mult);
-                        }
-                    }
-                }
-            }
-
-            for(j=0;j<jLen;j+=1){
-                animatorProps = renderedData.a[j].a;
-
-                if ('p' in animatorProps) {
-                    animatorSelector = renderedData.a[j].s;
-                    mult = animatorSelector.getMult(letters[i].anIndexes[j],data.t.a[j].s.totalChars);
-                    if(this.maskPath) {
-                        if(mult.length) {
-                            matrixHelper.translate(0, animatorProps.p.v[1] * mult[0], -animatorProps.p.v[2] * mult[1]);
-                        } else {
-                            matrixHelper.translate(0, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult);
-                        }
-                    }else{
-                        if(mult.length) {
-                            matrixHelper.translate(animatorProps.p.v[0] * mult[0], animatorProps.p.v[1] * mult[1], -animatorProps.p.v[2] * mult[2]);
-                        } else {
-                            matrixHelper.translate(animatorProps.p.v[0] * mult, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult);
-                        }
-                    }
-                }
-            }
-            if(documentData.strokeWidthAnim){
-                letterSw = sw < 0 ? 0 : sw;
-            }
-            if(documentData.strokeColorAnim){
-                letterSc = 'rgb('+Math.round(sc[0]*255)+','+Math.round(sc[1]*255)+','+Math.round(sc[2]*255)+')';
-            }
-            if(documentData.fillColorAnim){
-                letterFc = 'rgb('+Math.round(fc[0]*255)+','+Math.round(fc[1]*255)+','+Math.round(fc[2]*255)+')';
-            }
-
-            if(this.maskPath) {
-                matrixHelper.translate(0,-documentData.ls);
-
-                matrixHelper.translate(0, renderedData.m.a.v[1]*yOff/100 + yPos,0);
-                if (data.t.p.p) {
-                    tanAngle = (currentPoint.point[1] - prevPoint.point[1]) / (currentPoint.point[0] - prevPoint.point[0]);
-                    var rot = Math.atan(tanAngle) * 180 / Math.PI;
-                    if (currentPoint.point[0] < prevPoint.point[0]) {
-                        rot += 180;
-                    }
-                    matrixHelper.rotate(-rot * Math.PI / 180);
-                }
-                matrixHelper.translate(xPathPos, yPathPos, 0);
-                currentLength -= renderedData.m.a.v[0]*letters[i].an/200;
-                if(letters[i+1] && ind !== letters[i+1].ind){
-                    currentLength += letters[i].an / 2;
-                    currentLength += documentData.tr/1000*documentData.s;
-                }
-            }else{
-
-                matrixHelper.translate(xPos,yPos,0);
-
-                if(documentData.ps){
-                    //matrixHelper.translate(documentData.ps[0],documentData.ps[1],0);
-                    matrixHelper.translate(documentData.ps[0],documentData.ps[1] + documentData.ascent,0);
-                }
-                switch(documentData.j){
-                    case 1:
-                        matrixHelper.translate(documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line]),0,0);
-                        break;
-                    case 2:
-                        matrixHelper.translate(documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line])/2,0,0);
-                        break;
-                }
-                matrixHelper.translate(0,-documentData.ls);
-                matrixHelper.translate(offf,0,0);
-                matrixHelper.translate(renderedData.m.a.v[0]*letters[i].an/200,renderedData.m.a.v[1]*yOff/100,0);
-                xPos += letters[i].l + documentData.tr/1000*documentData.s;
-            }
-            if(renderType === 'html'){
-                letterM = matrixHelper.toCSS();
-            }else if(renderType === 'svg'){
-                letterM = matrixHelper.to2dCSS();
-            }else{
-                letterP = [matrixHelper.props[0],matrixHelper.props[1],matrixHelper.props[2],matrixHelper.props[3],matrixHelper.props[4],matrixHelper.props[5],matrixHelper.props[6],matrixHelper.props[7],matrixHelper.props[8],matrixHelper.props[9],matrixHelper.props[10],matrixHelper.props[11],matrixHelper.props[12],matrixHelper.props[13],matrixHelper.props[14],matrixHelper.props[15]];
-            }
-            letterO = elemOpacity;
-
-            lastLetter = this.renderedLetters[i];
-            if(lastLetter && (lastLetter.o !== letterO || lastLetter.sw !== letterSw || lastLetter.sc !== letterSc || lastLetter.fc !== letterFc)){
-                this.lettersChangedFlag = true;
-                letterValue = new LetterProps(letterO,letterSw,letterSc,letterFc,letterM,letterP);
-            }else{
-                if((renderType === 'svg' || renderType === 'html') && (!lastLetter || lastLetter.m !== letterM)){
-                    this.lettersChangedFlag = true;
-                    letterValue = new LetterProps(letterO,letterSw,letterSc,letterFc,letterM);
-                }else if(renderType === 'canvas' && (!lastLetter || (lastLetter.props[0] !== letterP[0] || lastLetter.props[1] !== letterP[1] || lastLetter.props[4] !== letterP[4] || lastLetter.props[5] !== letterP[5] || lastLetter.props[12] !== letterP[12] || lastLetter.props[13] !== letterP[13]))){
-                    this.lettersChangedFlag = true;
-                    letterValue = new LetterProps(letterO,letterSw,letterSc,letterFc,null,letterP);
-                } else {
-                    letterValue = lastLetter;
-                }
-            }
-            this.renderedLetters[i] = letterValue;
-        }
-    }
-};
-
 ITextElement.prototype.buildShapeString = IShapeElement.prototype.buildShapeString;
 
 ITextElement.prototype.emptyProp = new LetterProps();
diff --git a/player/js/elements/canvasElements/CVTextElement.js b/player/js/elements/canvasElements/CVTextElement.js
index 3be7267..d208240 100644
--- a/player/js/elements/canvasElements/CVTextElement.js
+++ b/player/js/elements/canvasElements/CVTextElement.js
@@ -20,7 +20,6 @@
 createElement(CVBaseElement, CVTextElement);
 
 CVTextElement.prototype.init = ITextElement.prototype.init;
-CVTextElement.prototype.getMeasures = ITextElement.prototype.getMeasures;
 CVTextElement.prototype.getMult = ITextElement.prototype.getMult;
 CVTextElement.prototype.prepareFrame = ITextElement.prototype.prepareFrame;
 
@@ -143,11 +142,11 @@
     ctx.miterLimit = 4;
 
     if(!this.data.singleShape){
-        this.getMeasures();
+        this.textAnimator.getMeasures(this.currentTextDocumentData, this.lettersChangedFlag);
     }
 
     var  i,len, j, jLen, k, kLen;
-    var renderedLetters = this.renderedLetters;
+    var renderedLetters = this.textAnimator.renderedLetters;
 
     var letters = this.currentTextDocumentData.l;
 
@@ -161,7 +160,7 @@
         renderedLetter = renderedLetters[i];
         if(renderedLetter){
             this.globalData.renderer.save();
-            this.globalData.renderer.ctxTransform(renderedLetter.props);
+            this.globalData.renderer.ctxTransform(renderedLetter.p);
             this.globalData.renderer.ctxOpacity(renderedLetter.o);
         }
         if(this.fill){
diff --git a/player/js/elements/htmlElements/HTextElement.js b/player/js/elements/htmlElements/HTextElement.js
index 26f395d..1389227 100644
--- a/player/js/elements/htmlElements/HTextElement.js
+++ b/player/js/elements/htmlElements/HTextElement.js
@@ -15,7 +15,6 @@
 createElement(HBaseElement, HTextElement);
 
 HTextElement.prototype.init = ITextElement.prototype.init;
-HTextElement.prototype.getMeasures = ITextElement.prototype.getMeasures;
 HTextElement.prototype.createPathShape = ITextElement.prototype.createPathShape;
 HTextElement.prototype.prepareFrame = ITextElement.prototype.prepareFrame;
 HTextElement.prototype.buildShapeString = ITextElement.prototype.buildShapeString;
@@ -220,12 +219,12 @@
         }
     }
 
-    this.getMeasures();
-    if(!this.lettersChangedFlag){
+    this.textAnimator.getMeasures(this.currentTextDocumentData, this.lettersChangedFlag);
+    if(!this.lettersChangedFlag && !this.textAnimator.lettersChangedFlag){
         return;
     }
     var  i,len;
-    var renderedLetters = this.renderedLetters;
+    var renderedLetters = this.textAnimator.renderedLetters;
 
     var letters = this.currentTextDocumentData.l;
 
diff --git a/player/js/elements/svgElements/SVGBaseElement.js b/player/js/elements/svgElements/SVGBaseElement.js
index 27f67f3..0eca493 100644
--- a/player/js/elements/svgElements/SVGBaseElement.js
+++ b/player/js/elements/svgElements/SVGBaseElement.js
@@ -8,6 +8,7 @@
     this.parentContainer = parentContainer;
     this.layerId = placeholder ? placeholder.layerId : 'ly_'+randomString(10);
     this.placeholder = placeholder;
+    this._sizeChanged = false;
     this.init();
 };
 
diff --git a/player/js/elements/svgElements/SVGTextElement.js b/player/js/elements/svgElements/SVGTextElement.js
index bebb0f5..a8bcd14 100644
--- a/player/js/elements/svgElements/SVGTextElement.js
+++ b/player/js/elements/svgElements/SVGTextElement.js
@@ -7,7 +7,6 @@
 
 SVGTextElement.prototype.init = ITextElement.prototype.init;
 SVGTextElement.prototype.createPathShape = ITextElement.prototype.createPathShape;
-SVGTextElement.prototype.getMeasures = ITextElement.prototype.getMeasures;
 SVGTextElement.prototype.prepareFrame = ITextElement.prototype.prepareFrame;
 SVGTextElement.prototype.buildShapeString = ITextElement.prototype.buildShapeString;
 
@@ -149,6 +148,61 @@
         tSpan.setAttribute('d',shapeStr);
         this.layerElement.appendChild(tSpan);
     }
+    this._sizeChanged = true;
+}
+
+SVGTextElement.prototype.sourceRectAtTime = function(time){
+    this.prepareFrame(this.comp.renderedFrame - this.data.st);
+    this.renderLetters();
+    if(this._sizeChanged){
+        this._sizeChanged = false;
+        var textBox = this.layerElement.getBBox();
+        this.bbox = {
+            top: textBox.y,
+            left: textBox.x,
+            width: textBox.width,
+            height: textBox.height
+        }
+    }
+    return this.bbox;
+}
+
+SVGTextElement.prototype.renderLetters = function(){
+
+    if(!this.data.singleShape){
+        this.textAnimator.getMeasures(this.currentTextDocumentData, this.lettersChangedFlag);
+        if(this.lettersChangedFlag || this.textAnimator.lettersChangedFlag){
+            this._sizeChanged = true;
+            var  i,len;
+            var renderedLetters = this.textAnimator.renderedLetters;
+
+            var letters = this.currentTextDocumentData.l;
+
+            len = letters.length;
+            var renderedLetter;
+            for(i=0;i<len;i+=1){
+                if(letters[i].n){
+                    continue;
+                }
+                renderedLetter = renderedLetters[i];
+                if(renderedLetter.mdf.m) {
+                    this.textSpans[i].setAttribute('transform',renderedLetter.m);
+                }
+                if(renderedLetter.mdf.o) {
+                    this.textSpans[i].setAttribute('opacity',renderedLetter.o);
+                }
+                if(renderedLetter.mdf.sw){
+                    this.textSpans[i].setAttribute('stroke-width',renderedLetter.sw);
+                }
+                if(renderedLetter.mdf.sc){
+                    this.textSpans[i].setAttribute('stroke',renderedLetter.sc);
+                }
+                if(renderedLetter.mdf.fc){
+                    this.textSpans[i].setAttribute('fill',renderedLetter.fc);
+                }
+            }
+        }
+    }
 }
 
 SVGTextElement.prototype.renderFrame = function(parentMatrix){
@@ -161,41 +215,10 @@
     if(this.hidden){
         this.show();
     }
-
-    if(this.data.singleShape){
-        return;
-    }
-    this.getMeasures();
-    if(!this.lettersChangedFlag){
-        return;
-    }
-    var  i,len;
-    var renderedLetters = this.renderedLetters;
-
-    var letters = this.currentTextDocumentData.l;
-
-    len = letters.length;
-    var renderedLetter;
-    for(i=0;i<len;i+=1){
-        if(letters[i].n){
-            continue;
-        }
-        renderedLetter = renderedLetters[i];
-        this.textSpans[i].setAttribute('transform',renderedLetter.m);
-        this.textSpans[i].setAttribute('opacity',renderedLetter.o);
-        if(renderedLetter.sw){
-            this.textSpans[i].setAttribute('stroke-width',renderedLetter.sw);
-        }
-        if(renderedLetter.sc){
-            this.textSpans[i].setAttribute('stroke',renderedLetter.sc);
-        }
-        if(renderedLetter.fc){
-            this.textSpans[i].setAttribute('fill',renderedLetter.fc);
-        }
-    }
     if(this.firstFrame) {
         this.firstFrame = false;
     }
+    this.renderLetters();
 }
 
 
diff --git a/player/js/utils/PropertyFactory.js b/player/js/utils/PropertyFactory.js
index b3a110d..492dc55 100644
--- a/player/js/utils/PropertyFactory.js
+++ b/player/js/utils/PropertyFactory.js
@@ -669,7 +669,8 @@
         var max = Math.max;
         var min = Math.min;
         var floor = Math.floor;
-        function updateRange(){
+        function updateRange(newCharsFlag){
+            this.mdf = newCharsFlag || false;
             if(this.dynamicProperties.length){
                 var i, len = this.dynamicProperties.length;
                 for(i=0;i<len;i+=1){
@@ -679,8 +680,11 @@
                     }
                 }
             }
-            var totalChars = this.data.totalChars;
-            var divisor = this.data.r === 2 ? 1 : 100/totalChars;
+            var totalChars = this.elem.currentTextDocumentData.l.length;
+            if(newCharsFlag && this.data.r === 2) {
+                this.e.v = totalChars;
+            }
+            var divisor = this.data.r === 2 ? 1 : 100 / totalChars;
             var o = this.o.v/divisor;
             var s = this.s.v/divisor + o;
             var e = (this.e.v/divisor) + o;
@@ -772,6 +776,7 @@
             this.dynamicProperties = [];
             this.getValue = updateRange;
             this.getMult = getMult;
+            this.elem = elem;
             this.comp = elem.comp;
             this.finalS = 0;
             this.finalE = 0;
@@ -779,7 +784,7 @@
             if('e' in data){
                 this.e = PropertyFactory.getProp(elem,data.e,0,0,this.dynamicProperties);
             }else{
-                this.e = {v:data.r === 2 ? data.totalChars : 100};
+                this.e = {v:100};
             }
             this.o = PropertyFactory.getProp(elem,data.o || {k:0},0,0,this.dynamicProperties);
             this.xe = PropertyFactory.getProp(elem,data.xe || {k:0},0,0,this.dynamicProperties);
diff --git a/player/js/utils/common.js b/player/js/utils/common.js
index 05a3632..5ba7943 100644
--- a/player/js/utils/common.js
+++ b/player/js/utils/common.js
@@ -307,15 +307,6 @@
     this.o = o;
 }
 
-function LetterProps(o,sw,sc,fc,m,p){
-    this.o = o;
-    this.sw = sw;
-    this.sc = sc;
-    this.fc = fc;
-    this.m = m;
-    this.props = p;
-}
-
 function iterateDynamicProperties(num){
     var i, len = this.dynamicProperties;
     for(i=0;i<len;i+=1){
diff --git a/player/js/utils/expressions/ExpressionManager.js b/player/js/utils/expressions/ExpressionManager.js
index ec58d73..3ef8806 100644
--- a/player/js/utils/expressions/ExpressionManager.js
+++ b/player/js/utils/expressions/ExpressionManager.js
@@ -620,6 +620,10 @@
             BMMath.seedrandom(randSeed + seed);
         };
 
+        function sourceRectAtTime() {
+            return elem.sourceRectAtTime();
+        }
+
         var time,velocity, value,textIndex,textTotal,selectorValue;
         var index = elem.data.ind;
         var hasParent = !!(elem.hierarchy && elem.hierarchy.length);
diff --git a/player/js/utils/expressions/LayerInterface.js b/player/js/utils/expressions/LayerInterface.js
index 59fb53f..bfe3c15 100644
--- a/player/js/utils/expressions/LayerInterface.js
+++ b/player/js/utils/expressions/LayerInterface.js
@@ -88,6 +88,7 @@
         _thisLayerFunction.fromWorld = fromWorld;
         _thisLayerFunction.toComp = toWorld;
         _thisLayerFunction.fromComp = fromComp;
+        _thisLayerFunction.sourceRectAtTime = elem.sourceRectAtTime.bind(elem);
         _thisLayerFunction._elem = elem;
         Object.defineProperty(_thisLayerFunction, 'hasParent', {
             get: function(){
diff --git a/player/js/utils/text/LetterProps.js b/player/js/utils/text/LetterProps.js
new file mode 100644
index 0000000..5788d47
--- /dev/null
+++ b/player/js/utils/text/LetterProps.js
@@ -0,0 +1,59 @@
+function LetterProps(o, sw, sc, fc, m, p){
+    this.o = o;
+    this.sw = sw;
+    this.sc = sc;
+    this.fc = fc;
+    this.m = m;
+    this.p = p;
+    this.mdf = {
+    	o: true,
+    	sw: !!sw,
+    	sc: !!sc,
+    	fc: !!fc,
+    	m: true,
+    	p: true
+    };
+}
+
+LetterProps.prototype.update = function(o, sw, sc, fc, m, p) {
+	this.mdf.o = false;
+	this.mdf.sw = false;
+	this.mdf.sc = false;
+	this.mdf.fc = false;
+	this.mdf.m = false;
+	this.mdf.p = false;
+	var updated = false;
+
+	if(this.o !== o) {
+		this.o = o;
+		this.mdf.o = true;
+		updated = true;
+	}
+	if(this.sw !== sw) {
+		this.sw = sw;
+		this.mdf.sw = true;
+		updated = true;
+	}
+	if(this.sc !== sc) {
+		this.sc = sc;
+		this.mdf.sc = true;
+		updated = true;
+	}
+	if(this.fc !== fc) {
+		this.fc = fc;
+		this.mdf.fc = true;
+		updated = true;
+	}
+	if(this.m !== m) {
+		this.m = m;
+		this.mdf.m = true;
+		updated = true;
+	}
+	if(p.length && (this.p[0] !== p[0] || this.p[1] !== p[1] || this.p[4] !== p[4] || this.p[5] !== p[5] || this.p[12] !== p[12] || this.p[13] !== p[13])) {
+
+		this.p = p;
+		this.mdf.p = true;
+		updated = true;
+	}
+	return updated;
+}
\ No newline at end of file
diff --git a/player/js/utils/text/TextAnimatorProperty.js b/player/js/utils/text/TextAnimatorProperty.js
new file mode 100644
index 0000000..ea38091
--- /dev/null
+++ b/player/js/utils/text/TextAnimatorProperty.js
@@ -0,0 +1,576 @@
+function TextAnimatorProperty(textData, renderType, elem){
+    this.mdf = false;
+    this._firstFrame = true;
+	this._hasMaskedPath = false;
+	this._frameId = -1;
+	this._dynamicProperties = [];
+	this._textData = textData;
+	this._renderType = renderType;
+	this._elem = elem;
+	this._animatorsData = Array.apply(null,{length:this._textData.a.length});
+	this._pathData = {}
+	this._moreOptions = {
+		alignment: {}
+	};
+	this.renderedLetters = [];
+    this.lettersChangedFlag = false;
+
+}
+
+TextAnimatorProperty.prototype.searchProperties = function(dynamicProperties){
+    var i, len = this._textData.a.length, animatorData, animatorProps;
+    for(i=0;i<len;i+=1){
+        animatorProps = this._textData.a[i];
+        animatorData = {
+            a: {},
+            s: {}
+        };
+        if('r' in animatorProps.a) {
+            animatorData.a.r = PropertyFactory.getProp(this._elem,animatorProps.a.r,0,degToRads,this._dynamicProperties);
+        }
+        if('rx' in animatorProps.a) {
+            animatorData.a.rx = PropertyFactory.getProp(this._elem,animatorProps.a.rx,0,degToRads,this._dynamicProperties);
+        }
+        if('ry' in animatorProps.a) {
+            animatorData.a.ry = PropertyFactory.getProp(this._elem,animatorProps.a.ry,0,degToRads,this._dynamicProperties);
+        }
+        if('sk' in animatorProps.a) {
+            animatorData.a.sk = PropertyFactory.getProp(this._elem,animatorProps.a.sk,0,degToRads,this._dynamicProperties);
+        }
+        if('sa' in animatorProps.a) {
+            animatorData.a.sa = PropertyFactory.getProp(this._elem,animatorProps.a.sa,0,degToRads,this._dynamicProperties);
+        }
+        if('s' in animatorProps.a) {
+            animatorData.a.s = PropertyFactory.getProp(this._elem,animatorProps.a.s,1,0.01,this._dynamicProperties);
+        }
+        if('a' in animatorProps.a) {
+            animatorData.a.a = PropertyFactory.getProp(this._elem,animatorProps.a.a,1,0,this._dynamicProperties);
+        }
+        if('o' in animatorProps.a) {
+            animatorData.a.o = PropertyFactory.getProp(this._elem,animatorProps.a.o,0,0.01,this._dynamicProperties);
+        }
+        if('p' in animatorProps.a) {
+            animatorData.a.p = PropertyFactory.getProp(this._elem,animatorProps.a.p,1,0,this._dynamicProperties);
+        }
+        if('sw' in animatorProps.a) {
+            animatorData.a.sw = PropertyFactory.getProp(this._elem,animatorProps.a.sw,0,0,this._dynamicProperties);
+        }
+        if('sc' in animatorProps.a) {
+            animatorData.a.sc = PropertyFactory.getProp(this._elem,animatorProps.a.sc,1,0,this._dynamicProperties);
+        }
+        if('fc' in animatorProps.a) {
+            animatorData.a.fc = PropertyFactory.getProp(this._elem,animatorProps.a.fc,1,0,this._dynamicProperties);
+        }
+        if('fh' in animatorProps.a) {
+            animatorData.a.fh = PropertyFactory.getProp(this._elem,animatorProps.a.fh,0,0,this._dynamicProperties);
+        }
+        if('fs' in animatorProps.a) {
+            animatorData.a.fs = PropertyFactory.getProp(this._elem,animatorProps.a.fs,0,0.01,this._dynamicProperties);
+        }
+        if('fb' in animatorProps.a) {
+            animatorData.a.fb = PropertyFactory.getProp(this._elem,animatorProps.a.fb,0,0.01,this._dynamicProperties);
+        }
+        if('t' in animatorProps.a) {
+            animatorData.a.t = PropertyFactory.getProp(this._elem,animatorProps.a.t,0,0,this._dynamicProperties);
+        }
+        animatorData.s = PropertyFactory.getTextSelectorProp(this._elem,animatorProps.s,this._dynamicProperties);
+        animatorData.s.t = animatorProps.s.t;
+        this._animatorsData[i] = animatorData;
+    }
+    if(this._textData.p && 'm' in this._textData.p){
+        this._pathData = {
+            f: PropertyFactory.getProp(this._elem,this._textData.p.f,0,0,this._dynamicProperties),
+            l: PropertyFactory.getProp(this._elem,this._textData.p.l,0,0,this._dynamicProperties),
+            r: this._textData.p.r,
+            m: this._elem.maskManager.getMaskProperty(this._textData.p.m)
+        };
+        this._hasMaskedPath = true;
+    } else {
+        this._hasMaskedPath = false;
+    }
+    this._moreOptions.alignment = PropertyFactory.getProp(this._elem,this._textData.m.a,1,0,this._dynamicProperties);
+    if(this._dynamicProperties.length) {
+    	dynamicProperties.push(this);
+    }
+}
+
+TextAnimatorProperty.prototype.getMeasures = function(documentData, lettersChangedFlag){
+    this.lettersChangedFlag = lettersChangedFlag;
+    if(!this.mdf && !this._firstFrame && !lettersChangedFlag && (!this._hasMaskedPath || !this._pathData.m.mdf)) {
+        return;
+    }
+    this._firstFrame = false;
+    var alignment = this._moreOptions.alignment.v;
+    var animators = this._animatorsData;
+    var textData = this._textData;
+    var matrixHelper = this.mHelper;
+    var renderType = this._renderType;
+    var renderedLettersCount = this.renderedLetters.length;
+    var data = this.data;
+    var xPos,yPos;
+    var i, len;
+    var letters = documentData.l;
+    if(this._hasMaskedPath) {
+        var mask = this._pathData.m;
+        if(!this._pathData.n || this._pathData.mdf){
+            var paths = mask.v;
+            if(this._pathData.r){
+                paths = reversePath(paths);
+            }
+            var pathInfo = {
+                tLength: 0,
+                segments: []
+            };
+            len = paths._length - 1;
+            var pathData;
+            var totalLength = 0;
+            for (i = 0; i < len; i += 1) {
+                pathData = {
+                    s: paths.v[i],
+                    e: paths.v[i + 1],
+                    to: [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]],
+                    ti: [paths.i[i + 1][0] - paths.v[i + 1][0], paths.i[i + 1][1] - paths.v[i + 1][1]]
+                };
+                bez.buildBezierData(pathData);
+                pathInfo.tLength += pathData.bezierData.segmentLength;
+                pathInfo.segments.push(pathData);
+                totalLength += pathData.bezierData.segmentLength;
+            }
+            i = len;
+            if (mask.v.c) {
+                pathData = {
+                    s: paths.v[i],
+                    e: paths.v[0],
+                    to: [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]],
+                    ti: [paths.i[0][0] - paths.v[0][0], paths.i[0][1] - paths.v[0][1]]
+                };
+                bez.buildBezierData(pathData);
+                pathInfo.tLength += pathData.bezierData.segmentLength;
+                pathInfo.segments.push(pathData);
+                totalLength += pathData.bezierData.segmentLength;
+            }
+            this._pathData.pi = pathInfo;
+        }
+        var pathInfo = this._pathData.pi;
+
+        var currentLength = this._pathData.f.v, segmentInd = 0, pointInd = 1, currentPoint, prevPoint, points;
+        var segmentLength = 0, flag = true;
+        var segments = pathInfo.segments;
+        if (currentLength < 0 && mask.v.c) {
+            if (pathInfo.tLength < Math.abs(currentLength)) {
+                currentLength = -Math.abs(currentLength) % pathInfo.tLength;
+            }
+            segmentInd = segments.length - 1;
+            points = segments[segmentInd].bezierData.points;
+            pointInd = points.length - 1;
+            while (currentLength < 0) {
+                currentLength += points[pointInd].partialLength;
+                pointInd -= 1;
+                if (pointInd < 0) {
+                    segmentInd -= 1;
+                    points = segments[segmentInd].bezierData.points;
+                    pointInd = points.length - 1;
+                }
+            }
+
+        }
+        points = segments[segmentInd].bezierData.points;
+        prevPoint = points[pointInd - 1];
+        currentPoint = points[pointInd];
+        var partialLength = currentPoint.partialLength;
+        var perc, tanAngle;
+    }
+
+
+    len = letters.length;
+    xPos = 0;
+    yPos = 0;
+    var yOff = documentData.s*1.2*.714;
+    var firstLine = true;
+    var animatorProps, animatorSelector;
+    var j, jLen;
+    var lettersValue = Array.apply(null,{length:len}), letterValue;
+
+    jLen = animators.length;
+    if (lettersChangedFlag) {
+        for (j = 0; j < jLen; j += 1) {
+            animatorSelector = animators[j].s;
+            animatorSelector.getValue(true);
+        }
+    }
+    var lastLetter;
+
+    var mult, ind = -1, offf, xPathPos, yPathPos;
+    var initPathPos = currentLength,initSegmentInd = segmentInd, initPointInd = pointInd, currentLine = -1;
+    var elemOpacity;
+    var sc,sw,fc,k;
+    var lineLength = 0;
+    var letterSw,letterSc,letterFc,letterM = '',letterP = this.defaultPropsArray,letterO;
+    for( i = 0; i < len; i += 1) {
+        matrixHelper.reset();
+        elemOpacity = 1;
+        if(letters[i].n) {
+            xPos = 0;
+            yPos += documentData.yOffset;
+            yPos += firstLine ? 1 : 0;
+            currentLength = initPathPos ;
+            firstLine = false;
+            lineLength = 0;
+            if(this._hasMaskedPath) {
+                segmentInd = initSegmentInd;
+                pointInd = initPointInd;
+                points = segments[segmentInd].bezierData.points;
+                prevPoint = points[pointInd - 1];
+                currentPoint = points[pointInd];
+                partialLength = currentPoint.partialLength;
+                segmentLength = 0;
+            }
+            lettersValue[i] = this.emptyProp;
+        }else{
+            if(this._hasMaskedPath) {
+                if(currentLine !== letters[i].line){
+                    switch(documentData.j){
+                        case 1:
+                            currentLength += totalLength - documentData.lineWidths[letters[i].line];
+                            break;
+                        case 2:
+                            currentLength += (totalLength - documentData.lineWidths[letters[i].line])/2;
+                            break;
+                    }
+                    currentLine = letters[i].line;
+                }
+                if (ind !== letters[i].ind) {
+                    if (letters[ind]) {
+                        currentLength += letters[ind].extra;
+                    }
+                    currentLength += letters[i].an / 2;
+                    ind = letters[i].ind;
+                }
+                currentLength += alignment[0] * letters[i].an / 200;
+                var animatorOffset = 0;
+                for (j = 0; j < jLen; j += 1) {
+                    animatorProps = animators[j].a;
+                    if ('p' in animatorProps) {
+                        animatorSelector = animators[j].s;
+                        mult = animatorSelector.getMult(letters[i].anIndexes[j],textData.a[j].s.totalChars);
+                        if(mult.length){
+                            animatorOffset += animatorProps.p.v[0] * mult[0];
+                        } else{
+                            animatorOffset += animatorProps.p.v[0] * mult;
+                        }
+
+                    }
+                    if ('a' in animatorProps) {
+                        animatorSelector = animators[j].s;
+                        mult = animatorSelector.getMult(letters[i].anIndexes[j],textData.a[j].s.totalChars);
+                        if(mult.length){
+                            animatorOffset += animatorProps.a.v[0] * mult[0];
+                        } else{
+                            animatorOffset += animatorProps.a.v[0] * mult;
+                        }
+
+                    }
+                }
+                flag = true;
+                while (flag) {
+                    if (segmentLength + partialLength >= currentLength + animatorOffset || !points) {
+                        perc = (currentLength + animatorOffset - segmentLength) / currentPoint.partialLength;
+                        xPathPos = prevPoint.point[0] + (currentPoint.point[0] - prevPoint.point[0]) * perc;
+                        yPathPos = prevPoint.point[1] + (currentPoint.point[1] - prevPoint.point[1]) * perc;
+                        matrixHelper.translate(-alignment[0]*letters[i].an/200, -(alignment[1] * yOff / 100));
+                        flag = false;
+                    } else if (points) {
+                        segmentLength += currentPoint.partialLength;
+                        pointInd += 1;
+                        if (pointInd >= points.length) {
+                            pointInd = 0;
+                            segmentInd += 1;
+                            if (!segments[segmentInd]) {
+                                if (mask.v.c) {
+                                    pointInd = 0;
+                                    segmentInd = 0;
+                                    points = segments[segmentInd].bezierData.points;
+                                } else {
+                                    segmentLength -= currentPoint.partialLength;
+                                    points = null;
+                                }
+                            } else {
+                                points = segments[segmentInd].bezierData.points;
+                            }
+                        }
+                        if (points) {
+                            prevPoint = currentPoint;
+                            currentPoint = points[pointInd];
+                            partialLength = currentPoint.partialLength;
+                        }
+                    }
+                }
+                offf = letters[i].an / 2 - letters[i].add;
+                matrixHelper.translate(-offf, 0, 0);
+            } else {
+                offf = letters[i].an/2 - letters[i].add;
+                matrixHelper.translate(-offf,0,0);
+
+                // Grouping alignment
+                matrixHelper.translate(-alignment[0]*letters[i].an/200, -alignment[1]*yOff/100, 0);
+            }
+
+            lineLength += letters[i].l/2;
+            for(j=0;j<jLen;j+=1){
+                animatorProps = animators[j].a;
+                if ('t' in animatorProps) {
+                    animatorSelector = animators[j].s;
+                    mult = animatorSelector.getMult(letters[i].anIndexes[j],textData.a[j].s.totalChars);
+                    if(this._hasMaskedPath) {
+                        if(mult.length) {
+                            currentLength += animatorProps.t*mult[0];
+                        } else {
+                            currentLength += animatorProps.t*mult;
+                        }
+                    }else{
+                        if(mult.length) {
+                            xPos += animatorProps.t.v*mult[0];
+                        } else {
+                            xPos += animatorProps.t.v*mult;
+                        }
+                    }
+                }
+            }
+            lineLength += letters[i].l/2;
+            if(documentData.strokeWidthAnim) {
+                sw = documentData.sw || 0;
+            }
+            if(documentData.strokeColorAnim) {
+                if(documentData.sc){
+                    sc = [documentData.sc[0], documentData.sc[1], documentData.sc[2]];
+                }else{
+                    sc = [0,0,0];
+                }
+            }
+            if(documentData.fillColorAnim && documentData.fc) {
+                fc = [documentData.fc[0], documentData.fc[1], documentData.fc[2]];
+            }
+            for(j=0;j<jLen;j+=1){
+                animatorProps = animators[j].a;
+                if ('a' in animatorProps) {
+                    animatorSelector = animators[j].s;
+                    mult = animatorSelector.getMult(letters[i].anIndexes[j],textData.a[j].s.totalChars);
+
+                    if(mult.length){
+                        matrixHelper.translate(-animatorProps.a.v[0]*mult[0], -animatorProps.a.v[1]*mult[1], animatorProps.a.v[2]*mult[2]);
+                    } else {
+                        matrixHelper.translate(-animatorProps.a.v[0]*mult, -animatorProps.a.v[1]*mult, animatorProps.a.v[2]*mult);
+                    }
+                }
+            }
+            for(j=0;j<jLen;j+=1){
+                animatorProps = animators[j].a;
+                if ('s' in animatorProps) {
+                    animatorSelector = animators[j].s;
+                    mult = animatorSelector.getMult(letters[i].anIndexes[j],textData.a[j].s.totalChars);
+                    if(mult.length){
+                        matrixHelper.scale(1+((animatorProps.s.v[0]-1)*mult[0]),1+((animatorProps.s.v[1]-1)*mult[1]),1);
+                    } else {
+                        matrixHelper.scale(1+((animatorProps.s.v[0]-1)*mult),1+((animatorProps.s.v[1]-1)*mult),1);
+                    }
+                }
+            }
+            for(j=0;j<jLen;j+=1) {
+                animatorProps = animators[j].a;
+                animatorSelector = animators[j].s;
+                mult = animatorSelector.getMult(letters[i].anIndexes[j],textData.a[j].s.totalChars);
+                if ('sk' in animatorProps) {
+                    if(mult.length) {
+                        matrixHelper.skewFromAxis(-animatorProps.sk.v * mult[0], animatorProps.sa.v * mult[1]);
+                    } else {
+                        matrixHelper.skewFromAxis(-animatorProps.sk.v * mult, animatorProps.sa.v * mult);
+                    }
+                }
+                if ('r' in animatorProps) {
+                    if(mult.length) {
+                        matrixHelper.rotateZ(-animatorProps.r.v * mult[2]);
+                    } else {
+                        matrixHelper.rotateZ(-animatorProps.r.v * mult);
+                    }
+                }
+                if ('ry' in animatorProps) {
+
+                    if(mult.length) {
+                        matrixHelper.rotateY(animatorProps.ry.v*mult[1]);
+                    }else{
+                        matrixHelper.rotateY(animatorProps.ry.v*mult);
+                    }
+                }
+                if ('rx' in animatorProps) {
+                    if(mult.length) {
+                        matrixHelper.rotateX(animatorProps.rx.v*mult[0]);
+                    } else {
+                        matrixHelper.rotateX(animatorProps.rx.v*mult);
+                    }
+                }
+                if ('o' in animatorProps) {
+                    if(mult.length) {
+                        elemOpacity += ((animatorProps.o.v)*mult[0] - elemOpacity)*mult[0];
+                    } else {
+                        elemOpacity += ((animatorProps.o.v)*mult - elemOpacity)*mult;
+                    }
+                }
+                if (documentData.strokeWidthAnim && 'sw' in animatorProps) {
+                    if(mult.length) {
+                        sw += animatorProps.sw.v*mult[0];
+                    } else {
+                        sw += animatorProps.sw.v*mult;
+                    }
+                }
+                if (documentData.strokeColorAnim && 'sc' in animatorProps) {
+                    for(k=0;k<3;k+=1){
+                        if(mult.length) {
+                            sc[k] = Math.round(255*(sc[k] + (animatorProps.sc.v[k] - sc[k])*mult[0]));
+                        } else {
+                            sc[k] = Math.round(255*(sc[k] + (animatorProps.sc.v[k] - sc[k])*mult));
+                        }
+                    }
+                }
+                if (documentData.fillColorAnim && documentData.fc) {
+                    if('fc' in animatorProps){
+                        for(k=0;k<3;k+=1){
+                            if(mult.length) {
+                                fc[k] = fc[k] + (animatorProps.fc.v[k] - fc[k])*mult[0];
+                            } else {
+                                fc[k] = fc[k] + (animatorProps.fc.v[k] - fc[k])*mult;
+                                //console.log('mult',mult);
+                                //console.log(Math.round(fc[k] + (animatorProps.fc.v[k] - fc[k])*mult));
+                            }
+                        }
+                    }
+                    if('fh' in animatorProps){
+                        if(mult.length) {
+                            fc = addHueToRGB(fc,animatorProps.fh.v*mult[0]);
+                        } else {
+                            fc = addHueToRGB(fc,animatorProps.fh.v*mult);
+                        }
+                    }
+                    if('fs' in animatorProps){
+                        if(mult.length) {
+                            fc = addSaturationToRGB(fc,animatorProps.fs.v*mult[0]);
+                        } else {
+                            fc = addSaturationToRGB(fc,animatorProps.fs.v*mult);
+                        }
+                    }
+                    if('fb' in animatorProps){
+                        if(mult.length) {
+                            fc = addBrightnessToRGB(fc,animatorProps.fb.v*mult[0]);
+                        } else {
+                            fc = addBrightnessToRGB(fc,animatorProps.fb.v*mult);
+                        }
+                    }
+                }
+            }
+
+            for(j=0;j<jLen;j+=1){
+                animatorProps = animators[j].a;
+
+                if ('p' in animatorProps) {
+                    animatorSelector = animators[j].s;
+                    mult = animatorSelector.getMult(letters[i].anIndexes[j],textData.a[j].s.totalChars);
+                    if(this._hasMaskedPath) {
+                        if(mult.length) {
+                            matrixHelper.translate(0, animatorProps.p.v[1] * mult[0], -animatorProps.p.v[2] * mult[1]);
+                        } else {
+                            matrixHelper.translate(0, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult);
+                        }
+                    }else{
+                        if(mult.length) {
+                            matrixHelper.translate(animatorProps.p.v[0] * mult[0], animatorProps.p.v[1] * mult[1], -animatorProps.p.v[2] * mult[2]);
+                        } else {
+                            matrixHelper.translate(animatorProps.p.v[0] * mult, animatorProps.p.v[1] * mult, -animatorProps.p.v[2] * mult);
+                        }
+                    }
+                }
+            }
+            if(documentData.strokeWidthAnim){
+                letterSw = sw < 0 ? 0 : sw;
+            }
+            if(documentData.strokeColorAnim){
+                letterSc = 'rgb('+Math.round(sc[0]*255)+','+Math.round(sc[1]*255)+','+Math.round(sc[2]*255)+')';
+            }
+            if(documentData.fillColorAnim && documentData.fc){
+                letterFc = 'rgb('+Math.round(fc[0]*255)+','+Math.round(fc[1]*255)+','+Math.round(fc[2]*255)+')';
+            }
+
+            if(this._hasMaskedPath) {
+                matrixHelper.translate(0,-documentData.ls);
+
+                matrixHelper.translate(0, alignment[1]*yOff/100 + yPos,0);
+                if (textData.p.p) {
+                    tanAngle = (currentPoint.point[1] - prevPoint.point[1]) / (currentPoint.point[0] - prevPoint.point[0]);
+                    var rot = Math.atan(tanAngle) * 180 / Math.PI;
+                    if (currentPoint.point[0] < prevPoint.point[0]) {
+                        rot += 180;
+                    }
+                    matrixHelper.rotate(-rot * Math.PI / 180);
+                }
+                matrixHelper.translate(xPathPos, yPathPos, 0);
+                currentLength -= alignment[0]*letters[i].an/200;
+                if(letters[i+1] && ind !== letters[i+1].ind){
+                    currentLength += letters[i].an / 2;
+                    currentLength += documentData.tr/1000*documentData.s;
+                }
+            }else{
+
+                matrixHelper.translate(xPos,yPos,0);
+
+                if(documentData.ps){
+                    //matrixHelper.translate(documentData.ps[0],documentData.ps[1],0);
+                    matrixHelper.translate(documentData.ps[0],documentData.ps[1] + documentData.ascent,0);
+                }
+                switch(documentData.j){
+                    case 1:
+                        matrixHelper.translate(documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line]),0,0);
+                        break;
+                    case 2:
+                        matrixHelper.translate(documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line])/2,0,0);
+                        break;
+                }
+                matrixHelper.translate(0,-documentData.ls);
+                matrixHelper.translate(offf,0,0);
+                matrixHelper.translate(alignment[0]*letters[i].an/200,alignment[1]*yOff/100,0);
+                xPos += letters[i].l + documentData.tr/1000*documentData.s;
+            }
+            if(renderType === 'html'){
+                letterM = matrixHelper.toCSS();
+            }else if(renderType === 'svg'){
+                letterM = matrixHelper.to2dCSS();
+            }else{
+                letterP = [matrixHelper.props[0],matrixHelper.props[1],matrixHelper.props[2],matrixHelper.props[3],matrixHelper.props[4],matrixHelper.props[5],matrixHelper.props[6],matrixHelper.props[7],matrixHelper.props[8],matrixHelper.props[9],matrixHelper.props[10],matrixHelper.props[11],matrixHelper.props[12],matrixHelper.props[13],matrixHelper.props[14],matrixHelper.props[15]];
+            }
+            letterO = elemOpacity;
+
+            if(renderedLettersCount <= i) {
+                letterValue = new LetterProps(letterO,letterSw,letterSc,letterFc,letterM,letterP);
+                this.renderedLetters.push(letterValue);
+                renderedLettersCount += 1;
+                this.lettersChangedFlag = true;
+            } else {
+                letterValue = this.renderedLetters[i];
+                this.lettersChangedFlag = letterValue.update(letterO, letterSw, letterSc, letterFc, letterM, letterP) || this.lettersChangedFlag;
+            }
+        }
+    }
+}
+
+TextAnimatorProperty.prototype.getValue = function(){
+	if(this._elem.globalData.frameId === this._frameId){
+        return;
+    }
+    this._frameId = this._elem.globalData.frameId;
+	var i, len = this._dynamicProperties.length;
+    this.mdf = false;
+	for(i = 0; i < len; i += 1) {
+		this._dynamicProperties[i].getValue();
+        this.mdf = this._dynamicProperties[i].mdf || this.mdf;
+	}
+}
+
+TextAnimatorProperty.prototype.mHelper = new Matrix();
+TextAnimatorProperty.prototype.defaultPropsArray = [];
\ No newline at end of file