import { extendPrototype, } from '../../utils/functionExtensions'; import { createSizedArray, } from '../../utils/helpers/arrays'; import createNS from '../../utils/helpers/svg_elements'; import BaseElement from '../BaseElement'; import TransformElement from '../helpers/TransformElement'; import SVGBaseElement from './SVGBaseElement'; import HierarchyElement from '../helpers/HierarchyElement'; 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'; var emptyShapeData = { shapes: [], }; function SVGTextLottieElement(data, globalData, comp) { this.textSpans = []; this.renderType = 'svg'; this.initElement(data, globalData, comp); } extendPrototype([BaseElement, TransformElement, SVGBaseElement, HierarchyElement, FrameElement, RenderableDOMElement, ITextElement], SVGTextLottieElement); SVGTextLottieElement.prototype.createContent = function () { if (this.data.singleShape && !this.globalData.fontManager.chars) { this.textContainer = createNS('text'); } }; SVGTextLottieElement.prototype.buildTextContents = function (textArray) { var i = 0; var len = textArray.length; var textContents = []; var currentTextContent = ''; while (i < len) { if (textArray[i] === String.fromCharCode(13) || textArray[i] === String.fromCharCode(3)) { textContents.push(currentTextContent); currentTextContent = ''; } else { currentTextContent += textArray[i]; } i += 1; } textContents.push(currentTextContent); return textContents; }; SVGTextLottieElement.prototype.buildShapeData = function (data, scale) { // data should probably be cloned to apply scale separately to each instance of a text on different layers // but since text internal content gets only rendered once and then it's never rerendered, // it's probably safe not to clone data and reuse always the same instance even if the object is mutated. // Avoiding cloning is preferred since cloning each character shape data is expensive if (data.shapes && data.shapes.length) { var shape = data.shapes[0]; if (shape.it) { var shapeItem = shape.it[shape.it.length - 1]; if (shapeItem.s) { shapeItem.s.k[0] = scale; shapeItem.s.k[1] = scale; } } } return data; }; SVGTextLottieElement.prototype.buildNewText = function () { this.addDynamicProperty(this); var i; var len; var documentData = this.textProperty.currentData; this.renderedLetters = createSizedArray(documentData ? documentData.l.length : 0); if (documentData.fc) { this.layerElement.setAttribute('fill', this.buildColor(documentData.fc)); } else { this.layerElement.setAttribute('fill', 'rgba(0,0,0,0)'); } if (documentData.sc) { this.layerElement.setAttribute('stroke', this.buildColor(documentData.sc)); this.layerElement.setAttribute('stroke-width', documentData.sw); } this.layerElement.setAttribute('font-size', documentData.finalSize); var fontData = this.globalData.fontManager.getFontByName(documentData.f); if (fontData.fClass) { this.layerElement.setAttribute('class', fontData.fClass); } else { this.layerElement.setAttribute('font-family', fontData.fFamily); var fWeight = documentData.fWeight; var fStyle = documentData.fStyle; this.layerElement.setAttribute('font-style', fStyle); this.layerElement.setAttribute('font-weight', fWeight); } this.layerElement.setAttribute('aria-label', documentData.t); var letters = documentData.l || []; var usesGlyphs = !!this.globalData.fontManager.chars; len = letters.length; var tSpan; var matrixHelper = this.mHelper; var shapeStr = ''; var singleShape = this.data.singleShape; var xPos = 0; var yPos = 0; var firstLine = true; var trackingOffset = documentData.tr * 0.001 * documentData.finalSize; if (singleShape && !usesGlyphs && !documentData.sz) { var tElement = this.textContainer; var justify = 'start'; switch (documentData.j) { case 1: justify = 'end'; break; case 2: justify = 'middle'; break; default: justify = 'start'; break; } tElement.setAttribute('text-anchor', justify); tElement.setAttribute('letter-spacing', trackingOffset); var textContent = this.buildTextContents(documentData.finalText); len = textContent.length; yPos = documentData.ps ? documentData.ps[1] + documentData.ascent : 0; for (i = 0; i < len; i += 1) { 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); 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 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].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].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'; } matrixHelper.reset(); if (singleShape) { if (letters[i].n) { xPos = -trackingOffset; yPos += documentData.yOffset; yPos += firstLine ? 1 : 0; firstLine = false; } this.applyTextPropertiesToMatrix(documentData, matrixHelper, letters[i].line, xPos, yPos); xPos += letters[i].l || 0; // xPos += letters[i].val === ' ' ? 0 : trackingOffset; xPos += trackingOffset; } if (usesGlyphs) { charData = this.globalData.fontManager.getCharData( documentData.finalText[i], fontData.fStyle, this.globalData.fontManager.getFontByName(documentData.f).fFamily ); var glyphElement; // t === 1 means the character has been replaced with an animated shaped if (charData.t === 1) { glyphElement = new SVGCompElement(charData.data, this.globalData, this); } else { var data = emptyShapeData; if (charData.data && charData.data.shapes) { data = this.buildShapeData(charData.data, documentData.finalSize); } glyphElement = new SVGShapeElement(data, this.globalData, this); } if (this.textSpans[i].glyph) { var glyph = this.textSpans[i].glyph; this.textSpans[i].childSpan.removeChild(glyph.layerElement); glyph.destroy(); } this.textSpans[i].glyph = glyphElement; glyphElement._debug = true; glyphElement.prepareFrame(0); glyphElement.renderFrame(); this.textSpans[i].childSpan.appendChild(glyphElement.layerElement); // when using animated shapes, the layer will be scaled instead of replacing the internal scale // this might have issues with strokes and might need a different solution if (charData.t === 1) { 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] + ')'); } tSpan.textContent = letters[i].val; tSpan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); } // } if (singleShape && tSpan) { tSpan.setAttribute('d', shapeStr); } } while (i < this.textSpans.length) { this.textSpans[i].span.style.display = 'none'; i += 1; } this._sizeChanged = true; }; SVGTextLottieElement.prototype.sourceRectAtTime = function () { this.prepareFrame(this.comp.renderedFrame - this.data.st); this.renderInnerContent(); 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; }; 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; if (glyphElement) { glyphElement.prepareFrame(this.comp.renderedFrame - this.data.st); if (glyphElement._mdf) { this._mdf = true; } } } }; SVGTextLottieElement.prototype.renderInnerContent = function () { this.validateText(); if (!this.data.singleShape || this._mdf) { this.textAnimator.getMeasures(this.textProperty.currentData, this.lettersChangedFlag); if (this.lettersChangedFlag || this.textAnimator.lettersChangedFlag) { this._sizeChanged = true; var i; var len; var renderedLetters = this.textAnimator.renderedLetters; var letters = this.textProperty.currentData.l; 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].span; glyphElement = this.textSpans[i].glyph; if (glyphElement) { glyphElement.renderFrame(); } if (renderedLetter._mdf.m) { textSpan.setAttribute('transform', renderedLetter.m); } if (renderedLetter._mdf.o) { textSpan.setAttribute('opacity', renderedLetter.o); } if (renderedLetter._mdf.sw) { textSpan.setAttribute('stroke-width', renderedLetter.sw); } if (renderedLetter._mdf.sc) { textSpan.setAttribute('stroke', renderedLetter.sc); } if (renderedLetter._mdf.fc) { textSpan.setAttribute('fill', renderedLetter.fc); } } } } } }; export default SVGTextLottieElement;