UNPKG

lottie-web

Version:

After Effects plugin for exporting animations to SVG + JavaScript or canvas + JavaScript

611 lines (588 loc) 22.3 kB
import { addSaturationToRGB, addBrightnessToRGB, addHueToRGB, } from '../common'; import { extendPrototype, } from '../functionExtensions'; import DynamicPropertyContainer from '../helpers/dynamicProperties'; import { createSizedArray, } from '../helpers/arrays'; import PropertyFactory from '../PropertyFactory'; import bez from '../bez'; import Matrix from '../../3rd_party/transformation-matrix'; import TextAnimatorDataProperty from './TextAnimatorDataProperty'; import LetterProps from './LetterProps'; function TextAnimatorProperty(textData, renderType, elem) { this._isFirstFrame = true; this._hasMaskedPath = false; this._frameId = -1; this._textData = textData; this._renderType = renderType; this._elem = elem; this._animatorsData = createSizedArray(this._textData.a.length); this._pathData = {}; this._moreOptions = { alignment: {}, }; this.renderedLetters = []; this.lettersChangedFlag = false; this.initDynamicPropertyContainer(elem); } TextAnimatorProperty.prototype.searchProperties = function () { var i; var len = this._textData.a.length; var animatorProps; var getProp = PropertyFactory.getProp; for (i = 0; i < len; i += 1) { animatorProps = this._textData.a[i]; this._animatorsData[i] = new TextAnimatorDataProperty(this._elem, animatorProps, this); } if (this._textData.p && 'm' in this._textData.p) { this._pathData = { a: getProp(this._elem, this._textData.p.a, 0, 0, this), f: getProp(this._elem, this._textData.p.f, 0, 0, this), l: getProp(this._elem, this._textData.p.l, 0, 0, this), r: getProp(this._elem, this._textData.p.r, 0, 0, this), p: getProp(this._elem, this._textData.p.p, 0, 0, this), m: this._elem.maskManager.getMaskProperty(this._textData.p.m), }; this._hasMaskedPath = true; } else { this._hasMaskedPath = false; } this._moreOptions.alignment = getProp(this._elem, this._textData.m.a, 1, 0, this); }; TextAnimatorProperty.prototype.getMeasures = function (documentData, lettersChangedFlag) { this.lettersChangedFlag = lettersChangedFlag; if (!this._mdf && !this._isFirstFrame && !lettersChangedFlag && (!this._hasMaskedPath || !this._pathData.m._mdf)) { return; } this._isFirstFrame = 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 xPos; var yPos; var i; var len; var letters = documentData.l; var pathInfo; var currentLength; var currentPoint; var segmentLength; var flag; var pointInd; var segmentInd; var prevPoint; var points; var segments; var partialLength; var totalLength; var perc; var tanAngle; var mask; if (this._hasMaskedPath) { mask = this._pathData.m; if (!this._pathData.n || this._pathData._mdf) { var paths = mask.v; if (this._pathData.r.v) { paths = paths.reverse(); } // TODO: release bezier data cached from previous pathInfo: this._pathData.pi pathInfo = { tLength: 0, segments: [], }; len = paths._length - 1; var bezierData; totalLength = 0; for (i = 0; i < len; i += 1) { bezierData = bez.buildBezierData(paths.v[i], paths.v[i + 1], [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]], [paths.i[i + 1][0] - paths.v[i + 1][0], paths.i[i + 1][1] - paths.v[i + 1][1]]); pathInfo.tLength += bezierData.segmentLength; pathInfo.segments.push(bezierData); totalLength += bezierData.segmentLength; } i = len; if (mask.v.c) { bezierData = bez.buildBezierData(paths.v[i], paths.v[0], [paths.o[i][0] - paths.v[i][0], paths.o[i][1] - paths.v[i][1]], [paths.i[0][0] - paths.v[0][0], paths.i[0][1] - paths.v[0][1]]); pathInfo.tLength += bezierData.segmentLength; pathInfo.segments.push(bezierData); totalLength += bezierData.segmentLength; } this._pathData.pi = pathInfo; } pathInfo = this._pathData.pi; currentLength = this._pathData.f.v; segmentInd = 0; pointInd = 1; segmentLength = 0; flag = true; 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].points; pointInd = points.length - 1; while (currentLength < 0) { currentLength += points[pointInd].partialLength; pointInd -= 1; if (pointInd < 0) { segmentInd -= 1; points = segments[segmentInd].points; pointInd = points.length - 1; } } } points = segments[segmentInd].points; prevPoint = points[pointInd - 1]; currentPoint = points[pointInd]; partialLength = currentPoint.partialLength; } len = letters.length; xPos = 0; yPos = 0; var yOff = documentData.finalSize * 1.2 * 0.714; var firstLine = true; var animatorProps; var animatorSelector; var j; var jLen; var letterValue; jLen = animators.length; var mult; var ind = -1; var offf; var xPathPos; var yPathPos; var initPathPos = currentLength; var initSegmentInd = segmentInd; var initPointInd = pointInd; var currentLine = -1; var elemOpacity; var sc; var sw; var fc; var k; var letterSw; var letterSc; var letterFc; var letterM = ''; var letterP = this.defaultPropsArray; var letterO; // if (documentData.j === 2 || documentData.j === 1) { var animatorJustifyOffset = 0; var animatorFirstCharOffset = 0; var justifyOffsetMult = documentData.j === 2 ? -0.5 : -1; var lastIndex = 0; var isNewLine = true; for (i = 0; i < len; i += 1) { if (letters[i].n) { if (animatorJustifyOffset) { animatorJustifyOffset += animatorFirstCharOffset; } while (lastIndex < i) { letters[lastIndex].animatorJustifyOffset = animatorJustifyOffset; lastIndex += 1; } animatorJustifyOffset = 0; isNewLine = true; } else { for (j = 0; j < jLen; j += 1) { animatorProps = animators[j].a; if (animatorProps.t.propType) { if (isNewLine && documentData.j === 2) { animatorFirstCharOffset += animatorProps.t.v * justifyOffsetMult; } animatorSelector = animators[j].s; mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); if (mult.length) { animatorJustifyOffset += animatorProps.t.v * mult[0] * justifyOffsetMult; } else { animatorJustifyOffset += animatorProps.t.v * mult * justifyOffsetMult; } } } isNewLine = false; } } if (animatorJustifyOffset) { animatorJustifyOffset += animatorFirstCharOffset; } while (lastIndex < i) { letters[lastIndex].animatorJustifyOffset = animatorJustifyOffset; lastIndex += 1; } } // 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; if (this._hasMaskedPath) { segmentInd = initSegmentInd; pointInd = initPointInd; points = segments[segmentInd].points; prevPoint = points[pointInd - 1]; currentPoint = points[pointInd]; partialLength = currentPoint.partialLength; segmentLength = 0; } letterM = ''; letterFc = ''; letterSw = ''; letterO = ''; letterP = this.defaultPropsArray; } 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; default: 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) * 0.005; var animatorOffset = 0; for (j = 0; j < jLen; j += 1) { animatorProps = animators[j].a; if (animatorProps.p.propType) { 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 (animatorProps.a.propType) { 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; // Force alignment only works with a single line for now if (this._pathData.a.v) { currentLength = letters[0].an * 0.5 + ((totalLength - this._pathData.f.v - letters[0].an * 0.5 - letters[letters.length - 1].an * 0.5) * ind) / (len - 1); currentLength += this._pathData.f.v; } 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) * 0.005, -(alignment[1] * yOff) * 0.01); 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].points; } else { segmentLength -= currentPoint.partialLength; points = null; } } else { points = segments[segmentInd].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) * 0.005, (-alignment[1] * yOff) * 0.01, 0); } for (j = 0; j < jLen; j += 1) { animatorProps = animators[j].a; if (animatorProps.t.propType) { animatorSelector = animators[j].s; mult = animatorSelector.getMult(letters[i].anIndexes[j], textData.a[j].s.totalChars); // This condition is to prevent applying tracking to first character in each line. Might be better to use a boolean "isNewLine" if (xPos !== 0 || documentData.j !== 0) { if (this._hasMaskedPath) { if (mult.length) { currentLength += animatorProps.t.v * mult[0]; } else { currentLength += animatorProps.t.v * mult; } } else if (mult.length) { xPos += animatorProps.t.v * mult[0]; } else { xPos += animatorProps.t.v * mult; } } } } 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 (animatorProps.a.propType) { 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 (animatorProps.s.propType) { 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 (animatorProps.sk.propType) { 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 (animatorProps.r.propType) { if (mult.length) { matrixHelper.rotateZ(-animatorProps.r.v * mult[2]); } else { matrixHelper.rotateZ(-animatorProps.r.v * mult); } } if (animatorProps.ry.propType) { if (mult.length) { matrixHelper.rotateY(animatorProps.ry.v * mult[1]); } else { matrixHelper.rotateY(animatorProps.ry.v * mult); } } if (animatorProps.rx.propType) { if (mult.length) { matrixHelper.rotateX(animatorProps.rx.v * mult[0]); } else { matrixHelper.rotateX(animatorProps.rx.v * mult); } } if (animatorProps.o.propType) { if (mult.length) { elemOpacity += ((animatorProps.o.v) * mult[0] - elemOpacity) * mult[0]; } else { elemOpacity += ((animatorProps.o.v) * mult - elemOpacity) * mult; } } if (documentData.strokeWidthAnim && animatorProps.sw.propType) { if (mult.length) { sw += animatorProps.sw.v * mult[0]; } else { sw += animatorProps.sw.v * mult; } } if (documentData.strokeColorAnim && animatorProps.sc.propType) { for (k = 0; k < 3; k += 1) { if (mult.length) { sc[k] += (animatorProps.sc.v[k] - sc[k]) * mult[0]; } else { sc[k] += (animatorProps.sc.v[k] - sc[k]) * mult; } } } if (documentData.fillColorAnim && documentData.fc) { if (animatorProps.fc.propType) { for (k = 0; k < 3; k += 1) { if (mult.length) { fc[k] += (animatorProps.fc.v[k] - fc[k]) * mult[0]; } else { fc[k] += (animatorProps.fc.v[k] - fc[k]) * mult; } } } if (animatorProps.fh.propType) { if (mult.length) { fc = addHueToRGB(fc, animatorProps.fh.v * mult[0]); } else { fc = addHueToRGB(fc, animatorProps.fh.v * mult); } } if (animatorProps.fs.propType) { if (mult.length) { fc = addSaturationToRGB(fc, animatorProps.fs.v * mult[0]); } else { fc = addSaturationToRGB(fc, animatorProps.fs.v * mult); } } if (animatorProps.fb.propType) { 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 (animatorProps.p.propType) { 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) * 0.01 + yPos, 0); if (this._pathData.p.v) { 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) * 0.005; if (letters[i + 1] && ind !== letters[i + 1].ind) { currentLength += letters[i].an / 2; currentLength += (documentData.tr * 0.001) * documentData.finalSize; } } 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(letters[i].animatorJustifyOffset + documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line]), 0, 0); break; case 2: matrixHelper.translate(letters[i].animatorJustifyOffset + documentData.justifyOffset + (documentData.boxWidth - documentData.lineWidths[letters[i].line]) / 2, 0, 0); break; default: break; } matrixHelper.translate(0, -documentData.ls); matrixHelper.translate(offf, 0, 0); matrixHelper.translate((alignment[0] * letters[i].an) * 0.005, (alignment[1] * yOff) * 0.01, 0); xPos += letters[i].l + (documentData.tr * 0.001) * documentData.finalSize; } 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; this.iterateDynamicProperties(); }; TextAnimatorProperty.prototype.mHelper = new Matrix(); TextAnimatorProperty.prototype.defaultPropsArray = []; extendPrototype([DynamicPropertyContainer], TextAnimatorProperty); export default TextAnimatorProperty;