Merge branch '86_animated_text'
diff --git a/player/index.html b/player/index.html
index fc7c5db..b657410 100644
--- a/player/index.html
+++ b/player/index.html
@@ -69,7 +69,7 @@
                     y: '-200%',
                 }
             },
-            path: 'exports/render/4-device_catalog.json',
+            path: 'exports/render/data.json',
             audioFactory: createAudio,
         };
         // lottie.setQuality('low');
@@ -78,7 +78,7 @@
         setTimeout(() => {
 
             anim = lottie.loadAnimation(animData);
-            // anim.setSubframe(false);
+            anim.setSubframe(false);
             anim.onError = function(errorType, nativeError, errorProps) {
                 console.log(errorType)
             }
diff --git a/player/js/elements/helpers/RenderableDOMElement.js b/player/js/elements/helpers/RenderableDOMElement.js
index 9db66cc..efc201b 100644
--- a/player/js/elements/helpers/RenderableDOMElement.js
+++ b/player/js/elements/helpers/RenderableDOMElement.js
@@ -21,6 +21,7 @@
       this.hide();
     },
     hide: function () {
+      // console.log('HIDE', this);
       if (!this.hidden && (!this.isInRange || this.isTransparent)) {
         var elem = this.baseElement || this.layerElement;
         elem.style.display = 'none';
@@ -28,6 +29,7 @@
       }
     },
     show: function () {
+      // console.log('SHOW', this);
       if (this.isInRange && !this.isTransparent) {
         if (!this.data.hd) {
           var elem = this.baseElement || this.layerElement;
diff --git a/player/js/elements/helpers/shapes/SVGElementsRenderer.js b/player/js/elements/helpers/shapes/SVGElementsRenderer.js
index 597c679..5638f55 100644
--- a/player/js/elements/helpers/shapes/SVGElementsRenderer.js
+++ b/player/js/elements/helpers/shapes/SVGElementsRenderer.js
@@ -27,6 +27,8 @@
         return renderPath;
       case 'tr':
         return renderContentTransform;
+      case 'no':
+        return renderNoop;
       default:
         return null;
     }
@@ -41,6 +43,10 @@
     }
   }
 
+  function renderNoop() {
+
+  }
+
   function renderPath(styleData, itemData, isFirstFrame) {
     var j;
     var jLen;
diff --git a/player/js/elements/helpers/shapes/SVGNoStyleData.js b/player/js/elements/helpers/shapes/SVGNoStyleData.js
new file mode 100644
index 0000000..7b7d06c
--- /dev/null
+++ b/player/js/elements/helpers/shapes/SVGNoStyleData.js
@@ -0,0 +1,15 @@
+import DynamicPropertyContainer from '../../../utils/helpers/dynamicProperties';
+
+import {
+  extendPrototype,
+} from '../../../utils/functionExtensions';
+
+function SVGNoStyleData(elem, data, styleOb) {
+  this.initDynamicPropertyContainer(elem);
+  this.getValue = this.iterateDynamicProperties;
+  this.style = styleOb;
+}
+
+extendPrototype([DynamicPropertyContainer], SVGNoStyleData);
+
+export default SVGNoStyleData;
diff --git a/player/js/elements/svgElements/SVGCompElement.js b/player/js/elements/svgElements/SVGCompElement.js
index f27e2ab..fb31ea1 100644
--- a/player/js/elements/svgElements/SVGCompElement.js
+++ b/player/js/elements/svgElements/SVGCompElement.js
@@ -5,7 +5,7 @@
   createSizedArray,
 } from '../../utils/helpers/arrays';
 import PropertyFactory from '../../utils/PropertyFactory';
-import SVGRendererBase from '../../renderers/SVGRendererBase';
+import SVGRendererBase from '../../renderers/SVGRendererBase'; // eslint-disable-line
 import SVGBaseElement from './SVGBaseElement';
 import ICompElement from '../CompElement';
 
diff --git a/player/js/elements/svgElements/SVGShapeElement.js b/player/js/elements/svgElements/SVGShapeElement.js
index 7ca0340..8dd408a 100644
--- a/player/js/elements/svgElements/SVGShapeElement.js
+++ b/player/js/elements/svgElements/SVGShapeElement.js
@@ -22,6 +22,7 @@
 import SVGStyleData from '../helpers/shapes/SVGStyleData';
 import SVGStrokeStyleData from '../helpers/shapes/SVGStrokeStyleData';
 import SVGFillStyleData from '../helpers/shapes/SVGFillStyleData';
+import SVGNoStyleData from '../helpers/shapes/SVGNoStyleData';
 import SVGGradientFillStyleData from '../helpers/shapes/SVGGradientFillStyleData';
 import SVGGradientStrokeStyleData from '../helpers/shapes/SVGGradientStrokeStyleData';
 import ShapeGroupData from '../helpers/shapes/ShapeGroupData';
@@ -120,6 +121,8 @@
       this.globalData.defs.appendChild(elementData.of);
       pathElement.setAttribute('mask', 'url(' + getLocationHref() + '#' + elementData.maskId + ')');
     }
+  } else if (data.ty === 'no') {
+    elementData = new SVGNoStyleData(this, data, styleOb);
   }
 
   if (data.ty === 'st' || data.ty === 'gs') {
@@ -248,7 +251,7 @@
     } else {
       itemsData[i] = prevViewData[processedPos - 1];
     }
-    if (arr[i].ty === 'fl' || arr[i].ty === 'st' || arr[i].ty === 'gf' || arr[i].ty === 'gs') {
+    if (arr[i].ty === 'fl' || arr[i].ty === 'st' || arr[i].ty === 'gf' || arr[i].ty === 'gs' || arr[i].ty === 'no') {
       if (!processedPos) {
         itemsData[i] = this.createStyleElement(arr[i], level);
       } else {
@@ -330,7 +333,6 @@
     this.stylesList[i].reset();
   }
   this.renderShape();
-
   for (i = 0; i < len; i += 1) {
     if (this.stylesList[i]._mdf || this._isFirstFrame) {
       if (this.stylesList[i].msElem) {
diff --git a/player/js/elements/svgElements/SVGTextElement.js b/player/js/elements/svgElements/SVGTextElement.js
index c86bdf4..c7ca0e7 100644
--- a/player/js/elements/svgElements/SVGTextElement.js
+++ b/player/js/elements/svgElements/SVGTextElement.js
@@ -12,6 +12,8 @@
 import FrameElement from '../helpers/FrameElement';
 import RenderableDOMElement from '../helpers/RenderableDOMElement';
 import ITextElement from '../TextElement';
+import SVGCompElement from './SVGCompElement'; // eslint-disable-line
+import SVGShapeElement from './SVGShapeElement';
 
 function SVGTextLottieElement(data, globalData, comp) {
   this.textSpans = [];
@@ -46,6 +48,7 @@
 };
 
 SVGTextLottieElement.prototype.buildNewText = function () {
+  this.addDynamicProperty(this);
   var i;
   var len;
 
@@ -79,7 +82,6 @@
 
   var tSpan;
   var matrixHelper = this.mHelper;
-  var shapes;
   var shapeStr = '';
   var singleShape = this.data.singleShape;
   var xPos = 0;
@@ -106,29 +108,47 @@
     len = textContent.length;
     yPos = documentData.ps ? documentData.ps[1] + documentData.ascent : 0;
     for (i = 0; i < len; i += 1) {
-      tSpan = this.textSpans[i] || createNS('tspan');
+      tSpan = this.textSpans[i].span || createNS('tspan');
       tSpan.textContent = textContent[i];
       tSpan.setAttribute('x', 0);
       tSpan.setAttribute('y', yPos);
       tSpan.style.display = 'inherit';
       tElement.appendChild(tSpan);
-      this.textSpans[i] = tSpan;
+      if (!this.textSpans[i]) {
+        this.textSpans[i] = {
+          span: null,
+          glyph: null,
+        };
+      }
+      this.textSpans[i].span = tSpan;
       yPos += documentData.finalLineHeight;
     }
 
     this.layerElement.appendChild(tElement);
   } else {
     var cachedSpansLength = this.textSpans.length;
-    var shapeData;
     var charData;
     for (i = 0; i < len; i += 1) {
+      if (!this.textSpans[i]) {
+        this.textSpans[i] = {
+          span: null,
+          childSpan: null,
+          glyph: null,
+        };
+      }
       if (!usesGlyphs || !singleShape || i === 0) {
-        tSpan = cachedSpansLength > i ? this.textSpans[i] : createNS(usesGlyphs ? 'path' : 'text');
+        tSpan = cachedSpansLength > i ? this.textSpans[i].span : createNS(usesGlyphs ? 'g' : 'text');
         if (cachedSpansLength <= i) {
           tSpan.setAttribute('stroke-linecap', 'butt');
           tSpan.setAttribute('stroke-linejoin', 'round');
           tSpan.setAttribute('stroke-miterlimit', '4');
-          this.textSpans[i] = tSpan;
+          this.textSpans[i].span = tSpan;
+          if (usesGlyphs) {
+            var childSpan = createNS('g');
+            tSpan.appendChild(childSpan);
+            this.textSpans[i].childSpan = childSpan;
+          }
+          this.textSpans[i].span = tSpan;
           this.layerElement.appendChild(tSpan);
         }
         tSpan.style.display = 'inherit';
@@ -150,13 +170,18 @@
       }
       if (usesGlyphs) {
         charData = this.globalData.fontManager.getCharData(documentData.finalText[i], fontData.fStyle, this.globalData.fontManager.getFontByName(documentData.f).fFamily);
-        shapeData = (charData && charData.data) || {};
-        shapes = shapeData.shapes ? shapeData.shapes[0].it : [];
-        if (!singleShape) {
-          tSpan.setAttribute('d', this.createPathShape(matrixHelper, shapes));
+        var glyphElement;
+        if (charData.t === 1) {
+          glyphElement = new SVGCompElement(charData.data, this.globalData, this);
         } else {
-          shapeStr += this.createPathShape(matrixHelper, shapes);
+          glyphElement = new SVGShapeElement(charData.data, this.globalData, this);
         }
+        this.textSpans[i].glyph = glyphElement;
+        glyphElement._debug = true;
+        glyphElement.prepareFrame(0);
+        glyphElement.renderFrame();
+        this.textSpans[i].childSpan.appendChild(glyphElement.layerElement);
+        this.textSpans[i].childSpan.setAttribute('transform', 'scale(' + documentData.finalSize / 100 + ',' + documentData.finalSize / 100 + ')');
       } else {
         if (singleShape) {
           tSpan.setAttribute('transform', 'translate(' + matrixHelper.props[12] + ',' + matrixHelper.props[13] + ')');
@@ -171,7 +196,7 @@
     }
   }
   while (i < this.textSpans.length) {
-    this.textSpans[i].style.display = 'none';
+    this.textSpans[i].span.style.display = 'none';
     i += 1;
   }
 
@@ -194,8 +219,22 @@
   return this.bbox;
 };
 
+SVGTextLottieElement.prototype.getValue = function () {
+  var i;
+  var len = this.textSpans.length;
+  var glyphElement;
+  this.renderedFrame = this.comp.renderedFrame;
+  for (i = 0; i < len; i += 1) {
+    glyphElement = this.textSpans[i].glyph;
+    glyphElement.prepareFrame(this.comp.renderedFrame - this.data.st);
+    if (glyphElement._mdf) {
+      this._mdf = true;
+    }
+  }
+};
+
 SVGTextLottieElement.prototype.renderInnerContent = function () {
-  if (!this.data.singleShape) {
+  if (!this.data.singleShape || this._mdf) {
     this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag);
     if (this.lettersChangedFlag || this.textAnimator.lettersChangedFlag) {
       this._sizeChanged = true;
@@ -208,10 +247,13 @@
       len = letters.length;
       var renderedLetter;
       var textSpan;
+      var glyphElement;
       for (i = 0; i < len; i += 1) {
         if (!letters[i].n) {
           renderedLetter = renderedLetters[i];
-          textSpan = this.textSpans[i];
+          textSpan = this.textSpans[i].span;
+          glyphElement = this.textSpans[i].glyph;
+          glyphElement.renderFrame();
           if (renderedLetter._mdf.m) {
             textSpan.setAttribute('transform', renderedLetter.m);
           }
diff --git a/player/js/renderers/SVGRendererBase.js b/player/js/renderers/SVGRendererBase.js
index 23b3785..4e0f82f 100644
--- a/player/js/renderers/SVGRendererBase.js
+++ b/player/js/renderers/SVGRendererBase.js
@@ -13,7 +13,7 @@
 import BaseRenderer from './BaseRenderer';
 import IImageElement from '../elements/ImageElement';
 import SVGShapeElement from '../elements/svgElements/SVGShapeElement';
-import SVGTextLottieElement from '../elements/svgElements/SVGTextElement';
+import SVGTextLottieElement from '../elements/svgElements/SVGTextElement'; // eslint-disable-line import/no-cycle
 import ISolidElement from '../elements/SolidElement';
 import NullElement from '../elements/NullElement';
 
diff --git a/player/js/utils/DataManager.js b/player/js/utils/DataManager.js
index bb40542..fc36d63 100644
--- a/player/js/utils/DataManager.js
+++ b/player/js/utils/DataManager.js
@@ -83,22 +83,56 @@
             }
           }
 
-          function findCompLayers(id, comps) {
+          function completeChars(chars, assets) {
+            if (chars) {
+              var i = 0;
+              var len = chars.length;
+              for (i = 0; i < len; i += 1) {
+                if (chars[i].t === 1) {
+                  // var compData = findComp(chars[i].data.refId, assets);
+                  chars[i].data.layers = findCompLayers(chars[i].data.refId, assets);
+                  // chars[i].data.ip = 0;
+                  // chars[i].data.op = 99999;
+                  // chars[i].data.st = 0;
+                  // chars[i].data.sr = 1;
+                  // chars[i].w = compData.w;
+                  // chars[i].data.ks = {
+                  //   a: { k: [0, 0, 0], a: 0 },
+                  //   p: { k: [0, -compData.h, 0], a: 0 },
+                  //   r: { k: 0, a: 0 },
+                  //   s: { k: [100, 100], a: 0 },
+                  //   o: { k: 100, a: 0 },
+                  // };
+                  completeLayers(chars[i].data.layers, assets);
+                }
+              }
+            }
+          }
+
+          function findComp(id, comps) {
             var i = 0;
             var len = comps.length;
             while (i < len) {
               if (comps[i].id === id) {
-                if (!comps[i].layers.__used) {
-                  comps[i].layers.__used = true;
-                  return comps[i].layers;
-                }
-                return JSON.parse(JSON.stringify(comps[i].layers));
+                return comps[i];
               }
               i += 1;
             }
             return null;
           }
 
+          function findCompLayers(id, comps) {
+            var comp = findComp(id, comps);
+            if (comp) {
+              if (!comp.layers.__used) {
+                comp.layers.__used = true;
+                return comp.layers;
+              }
+              return JSON.parse(JSON.stringify(comp.layers));
+            }
+            return null;
+          }
+
           function completeShapes(arr) {
             var i;
             var len = arr.length;
@@ -203,21 +237,39 @@
               if (animationData.chars && !checkVersion(minimumVersion, animationData.v)) {
                 var i;
                 var len = animationData.chars.length;
-                var j;
-                var jLen;
-                var pathData;
-                var 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;
-                      if (!pathData.__converted) {
-                        convertPathsToAbsoluteValues(paths[j].ks.k);
-                        pathData.__converted = true;
-                      }
+                  var charData = animationData.chars[i];
+                  if (charData.data && charData.data.shapes) {
+                    completeShapes(charData.data.shapes);
+                    charData.data.ip = 0;
+                    charData.data.op = 99999;
+                    charData.data.st = 0;
+                    charData.data.sr = 1;
+                    charData.data.ks = {
+                      p: { k: [0, 0], a: 0 },
+                      s: { k: [100, 100], a: 0 },
+                      a: { k: [0, 0], a: 0 },
+                      r: { k: 0, a: 0 },
+                      o: { k: 100, a: 0 },
+                    };
+                    if (!animationData.chars[i].t) {
+                      charData.data.shapes.push(
+                        {
+                          ty: 'no',
+                        }
+                      );
+                      charData.data.shapes[0].it.push(
+                        {
+                          p: { k: [0, 0], a: 0 },
+                          s: { k: [100, 100], a: 0 },
+                          a: { k: [0, 0], a: 0 },
+                          r: { k: 0, a: 0 },
+                          o: { k: 100, a: 0 },
+                          sk: { k: 0, a: 0 },
+                          sa: { k: 0, a: 0 },
+                          ty: 'tr',
+                        }
+                      );
                     }
                   }
                 }
@@ -430,12 +482,13 @@
             checkPathProperties(animationData);
             checkShapes(animationData);
             completeLayers(animationData.layers, animationData.assets);
+            completeChars(animationData.chars, animationData.assets);
             animationData.__complete = true;
           }
 
           function completeText(data) {
             if (data.t.a.length === 0 && !('m' in data.t.p)) {
-              data.singleShape = true;
+              // data.singleShape = true;
             }
           }